feature/effects #37

Manually merged
neckrat merged 64 commits from feature/effects into main 2026-05-06 10:19:49 +03:00
Owner

эффекты, что случайно походу вышли полными по тьюрингу ¯_(ツ)_/¯

эффекты, что случайно походу вышли полными по тьюрингу ¯\_(ツ)_/¯
neckrat added 49 commits 2026-04-28 22:14:35 +03:00
now we should replace all self.hp - damage with this function

damn...
added afterCast and beforeCast effect implementation
function
make the boar work with new AI features
add new property to aversion to death effect

fix beforeTurn function call in turnOrder
Owner

😭

😭
Owner

как будто в сборку от зверя зашел, ей богу

[Effects]: удаляем стаки!!
[Effects]:	bleeding	ДОЛЖЕН БЫТЬ СТЁРТ
[Effects]:	bleeding	СТЁРТ
как будто в сборку от зверя зашел, ей богу ``` [Effects]: ╤â╨┤╨░╨╗╤Å╨╡╨╝ ╤ü╤é╨░╨║╨╕!! [Effects]: bleeding ╨ö╨₧╨¢╨û╨ò╨¥ ╨æ╨½╨ó╨¼ ╨í╨ó╨ü╨á╨ó [Effects]: bleeding ╨í╨ó╨ü╨á╨ó ```
Author
Owner

пропиши у себя в повершелл или куда там:

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
пропиши у себя в повершелл или куда там: ``` [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::InputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8 ```
Owner

шиза

for i, ef in ipairs(self.effectsPriority) do
        if ef == effect then
            self.effectsProperties[ef] = {
                stacks = stacks + self.effectsProperties[ef].stacks,
                intensity = intensity
            }
            local task2 = effect:afterBirth(self.owner, intensity)
            if task2 then
                task2(function() end)
            end
            return
        end

        if effect.tag == ef.tag then break end
        for k, v in pairs(efb.sums) do
            if k[effect.tag] and k[ef.tag] then
                if not v(self.owner, effect, ef) then return end
            end
        end
    end
шиза ```lua for i, ef in ipairs(self.effectsPriority) do if ef == effect then self.effectsProperties[ef] = { stacks = stacks + self.effectsProperties[ef].stacks, intensity = intensity } local task2 = effect:afterBirth(self.owner, intensity) if task2 then task2(function() end) end return end if effect.tag == ef.tag then break end for k, v in pairs(efb.sums) do if k[effect.tag] and k[ef.tag] then if not v(self.owner, effect, ef) then return end end end end ```
Author
Owner

да норм всё
в первом условии мы складываем силу и стаки как в дивинити если эффекты одинаковые, во втором условии перед циклом мы проверяем одинаковые ли тэги (суммирование сломается, если да), ну и в цикле мы проверяем на наличие сумм

да норм всё в первом условии мы складываем силу и стаки как в дивинити если эффекты одинаковые, во втором условии перед циклом мы проверяем одинаковые ли тэги (суммирование сломается, если да), ну и в цикле мы проверяем на наличие сумм
Owner
@field effectsPriority Effect[] хранит эффекты в порядке их применения

неприкольная структура данных для этой задачи. Из нее всякие поиски и удаления за линейное время. Нам ведь они не нужны, да? Да?

--- Удаляет один эффект по порядку
--- @param effect Effect
function behavior:deleteEffect(effect)
    self.effectsProperties[effect] = nil
    for i, ef in ipairs(self.effectsPriority) do
        if ef == effect then
            table.remove(self.effectsPriority, i)
            return
        end
    end
end
```lua @field effectsPriority Effect[] хранит эффекты в порядке их применения ``` неприкольная структура данных для этой задачи. Из нее всякие поиски и удаления за линейное время. Нам ведь они не нужны, да? Да? ```lua --- Удаляет один эффект по порядку --- @param effect Effect function behavior:deleteEffect(effect) self.effectsProperties[effect] = nil for i, ef in ipairs(self.effectsPriority) do if ef == effect then table.remove(self.effectsPriority, i) return end end end ```
Owner
--- @type table<table<string, boolean>, fun(owner: Character, effect1: Effect, effect2: Effect): boolean>

еще менее прикольная структура данных. Таблица с ключами-таблицами - это сомнительно

```lua --- @type table<table<string, boolean>, fun(owner: Character, effect1: Effect, effect2: Effect): boolean> ``` еще менее прикольная структура данных. Таблица с ключами-таблицами - это сомнительно
Author
Owner

насчёт effectsPriority, он необходим для определения порядка применения эффекта к персонажу, мы это обсуждали

насчёт `effectsPriority`, он необходим для определения порядка применения эффекта к персонажу, мы это обсуждали
Author
Owner

таблица с ключами-таблицами я согласен, это сомнительно, но я посчитал это достаточным для суммирования с соблюдением закона коммутативности с не слишком сложной проверкой на наличие суммы
другой вариант это сделать я придумал конкатенацией тэгов, но там тяжело с порядком этой самой конкатенацией, зато не таблица-ключи

таблица с ключами-таблицами я согласен, это сомнительно, но я посчитал это достаточным для суммирования с соблюдением закона коммутативности с не слишком сложной проверкой на наличие суммы другой вариант это сделать я придумал конкатенацией тэгов, но там тяжело с порядком этой самой конкатенацией, зато не таблица-ключи
Owner

таблица с ключами-таблицами я согласен, это сомнительно, но я посчитал это достаточным для суммирования с соблюдением закона коммутативности с не слишком сложной проверкой на наличие суммы

из-за коммутативности достаточно хранить сумму только относительно одного эффекта из пары (ef1, ef2). При наложении эффектов в любом порядке делать две проверки: есть ли запись о сумме (ef1, ef2) или (ef2, ef1).

> таблица с ключами-таблицами я согласен, это сомнительно, но я посчитал это достаточным для суммирования с соблюдением закона коммутативности с не слишком сложной проверкой на наличие суммы из-за коммутативности достаточно хранить сумму только относительно одного эффекта из пары `(ef1, ef2)`. При наложении эффектов в любом порядке делать две проверки: есть ли запись о сумме `(ef1, ef2)` или `(ef2, ef1)`.
Author
Owner

ну тут фактически это и делается, я вот правда не придумал как эту пару сохранить иначе
опять же конкатенацией тэгов? нужна функция конкатенации, что превратит bleeding и aversion_to_death в bleeding&aversion_to_death? не знаю насколько это лучше
лично я не вижу больших проблем с таблицами-ключами, как бы сомнительно это не выглядело, ведь фактически оно исполняет роль простого иммутабельного кортежа (аля pair в хаскелле)
может есть какие-то технические сложности с этим, о которых я не знаю, что сильно скажутся на производительности?

ну тут фактически это и делается, я вот правда не придумал как эту пару сохранить иначе опять же конкатенацией тэгов? нужна функция конкатенации, что превратит `bleeding` и `aversion_to_death` в `bleeding&aversion_to_death`? не знаю насколько это лучше лично я не вижу больших проблем с таблицами-ключами, как бы сомнительно это не выглядело, ведь фактически оно исполняет роль простого иммутабельного кортежа (аля pair в хаскелле) может есть какие-то технические сложности с этим, о которых я не знаю, что сильно скажутся на производительности?
Owner
sums = {
    fire =  {
        water = "огонь + вода или вода + огонь"
    },
    earth = {
        lightning = "земля и молния..."
    },
    life = {
        death = "жизнь и смерть..."
    }
}

function sum(ef1, ef2)
    if sums[ef1] return sums[ef1][ef2] end
    return sums[ef2][ef1]
end
```lua sums = { fire = { water = "огонь + вода или вода + огонь" }, earth = { lightning = "земля и молния..." }, life = { death = "жизнь и смерть..." } } function sum(ef1, ef2) if sums[ef1] return sums[ef1][ef2] end return sums[ef2][ef1] end ```
Author
Owner

ну да, да, так действительно проще

ну да, да, так действительно проще
neckrat added 1 commit 2026-04-29 16:43:13 +03:00
Author
Owner

вроде оно

вроде оно
Owner
local effectbook = {
    sums = sums,
    bleeding = bleeding,
    aversionToDeath = aversionToDeath
}

некрасиво, потому что данные разного уровня и типа оказываются смешаны. Еще неявно запрещает иметь эффект с тэгом sums.

А потом мы используем эту таблицу со всем подряд, чтобы создать ее версию без sums? Логика мне не очень ясна.

--- @field effectbook Effect[] все возможные эффекты (хз надо ли так вообще)

нет, не надо. До тех пор, пока ты не хочешь, чтобы у разных персонажей были разные возможные эффекты.
Хотя даже так - не надо.

if efb.sums[effect.tag] then
    if not efb.sums[effect.tag][ef.tag](self.owner, effect, ef) then return end
else
    if not efb.sums[ef.tag][effect.tag](self.owner, ef, effect) then return end
end

упадет, если вообще существуют суммы с effect, но не существует конкретно суммы effect + ef. Будет то попытка обратиться к null по индексу, то вызвать его как функцию.

```lua local effectbook = { sums = sums, bleeding = bleeding, aversionToDeath = aversionToDeath } ``` некрасиво, потому что данные разного уровня и типа оказываются смешаны. Еще неявно запрещает иметь эффект с тэгом `sums`. А потом мы используем эту таблицу со всем подряд, чтобы создать ее версию *без* `sums`? Логика мне не очень ясна. ```lua --- @field effectbook Effect[] все возможные эффекты (хз надо ли так вообще) ``` нет, не надо. До тех пор, пока ты не хочешь, чтобы у разных персонажей были разные *возможные* эффекты. Хотя даже так - не надо. ```lua if efb.sums[effect.tag] then if not efb.sums[effect.tag][ef.tag](self.owner, effect, ef) then return end else if not efb.sums[ef.tag][effect.tag](self.owner, ef, effect) then return end end ``` упадет, если вообще существуют суммы с `effect`, но не существует конкретно суммы `effect + ef`. Будет то попытка обратиться к null по индексу, то вызвать его как функцию.
Author
Owner

уф

уф
PeaAshMeter requested changes 2026-04-29 17:43:40 +03:00
@ -0,0 +34,4 @@
if not birthStatement then return end
-- проверяем эффект на возможности суммирования (aka противоречия)
for i, ef in ipairs(self.effectsPriority) do
if ef == effect then
Owner

Всё таки позволяет через наложение сильного короткого эффекта поверх слабого длинного получить и сильный, и длинный эффект одновременно?

Всё таки позволяет через наложение сильного короткого эффекта поверх слабого длинного получить и сильный, и длинный эффект одновременно?
Author
Owner

да, мы вроде так и договорились по итогу

да, мы вроде так и договорились по итогу
Owner

Так вроде смысл реферата по DOS сводился к тому, что так делать не надо? Мы сделали разные выводы?

Так вроде смысл реферата по DOS сводился к тому, что так делать не надо? Мы сделали разные выводы?
Owner

Так вроде смысл реферата по DOS сводился к тому, что так делать не надо? Мы сделали разные выводы?
получается, ты сделал

В этот момент новый, невероятно слабый эффект полностью перезапишет сильный эффект высокоуровневого мага.

Так вроде смысл реферата по DOS сводился к тому, что так делать не надо? Мы сделали разные выводы? получается, ты сделал > В этот момент новый, невероятно слабый эффект полностью перезапишет сильный эффект высокоуровневого мага.
Author
Owner

видимо ¯(ツ)/¯, я предположил так сделать из-за всех этих скрытых механик, где ты переопределяешь бесконечный эффект конечным и тд

видимо ¯_(ツ)_/¯, я предположил так сделать из-за всех этих скрытых механик, где ты переопределяешь бесконечный эффект конечным и тд
Owner

а поддержка бесконечных эффектов же не присутствует в каком-то дополнительном виде, кроме как не указывать эффекту момент потери стака?

а поддержка бесконечных эффектов же не присутствует в каком-то дополнительном виде, кроме как не указывать эффекту момент потери стака?
Author
Owner

да, можно добавить просто какое-то магическое значение по типу -1 ради ui, ну или сделать флаг для этого

да, можно добавить просто какое-то магическое значение по типу -1 ради ui, ну или сделать флаг для этого
@ -0,0 +72,4 @@
self.effectsProperties[effect] = nil
for i, ef in ipairs(self.effectsPriority) do
if ef == effect then
table.remove(self.effectsPriority, i)
Owner

ладно, это ужас, но не ужас-ужас

ладно, это ужас, но не ужас-ужас
Author
Owner

по академически я должен был написать линейный поиск через цикл while, но я необучаемое быдло 🤡

по академически я должен был написать линейный поиск через цикл while, но я необучаемое быдло 🤡
Owner

академически у тебя не должно быть линейных поисков вообще, в этом-то и суть

академически у тебя не должно быть линейных поисков вообще, в этом-то и суть
Owner

а у тебя получается внутри линейного for i, ef in ipairs линейный table.remove. Это не ужас-ужас, потому что ты гарантируешь срабатывание table.remove не более одного раза

а у тебя получается внутри линейного `for i, ef in ipairs` линейный `table.remove`. Это не ужас-ужас, потому что ты гарантируешь срабатывание `table.remove` не более одного раза
Author
Owner

этот моментик я бы тоже переписал

этот моментик я бы тоже переписал
Owner

типа красиво-алгоритмически - это все на тегах и все за O(1), кроме последовательного срабатывания всех эффектов (очевидно)

типа красиво-алгоритмически - это все на тегах и все за O(1), **кроме** последовательного срабатывания всех эффектов (очевидно)
Author
Owner

думаю, хотя бы для этого система тэгов не пригодится (надеюсь)

думаю, хотя бы для этого система тэгов не пригодится (надеюсь)
Owner

ну если я правильно собираю в голове ход мыслей, то нужны адекватные теги (уникальные; один тег к одному эффекту), и отдельно группы тегов (один ко многим). Например, чтобы объединить 15 версий невидимости в одну группу "невидимость" и не давать выбрать таких персонажей в таргет

ну если я правильно собираю в голове ход мыслей, то нужны адекватные теги (уникальные; один тег к одному эффекту), и отдельно группы тегов (один ко многим). Например, чтобы объединить 15 версий невидимости в одну группу "невидимость" и не давать выбрать таких персонажей в таргет
Owner

это переусложнение, которое уже ближе к реальному геймдизайну, где, как обычно, боссы могут иметь немного другие версии тех же самых эффектов

это переусложнение, которое уже ближе к реальному геймдизайну, где, как обычно, боссы могут иметь немного другие версии тех же самых эффектов
Author
Owner

нам же эти же тэги пригодятся для спеллов в конце концов, для их систематизации

нам же эти же тэги пригодятся для спеллов в конце концов, для их систематизации
Owner

не вижу необходимости иметь одну общую систему тегов для спеллов и для эффектов

не вижу необходимости иметь одну общую систему тегов для спеллов и для эффектов
Author
Owner

не думаю, что это такое уж переусложнение, мы же не только ради эффектов это делаем в конце концов

не думаю, что это такое уж переусложнение, мы же не только ради эффектов это делаем в конце концов
Owner

не вижу необходимости иметь одну общую систему тегов для спеллов и для эффектов

типа можно, но пока не знаю, зачем

> не вижу необходимости иметь одну общую систему тегов для спеллов и для эффектов типа можно, но пока не знаю, зачем
Author
Owner

вай нот? dry и всё такое

не вижу необходимости иметь одну общую систему тегов для спеллов и для эффектов

вай нот? dry и всё такое > не вижу необходимости иметь одну общую систему тегов для спеллов и для эффектов
Owner

тогда будет нейминг "spell_myspell" и "effect_myeffect"

тогда будет нейминг "spell_myspell" и "effect_myeffect"
Owner

так-то у нас не стоит задача по тегу понимать еще и тип объекта, у нас для этого инструменты языка есть, не на бейсике пишем

так-то у нас не стоит задача по тегу понимать еще и тип объекта, у нас для этого инструменты языка есть, не на бейсике пишем
Author
Owner

справедливо

справедливо
Owner

фактически у нас как будто есть таблицы в реляционной СУБД Microsoft Access: spells и effects.
мы и так понимаем, что это разные таблицы. В рамках разных таблиц айдишники не обязаны быть уникальными

фактически у нас как будто есть таблицы в реляционной СУБД Microsoft Access: spells и effects. мы и так понимаем, что это разные таблицы. В рамках разных таблиц айдишники не обязаны быть уникальными
Owner

типа другое дело что у нас у characters айдишники номерные, а у статических данных - строковые. Но это по историческим причинам и потому что, как бы, статические данные типа спеллов и эффектов мы как раз хотим находить по удобному идентификатору, а динамические типа персонажей - не хотим

типа другое дело что у нас у characters айдишники номерные, а у статических данных - строковые. Но это по историческим причинам и потому что, как бы, статические данные типа спеллов и эффектов мы как раз хотим находить по удобному идентификатору, а динамические типа персонажей - не хотим
Author
Owner

ну да, да, очевидно

ну да, да, очевидно
Author
Owner

у нас персонажи вообще в роли энтити, там со строковыми тэгами не выйдет

у нас персонажи вообще в роли энтити, там со строковыми тэгами не выйдет
Owner

короче нормальная архитектура вроде

короче нормальная архитектура вроде
@ -0,0 +84,4 @@
--- Принимает таблицу, в ключах которых тэги эффектов, которые мы хотим просуммировать, и в значениях которых функция,
--- возвращающая булево значение: применять ли эффект после суммирования.
--- @type table<string, table<string, fun(owner: Character, effect1: Effect, effect2: Effect): boolean>>
Owner

лучше алиас сделать для гигафункции

лучше алиас сделать для гигафункции
@ -0,0 +1,230 @@
local utils = require "lib.utils.utils"
local taskUtils = require "lib.utils.task"
Owner

Почему это вообще в папке со спеллами?

Почему это вообще в папке со спеллами?
Author
Owner

я не придумал куда в другое место это запихать, не в либ же кидать 🥵

я не придумал куда в другое место это запихать, не в либ же кидать 🥵
@ -0,0 +141,4 @@
}, effect)
function newEffect:beforeBirth(owner, intensity)
if not data.beforeBirth then return taskUtils.wait {}, true end
Owner

ок, это интересный способ создать пустой таск. Я даже не знал, что так можно 🥵
вообще семантически правильный способ это сделать - task.fromValue()

ок, это интересный способ создать пустой таск. Я даже не знал, что так можно 🥵 вообще семантически правильный способ это сделать - `task.fromValue()`
Author
Owner

даже так

даже так
@ -0,0 +146,4 @@
return task, statement
end
function newEffect:afterBirth(owner, intensity)
Owner

оно явно не должно так дублироваться.
вообще-то дефолтные методы ты и так определил в effect.
хотя логичнее было бы сделать "если обработчика события нет, то не обрабатываем событие".

оно явно не должно так дублироваться. вообще-то дефолтные методы ты и так определил в `effect`. хотя логичнее было бы сделать "если обработчика события нет, то не обрабатываем событие".
Author
Owner

это итог того, что я отказался от нилов в функциях
хотя полагаю это можно сократить, я не тыкал этот момент

это итог того, что я отказался от нилов в функциях хотя полагаю это можно сократить, я не тыкал этот момент
@ -0,0 +220,4 @@
--- дип сравнение эффектов
--- @param other Effect
--- @return boolean
function newEffect:__eq(other)
Owner

так я не понял, все таки эффекты сравниваются глубоко? а зачем, если два эффекта с одинаковым тегом не могут произойти? или могут?
кроме того, добавление таблицы в таблицу как ключа, т.е. effects[newEffect] = 'foobar' не будет проверять __eq.

так я не понял, все таки эффекты сравниваются глубоко? а зачем, если два эффекта с одинаковым тегом не могут произойти? или могут? кроме того, добавление таблицы в таблицу как ключа, т.е. `effects[newEffect] = 'foobar'` не будет проверять `__eq`.
Author
Owner

эффекты сравниваются глубоко как раз потому, что мы можем иметь два эффекта с одинаковыми тэгами, но с разными функциями например
это позволяет нам например не прописывать ему доп суммы, если суть у них одна и та же, но это фактически костыль и нам надо писать нормальные тэги для эффектов (чтобы отсортировать доты, шизоэффекты и тд)

эффекты сравниваются глубоко как раз потому, что мы можем иметь два эффекта с одинаковыми тэгами, но с разными функциями например это позволяет нам например не прописывать ему доп суммы, если суть у них одна и та же, но это фактически костыль и нам надо писать нормальные тэги для эффектов (чтобы отсортировать доты, шизоэффекты и тд)
Owner

у тебя костыль не в ту сторону. Это надо делать два одинаковых эффекта с двумя разными тегами тогда. А то теги перестают быть тегами

у тебя костыль не в ту сторону. Это надо делать два одинаковых эффекта с двумя разными тегами тогда. А то теги перестают быть тегами
Owner

получается что даже банальная проверка "есть ли на персонаже такой-то эффект" будет генерировать UB

получается что даже банальная проверка "есть ли на персонаже такой-то эффект" будет генерировать UB
Author
Owner

да! но суммы сравнивают именно тэги, поэтому оно работает i suppose
надо короче переписать под нормальную систему тегов, но полагаю система тегов слишком большая, чтобы писать её в ветке с эффектами и надо делать отдельную ветку
но как(ать)

да! но суммы сравнивают именно тэги, поэтому оно работает i suppose надо короче переписать под нормальную систему тегов, но полагаю система тегов слишком большая, чтобы писать её в ветке с эффектами и надо делать отдельную ветку но как(ать)
Author
Owner

хз ub выкидывать он не должен, мы же там вроде перекидываемся по итогу ссылками на одну и ту же таблицу

хз ub выкидывать он не должен, мы же там вроде перекидываемся по итогу ссылками на одну и ту же таблицу
Owner

теги суть уникальные, они дают возможность однозначно получить по строке ожидаемый объект

теги суть уникальные, они дают возможность однозначно получить по строке ожидаемый объект
Author
Owner

да ты прав, я чёт опять перемудрил
у меня где-то в голове было всё делать на тэгах, но я не понимаю что меня остановило
возможно факт того что я писал всё это в кфс и там диваны не удобные

да ты прав, я чёт опять перемудрил у меня где-то в голове было всё делать на тэгах, но я не понимаю что меня остановило возможно факт того что я писал всё это в кфс и там диваны не удобные
neckrat added 1 commit 2026-04-30 22:42:00 +03:00
neckrat added 2 commits 2026-04-30 22:49:11 +03:00
neckrat added 1 commit 2026-04-30 23:43:47 +03:00
neckrat added 1 commit 2026-05-01 01:30:23 +03:00
PeaAshMeter reviewed 2026-05-01 01:56:53 +03:00
@ -0,0 +89,4 @@
--- Принимает таблицу, в ключах которых тэги эффектов, которые мы хотим просуммировать, и в значениях которых функция,
--- возвращающая булево значение: применять ли эффект после суммирования.
--- @type table<EffectTag, table<EffectTag, SumFunc>>
Owner

Этот алиас имеет слишком общее название, у аннотаций глобальная область видимости 👎

Этот алиас имеет слишком общее название, у аннотаций глобальная область видимости 👎
Author
Owner

чорт

чорт
neckrat added 1 commit 2026-05-01 01:59:57 +03:00
neckrat added 2 commits 2026-05-01 02:34:12 +03:00
neckrat added 1 commit 2026-05-01 14:56:11 +03:00
neckrat requested review from PeaAshMeter 2026-05-01 14:57:56 +03:00
neckrat added 3 commits 2026-05-02 02:32:05 +03:00
neckrat added 1 commit 2026-05-02 02:35:48 +03:00
neckrat added 1 commit 2026-05-02 15:33:28 +03:00
neckrat manually merged commit 781a09a947 into main 2026-05-06 10:19:49 +03:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ArcMutex/heroes-of-nerevelon#37
No description provided.