feature/effects #37

Manually merged
neckrat merged 64 commits from feature/effects into main 2026-05-06 10:19:49 +03:00
5 changed files with 82 additions and 67 deletions
Showing only changes of commit be386be601 - Show all commits

View File

@ -6,7 +6,7 @@ local task = require "lib.utils.task"
--- behavior thats holds all effects that we applied
--- @class EffectsBehavior : Behavior
--- @field effectsPriority Effect[] хранит эффекты в порядке их применения
--- @field effectsStacks table<Effect, integer> хранит стаки эффектов
--- @field effectsProperties table<Effect, { stacks: integer, intensity: integer }> хранит характеристики эффектов
--- @field effectbook Effect[] все возможные эффекты (хз надо ли так вообще)
local behavior = {}
behavior.__index = behavior
@ -18,20 +18,20 @@ function behavior.new()
return setmetatable({
effectbook = efb.of { efb.bleeding },
effectsPriority = {},
effectsStacks = {},
effectsProperties = {},
}, behavior)
end
--- проверяет, можно ли наложить эффект и при наложении его применяет
--- @param effect Effect
--- @param stacks integer
function behavior:addEffect(effect, stacks)
function behavior:addEffect(effect, stacks, intensity)
-- if not effect:beforeBirth() then return end
-- проверяем эффект на возможности суммирования (aka противоречия)
for i, ef in ipairs(self.effectsPriority) do
if ef == effect then
self.effectsStacks[ef] = stacks
local task1 = effect:afterBirth(self.owner)
self.effectsProperties[ef] = { stacks = stacks, intensity = intensity }
local task1 = effect:afterBirth(self.owner, intensity)
if task1 then
task1(function() end)
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

да, можно добавить просто какое-то магическое значение по типу -1 ради ui, ну или сделать флаг для этого
@ -45,9 +45,9 @@ function behavior:addEffect(effect, stacks)
end
self.effectsPriority[#self.effectsPriority + 1] = effect
self.effectsStacks[effect] = stacks
self.effectsProperties[effect] = { stacks = stacks, intensity = intensity }
print("[Effects]: мы применили эффект!!")
local task1 = effect:afterBirth(self.owner)
local task1 = effect:afterBirth(self.owner, intensity)
if task1 then
task1(function() end)
end
@ -56,7 +56,7 @@ end
--- должен вызываться в начале хода
function behavior:beforeTurn()
for i, ef in ipairs(self.effectsPriority) do
local task1 = ef:beforeTurn(self.owner)
local task1 = ef:beforeTurn(self.owner, self.effectsProperties[ef].intensity)
if task1 then
task1(function() end)
end
@ -68,10 +68,11 @@ end
--- @param amount integer
function behavior:deleteStacks(effect, amount)
print("[Effects]: удаляем стаки!!")
self.effectsStacks[effect] = self.effectsStacks[effect] - amount -- !!!!!!!!!!!!!!!! <<<<< 21+ only
if self.effectsStacks[effect] <= 0 then
self.effectsProperties[effect].stacks = self.effectsProperties[effect].stacks -
amount -- !!!!!!!!!!!!!!!! <<<<< 21+ only
if self.effectsProperties[effect].stacks <= 0 then
print("[Effects]:", effect.tag, "ДОЛЖЕН БЫТЬ СТЁРТ")
self.effectsStacks[effect] = nil
self.effectsProperties[effect] = nil

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

справедливо

справедливо

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

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

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

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

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

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

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

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

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

короче нормальная архитектура вроде
for i, ef in ipairs(self.effectsPriority) do
if ef == effect then
table.remove(self.effectsPriority, i)
@ -84,7 +85,7 @@ end
--- должен вызываться в конце хода
function behavior:afterTurn()
for i, ef in pairs(self.effectsPriority) do
local task1 = ef:afterTurn(self.owner)
local task1 = ef:afterTurn(self.owner, self.effectsProperties[ef].intensity)
if task1 then
task1(function() end)
end

View File

@ -4,7 +4,7 @@ local easing = require "lib.utils.easing"
local bleeding = effect.new({
tag = "bleeding",
afterBirth = function(owner)
afterBirth = function(owner, intensity)
local light = require "lib/character/character".spawn("Bleeding Light Effect")
light:addBehavior {
Tree.behaviors.light.new { color = Vec3 { 1, 0., 0. }, intensity = 4 },
@ -19,15 +19,15 @@ local bleeding = effect.new({
end
})
function bleeding:beforeTurn(owner)
function bleeding:beforeTurn(owner, intensity)
local stats = owner:has(Tree.behaviors.stats)
local sprite = owner:has(Tree.behaviors.sprite)
if not stats or not sprite then return end
stats:dealDamage(2)
stats:dealDamage(intensity)
return task.wait({ sprite:animate("hurt") })
end
function bleeding:afterTurn(owner)
function bleeding:afterTurn(owner, intensity)
local behavior = owner:has(Tree.behaviors.effects)
if not behavior then
print('[Effect]: yo man what the hell wheres your behavior how thats possible please stop thats not normal')

View File

@ -50,7 +50,7 @@ function turnOrder:next()
end)
else
char:try(Tree.behaviors.effects, function(effects)
print("[TurnOrder]: ну мы пытаемся применить эффект к", char.id)
-- print("[TurnOrder]: ну мы пытаемся применить эффект к", char.id)
effects:beforeTurn()
end)
Tree.level.selector:unlock()

View File

@ -5,70 +5,84 @@ local utils = require "lib.utils.utils"
local effect = {}
effect.__index = effect
--- @alias EffectFunc fun(owner: Character): Task<nil>|nil
--- @alias EffectDamageFunc fun(owner: Character, damage: integer): Task<nil>|nil
--- @alias EffectRegenFunc fun(owner: Character, amountHp: integer): Task<nil>|nil
--- Предполагается, что в каждую функцию будет передаваться `Character` и параметр `intensity`, который отвечает за силу спелла
--- @alias EffectFunc fun(owner: Character, intensity: integer): Task<nil>|nil
--- @alias EffectDamageFunc fun(owner: Character, intensity: integer, damage: integer): Task<nil>|nil
--- @alias EffectRegenFunc fun(owner: Character, intensity: integer, amountHp: integer): Task<nil>|nil
--- @alias EffectData { tag: string, [string]: EffectFunc|EffectDamageFunc|EffectRegenFunc }
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:beforeBirth(owner) end
function effect:beforeBirth(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterBirth(owner) end
function effect:afterBirth(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:beforeDeath(owner) end
function effect:beforeDeath(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterDeath(owner) end
--- пока что это единственный метод, который работает
--- @param owner Character
--- @return Task<nil>|nil
function effect:beforeTurn(owner) end
function effect:afterDeath(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterTurn(owner) end
function effect:beforeTurn(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:beforeCast(owner) end
function effect:afterTurn(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterCast(owner) end
function effect:beforeCast(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterCast(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @param damage integer
--- @return Task<nil>|nil
function effect:beforeAttack(owner, damage) end
function effect:beforeAttack(owner, intensity, damage) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterAttack(owner) end
function effect:afterAttack(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @param damage integer
--- @return Task<nil>|nil
function effect:beforeDamage(owner, damage) end
function effect:beforeDamage(owner, intensity, damage) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterDamage(owner) end
function effect:afterDamage(owner, intensity) end
--- @param owner Character
--- @param intensity integer
--- @param amountHp integer кол-во хп для регена
--- @return Task<nil>|nil
function effect:beforeRegeneration(owner, amountHp) end
function effect:beforeRegeneration(owner, intensity, amountHp) end
--- @param owner Character
--- @param intensity integer
--- @return Task<nil>|nil
function effect:afterRegeneration(owner) end
function effect:afterRegeneration(owner, intensity) end
--- @param other Effect
--- @return Effect|nil
@ -94,74 +108,74 @@ local function new(data)
tag = data.tag
}, effect)
function newEffect:beforeBirth(owner)
function newEffect:beforeBirth(owner, intensity)
if not data.beforeBirth then return end
return data.beforeBirth(owner)
return data.beforeBirth(owner, intensity)
end
function newEffect:afterBirth(owner)
function newEffect:afterBirth(owner, intensity)
if not data.afterBirth then return end
return data.afterBirth(owner)
return data.afterBirth(owner, intensity)
end
function newEffect:beforeDeath(owner)
function newEffect:beforeDeath(owner, intensity)
if not data.beforeDeath then return end
return data.beforeDeath(owner)
return data.beforeDeath(owner, intensity)
end
function newEffect:afterDeath(owner)
function newEffect:afterDeath(owner, intensity)
if not data.afterDeath then return end
return data.afterDeath(owner)
return data.afterDeath(owner, intensity)
end
function newEffect:beforeTurn(owner)
function newEffect:beforeTurn(owner, intensity)
if not data.beforeTurn then return end
return data.beforeTurn(owner)
return data.beforeTurn(owner, intensity)
end
function newEffect:afterTurn(owner)
function newEffect:afterTurn(owner, intensity)
if not data.afterTurn then return end
return data.afterTurn(owner)
return data.afterTurn(owner, intensity)
end
function newEffect:beforeCast(owner)
function newEffect:beforeCast(owner, intensity)
if not data.beforeCast then return end
return data.beforeCast(owner)
return data.beforeCast(owner, intensity)
end

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

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

даже так

даже так
function newEffect:afterCast(owner)
function newEffect:afterCast(owner, intensity)
if not data.afterCast then return end
return data.afterCast(owner)
return data.afterCast(owner, intensity)
end

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

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

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

это итог того, что я отказался от нилов в функциях хотя полагаю это можно сократить, я не тыкал этот момент
function newEffect:beforeAttack(owner, damage)
function newEffect:beforeAttack(owner, intensity, damage)
if not data.beforeAttack then return end
return data.beforeAttack(owner, damage)
return data.beforeAttack(owner, intensity, damage)
end
function newEffect:afterAttack(owner)
function newEffect:afterAttack(owner, intensity)
if not data.afterAttack then return end
return data.afterAttack(owner)
return data.afterAttack(owner, intensity)
end
function newEffect:beforeDamage(owner, damage)
function newEffect:beforeDamage(owner, intensity, damage)
if not data.beforeDamage then return end
return data.beforeDamage(owner, damage)
return data.beforeDamage(owner, intensity, damage)
end
function newEffect:afterDamage(owner)
function newEffect:afterDamage(owner, intensity)
if not data.afterDamage then return end
return data.afterDamage(owner)
return data.afterDamage(owner, intensity)
end
function newEffect:beforeRegeneration(owner, amountHp)
function newEffect:beforeRegeneration(owner, intensity, amountHp)
if not data.beforeRegeneration then return end
return data.beforeRegeneration(owner, amountHp)
return data.beforeRegeneration(owner, intensity, amountHp)
end
function newEffect:afterRegeneration(owner)
function newEffect:afterRegeneration(owner, intensity)
if not data.afterRegeneration then return end
return data.afterRegeneration(owner)
return data.afterRegeneration(owner, intensity)
end
--- дип сравнение эффектов

View File

@ -112,7 +112,7 @@ local attack = spell.new {
return task.fromValue()
end),
targetSprite:animate("hurt"),
targetEffects:addEffect(targetEffects.effectbook[1], 3)
targetEffects:addEffect(targetEffects.effectbook[1], 3, 3)
}
end
),