From 746a1869f7d065252bac9f723e0c20e647376291 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 29 Sep 2024 21:48:51 +0200 Subject: [PATCH 1/6] Implement Luocha * init + skeleton + data * implement stats, attack, skill (without insert), talent (with workaround for sub modifiers), E1, E2, E4, A2, A4 To-Do: * implement ult, E6, technique, A6, insert --- internal/character/luocha/attack.go | 32 ++++ internal/character/luocha/data.go | 164 +++++++++++++++++++++ internal/character/luocha/luocha.go | 54 +++++++ internal/character/luocha/skill.go | 102 +++++++++++++ internal/character/luocha/stats.go | 105 +++++++++++++ internal/character/luocha/talent.go | 195 +++++++++++++++++++++++++ internal/character/luocha/technique.go | 10 ++ internal/character/luocha/ult.go | 10 ++ 8 files changed, 672 insertions(+) create mode 100644 internal/character/luocha/attack.go create mode 100644 internal/character/luocha/data.go create mode 100644 internal/character/luocha/luocha.go create mode 100644 internal/character/luocha/skill.go create mode 100644 internal/character/luocha/stats.go create mode 100644 internal/character/luocha/talent.go create mode 100644 internal/character/luocha/technique.go create mode 100644 internal/character/luocha/ult.go diff --git a/internal/character/luocha/attack.go b/internal/character/luocha/attack.go new file mode 100644 index 00000000..e16fad0e --- /dev/null +++ b/internal/character/luocha/attack.go @@ -0,0 +1,32 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/key" + "github.com/simimpact/srsim/pkg/model" +) + +const Normal key.Attack = "kafka-normal" + +var hitSplit = []float64{0.3, 0.3, 0.4} + +func (c *char) Attack(target key.TargetID, state info.ActionState) { + for i, hitRatio := range hitSplit { + c.engine.Attack(info.Attack{ + Key: Normal, + HitIndex: i, + Targets: []key.TargetID{target}, + Source: c.id, + AttackType: model.AttackType_NORMAL, + DamageType: model.DamageType_IMAGINARY, + BaseDamage: info.DamageMap{ + model.DamageFormula_BY_ATK: basic[c.info.AttackLevelIndex()], + }, + EnergyGain: 20, + StanceDamage: 30, + HitRatio: hitRatio, + }) + } + + c.engine.EndAttack() +} diff --git a/internal/character/luocha/data.go b/internal/character/luocha/data.go new file mode 100644 index 00000000..49b0e4cd --- /dev/null +++ b/internal/character/luocha/data.go @@ -0,0 +1,164 @@ +// Code generated by "charstat"; DO NOT EDIT. + +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/prop" + "github.com/simimpact/srsim/pkg/engine/target/character" +) + +var promotions = []character.PromotionData{ + { + MaxLevel: 20, + ATKBase: 102.96, + ATKAdd: 5.148, + DEFBase: 49.5, + DEFAdd: 2.475, + HPBase: 174.24, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 30, + ATKBase: 144.144, + ATKAdd: 5.148, + DEFBase: 69.3, + DEFAdd: 2.475, + HPBase: 243.936, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 40, + ATKBase: 185.328, + ATKAdd: 5.148, + DEFBase: 89.1, + DEFAdd: 2.475, + HPBase: 313.632, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 50, + ATKBase: 226.512, + ATKAdd: 5.148, + DEFBase: 108.9, + DEFAdd: 2.475, + HPBase: 383.328, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 60, + ATKBase: 267.696, + ATKAdd: 5.148, + DEFBase: 128.7, + DEFAdd: 2.475, + HPBase: 453.024, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 70, + ATKBase: 308.88, + ATKAdd: 5.148, + DEFBase: 148.5, + DEFAdd: 2.475, + HPBase: 522.72, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 80, + ATKBase: 350.064, + ATKAdd: 5.148, + DEFBase: 168.3, + DEFAdd: 2.475, + HPBase: 592.416, + HPAdd: 8.712, + SPD: 101, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, +} + +var traces = character.TraceMap{ + "101": { + Ascension: 2, + }, + "102": { + Ascension: 4, + }, + "103": { + Ascension: 6, + }, + "201": { + Stat: prop.ATKPercent, + Amount: 0.04, + Level: 1, + }, + "202": { + Stat: prop.HPPercent, + Amount: 0.04, + Ascension: 2, + }, + "203": { + Stat: prop.ATKPercent, + Amount: 0.04, + Ascension: 3, + }, + "204": { + Stat: prop.DEFPercent, + Amount: 0.05, + Ascension: 3, + }, + "205": { + Stat: prop.ATKPercent, + Amount: 0.06, + Ascension: 4, + }, + "206": { + Stat: prop.HPPercent, + Amount: 0.06, + Ascension: 5, + }, + "207": { + Stat: prop.ATKPercent, + Amount: 0.06, + Ascension: 5, + }, + "208": { + Stat: prop.DEFPercent, + Amount: 0.075, + Ascension: 6, + }, + "209": { + Stat: prop.HPPercent, + Amount: 0.08, + Level: 75, + }, + "210": { + Stat: prop.ATKPercent, + Amount: 0.08, + Level: 80, + }, +} \ No newline at end of file diff --git a/internal/character/luocha/luocha.go b/internal/character/luocha/luocha.go new file mode 100644 index 00000000..989d8957 --- /dev/null +++ b/internal/character/luocha/luocha.go @@ -0,0 +1,54 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine" + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/engine/target/character" + "github.com/simimpact/srsim/pkg/key" + "github.com/simimpact/srsim/pkg/model" +) + +func init() { + character.Register(key.Luocha, character.Config{ + Create: NewInstance, + Rarity: 5, + Element: model.DamageType_IMAGINARY, + Path: model.Path_ABUNDANCE, + MaxEnergy: 100, + Promotions: promotions, + Traces: traces, + SkillInfo: character.SkillInfo{ + Attack: character.Attack{ + SPAdd: 1, + TargetType: model.TargetType_ENEMIES, + }, + Skill: character.Skill{ + SPNeed: 1, + TargetType: model.TargetType_ALLIES, + }, + Ult: character.Ult{ + TargetType: model.TargetType_ENEMIES, + }, + Technique: character.Technique{ + TargetType: model.TargetType_SELF, + IsAttack: false, + }, + }, + }) +} + +type char struct { + engine engine.Engine + id key.TargetID + info info.Character +} + +func NewInstance(engine engine.Engine, id key.TargetID, charInfo info.Character) info.CharInstance { + c := &char{ + engine: engine, + id: id, + info: charInfo, + } + + return c +} diff --git a/internal/character/luocha/skill.go b/internal/character/luocha/skill.go new file mode 100644 index 00000000..cfff0ae5 --- /dev/null +++ b/internal/character/luocha/skill.go @@ -0,0 +1,102 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/event" + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/engine/modifier" + "github.com/simimpact/srsim/pkg/engine/prop" + "github.com/simimpact/srsim/pkg/key" + "github.com/simimpact/srsim/pkg/model" +) + +const ( + Skill = "luocha-skill" + E2HealBoost = "luocha-e2-healboost" + E2Shield = "luocha-e2-shield" +) + +func init() { + modifier.Register(E2HealBoost, modifier.Config{ + Stacking: modifier.ReplaceBySource, + Listeners: modifier.Listeners{ + OnBeforeDealHeal: applyE2HealBoost, + }, + }) + + modifier.Register(E2Shield, modifier.Config{ + Stacking: modifier.Replace, + StatusType: model.StatusType_STATUS_BUFF, + // CanDispel: true, + Duration: 2, + Listeners: modifier.Listeners{ + OnAdd: func(mod *modifier.Instance) { + mod.Engine().AddShield(E2Shield, info.Shield{ + Source: mod.Source(), + Target: mod.Owner(), + BaseShield: info.ShieldMap{model.ShieldFormula_SHIELD_BY_SHIELDER_ATK: 0.18}, + ShieldValue: 240, + }) + }, + OnRemove: func(mod *modifier.Instance) { + mod.Engine().RemoveShield(E2Shield, mod.Owner()) + }, + }, + }) +} + +func (c *char) Skill(target key.TargetID, state info.ActionState) { + // do A2 + if c.info.Traces["102"] { + c.engine.DispelStatus(target, info.Dispel{ + Status: model.StatusType_STATUS_DEBUFF, + Order: model.DispelOrder_LAST_ADDED, + Count: 1, + }) + } + + // do E2 + if c.info.Eidolon >= 2 { + if c.engine.HPRatio(target) < 0.5 { + c.engine.AddModifier(c.id, info.Modifier{ + Name: E2HealBoost, + Source: c.id, + }) + } else { + c.engine.AddModifier(target, info.Modifier{ + Name: E2Shield, + Source: c.id, + }) + } + } + + // heal target + c.engine.Heal(info.Heal{ + Key: Skill, + Targets: []key.TargetID{target}, + Source: c.id, + BaseHeal: info.HealMap{ + model.HealFormula_BY_HEALER_ATK: skillPer[c.info.SkillLevelIndex()], + }, + HealValue: skillFlat[c.info.SkillLevelIndex()], + }) + + c.engine.ModifyEnergy(info.ModifyAttribute{ + Key: Skill, + Target: c.id, + Source: c.id, + Amount: 30, + }) + + // add 1 stack of Abyssal Flower + c.engine.AddModifier(c.id, info.Modifier{ + Name: abyssFlower, + Source: c.id, + }) + + // something with inserts +} + +func applyE2HealBoost(mod *modifier.Instance, e *event.HealStart) { + e.Healer.AddProperty(E2HealBoost, prop.HealBoost, 0.3) + mod.RemoveSelf() +} diff --git a/internal/character/luocha/stats.go b/internal/character/luocha/stats.go new file mode 100644 index 00000000..a797ff87 --- /dev/null +++ b/internal/character/luocha/stats.go @@ -0,0 +1,105 @@ +package luocha + +var ( + basic = []float64{ + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1, + 1.1, + 1.2, + 1.3, + } + + skillPer = []float64{ + 0.4, + 0.425, + 0.45, + 0.475, + 0.5, + 0.52, + 0.54, + 0.56, + 0.58, + 0.6, + 0.62, + 0.64, + 0.66, + 0.68, + 0.7, + } + + skillFlat = []float64{ + 200, + 320, + 410, + 500, + 560, + 620, + 665, + 710, + 755, + 800, + 845, + 890, + 935, + 980, + 1025, + } + + talentPer = []float64{ + 0.12, + 0.1275, + 0.135, + 0.1425, + 0.15, + 0.156, + 0.162, + 0.168, + 0.174, + 0.18, + 0.186, + 0.192, + 0.198, + 0.204, + 0.21, + } + + talentFlat = []float64{ + 60, + 96, + 123, + 150, + 168, + 186, + 199.5, + 213, + 226.5, + 240, + 253.5, + 267, + 280.5, + 294, + 307.5, + } + + ult = []float64{ + 1.2, + 1.28, + 1.36, + 1.44, + 1.52, + 1.6, + 1.7, + 1.8, + 1.9, + 2, + 2.08, + 2.16, + 2.24, + 2.32, + 2.4, + } +) diff --git a/internal/character/luocha/talent.go b/internal/character/luocha/talent.go new file mode 100644 index 00000000..a9cc46ed --- /dev/null +++ b/internal/character/luocha/talent.go @@ -0,0 +1,195 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/event" + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/engine/modifier" + "github.com/simimpact/srsim/pkg/engine/prop" + "github.com/simimpact/srsim/pkg/key" + "github.com/simimpact/srsim/pkg/model" +) + +const ( + abyssFlower = "luocha-abyss-flower" + Field = "luocha-field" + FieldHeal = "luocha-field-heal" + A4 = "luocha-a4" + Insert = "luocha-insert" + InsertMark = "luocha-insert-mark" + DisableInsertMark = "luocha-disable-insert-mark" + E1 = "luocha-e1" + E4 = "luocha-e4" +) + +type state struct { + talentPer float64 + talentFlat float64 +} + +func (c *char) init() { + modifier.Register(abyssFlower, modifier.Config{ + Stacking: modifier.ReplaceBySource, + MaxCount: 2, + CountAddWhenStack: 1, + Listeners: modifier.Listeners{ + OnAdd: checkStacks, + }, + }) + + modifier.Register(InsertMark, modifier.Config{ + Listeners: modifier.Listeners{ + OnAdd: doInsert, + }, + }) + + modifier.Register(DisableInsertMark, modifier.Config{ + Listeners: modifier.Listeners{}, + }) + + modifier.Register(Field, modifier.Config{ + Stacking: modifier.ReplaceBySource, + Listeners: modifier.Listeners{ + OnAdd: addSubMods, + OnRemove: removeSubMods, + }, + }) + + modifier.Register(FieldHeal, modifier.Config{ + Listeners: modifier.Listeners{ + OnAfterAttack: doFieldHeal, + }, + }) + + modifier.Register(E1, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + }) + + modifier.Register(E4, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_DEBUFF, + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_FATIGUE}, + }) +} + +func checkStacks(mod *modifier.Instance) { + if mod.Count() >= 2 { + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: InsertMark, + Source: mod.Owner(), + }) + } +} + +func doInsert(mod *modifier.Instance) { + mod.Engine().RemoveModifier(mod.Source(), DisableInsertMark) + + luo := mod.Owner() + ci, _ := mod.Engine().CharacterInfo(luo) + mod.Engine().InsertAbility(info.Insert{ + Key: Insert, + Priority: info.CharBuffSelf, + Execute: func() { + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: Field, + Source: luo, + State: state{ + talentPer: talentPer[ci.TalentLevelIndex()], + talentFlat: talentFlat[ci.TalentLevelIndex()], + }, + }) + }, + Source: luo, + AbortFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_CTRL, model.BehaviorFlag_DISABLE_ACTION}, + }) + + mod.Engine().RemoveModifier(mod.Source(), abyssFlower) + mod.Engine().RemoveModifier(mod.Source(), InsertMark) +} + +func addSubMods(mod *modifier.Instance) { + // apply sub modifiers as normal modifiers + st := mod.State().(state) + ci, _ := mod.Engine().CharacterInfo(mod.Owner()) + + for _, trg := range mod.Engine().Characters() { + // Talent and A4 heal + mod.Engine().AddModifier(trg, info.Modifier{ + Name: FieldHeal, + Source: mod.Owner(), + State: state{ + talentPer: st.talentPer, + talentFlat: st.talentFlat, + }, + }) + + // E1 + mod.Engine().AddModifier(trg, info.Modifier{ + Name: E1, + Source: mod.Owner(), + Stats: info.PropMap{prop.ATKPercent: 0.2}, + }) + } + + // E4 + if ci.Eidolon >= 4 { + for _, trg := range mod.Engine().Enemies() { + mod.Engine().AddModifier(trg, info.Modifier{ + Name: E4, + Source: mod.Owner(), + Stats: info.PropMap{prop.Fatigue: 0.12}, + }) + } + } +} + +func removeSubMods(mod *modifier.Instance) { + // remove sub modifiers with a workaround + ci, _ := mod.Engine().CharacterInfo(mod.Owner()) + + for _, trg := range mod.Engine().Characters() { + mod.Engine().RemoveModifierFromSource(trg, mod.Source(), FieldHeal) + if ci.Eidolon >= 1 { + mod.Engine().RemoveModifierFromSource(trg, mod.Source(), E1) + } + } + + if ci.Eidolon >= 4 { + for _, trg := range mod.Engine().Enemies() { + mod.Engine().RemoveModifierFromSource(trg, mod.Source(), E4) + } + } +} + +func doFieldHeal(mod *modifier.Instance, e event.AttackEnd) { + st := mod.State().(state) + // heal self + mod.Engine().Heal(info.Heal{ + Key: FieldHeal, + Targets: []key.TargetID{mod.Owner()}, + Source: mod.Source(), + BaseHeal: info.HealMap{ + model.HealFormula_BY_HEALER_ATK: st.talentPer, + }, + HealValue: st.talentFlat, + }) + + // heal other allies (A4) + ci, _ := mod.Engine().CharacterInfo(mod.Source()) + if ci.Traces["102"] { + mod.Engine().Heal(info.Heal{ + Key: FieldHeal, + Targets: mod.Engine().Retarget(info.Retarget{ + Targets: mod.Engine().Characters(), + Filter: func(target key.TargetID) bool { + return target != mod.Owner() + }, + }), + Source: mod.Source(), + BaseHeal: info.HealMap{ + model.HealFormula_BY_HEALER_ATK: 0.07, + }, + HealValue: 93, + }) + } +} diff --git a/internal/character/luocha/technique.go b/internal/character/luocha/technique.go new file mode 100644 index 00000000..f368b20e --- /dev/null +++ b/internal/character/luocha/technique.go @@ -0,0 +1,10 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/key" +) + +func (c *char) Technique(target key.TargetID, state info.ActionState) { + +} diff --git a/internal/character/luocha/ult.go b/internal/character/luocha/ult.go new file mode 100644 index 00000000..e45129ea --- /dev/null +++ b/internal/character/luocha/ult.go @@ -0,0 +1,10 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/key" +) + +func (c *char) Ult(target key.TargetID, state info.ActionState) { + +} From 82490a7e3087fdef1022f18422286f13df343a2a Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Mon, 30 Sep 2024 00:16:33 +0200 Subject: [PATCH 2/6] Continue implementation * implement ult, E6, technique, A6 * move most of Eidolon logic to `eidolon.go` * add missing key and import * fix typos To-Do: * finish skill insert --- internal/character/luocha/attack.go | 2 +- internal/character/luocha/eidolon.go | 69 ++++++++++++++++++++ internal/character/luocha/luocha.go | 3 + internal/character/luocha/skill.go | 88 +++++++++++++++----------- internal/character/luocha/talent.go | 19 +----- internal/character/luocha/technique.go | 8 ++- internal/character/luocha/trace.go | 23 +++++++ internal/character/luocha/ult.go | 48 +++++++++++++- pkg/key/character.go | 1 + pkg/simulation/imports.go | 1 + 10 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 internal/character/luocha/eidolon.go create mode 100644 internal/character/luocha/trace.go diff --git a/internal/character/luocha/attack.go b/internal/character/luocha/attack.go index e16fad0e..ad18f84d 100644 --- a/internal/character/luocha/attack.go +++ b/internal/character/luocha/attack.go @@ -6,7 +6,7 @@ import ( "github.com/simimpact/srsim/pkg/model" ) -const Normal key.Attack = "kafka-normal" +const Normal key.Attack = "luocha-normal" var hitSplit = []float64{0.3, 0.3, 0.4} diff --git a/internal/character/luocha/eidolon.go b/internal/character/luocha/eidolon.go new file mode 100644 index 00000000..c94c1e0c --- /dev/null +++ b/internal/character/luocha/eidolon.go @@ -0,0 +1,69 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/event" + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/engine/modifier" + "github.com/simimpact/srsim/pkg/engine/prop" + "github.com/simimpact/srsim/pkg/model" +) + +const ( + E1 = "luocha-e1" + E2HealBoost = "luocha-e2-healboost" + E2Shield = "luocha-e2-shield" + E4 = "luocha-e4" + E6 = "luocha-e6" +) + +func init() { + modifier.Register(E1, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + }) + + modifier.Register(E2HealBoost, modifier.Config{ + Stacking: modifier.ReplaceBySource, + Listeners: modifier.Listeners{ + OnBeforeDealHeal: applyE2HealBoost, + }, + }) + + modifier.Register(E2Shield, modifier.Config{ + Stacking: modifier.Replace, + StatusType: model.StatusType_STATUS_BUFF, + // CanDispel: true, + Duration: 2, + Listeners: modifier.Listeners{ + OnAdd: func(mod *modifier.Instance) { + mod.Engine().AddShield(E2Shield, info.Shield{ + Source: mod.Source(), + Target: mod.Owner(), + BaseShield: info.ShieldMap{model.ShieldFormula_SHIELD_BY_SHIELDER_ATK: 0.18}, + ShieldValue: 240, + }) + }, + OnRemove: func(mod *modifier.Instance) { + mod.Engine().RemoveShield(E2Shield, mod.Owner()) + }, + }, + }) + + modifier.Register(E4, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_DEBUFF, + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_FATIGUE}, + }) + + modifier.Register(E6, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_DEBUFF, + // CanDispel: true, + Duration: 2, + }) +} + +func applyE2HealBoost(mod *modifier.Instance, e *event.HealStart) { + e.Healer.AddProperty(E2HealBoost, prop.HealBoost, 0.3) + mod.RemoveSelf() +} diff --git a/internal/character/luocha/luocha.go b/internal/character/luocha/luocha.go index 989d8957..96d18e55 100644 --- a/internal/character/luocha/luocha.go +++ b/internal/character/luocha/luocha.go @@ -50,5 +50,8 @@ func NewInstance(engine engine.Engine, id key.TargetID, charInfo info.Character) info: charInfo, } + c.initSkillInsert() + c.initTraces() + return c } diff --git a/internal/character/luocha/skill.go b/internal/character/luocha/skill.go index cfff0ae5..78f97706 100644 --- a/internal/character/luocha/skill.go +++ b/internal/character/luocha/skill.go @@ -4,46 +4,30 @@ import ( "github.com/simimpact/srsim/pkg/engine/event" "github.com/simimpact/srsim/pkg/engine/info" "github.com/simimpact/srsim/pkg/engine/modifier" - "github.com/simimpact/srsim/pkg/engine/prop" "github.com/simimpact/srsim/pkg/key" "github.com/simimpact/srsim/pkg/model" ) const ( - Skill = "luocha-skill" - E2HealBoost = "luocha-e2-healboost" - E2Shield = "luocha-e2-shield" + Skill = "luocha-skill" + SkillInsert = "luocha-skill-insert" + SkillInsertCD = "luocha-skill-insert-cooldown" ) +var IsInsert bool + func init() { - modifier.Register(E2HealBoost, modifier.Config{ - Stacking: modifier.ReplaceBySource, - Listeners: modifier.Listeners{ - OnBeforeDealHeal: applyE2HealBoost, - }, - }) + modifier.Register(SkillInsert, modifier.Config{}) - modifier.Register(E2Shield, modifier.Config{ - Stacking: modifier.Replace, - StatusType: model.StatusType_STATUS_BUFF, - // CanDispel: true, - Duration: 2, - Listeners: modifier.Listeners{ - OnAdd: func(mod *modifier.Instance) { - mod.Engine().AddShield(E2Shield, info.Shield{ - Source: mod.Source(), - Target: mod.Owner(), - BaseShield: info.ShieldMap{model.ShieldFormula_SHIELD_BY_SHIELDER_ATK: 0.18}, - ShieldValue: 240, - }) - }, - OnRemove: func(mod *modifier.Instance) { - mod.Engine().RemoveShield(E2Shield, mod.Owner()) - }, - }, + modifier.Register(SkillInsertCD, modifier.Config{ + TickMoment: modifier.ModifierPhase1End, }) } +func (c *char) initSkillInsert() { + c.engine.Events().HPChange.Subscribe(c.SkillInsertListener) +} + func (c *char) Skill(target key.TargetID, state info.ActionState) { // do A2 if c.info.Traces["102"] { @@ -87,16 +71,48 @@ func (c *char) Skill(target key.TargetID, state info.ActionState) { Amount: 30, }) - // add 1 stack of Abyssal Flower - c.engine.AddModifier(c.id, info.Modifier{ - Name: abyssFlower, - Source: c.id, - }) + // add 1 stack of Abyss Flower if no Field active + if !c.engine.HasModifier(c.id, Field) { + c.engine.AddModifier(c.id, info.Modifier{ + Name: AbyssFlower, + Source: c.id, + }) + } // something with inserts + if IsInsert { + IsInsert = false + } } -func applyE2HealBoost(mod *modifier.Instance, e *event.HealStart) { - e.Healer.AddProperty(E2HealBoost, prop.HealBoost, 0.3) - mod.RemoveSelf() +func (c *char) SkillInsertListener(e event.HPChange) { + // bypass if hp change is positive + if e.NewHP > e.OldHP { + return + } + + // bypass if on cooldown + if c.engine.HasModifier(c.id, SkillInsertCD) { + return + } + + // bypass if CC'd or unable to act + cond1 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_STAT_CTRL) + cond2 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_DISABLE_ACTION) + if cond1 || cond2 { + return + } + + trg := c.engine.Retarget(info.Retarget{ + Targets: c.engine.Enemies(), + Filter: func(target key.TargetID) bool { + // missing filters + return true + }, + Max: 1, + }) + + if c.engine.HPRatio(trg[0]) <= 0.5 { + // // apply another mod that applies another mod... + } } diff --git a/internal/character/luocha/talent.go b/internal/character/luocha/talent.go index a9cc46ed..ecaf65bb 100644 --- a/internal/character/luocha/talent.go +++ b/internal/character/luocha/talent.go @@ -10,15 +10,13 @@ import ( ) const ( - abyssFlower = "luocha-abyss-flower" + AbyssFlower = "luocha-Abyss-flower" Field = "luocha-field" FieldHeal = "luocha-field-heal" A4 = "luocha-a4" Insert = "luocha-insert" InsertMark = "luocha-insert-mark" DisableInsertMark = "luocha-disable-insert-mark" - E1 = "luocha-e1" - E4 = "luocha-e4" ) type state struct { @@ -27,7 +25,7 @@ type state struct { } func (c *char) init() { - modifier.Register(abyssFlower, modifier.Config{ + modifier.Register(AbyssFlower, modifier.Config{ Stacking: modifier.ReplaceBySource, MaxCount: 2, CountAddWhenStack: 1, @@ -59,17 +57,6 @@ func (c *char) init() { OnAfterAttack: doFieldHeal, }, }) - - modifier.Register(E1, modifier.Config{ - Stacking: modifier.ReplaceBySource, - StatusType: model.StatusType_STATUS_BUFF, - }) - - modifier.Register(E4, modifier.Config{ - Stacking: modifier.ReplaceBySource, - StatusType: model.StatusType_STATUS_DEBUFF, - BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_FATIGUE}, - }) } func checkStacks(mod *modifier.Instance) { @@ -103,7 +90,7 @@ func doInsert(mod *modifier.Instance) { AbortFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_CTRL, model.BehaviorFlag_DISABLE_ACTION}, }) - mod.Engine().RemoveModifier(mod.Source(), abyssFlower) + mod.Engine().RemoveModifier(mod.Source(), AbyssFlower) mod.Engine().RemoveModifier(mod.Source(), InsertMark) } diff --git a/internal/character/luocha/technique.go b/internal/character/luocha/technique.go index f368b20e..ae3ec1dc 100644 --- a/internal/character/luocha/technique.go +++ b/internal/character/luocha/technique.go @@ -5,6 +5,12 @@ import ( "github.com/simimpact/srsim/pkg/key" ) +const Technique = "luocha-technique" + func (c *char) Technique(target key.TargetID, state info.ActionState) { - + c.engine.AddModifier(c.id, info.Modifier{ + Name: AbyssFlower, + Source: c.id, + Count: 2, + }) } diff --git a/internal/character/luocha/trace.go b/internal/character/luocha/trace.go new file mode 100644 index 00000000..60be03e7 --- /dev/null +++ b/internal/character/luocha/trace.go @@ -0,0 +1,23 @@ +package luocha + +import ( + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/engine/modifier" + "github.com/simimpact/srsim/pkg/model" +) + +const A6 = "luocha-a6" + +func init() { + modifier.Register(A6, modifier.Config{}) +} + +func (c *char) initTraces() { + if c.info.Traces["103"] { + c.engine.AddModifier(c.id, info.Modifier{ + Name: A6, + Source: c.id, + DebuffRES: info.DebuffRESMap{model.BehaviorFlag_STAT_CTRL: 0.7}, + }) + } +} diff --git a/internal/character/luocha/ult.go b/internal/character/luocha/ult.go index e45129ea..24df7f87 100644 --- a/internal/character/luocha/ult.go +++ b/internal/character/luocha/ult.go @@ -2,9 +2,55 @@ package luocha import ( "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/engine/prop" "github.com/simimpact/srsim/pkg/key" + "github.com/simimpact/srsim/pkg/model" +) + +const ( + Ult = "luocha-ult" ) func (c *char) Ult(target key.TargetID, state info.ActionState) { - + // do E6 + if c.info.Eidolon >= 6 { + for _, trg := range c.engine.Enemies() { + c.engine.AddModifier(trg, info.Modifier{ + Name: E6, + Source: c.id, + Stats: info.PropMap{prop.AllDamageRES: 0.2}, + }) + } + } + + // dispel 1 buff on all enemies + for _, trg := range c.engine.Enemies() { + c.engine.DispelStatus(trg, info.Dispel{ + Status: model.StatusType_STATUS_BUFF, + Order: model.DispelOrder_LAST_ADDED, + Count: 1, + }) + } + + // add 1 stack of Abyss Flower if no Field active + if !c.engine.HasModifier(c.id, Field) { + c.engine.AddModifier(c.id, info.Modifier{ + Name: AbyssFlower, + Source: c.id, + }) + } + + // do damage + c.engine.Attack(info.Attack{ + Key: Ult, + AttackType: model.AttackType_ULT, + DamageType: model.DamageType_IMAGINARY, + BaseDamage: info.DamageMap{ + model.DamageFormula_BY_ATK: ult[c.info.UltLevelIndex()], + }, + Targets: c.engine.Enemies(), + Source: c.id, + EnergyGain: 5, + StanceDamage: 60, + }) } diff --git a/pkg/key/character.go b/pkg/key/character.go index 74e2eb20..0fbee573 100644 --- a/pkg/key/character.go +++ b/pkg/key/character.go @@ -26,6 +26,7 @@ const ( March7th Character = "march7th" Seele Character = "seele" Huohuo Character = "huohuo" + Luocha Character = "luocha" ) func (c Character) String() string { diff --git a/pkg/simulation/imports.go b/pkg/simulation/imports.go index 491d8566..e99ec80c 100644 --- a/pkg/simulation/imports.go +++ b/pkg/simulation/imports.go @@ -15,6 +15,7 @@ import ( _ "github.com/simimpact/srsim/internal/character/hook" _ "github.com/simimpact/srsim/internal/character/huohuo" _ "github.com/simimpact/srsim/internal/character/kafka" + _ "github.com/simimpact/srsim/internal/character/luocha" _ "github.com/simimpact/srsim/internal/character/march7th" _ "github.com/simimpact/srsim/internal/character/natasha" _ "github.com/simimpact/srsim/internal/character/pela" From 45863c83f28b7eb7b6e915e78fea8635232a10db Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Mon, 30 Sep 2024 23:16:33 +0200 Subject: [PATCH 3/6] Continue implementation (2) and fixes * continue inserted skill implementation * refactored some functions * added helper functions * rename insert names to better differentiate between inserted skill and talent insert To-Do: * finish insert skill * potentially refactor skill so that it can be run by main skill and insert skill --- internal/character/luocha/luocha.go | 2 +- internal/character/luocha/skill.go | 181 +++++++++++++++++++++++++--- internal/character/luocha/talent.go | 30 ++--- 3 files changed, 181 insertions(+), 32 deletions(-) diff --git a/internal/character/luocha/luocha.go b/internal/character/luocha/luocha.go index 96d18e55..f281f068 100644 --- a/internal/character/luocha/luocha.go +++ b/internal/character/luocha/luocha.go @@ -50,7 +50,7 @@ func NewInstance(engine engine.Engine, id key.TargetID, charInfo info.Character) info: charInfo, } - c.initSkillInsert() + c.initInsertSkill() c.initTraces() return c diff --git a/internal/character/luocha/skill.go b/internal/character/luocha/skill.go index 78f97706..a79d0082 100644 --- a/internal/character/luocha/skill.go +++ b/internal/character/luocha/skill.go @@ -1,6 +1,7 @@ package luocha import ( + "github.com/simimpact/srsim/pkg/engine" "github.com/simimpact/srsim/pkg/engine/event" "github.com/simimpact/srsim/pkg/engine/info" "github.com/simimpact/srsim/pkg/engine/modifier" @@ -9,23 +10,42 @@ import ( ) const ( - Skill = "luocha-skill" - SkillInsert = "luocha-skill-insert" - SkillInsertCD = "luocha-skill-insert-cooldown" + Skill = "luocha-skill" + InsertSkill = "luocha-insert-skill" + InsertSkillCD = "luocha-insert-skill-cooldown" + InsertSkillRetarget = "luocha-insert-skill-retarget" + InsertSkillMark = "luocha-insert-skill-mark" ) +// this flag might be a global value, not sure what else it does var IsInsert bool func init() { - modifier.Register(SkillInsert, modifier.Config{}) + modifier.Register(InsertSkill, modifier.Config{}) - modifier.Register(SkillInsertCD, modifier.Config{ + modifier.Register(InsertSkillCD, modifier.Config{ TickMoment: modifier.ModifierPhase1End, }) + + modifier.Register(InsertSkillRetarget, modifier.Config{ + Listeners: modifier.Listeners{ + OnAdd: applyInsertSkillMark, + }, + }) + + modifier.Register(InsertSkillMark, modifier.Config{ + Listeners: modifier.Listeners{ + OnAdd: doInsertSkill, + OnBeforeDying: func(mod *modifier.Instance) { + mod.Engine().RemoveModifier(mod.Source(), InsertSkillRetarget) + }, + }, + }) } -func (c *char) initSkillInsert() { - c.engine.Events().HPChange.Subscribe(c.SkillInsertListener) +func (c *char) initInsertSkill() { + c.engine.Events().HPChange.Subscribe(c.InsertSkillListener) + c.engine.Events().InsertEnd.Subscribe(c.onInsertFinish) } func (c *char) Skill(target key.TargetID, state info.ActionState) { @@ -79,40 +99,169 @@ func (c *char) Skill(target key.TargetID, state info.ActionState) { }) } - // something with inserts + // reset insert flag if IsInsert { IsInsert = false } } -func (c *char) SkillInsertListener(e event.HPChange) { +func (c *char) InsertSkillListener(e event.HPChange) { // bypass if hp change is positive if e.NewHP > e.OldHP { return } // bypass if on cooldown - if c.engine.HasModifier(c.id, SkillInsertCD) { + if c.engine.HasModifier(c.id, InsertSkillCD) { return } // bypass if CC'd or unable to act - cond1 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_STAT_CTRL) - cond2 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_DISABLE_ACTION) - if cond1 || cond2 { + if c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_STAT_CTRL) { + return + } + if c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_DISABLE_ACTION) { return } trg := c.engine.Retarget(info.Retarget{ - Targets: c.engine.Enemies(), + Targets: c.engine.Characters(), Filter: func(target key.TargetID) bool { - // missing filters + // Filter conditions as bypasses + // bypass if HP at or below 0 + if c.engine.HPRatio(target) <= 0 { + return false + } + // bypass if BattleEventEntity (using workaround) + if !c.engine.IsCharacter(target) { + return false + } + // bypass if not lowest HP ratio + if !isMinHPRatio(c.engine, target, c.engine.Characters()) { + return false + } return true }, Max: 1, }) if c.engine.HPRatio(trg[0]) <= 0.5 { - // // apply another mod that applies another mod... + // apply another mod that applies another mod... + c.engine.AddModifier(c.id, info.Modifier{ + Name: InsertSkillRetarget, + Source: c.id, + }) } } + +func applyInsertSkillMark(mod *modifier.Instance) { + trg := doRetarget(mod) + + mod.Engine().AddModifier(trg[0], info.Modifier{ + Name: InsertSkillMark, + Source: mod.Source(), + }) +} + +func doInsertSkill(mod *modifier.Instance) { + mod.Engine().InsertAbility(info.Insert{ + Key: InsertSkill, + Execute: func() { + IsInsert = true + trg := doRetarget(mod) + if mod.Engine().HPRatio(trg[0]) <= 0.5 { + // apply cooldown mod + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: InsertSkillCD, + Source: mod.Source(), + Duration: 2, + }) + // remove mark mod on all allies and retarget mod on source + for _, marktrg := range mod.Engine().Characters() { + mod.Engine().RemoveModifier(marktrg, InsertSkillMark) + } + mod.Engine().RemoveModifier(mod.Source(), InsertSkillRetarget) + // do skill as insert + + } else { + // remove mark mod on all allies and retarget mod on source + for _, marktrg := range mod.Engine().Characters() { + mod.Engine().RemoveModifier(marktrg, InsertSkillMark) + } + mod.Engine().RemoveModifier(mod.Source(), InsertSkillRetarget) + IsInsert = false + } + }, + Source: mod.Source(), + AbortFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_CTRL, model.BehaviorFlag_DISABLE_ACTION}, + Priority: info.CharHealOthers, + }) +} + +func (c *char) onInsertFinish(e event.InsertEnd) { + // check if CC'd or unable to act + cond1 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_STAT_CTRL) + cond2 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_DISABLE_ACTION) + if cond1 || cond2 { + // remove mark mod and retarget mod from all allies + for _, trg := range c.engine.Characters() { + c.engine.RemoveModifier(trg, InsertSkillMark) + c.engine.RemoveModifier(trg, InsertSkillRetarget) + } + IsInsert = false + if c.engine.HasModifier(c.id, TalentInsertMark) { + c.engine.RemoveModifier(c.id, TalentInsertMark) + c.engine.AddModifier(c.id, info.Modifier{ + Name: DisableTalentInsertMark, + Source: c.id, + }) + } + } +} + +// helper function for evaluating whether target is lowest HP ratio out of a list comparetargets +func isMinHPRatio(engine engine.Engine, target key.TargetID, comparetargets []key.TargetID) bool { + // bypass if empty list + if len(comparetargets) == 0 { + return false + } + + // Start by assuming the first target has the minimum HP ratio + minTarget := comparetargets[0] + minHPRatio := engine.HPRatio(minTarget) + + // Loop through all the targets to find the one with the smallest HP ratio + for _, target := range comparetargets { + currentHPRatio := engine.HPRatio(target) + if currentHPRatio < minHPRatio { + minHPRatio = currentHPRatio + minTarget = target + } + } + + // Compare target's HPRatio with the minimum found + return target == minTarget +} + +// helper function for small retarget (without BattleEvenEntity) +func doRetarget(mod *modifier.Instance) []key.TargetID { + trg := mod.Engine().Retarget(info.Retarget{ + Targets: mod.Engine().Characters(), + Filter: func(target key.TargetID) bool { + // Filter conditions as bypasses + // bypass if HP at or below 0 + if mod.Engine().HPRatio(target) <= 0 { + return false + } + // no check for BattleEventEntity + // bypass if not lowest HP ratio + if !isMinHPRatio(mod.Engine(), target, mod.Engine().Characters()) { + return false + } + return true + }, + Max: 1, + }) + + return trg +} diff --git a/internal/character/luocha/talent.go b/internal/character/luocha/talent.go index ecaf65bb..755a76f4 100644 --- a/internal/character/luocha/talent.go +++ b/internal/character/luocha/talent.go @@ -10,13 +10,13 @@ import ( ) const ( - AbyssFlower = "luocha-Abyss-flower" - Field = "luocha-field" - FieldHeal = "luocha-field-heal" - A4 = "luocha-a4" - Insert = "luocha-insert" - InsertMark = "luocha-insert-mark" - DisableInsertMark = "luocha-disable-insert-mark" + AbyssFlower = "luocha-Abyss-flower" + Field = "luocha-field" + FieldHeal = "luocha-field-heal" + A4 = "luocha-a4" + TalentInsert = "luocha-talent-insert" + TalentInsertMark = "luocha-talent-insert-mark" + DisableTalentInsertMark = "luocha-disable-talent-insert-mark" ) type state struct { @@ -34,13 +34,13 @@ func (c *char) init() { }, }) - modifier.Register(InsertMark, modifier.Config{ + modifier.Register(TalentInsertMark, modifier.Config{ Listeners: modifier.Listeners{ - OnAdd: doInsert, + OnAdd: doTalentInsert, }, }) - modifier.Register(DisableInsertMark, modifier.Config{ + modifier.Register(DisableTalentInsertMark, modifier.Config{ Listeners: modifier.Listeners{}, }) @@ -62,19 +62,19 @@ func (c *char) init() { func checkStacks(mod *modifier.Instance) { if mod.Count() >= 2 { mod.Engine().AddModifier(mod.Source(), info.Modifier{ - Name: InsertMark, + Name: TalentInsertMark, Source: mod.Owner(), }) } } -func doInsert(mod *modifier.Instance) { - mod.Engine().RemoveModifier(mod.Source(), DisableInsertMark) +func doTalentInsert(mod *modifier.Instance) { + mod.Engine().RemoveModifier(mod.Source(), DisableTalentInsertMark) luo := mod.Owner() ci, _ := mod.Engine().CharacterInfo(luo) mod.Engine().InsertAbility(info.Insert{ - Key: Insert, + Key: TalentInsert, Priority: info.CharBuffSelf, Execute: func() { mod.Engine().AddModifier(mod.Source(), info.Modifier{ @@ -91,7 +91,7 @@ func doInsert(mod *modifier.Instance) { }) mod.Engine().RemoveModifier(mod.Source(), AbyssFlower) - mod.Engine().RemoveModifier(mod.Source(), InsertMark) + mod.Engine().RemoveModifier(mod.Source(), TalentInsertMark) } func addSubMods(mod *modifier.Instance) { From 4dd3caa7ecd690583d0a649acee04c67bd4ee1c2 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Tue, 1 Oct 2024 18:12:37 +0200 Subject: [PATCH 4/6] Finish implementation * refactor `Skill` and `InsertSkill` to use helper function `ExecuteSkill` * comment out IsInsert variable * simplify isMinHPRatio function * finish skill and talent * fix comments --- internal/character/luocha/skill.go | 179 +++++++++++++++------------- internal/character/luocha/talent.go | 34 ++++-- internal/character/luocha/ult.go | 8 +- 3 files changed, 125 insertions(+), 96 deletions(-) diff --git a/internal/character/luocha/skill.go b/internal/character/luocha/skill.go index a79d0082..83d0919b 100644 --- a/internal/character/luocha/skill.go +++ b/internal/character/luocha/skill.go @@ -18,7 +18,8 @@ const ( ) // this flag might be a global value, not sure what else it does -var IsInsert bool +// for now, this is commented out everywhere as there is no deeper functionality +// var IsInsert bool func init() { modifier.Register(InsertSkill, modifier.Config{}) @@ -49,74 +50,22 @@ func (c *char) initInsertSkill() { } func (c *char) Skill(target key.TargetID, state info.ActionState) { - // do A2 - if c.info.Traces["102"] { - c.engine.DispelStatus(target, info.Dispel{ - Status: model.StatusType_STATUS_DEBUFF, - Order: model.DispelOrder_LAST_ADDED, - Count: 1, - }) - } - - // do E2 - if c.info.Eidolon >= 2 { - if c.engine.HPRatio(target) < 0.5 { - c.engine.AddModifier(c.id, info.Modifier{ - Name: E2HealBoost, - Source: c.id, - }) - } else { - c.engine.AddModifier(target, info.Modifier{ - Name: E2Shield, - Source: c.id, - }) - } - } - - // heal target - c.engine.Heal(info.Heal{ - Key: Skill, - Targets: []key.TargetID{target}, - Source: c.id, - BaseHeal: info.HealMap{ - model.HealFormula_BY_HEALER_ATK: skillPer[c.info.SkillLevelIndex()], - }, - HealValue: skillFlat[c.info.SkillLevelIndex()], - }) - - c.engine.ModifyEnergy(info.ModifyAttribute{ - Key: Skill, - Target: c.id, - Source: c.id, - Amount: 30, - }) - - // add 1 stack of Abyss Flower if no Field active - if !c.engine.HasModifier(c.id, Field) { - c.engine.AddModifier(c.id, info.Modifier{ - Name: AbyssFlower, - Source: c.id, - }) - } - - // reset insert flag - if IsInsert { - IsInsert = false - } + // Uses helper function + ExecuteSkill(c.engine, c.id, target) } func (c *char) InsertSkillListener(e event.HPChange) { - // bypass if hp change is positive + // Bypass if hp change is positive if e.NewHP > e.OldHP { return } - // bypass if on cooldown + // Bypass if on cooldown if c.engine.HasModifier(c.id, InsertSkillCD) { return } - // bypass if CC'd or unable to act + // Bypass if CC'd or unable to act if c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_STAT_CTRL) { return } @@ -128,15 +77,15 @@ func (c *char) InsertSkillListener(e event.HPChange) { Targets: c.engine.Characters(), Filter: func(target key.TargetID) bool { // Filter conditions as bypasses - // bypass if HP at or below 0 + // Bypass if HP at or below 0 if c.engine.HPRatio(target) <= 0 { return false } - // bypass if BattleEventEntity (using workaround) + // Bypass if BattleEventEntity (using workaround) if !c.engine.IsCharacter(target) { return false } - // bypass if not lowest HP ratio + // Bypass if not lowest HP ratio if !isMinHPRatio(c.engine, target, c.engine.Characters()) { return false } @@ -146,7 +95,7 @@ func (c *char) InsertSkillListener(e event.HPChange) { }) if c.engine.HPRatio(trg[0]) <= 0.5 { - // apply another mod that applies another mod... + // Apply another mod that applies another mod... c.engine.AddModifier(c.id, info.Modifier{ Name: InsertSkillRetarget, Source: c.id, @@ -167,29 +116,30 @@ func doInsertSkill(mod *modifier.Instance) { mod.Engine().InsertAbility(info.Insert{ Key: InsertSkill, Execute: func() { - IsInsert = true + // IsInsert = true trg := doRetarget(mod) if mod.Engine().HPRatio(trg[0]) <= 0.5 { - // apply cooldown mod + // Apply cooldown mod mod.Engine().AddModifier(mod.Source(), info.Modifier{ Name: InsertSkillCD, Source: mod.Source(), Duration: 2, }) - // remove mark mod on all allies and retarget mod on source + // Remove mark mod on all allies and retarget mod on source for _, marktrg := range mod.Engine().Characters() { mod.Engine().RemoveModifier(marktrg, InsertSkillMark) } mod.Engine().RemoveModifier(mod.Source(), InsertSkillRetarget) - // do skill as insert + // Do skill as insert + ExecuteSkill(mod.Engine(), mod.Source(), trg[0]) } else { - // remove mark mod on all allies and retarget mod on source + // Remove mark mod on all allies and retarget mod on source for _, marktrg := range mod.Engine().Characters() { mod.Engine().RemoveModifier(marktrg, InsertSkillMark) } mod.Engine().RemoveModifier(mod.Source(), InsertSkillRetarget) - IsInsert = false + // IsInsert = false } }, Source: mod.Source(), @@ -199,16 +149,16 @@ func doInsertSkill(mod *modifier.Instance) { } func (c *char) onInsertFinish(e event.InsertEnd) { - // check if CC'd or unable to act + // Check if CC'd or unable to act cond1 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_STAT_CTRL) cond2 := c.engine.HasBehaviorFlag(c.id, model.BehaviorFlag_DISABLE_ACTION) if cond1 || cond2 { - // remove mark mod and retarget mod from all allies + // Remove mark mod and retarget mod from all allies for _, trg := range c.engine.Characters() { c.engine.RemoveModifier(trg, InsertSkillMark) c.engine.RemoveModifier(trg, InsertSkillRetarget) } - IsInsert = false + // IsInsert = false if c.engine.HasModifier(c.id, TalentInsertMark) { c.engine.RemoveModifier(c.id, TalentInsertMark) c.engine.AddModifier(c.id, info.Modifier{ @@ -219,42 +169,101 @@ func (c *char) onInsertFinish(e event.InsertEnd) { } } -// helper function for evaluating whether target is lowest HP ratio out of a list comparetargets +// Helper function for executing Skill through c.Skill and through doInsertSkill +func ExecuteSkill(engine engine.Engine, source key.TargetID, target key.TargetID) { + charInfo, _ := engine.CharacterInfo(source) + + // Do A2: Dispel debuff if applicable + if charInfo.Traces["102"] { + engine.DispelStatus(target, info.Dispel{ + Status: model.StatusType_STATUS_DEBUFF, + Order: model.DispelOrder_LAST_ADDED, + Count: 1, + }) + } + + // Do E2: Apply healing or shield based on target's HP ratio + if charInfo.Eidolon >= 2 { + if engine.HPRatio(target) < 0.5 { + engine.AddModifier(source, info.Modifier{ + Name: E2HealBoost, + Source: source, + }) + } else { + engine.AddModifier(target, info.Modifier{ + Name: E2Shield, + Source: source, + }) + } + } + + // Heal target + skillLevelIndex := charInfo.SkillLevelIndex() + engine.Heal(info.Heal{ + Key: Skill, + Targets: []key.TargetID{target}, + Source: source, + BaseHeal: info.HealMap{ + model.HealFormula_BY_HEALER_ATK: skillPer[skillLevelIndex], + }, + HealValue: skillFlat[skillLevelIndex], + }) + + // Modify energy + engine.ModifyEnergy(info.ModifyAttribute{ + Key: Skill, + Target: source, + Source: source, + Amount: 30, + }) + + // Add stack of Abyss Flower if no Field active + if !engine.HasModifier(source, Field) { + engine.AddModifier(source, info.Modifier{ + Name: AbyssFlower, + Source: source, + }) + } + + // Reset insert flag + // if IsInsert { + // IsInsert = false + // } +} + +// Helper function for evaluating whether target is lowest HP ratio out of a list comparetargets func isMinHPRatio(engine engine.Engine, target key.TargetID, comparetargets []key.TargetID) bool { - // bypass if empty list + // Bypass if empty list if len(comparetargets) == 0 { return false } // Start by assuming the first target has the minimum HP ratio - minTarget := comparetargets[0] - minHPRatio := engine.HPRatio(minTarget) + minHPRatio := engine.HPRatio(comparetargets[0]) // Loop through all the targets to find the one with the smallest HP ratio - for _, target := range comparetargets { - currentHPRatio := engine.HPRatio(target) - if currentHPRatio < minHPRatio { - minHPRatio = currentHPRatio - minTarget = target + for _, t := range comparetargets[1:] { + if engine.HPRatio(t) < minHPRatio { + minHPRatio = engine.HPRatio(t) } } // Compare target's HPRatio with the minimum found - return target == minTarget + return engine.HPRatio(target) == minHPRatio } -// helper function for small retarget (without BattleEvenEntity) +// Helper function for small retarget (without BattleEventEntity check) func doRetarget(mod *modifier.Instance) []key.TargetID { trg := mod.Engine().Retarget(info.Retarget{ Targets: mod.Engine().Characters(), Filter: func(target key.TargetID) bool { // Filter conditions as bypasses - // bypass if HP at or below 0 + // Bypass if HP at or below 0 if mod.Engine().HPRatio(target) <= 0 { return false } - // no check for BattleEventEntity - // bypass if not lowest HP ratio + // No check for BattleEventEntity + // Bypass if not lowest HP ratio if !isMinHPRatio(mod.Engine(), target, mod.Engine().Characters()) { return false } diff --git a/internal/character/luocha/talent.go b/internal/character/luocha/talent.go index 755a76f4..f4f1789e 100644 --- a/internal/character/luocha/talent.go +++ b/internal/character/luocha/talent.go @@ -41,7 +41,9 @@ func (c *char) init() { }) modifier.Register(DisableTalentInsertMark, modifier.Config{ - Listeners: modifier.Listeners{}, + Listeners: modifier.Listeners{ + OnAdd: modRemoveSubscribe, + }, }) modifier.Register(Field, modifier.Config{ @@ -95,7 +97,7 @@ func doTalentInsert(mod *modifier.Instance) { } func addSubMods(mod *modifier.Instance) { - // apply sub modifiers as normal modifiers + // Apply sub modifiers as normal modifiers st := mod.State().(state) ci, _ := mod.Engine().CharacterInfo(mod.Owner()) @@ -110,7 +112,7 @@ func addSubMods(mod *modifier.Instance) { }, }) - // E1 + // Do E1 mod.Engine().AddModifier(trg, info.Modifier{ Name: E1, Source: mod.Owner(), @@ -118,7 +120,7 @@ func addSubMods(mod *modifier.Instance) { }) } - // E4 + // Do E4 if ci.Eidolon >= 4 { for _, trg := range mod.Engine().Enemies() { mod.Engine().AddModifier(trg, info.Modifier{ @@ -131,7 +133,7 @@ func addSubMods(mod *modifier.Instance) { } func removeSubMods(mod *modifier.Instance) { - // remove sub modifiers with a workaround + // Remove sub modifiers with a workaround ci, _ := mod.Engine().CharacterInfo(mod.Owner()) for _, trg := range mod.Engine().Characters() { @@ -150,7 +152,7 @@ func removeSubMods(mod *modifier.Instance) { func doFieldHeal(mod *modifier.Instance, e event.AttackEnd) { st := mod.State().(state) - // heal self + // Heal self mod.Engine().Heal(info.Heal{ Key: FieldHeal, Targets: []key.TargetID{mod.Owner()}, @@ -161,7 +163,7 @@ func doFieldHeal(mod *modifier.Instance, e event.AttackEnd) { HealValue: st.talentFlat, }) - // heal other allies (A4) + // Do A4: heal other allies ci, _ := mod.Engine().CharacterInfo(mod.Source()) if ci.Traces["102"] { mod.Engine().Heal(info.Heal{ @@ -180,3 +182,21 @@ func doFieldHeal(mod *modifier.Instance, e event.AttackEnd) { }) } } + +// Function to mimic OnListenModifierRemove +func modRemoveSubscribe(mod *modifier.Instance) { + mod.Engine().Events().ModifierRemoved.Subscribe(func(event event.ModifierRemoved) { + if event.Target == mod.Source() { + cond1 := mod.Engine().HasBehaviorFlag(mod.Source(), model.BehaviorFlag_STAT_CTRL) + cond2 := mod.Engine().HasBehaviorFlag(mod.Source(), model.BehaviorFlag_DISABLE_ACTION) + // Bypass if CC'd or unable to act + if cond1 || cond2 { + return + } + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: TalentInsertMark, + Source: mod.Source(), + }) + } + }) +} diff --git a/internal/character/luocha/ult.go b/internal/character/luocha/ult.go index 24df7f87..01708b03 100644 --- a/internal/character/luocha/ult.go +++ b/internal/character/luocha/ult.go @@ -12,7 +12,7 @@ const ( ) func (c *char) Ult(target key.TargetID, state info.ActionState) { - // do E6 + // Do E6 if c.info.Eidolon >= 6 { for _, trg := range c.engine.Enemies() { c.engine.AddModifier(trg, info.Modifier{ @@ -23,7 +23,7 @@ func (c *char) Ult(target key.TargetID, state info.ActionState) { } } - // dispel 1 buff on all enemies + // Dispel 1 buff on all enemies for _, trg := range c.engine.Enemies() { c.engine.DispelStatus(trg, info.Dispel{ Status: model.StatusType_STATUS_BUFF, @@ -32,7 +32,7 @@ func (c *char) Ult(target key.TargetID, state info.ActionState) { }) } - // add 1 stack of Abyss Flower if no Field active + // Add 1 stack of Abyss Flower if no Field active if !c.engine.HasModifier(c.id, Field) { c.engine.AddModifier(c.id, info.Modifier{ Name: AbyssFlower, @@ -40,7 +40,7 @@ func (c *char) Ult(target key.TargetID, state info.ActionState) { }) } - // do damage + // Do damage c.engine.Attack(info.Attack{ Key: Ult, AttackType: model.AttackType_ULT, From e8fedac1cf17d883d0055b70bc6b66f09a14af5a Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Tue, 1 Oct 2024 19:15:12 +0200 Subject: [PATCH 5/6] Fix linter --- internal/character/luocha/skill.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/character/luocha/skill.go b/internal/character/luocha/skill.go index 83d0919b..245ea86e 100644 --- a/internal/character/luocha/skill.go +++ b/internal/character/luocha/skill.go @@ -132,7 +132,6 @@ func doInsertSkill(mod *modifier.Instance) { mod.Engine().RemoveModifier(mod.Source(), InsertSkillRetarget) // Do skill as insert ExecuteSkill(mod.Engine(), mod.Source(), trg[0]) - } else { // Remove mark mod on all allies and retarget mod on source for _, marktrg := range mod.Engine().Characters() { @@ -170,7 +169,7 @@ func (c *char) onInsertFinish(e event.InsertEnd) { } // Helper function for executing Skill through c.Skill and through doInsertSkill -func ExecuteSkill(engine engine.Engine, source key.TargetID, target key.TargetID) { +func ExecuteSkill(engine engine.Engine, source, target key.TargetID) { charInfo, _ := engine.CharacterInfo(source) // Do A2: Dispel debuff if applicable From 94cc0a5bd6d7cfe8af0b6d877949c097ab9d89c9 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 24 Nov 2024 18:30:30 +0100 Subject: [PATCH 6/6] Update with `CanDispel` field --- internal/character/luocha/eidolon.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/character/luocha/eidolon.go b/internal/character/luocha/eidolon.go index c94c1e0c..3c602688 100644 --- a/internal/character/luocha/eidolon.go +++ b/internal/character/luocha/eidolon.go @@ -32,8 +32,8 @@ func init() { modifier.Register(E2Shield, modifier.Config{ Stacking: modifier.Replace, StatusType: model.StatusType_STATUS_BUFF, - // CanDispel: true, - Duration: 2, + CanDispel: true, + Duration: 2, Listeners: modifier.Listeners{ OnAdd: func(mod *modifier.Instance) { mod.Engine().AddShield(E2Shield, info.Shield{ @@ -58,8 +58,8 @@ func init() { modifier.Register(E6, modifier.Config{ Stacking: modifier.ReplaceBySource, StatusType: model.StatusType_STATUS_DEBUFF, - // CanDispel: true, - Duration: 2, + CanDispel: true, + Duration: 2, }) }