From bdd148b45703bad9ac3e6498e93973109a6343b6 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 6 Oct 2024 00:07:03 +0200 Subject: [PATCH 01/10] Implement Ruan Mei * init + skeleton + data * implement stats, attack * implement (unfinished) skill, talent Additional notes: * merged some modifiers while keeping same functionality for sim --- internal/character/ruanmei/attack.go | 24 ++++ internal/character/ruanmei/data.go | 164 ++++++++++++++++++++++++ internal/character/ruanmei/eidolon.go | 3 + internal/character/ruanmei/ruanmei.go | 56 ++++++++ internal/character/ruanmei/skill.go | 73 +++++++++++ internal/character/ruanmei/stats.go | 100 +++++++++++++++ internal/character/ruanmei/talent.go | 154 ++++++++++++++++++++++ internal/character/ruanmei/technique.go | 10 ++ internal/character/ruanmei/ult.go | 10 ++ pkg/key/character.go | 1 + pkg/simulation/imports.go | 1 + 11 files changed, 596 insertions(+) create mode 100644 internal/character/ruanmei/attack.go create mode 100644 internal/character/ruanmei/data.go create mode 100644 internal/character/ruanmei/eidolon.go create mode 100644 internal/character/ruanmei/ruanmei.go create mode 100644 internal/character/ruanmei/skill.go create mode 100644 internal/character/ruanmei/stats.go create mode 100644 internal/character/ruanmei/talent.go create mode 100644 internal/character/ruanmei/technique.go create mode 100644 internal/character/ruanmei/ult.go diff --git a/internal/character/ruanmei/attack.go b/internal/character/ruanmei/attack.go new file mode 100644 index 00000000..3548f13f --- /dev/null +++ b/internal/character/ruanmei/attack.go @@ -0,0 +1,24 @@ +package ruanmei + +import ( + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/key" + "github.com/simimpact/srsim/pkg/model" +) + +const Normal key.Attack = "ruanmei-normal" + +func (c *char) Attack(target key.TargetID, state info.ActionState) { + c.engine.Attack(info.Attack{ + Key: Normal, + Source: c.id, + Targets: []key.TargetID{target}, + DamageType: model.DamageType_ICE, + AttackType: model.AttackType_NORMAL, + BaseDamage: info.DamageMap{ + model.DamageFormula_BY_ATK: atk[c.info.AttackLevelIndex()], + }, + StanceDamage: 30.0, + EnergyGain: 20.0, + }) +} diff --git a/internal/character/ruanmei/data.go b/internal/character/ruanmei/data.go new file mode 100644 index 00000000..ffbb88ae --- /dev/null +++ b/internal/character/ruanmei/data.go @@ -0,0 +1,164 @@ +// Code generated by "charstat"; DO NOT EDIT. + +package ruanmei + +import ( + "github.com/simimpact/srsim/pkg/engine/prop" + "github.com/simimpact/srsim/pkg/engine/target/character" +) + +var promotions = []character.PromotionData{ + { + MaxLevel: 20, + ATKBase: 89.76, + ATKAdd: 4.488, + DEFBase: 66, + DEFAdd: 3.3, + HPBase: 147.84, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 30, + ATKBase: 125.664, + ATKAdd: 4.488, + DEFBase: 92.4, + DEFAdd: 3.3, + HPBase: 206.976, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 40, + ATKBase: 161.568, + ATKAdd: 4.488, + DEFBase: 118.8, + DEFAdd: 3.3, + HPBase: 266.112, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 50, + ATKBase: 197.472, + ATKAdd: 4.488, + DEFBase: 145.2, + DEFAdd: 3.3, + HPBase: 325.248, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 60, + ATKBase: 233.376, + ATKAdd: 4.488, + DEFBase: 171.6, + DEFAdd: 3.3, + HPBase: 384.384, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 70, + ATKBase: 269.28, + ATKAdd: 4.488, + DEFBase: 198, + DEFAdd: 3.3, + HPBase: 443.52, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, + { + MaxLevel: 80, + ATKBase: 305.184, + ATKAdd: 4.488, + DEFBase: 224.4, + DEFAdd: 3.3, + HPBase: 502.656, + HPAdd: 7.392, + SPD: 104, + CritChance: 0.05, + CritDMG: 0.5, + Aggro: 100, + }, +} + +var traces = character.TraceMap{ + "101": { + Ascension: 2, + }, + "102": { + Ascension: 4, + }, + "103": { + Ascension: 6, + }, + "201": { + Stat: prop.BreakEffect, + Amount: 0.053, + Level: 1, + }, + "202": { + Stat: prop.DEFPercent, + Amount: 0.05, + Ascension: 2, + }, + "203": { + Stat: prop.BreakEffect, + Amount: 0.053, + Ascension: 3, + }, + "204": { + Stat: prop.SPDFlat, + Amount: 2, + Ascension: 3, + }, + "205": { + Stat: prop.BreakEffect, + Amount: 0.08, + Ascension: 4, + }, + "206": { + Stat: prop.DEFPercent, + Amount: 0.075, + Ascension: 5, + }, + "207": { + Stat: prop.BreakEffect, + Amount: 0.08, + Ascension: 5, + }, + "208": { + Stat: prop.SPDFlat, + Amount: 3, + Ascension: 6, + }, + "209": { + Stat: prop.DEFPercent, + Amount: 0.1, + Level: 75, + }, + "210": { + Stat: prop.BreakEffect, + Amount: 0.107, + Level: 80, + }, +} \ No newline at end of file diff --git a/internal/character/ruanmei/eidolon.go b/internal/character/ruanmei/eidolon.go new file mode 100644 index 00000000..71a4be91 --- /dev/null +++ b/internal/character/ruanmei/eidolon.go @@ -0,0 +1,3 @@ +package ruanmei + +func init() {} diff --git a/internal/character/ruanmei/ruanmei.go b/internal/character/ruanmei/ruanmei.go new file mode 100644 index 00000000..3ee74d62 --- /dev/null +++ b/internal/character/ruanmei/ruanmei.go @@ -0,0 +1,56 @@ +package ruanmei + +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.RuanMei, character.Config{ + Create: NewInstance, + Rarity: 5, + Element: model.DamageType_ICE, + Path: model.Path_HARMONY, + MaxEnergy: 130, + Promotions: promotions, + Traces: traces, + SkillInfo: character.SkillInfo{ + Attack: character.Attack{ + SPAdd: 1, + TargetType: model.TargetType_ENEMIES, + }, + Skill: character.Skill{ + SPNeed: 1, + TargetType: model.TargetType_SELF, + }, + Ult: character.Ult{ + TargetType: model.TargetType_ALLIES, + }, + 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, + } + + c.initTalent() + + return c +} diff --git a/internal/character/ruanmei/skill.go b/internal/character/ruanmei/skill.go new file mode 100644 index 00000000..eec272fa --- /dev/null +++ b/internal/character/ruanmei/skill.go @@ -0,0 +1,73 @@ +package ruanmei + +import ( + "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 ( + SkillMain = "ruanmei-skill" + OvertoneDmgBuff = "ruanmei-skill-overtone-dmg-buff" + OvertoneBreakEfficiency = "ruanmei-skill-overtone-weakness-break-efficiency" +) + +func init() { + modifier.Register(SkillMain, modifier.Config{ + Stacking: modifier.ReplaceBySource, + TickMoment: modifier.ModifierPhase1End, + Listeners: modifier.Listeners{ + OnAdd: addOvertone, + OnRemove: removeOvertone, + }, + }) + modifier.Register(OvertoneDmgBuff, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + }) + modifier.Register(OvertoneBreakEfficiency, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + }) +} + +func (c *char) Skill(target key.TargetID, state info.ActionState) { + c.engine.AddModifier(c.id, info.Modifier{ + Name: SkillMain, + Source: c.id, + }) +} + +// Overtone is summarized from 4 "sub" mods with 2 being purely for display to only 2 "sub" mods +func addOvertone(mod *modifier.Instance) { + rm, _ := mod.Engine().CharacterInfo(mod.Owner()) + dmgAmt := skillDmg[rm.SkillLevelIndex()] + if rm.Traces["103"] { + dmgAmtA6 := 0.06 * float64(int((mod.OwnerStats().BreakEffect()-1.2)/0.1)) + if dmgAmtA6 > 0.36 { + dmgAmtA6 = 0.36 + } + dmgAmt += dmgAmtA6 + } + for _, trg := range mod.Engine().Characters() { + mod.Engine().AddModifier(trg, info.Modifier{ + Name: OvertoneDmgBuff, + Source: mod.Owner(), + Stats: info.PropMap{prop.AllDamagePercent: dmgAmt}, + }) + mod.Engine().AddModifier(trg, info.Modifier{ + Name: OvertoneBreakEfficiency, + Source: mod.Owner(), + Stats: info.PropMap{prop.AllStanceDMGPercent: 0.5}, + }) + } +} + +func removeOvertone(mod *modifier.Instance) { + for _, trg := range mod.Engine().Characters() { + mod.Engine().RemoveModifier(trg, OvertoneDmgBuff) + mod.Engine().RemoveModifier(trg, OvertoneBreakEfficiency) + } +} diff --git a/internal/character/ruanmei/stats.go b/internal/character/ruanmei/stats.go new file mode 100644 index 00000000..520ac69a --- /dev/null +++ b/internal/character/ruanmei/stats.go @@ -0,0 +1,100 @@ +package ruanmei + +var ( + atk = []float64{ + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1.0, + 1.1, + 1.2, + 1.3, + } + skillDmg = []float64{ + 0.16, + 0.176, + 0.192, + 0.208, + 0.224, + 0.24, + 0.26, + 0.28, + 0.3, + 0.32, + 0.336, + 0.352, + 0.368, + 0.384, + 0.4, + } + ultResPen = []float64{ + 0.15, + 0.16, + 0.17, + 0.18, + 0.19, + 0.2, + 0.2125, + 0.225, + 0.2375, + 0.25, + 0.26, + 0.27, + 0.28, + 0.29, + 0.3, + } + ultBreakDamage = []float64{ + 0.3, + 0.32, + 0.34, + 0.36, + 0.38, + 0.4, + 0.425, + 0.45, + 0.475, + 0.5, + 0.52, + 0.54, + 0.56, + 0.58, + 0.6, + } + talentSpd = []float64{ + 0.08, + 0.082, + 0.084, + 0.086, + 0.088, + 0.09, + 0.0925, + 0.095, + 0.0975, + 0.1, + 0.102, + 0.104, + 0.106, + 0.108, + 0.11, + } + talentBreakDamage = []float64{ + 0.6, + 0.66, + 0.72, + 0.78, + 0.84, + 0.9, + 0.975, + 1.05, + 1.125, + 1.2, + 1.26, + 1.32, + 1.38, + 1.44, + 1.5, + } +) diff --git a/internal/character/ruanmei/talent.go b/internal/character/ruanmei/talent.go new file mode 100644 index 00000000..f6639bb3 --- /dev/null +++ b/internal/character/ruanmei/talent.go @@ -0,0 +1,154 @@ +package ruanmei + +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 ( + Talent = "ruanmei-talent" + TalentSpdBuff = "ruanmei-talent-spd-buff" + TalentBreakListener = "ruanmei-talent-break-listener" + UltResPen = "ruanmei-ult-res-pen" + A2 = "ruanmei-a2" + E2 = "ruanmei-e2" + E4 = "ruanmei-e4" + E4Listener = "ruanmei-e4-listener" +) + +func init() { + modifier.Register(Talent, modifier.Config{ + Listeners: modifier.Listeners{ + OnAdd: addSpdAndA2, + OnRemove: removeUltResPen, + }, + }) + modifier.Register(TalentBreakListener, modifier.Config{ + Stacking: modifier.ReplaceBySource, + Listeners: modifier.Listeners{ + OnBeingBreak: doTalentBreakDamage, + }, + }) + modifier.Register(UltResPen, modifier.Config{ + Stacking: modifier.Refresh, + }) + modifier.Register(A2, modifier.Config{ + StatusType: model.StatusType_STATUS_BUFF, + }) + modifier.Register(E2, modifier.Config{ + StatusType: model.StatusType_STATUS_BUFF, + Listeners: modifier.Listeners{ + OnBeforeHitAll: applyE2, + }, + }) + // E4 is summarized to 1 buff mod + modifier.Register(E4, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + // CanDispel: true, + }) + // Causes known bug of the breaking hit not benefitting from E4 as this should apply with OnBeforeBeingBreak + modifier.Register(E4Listener, modifier.Config{ + Listeners: modifier.Listeners{ + OnBeingBreak: func(mod *modifier.Instance) { + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: E4, + Source: mod.Source(), + Duration: 3, + }) + }, + }, + }) +} + +func (c *char) initTalent() { + c.engine.AddModifier(c.id, info.Modifier{ + Name: Talent, + Source: c.id, + }) + + // Add mods to upcoming allies + // Slightly inaccurate as this should: + // - trigger after any entity gets created, + // - check if it is part of allied team, + // - apply the mods to the target. + c.engine.Events().CharactersAdded.Subscribe( + func(event event.CharactersAdded) { + for _, trg := range c.engine.Characters() { + c.engine.AddModifier(trg, info.Modifier{ + Name: TalentSpdBuff, + Source: c.id, + Stats: info.PropMap{prop.SPDPercent: 0.1}, + }) + if c.info.Traces["101"] { + c.engine.AddModifier(trg, info.Modifier{ + Name: A2, + Source: c.id, + Stats: info.PropMap{prop.BreakEffect: 0.2}, + }) + } + if c.info.Eidolon >= 2 { + c.engine.AddModifier(trg, info.Modifier{ + Name: E2, + Source: c.id, + }) + } + } + }, + ) + + // Add mods to upcoming enemies + c.engine.Events().EnemiesAdded.Subscribe( + func(event event.EnemiesAdded) { + for _, trg := range c.engine.Enemies() { + if c.info.Eidolon >= 4 { + c.engine.AddModifier(trg, info.Modifier{ + Name: E4, + Source: c.id, + }) + } + c.engine.AddModifier(trg, info.Modifier{ + Name: TalentBreakListener, + Source: c.id, + }) + } + }, + ) + + // Remove TalentBreakListener when Ruan Mei dies +} + +// Add mods to existing allies +func addSpdAndA2(mod *modifier.Instance) { + for _, trg := range mod.Engine().Characters() { + if trg != mod.Owner() { + mod.Engine().AddModifier(trg, info.Modifier{ + Name: TalentSpdBuff, + Source: mod.Owner(), + Stats: info.PropMap{prop.SPDPercent: 0.1}, + }) + } + mod.Engine().AddModifier(trg, info.Modifier{ + Name: A2, + Source: mod.Owner(), + Stats: info.PropMap{prop.BreakEffect: 0.2}, + }) + } +} + +func removeUltResPen(mod *modifier.Instance) { + for _, trg := range mod.Engine().Characters() { + mod.Engine().RemoveModifier(trg, UltResPen) + } +} + +func doTalentBreakDamage(mod *modifier.Instance) { + +} + +func applyE2(mod *modifier.Instance, e event.HitStart) { + +} diff --git a/internal/character/ruanmei/technique.go b/internal/character/ruanmei/technique.go new file mode 100644 index 00000000..cda2ad79 --- /dev/null +++ b/internal/character/ruanmei/technique.go @@ -0,0 +1,10 @@ +package ruanmei + +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/ruanmei/ult.go b/internal/character/ruanmei/ult.go new file mode 100644 index 00000000..014f4a91 --- /dev/null +++ b/internal/character/ruanmei/ult.go @@ -0,0 +1,10 @@ +package ruanmei + +import ( + "github.com/simimpact/srsim/pkg/engine/info" + "github.com/simimpact/srsim/pkg/key" +) + +func (c *char) Ult(target key.TargetID, state info.ActionState) { + +} diff --git a/pkg/key/character.go b/pkg/key/character.go index be376802..1a137228 100644 --- a/pkg/key/character.go +++ b/pkg/key/character.go @@ -28,6 +28,7 @@ const ( March7th Character = "march7th" Seele Character = "seele" Huohuo Character = "huohuo" + RuanMei Character = "ruanmei" ) func (c Character) String() string { diff --git a/pkg/simulation/imports.go b/pkg/simulation/imports.go index 4a851c79..08a63d9c 100644 --- a/pkg/simulation/imports.go +++ b/pkg/simulation/imports.go @@ -21,6 +21,7 @@ import ( _ "github.com/simimpact/srsim/internal/character/natasha" _ "github.com/simimpact/srsim/internal/character/pela" _ "github.com/simimpact/srsim/internal/character/qingque" + _ "github.com/simimpact/srsim/internal/character/ruanmei" _ "github.com/simimpact/srsim/internal/character/sampo" _ "github.com/simimpact/srsim/internal/character/seele" _ "github.com/simimpact/srsim/internal/character/serval" From 29a8cdc9b5fa169845301ecac02f1245685a848d Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Mon, 7 Oct 2024 19:42:11 +0200 Subject: [PATCH 02/10] Continue implementation * finish skill, talent * add ult (unfinished), traces, eidolons * moved several modifiers and functions to trace.go/eidolon.go Additional notes: * merged some modifiers while keeping same functionality for sim * ult requires implementation of `AllDamagePEN` * technique unimplemented, requires changes in the core to be implemented properly --- internal/character/ruanmei/eidolon.go | 53 +++++++++++- internal/character/ruanmei/ruanmei.go | 2 +- internal/character/ruanmei/skill.go | 3 + internal/character/ruanmei/talent.go | 115 ++++++++++++++++---------- internal/character/ruanmei/trace.go | 72 ++++++++++++++++ internal/character/ruanmei/ult.go | 100 +++++++++++++++++++++- 6 files changed, 298 insertions(+), 47 deletions(-) create mode 100644 internal/character/ruanmei/trace.go diff --git a/internal/character/ruanmei/eidolon.go b/internal/character/ruanmei/eidolon.go index 71a4be91..62af02f3 100644 --- a/internal/character/ruanmei/eidolon.go +++ b/internal/character/ruanmei/eidolon.go @@ -1,3 +1,54 @@ package ruanmei -func init() {} +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 = "ruanmei-e1" + E2 = "ruanmei-e2" + E4 = "ruanmei-e4" + E4Listener = "ruanmei-e4-listener" +) + +func init() { + modifier.Register(E1, modifier.Config{ + Stacking: modifier.ReplaceBySource, + }) + modifier.Register(E2, modifier.Config{ + StatusType: model.StatusType_STATUS_BUFF, + Listeners: modifier.Listeners{ + OnBeforeHitAll: applyE2, + }, + }) + // E4 is summarized to 1 buff mod + modifier.Register(E4, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + // CanDispel: true, + }) + // Causes known bug (?) of the breaking hit not benefitting from E4 as this should apply with OnBeforeBeingBreak + modifier.Register(E4Listener, modifier.Config{ + Listeners: modifier.Listeners{ + OnBeingBreak: func(mod *modifier.Instance) { + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: E4, + Source: mod.Source(), + Stats: info.PropMap{prop.BreakEffect: 1}, + Duration: 3, + }) + }, + }, + }) +} + +func applyE2(mod *modifier.Instance, e event.HitStart) { + // Needs to check for Break flag (uses workaround for now) + if mod.Engine().Stance(mod.Owner()) == 0 { + e.Hit.Attacker.AddProperty(E2, prop.ATKPercent, 0.4) + } +} diff --git a/internal/character/ruanmei/ruanmei.go b/internal/character/ruanmei/ruanmei.go index 3ee74d62..85ca8dda 100644 --- a/internal/character/ruanmei/ruanmei.go +++ b/internal/character/ruanmei/ruanmei.go @@ -51,6 +51,6 @@ func NewInstance(engine engine.Engine, id key.TargetID, charInfo info.Character) } c.initTalent() - + c.initTraces() return c } diff --git a/internal/character/ruanmei/skill.go b/internal/character/ruanmei/skill.go index eec272fa..3159cc3d 100644 --- a/internal/character/ruanmei/skill.go +++ b/internal/character/ruanmei/skill.go @@ -42,6 +42,7 @@ func (c *char) Skill(target key.TargetID, state info.ActionState) { // Overtone is summarized from 4 "sub" mods with 2 being purely for display to only 2 "sub" mods func addOvertone(mod *modifier.Instance) { + // Calculate how much DMG Bonus should be given rm, _ := mod.Engine().CharacterInfo(mod.Owner()) dmgAmt := skillDmg[rm.SkillLevelIndex()] if rm.Traces["103"] { @@ -71,3 +72,5 @@ func removeOvertone(mod *modifier.Instance) { mod.Engine().RemoveModifier(trg, OvertoneBreakEfficiency) } } + +// Technique doing Insert with autocast Skill (without consuming SP) diff --git a/internal/character/ruanmei/talent.go b/internal/character/ruanmei/talent.go index f6639bb3..b1ababcc 100644 --- a/internal/character/ruanmei/talent.go +++ b/internal/character/ruanmei/talent.go @@ -5,6 +5,7 @@ import ( "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" ) @@ -12,56 +13,28 @@ const ( Talent = "ruanmei-talent" TalentSpdBuff = "ruanmei-talent-spd-buff" TalentBreakListener = "ruanmei-talent-break-listener" - UltResPen = "ruanmei-ult-res-pen" - A2 = "ruanmei-a2" - E2 = "ruanmei-e2" - E4 = "ruanmei-e4" - E4Listener = "ruanmei-e4-listener" + TalentBreak = "ruanmei-talent-break-damage" ) func init() { modifier.Register(Talent, modifier.Config{ Listeners: modifier.Listeners{ - OnAdd: addSpdAndA2, - OnRemove: removeUltResPen, + OnAdd: addSpdAndA2, + OnRemove: removeUltResPen, + OnBeforeDying: removeTalentA2E2, }, }) + modifier.Register(TalentSpdBuff, modifier.Config{ + Stacking: modifier.Refresh, + StatusType: model.StatusType_STATUS_BUFF, + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_SPEED_UP}, + }) modifier.Register(TalentBreakListener, modifier.Config{ Stacking: modifier.ReplaceBySource, Listeners: modifier.Listeners{ OnBeingBreak: doTalentBreakDamage, }, }) - modifier.Register(UltResPen, modifier.Config{ - Stacking: modifier.Refresh, - }) - modifier.Register(A2, modifier.Config{ - StatusType: model.StatusType_STATUS_BUFF, - }) - modifier.Register(E2, modifier.Config{ - StatusType: model.StatusType_STATUS_BUFF, - Listeners: modifier.Listeners{ - OnBeforeHitAll: applyE2, - }, - }) - // E4 is summarized to 1 buff mod - modifier.Register(E4, modifier.Config{ - Stacking: modifier.ReplaceBySource, - StatusType: model.StatusType_STATUS_BUFF, - // CanDispel: true, - }) - // Causes known bug of the breaking hit not benefitting from E4 as this should apply with OnBeforeBeingBreak - modifier.Register(E4Listener, modifier.Config{ - Listeners: modifier.Listeners{ - OnBeingBreak: func(mod *modifier.Instance) { - mod.Engine().AddModifier(mod.Source(), info.Modifier{ - Name: E4, - Source: mod.Source(), - Duration: 3, - }) - }, - }, - }) } func (c *char) initTalent() { @@ -100,13 +73,13 @@ func (c *char) initTalent() { }, ) - // Add mods to upcoming enemies + // Add mods to upcoming enemies (similar inaccuracy as above) c.engine.Events().EnemiesAdded.Subscribe( func(event event.EnemiesAdded) { for _, trg := range c.engine.Enemies() { if c.info.Eidolon >= 4 { c.engine.AddModifier(trg, info.Modifier{ - Name: E4, + Name: E4Listener, Source: c.id, }) } @@ -118,7 +91,40 @@ func (c *char) initTalent() { }, ) - // Remove TalentBreakListener when Ruan Mei dies + // Remove TalentBreakListener from all enemies when Ruan Mei dies + c.engine.Events().TargetDeath.Subscribe(func(event event.TargetDeath) { + if event.Target == c.id { + for _, trg := range c.engine.Enemies() { + c.engine.RemoveModifierFromSource(trg, c.id, TalentBreakListener) + } + } + }) + + // Add mods on BattleStart + c.engine.Events().BattleStart.Subscribe(func(event event.BattleStart) { + if c.info.Eidolon >= 2 { + for _, trg := range c.engine.Characters() { + c.engine.AddModifier(trg, info.Modifier{ + Name: E2, + Source: c.id, + }) + } + } + if c.info.Eidolon >= 4 { + for _, trg := range c.engine.Enemies() { + c.engine.AddModifier(trg, info.Modifier{ + Name: E4Listener, + Source: c.id, + }) + } + } + if c.info.Traces["103"] { + c.engine.AddModifier(c.id, info.Modifier{ + Name: A6, + Source: c.id, + }) + } + }) } // Add mods to existing allies @@ -141,14 +147,35 @@ func addSpdAndA2(mod *modifier.Instance) { func removeUltResPen(mod *modifier.Instance) { for _, trg := range mod.Engine().Characters() { - mod.Engine().RemoveModifier(trg, UltResPen) + mod.Engine().RemoveModifier(trg, UltResPenAlly) } } -func doTalentBreakDamage(mod *modifier.Instance) { - +func removeTalentA2E2(mod *modifier.Instance) { + for _, trg := range mod.Engine().Characters() { + mod.Engine().RemoveModifier(trg, TalentSpdBuff) + mod.Engine().RemoveModifier(trg, A2) + mod.Engine().RemoveModifier(trg, E2) + } } -func applyE2(mod *modifier.Instance, e event.HitStart) { +// Applies Talent's Break Damage without adding another mod +func doTalentBreakDamage(mod *modifier.Instance) { + // Team member check skipped as it is deemed unnecessary + rm, _ := mod.Engine().CharacterInfo(mod.Source()) + mult := talentBreakDamage[rm.TalentLevelIndex()] + if rm.Eidolon >= 6 { + mult += 2 + } + maxStanceMult := ((mod.Engine().MaxStance(mod.Owner()) / 30) + 2) / 4 + mod.Engine().Attack(info.Attack{ + Key: TalentBreak, + Targets: []key.TargetID{mod.Owner()}, + Source: mod.Source(), + DamageType: model.DamageType_ICE, + AttackType: model.AttackType_ELEMENT_DAMAGE, + BaseDamage: info.DamageMap{model.DamageFormula_BY_BREAK_DAMAGE: mult * maxStanceMult}, + AsPureDamage: true, + }) } diff --git a/internal/character/ruanmei/trace.go b/internal/character/ruanmei/trace.go new file mode 100644 index 00000000..ecc37b30 --- /dev/null +++ b/internal/character/ruanmei/trace.go @@ -0,0 +1,72 @@ +package ruanmei + +import ( + "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 ( + A2 = "ruanmei-a2" + A4 = "ruanmei-a4" + A6 = "ruanmei-a6" +) + +func init() { + modifier.Register(A2, modifier.Config{ + StatusType: model.StatusType_STATUS_BUFF, + }) + modifier.Register(A4, modifier.Config{ + Listeners: modifier.Listeners{ + OnPhase1: doA4, + }, + }) + modifier.Register(A6, modifier.Config{ + Listeners: modifier.Listeners{ + OnPropertyChange: checkA6, + }, + }) +} + +func (c *char) initTraces() { + if c.info.Traces["102"] { + c.engine.AddModifier(c.id, info.Modifier{ + Name: A4, + Source: c.id, + }) + } +} + +func doA4(mod *modifier.Instance) { + mod.Engine().ModifyEnergy(info.ModifyAttribute{ + Key: A4, + Target: mod.Source(), + Source: mod.Source(), + Amount: 5, + }) +} + +func checkA6(mod *modifier.Instance) { + // Calculate how much DMG Bonus should be given + rm, _ := mod.Engine().CharacterInfo(mod.Owner()) + dmgAmt := skillDmg[rm.SkillLevelIndex()] + if rm.Traces["103"] { + dmgAmtA6 := 0.06 * float64(int((mod.OwnerStats().BreakEffect()-1.2)/0.1)) + if dmgAmtA6 > 0.36 { + dmgAmtA6 = 0.36 + } + dmgAmt += dmgAmtA6 + } + + // Reapply Overtone's DMG Bonus Buff to all allies with the new amount + for _, trg := range mod.Engine().Characters() { + if mod.Engine().HasModifier(trg, OvertoneDmgBuff) { + mod.Engine().AddModifier(trg, info.Modifier{ + Name: OvertoneDmgBuff, + Source: mod.Owner(), + Stats: info.PropMap{prop.AllDamagePercent: dmgAmt}, + }) + } + } +} diff --git a/internal/character/ruanmei/ult.go b/internal/character/ruanmei/ult.go index 014f4a91..408195ea 100644 --- a/internal/character/ruanmei/ult.go +++ b/internal/character/ruanmei/ult.go @@ -2,9 +2,107 @@ package ruanmei import ( "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 ( + Ult = "ruanmei-ult" + UltResPenAlly = "ruanmei-ult-res-pen-ally" + UltBuffAlly = "ruanmei-ult-buff-ally" + UltDebuff = "ruanmei-thanatopium-rebloom" + UltDebuffCD = "ruanmei-thanatopium-rebloom-cooldown" +) + +func init() { + // 2 mods summarized to 1 mod that represents both logic and visual/Buff status type + modifier.Register(Ult, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + TickMoment: modifier.ModifierPhase1End, + Listeners: modifier.Listeners{ + OnAdd: addResPen, + OnRemove: removeUltMods, + }, + }) + modifier.Register(UltResPenAlly, modifier.Config{ + Stacking: modifier.Refresh, + }) + // Purely for visual/Buff status type + modifier.Register(UltBuffAlly, modifier.Config{ + Stacking: modifier.ReplaceBySource, + StatusType: model.StatusType_STATUS_BUFF, + }) + modifier.Register(UltDebuff, modifier.Config{ + Listeners: modifier.Listeners{ + // OnAllowAction: removeReset + // OnListenTurnEnd: removeSelf + // OnHPChange: removeCDAndSelf + OnEndBreak: doUltImprint, + // OnDispel: removeCD, + // OnBreakExtendAnim: doUltImprintWithoutRemove (??) + }, + }) + modifier.Register(UltDebuffCD, modifier.Config{ + Listeners: modifier.Listeners{ + OnLimboWaitHeal: func(mod *modifier.Instance) bool { + mod.RemoveSelf() + return false + }, + OnEndBreak: func(mod *modifier.Instance) { + mod.RemoveSelf() + }, + }, + }) +} + func (c *char) Ult(target key.TargetID, state info.ActionState) { - + durationUlt := 2 + if c.info.Eidolon >= 6 { + durationUlt += 1 + } + c.engine.AddModifier(c.id, info.Modifier{ + Name: Ult, + Source: c.id, + Duration: durationUlt, + State: ultResPen[c.info.UltLevelIndex()], + }) + if c.info.Eidolon >= 1 { + for _, trg := range c.engine.Characters() { + c.engine.AddModifier(trg, info.Modifier{ + Name: E1, + Source: c.id, + }) + } + } + // Subscribe to CharactersAdded for E1 and AttackEnd for Debuff +} + +// Need to implement AllDamagePEN +func addResPen(mod *modifier.Instance) { + for _, trg := range mod.Engine().Characters() { + if trg == mod.Owner() { + mod.AddProperty(prop.IcePEN, mod.State().(float64)) + } else { + mod.Engine().AddModifier(trg, info.Modifier{ + Name: UltResPenAlly, + Source: mod.Owner(), + Stats: info.PropMap{prop.IcePEN: mod.State().(float64)}, + }) + } + } +} + +func removeUltMods(mod *modifier.Instance) { + for _, trg := range mod.Engine().Characters() { + mod.Engine().RemoveModifier(trg, UltResPenAlly) + mod.Engine().RemoveModifier(trg, UltBuffAlly) + mod.Engine().RemoveModifier(trg, E1) + } +} + +func doUltImprint(mod *modifier.Instance) { + } From 35abd2fce91e8163dc220ec2574b3ef26e4a83c3 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sat, 19 Oct 2024 20:53:17 +0200 Subject: [PATCH 03/10] Continue Implementation (2) * continue ult Additional notes: * no implementation of `ImprintReset` logic because unknown * need to implement new listeners first --- internal/character/ruanmei/eidolon.go | 7 ++ internal/character/ruanmei/ruanmei.go | 1 + internal/character/ruanmei/ult.go | 95 ++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/internal/character/ruanmei/eidolon.go b/internal/character/ruanmei/eidolon.go index 62af02f3..c05ee323 100644 --- a/internal/character/ruanmei/eidolon.go +++ b/internal/character/ruanmei/eidolon.go @@ -18,6 +18,9 @@ const ( func init() { modifier.Register(E1, modifier.Config{ Stacking: modifier.ReplaceBySource, + Listeners: modifier.Listeners{ + OnBeforeHitAll: applyE1, + }, }) modifier.Register(E2, modifier.Config{ StatusType: model.StatusType_STATUS_BUFF, @@ -46,6 +49,10 @@ func init() { }) } +func applyE1(mod *modifier.Instance, e event.HitStart) { + e.Hit.Defender.AddProperty(E1, prop.DEFPercent, -0.2) +} + func applyE2(mod *modifier.Instance, e event.HitStart) { // Needs to check for Break flag (uses workaround for now) if mod.Engine().Stance(mod.Owner()) == 0 { diff --git a/internal/character/ruanmei/ruanmei.go b/internal/character/ruanmei/ruanmei.go index 85ca8dda..8a146ebd 100644 --- a/internal/character/ruanmei/ruanmei.go +++ b/internal/character/ruanmei/ruanmei.go @@ -52,5 +52,6 @@ func NewInstance(engine engine.Engine, id key.TargetID, charInfo info.Character) c.initTalent() c.initTraces() + c.initUlt() return c } diff --git a/internal/character/ruanmei/ult.go b/internal/character/ruanmei/ult.go index 408195ea..dec30a06 100644 --- a/internal/character/ruanmei/ult.go +++ b/internal/character/ruanmei/ult.go @@ -1,6 +1,7 @@ package ruanmei 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" @@ -27,22 +28,22 @@ func init() { OnRemove: removeUltMods, }, }) + // 2 mods summarized to 1 mod that represents both logic and visual/Buff status type modifier.Register(UltResPenAlly, modifier.Config{ Stacking: modifier.Refresh, }) - // Purely for visual/Buff status type + // Purely for visual/Buff status type for other allies modifier.Register(UltBuffAlly, modifier.Config{ Stacking: modifier.ReplaceBySource, StatusType: model.StatusType_STATUS_BUFF, }) modifier.Register(UltDebuff, modifier.Config{ Listeners: modifier.Listeners{ - // OnAllowAction: removeReset - // OnListenTurnEnd: removeSelf - // OnHPChange: removeCDAndSelf - OnEndBreak: doUltImprint, - // OnDispel: removeCD, - // OnBreakExtendAnim: doUltImprintWithoutRemove (??) + // OnAllowAction: removeReset, (unknown mechanic, will be ignored) + OnHPChange: removeCDAndSelf, + OnEndBreak: doUltImprintWithRemove, + // OnDispel: removeCD, (missing listener) + // OnBreakExtendAnim: doUltImprint, (missing listener) }, }) modifier.Register(UltDebuffCD, modifier.Config{ @@ -58,6 +59,55 @@ func init() { }) } +func (c *char) initUlt() { + if c.info.Eidolon >= 1 { + // Apply E1 to allies created while Ult is active + c.engine.Events().CharactersAdded.Subscribe(func(event event.CharactersAdded) { + for _, char := range event.Characters { + trg := char.ID + c.engine.AddModifier(trg, info.Modifier{ + Name: E1, + Source: c.id, + }) + } + }) + } + // Apply TR Debuff with AttackEnd while RM has Ult + c.engine.Events().AttackEnd.Subscribe(func(event event.AttackEnd) { + if !c.engine.IsCharacter(event.Attacker) { + return + } + if c.engine.HasModifier(c.id, Ult) { + for _, trg := range event.Targets { + c.engine.AddModifier(trg, info.Modifier{ + Name: UltDebuff, + Source: c.id, + }) + c.engine.AddModifier(trg, info.Modifier{ + Name: UltDebuffCD, + Source: c.id, + }) + } + } + }) + // Remove UltDebuff after it procs its damage + c.engine.Events().TurnEnd.Subscribe(func(event event.TurnEnd) { + for _, trg := range c.engine.Enemies() { + if c.engine.HasModifierFromSource(trg, c.id, UltDebuff) { + // Get UltDebuff's dynamic value + } + } + }) + // Remove UltDebuff from all enemies if RM dies + c.engine.Events().TargetDeath.Subscribe(func(event event.TargetDeath) { + if event.Target == c.id { + for _, trg := range c.engine.Enemies() { + c.engine.RemoveModifierFromSource(trg, c.id, UltDebuff) + } + } + }) +} + func (c *char) Ult(target key.TargetID, state info.ActionState) { durationUlt := 2 if c.info.Eidolon >= 6 { @@ -77,10 +127,15 @@ func (c *char) Ult(target key.TargetID, state info.ActionState) { }) } } - // Subscribe to CharactersAdded for E1 and AttackEnd for Debuff + for _, trg := range c.engine.Characters() { + c.engine.AddModifier(trg, info.Modifier{ + Name: UltBuffAlly, + Source: c.id, + }) + } } -// Need to implement AllDamagePEN +// Missing AllDamagePEN func addResPen(mod *modifier.Instance) { for _, trg := range mod.Engine().Characters() { if trg == mod.Owner() { @@ -103,6 +158,28 @@ func removeUltMods(mod *modifier.Instance) { } } +func removeCDAndSelf(mod *modifier.Instance, e event.HPChange) { + if mod.Engine().HPRatio(mod.Owner()) == 0 { + mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) + mod.RemoveSelf() + } +} + +func doUltImprintWithRemove(mod *modifier.Instance) { + doUltImprint(mod) + mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) + mod.RemoveSelf() +} + func doUltImprint(mod *modifier.Instance) { + mod.Engine().InsertAbility(info.Insert{ + Key: UltDebuff, + Execute: func() { + }, + Source: mod.Source(), + Priority: info.CharInsertAttackSelf, + }) } + +// Need to do logic for "..._Count" dynamic value (declare inside UltDebuff) From 47a6987912bd5f1416d1a718e7055d7c53f7ba16 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 20 Oct 2024 19:12:19 +0200 Subject: [PATCH 04/10] Finish Implementation * finish ult Additional notes: * known bug: if dispelled, CD mod won't be removed (requires `OnDispel` listener) * `ImprintReset` logic unimplemented since no functionality has been discovered * waiting for implementation of `AllDamagePEN` prop * waiting for implementation of `BreakExtend` --- internal/character/ruanmei/talent.go | 6 +-- internal/character/ruanmei/ult.go | 62 ++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/internal/character/ruanmei/talent.go b/internal/character/ruanmei/talent.go index b1ababcc..6c2452ab 100644 --- a/internal/character/ruanmei/talent.go +++ b/internal/character/ruanmei/talent.go @@ -46,8 +46,8 @@ func (c *char) initTalent() { // Add mods to upcoming allies // Slightly inaccurate as this should: // - trigger after any entity gets created, - // - check if it is part of allied team, - // - apply the mods to the target. + // - check if the target is part of allied team, + // - apply the relevant mods to the target. c.engine.Events().CharactersAdded.Subscribe( func(event event.CharactersAdded) { for _, trg := range c.engine.Characters() { @@ -159,7 +159,7 @@ func removeTalentA2E2(mod *modifier.Instance) { } } -// Applies Talent's Break Damage without adding another mod +// Applies Talent's Break Damage directly, without adding another mod func doTalentBreakDamage(mod *modifier.Instance) { // Team member check skipped as it is deemed unnecessary diff --git a/internal/character/ruanmei/ult.go b/internal/character/ruanmei/ult.go index dec30a06..7ea4df92 100644 --- a/internal/character/ruanmei/ult.go +++ b/internal/character/ruanmei/ult.go @@ -42,8 +42,9 @@ func init() { // OnAllowAction: removeReset, (unknown mechanic, will be ignored) OnHPChange: removeCDAndSelf, OnEndBreak: doUltImprintWithRemove, - // OnDispel: removeCD, (missing listener) - // OnBreakExtendAnim: doUltImprint, (missing listener) + // OnDispel: removeCD, (Missing OnDispel listener) + // OnBreakExtendAnim: doUltImprint, (Missing OnBreakExtend listener) + // Missing BreakExtend flag implementation }, }) modifier.Register(UltDebuffCD, modifier.Config{ @@ -72,16 +73,19 @@ func (c *char) initUlt() { } }) } - // Apply TR Debuff with AttackEnd while RM has Ult + + // Apply UltDebuff with AttackEnd while RM has Ult c.engine.Events().AttackEnd.Subscribe(func(event event.AttackEnd) { if !c.engine.IsCharacter(event.Attacker) { return } if c.engine.HasModifier(c.id, Ult) { for _, trg := range event.Targets { + removeThisTurn := false c.engine.AddModifier(trg, info.Modifier{ Name: UltDebuff, Source: c.id, + State: &removeThisTurn, }) c.engine.AddModifier(trg, info.Modifier{ Name: UltDebuffCD, @@ -90,15 +94,25 @@ func (c *char) initUlt() { } } }) - // Remove UltDebuff after it procs its damage + + // Remove UltDebuff if Imprint Damage was during this turn c.engine.Events().TurnEnd.Subscribe(func(event event.TurnEnd) { for _, trg := range c.engine.Enemies() { if c.engine.HasModifierFromSource(trg, c.id, UltDebuff) { // Get UltDebuff's dynamic value + mod := c.engine.GetModifiers(trg, UltDebuff)[0] + removeThisTurn, ok := mod.State.(*bool) + if !ok { + panic("expected *bool for State") + } + if *removeThisTurn { + *removeThisTurn = false + c.engine.RemoveModifier(trg, UltDebuff) + } } } }) - // Remove UltDebuff from all enemies if RM dies + // Remove UltDebuff when RM dies c.engine.Events().TargetDeath.Subscribe(func(event event.TargetDeath) { if event.Target == c.id { for _, trg := range c.engine.Enemies() { @@ -172,14 +186,48 @@ func doUltImprintWithRemove(mod *modifier.Instance) { } func doUltImprint(mod *modifier.Instance) { + removeThisTurn, ok := mod.State().(*bool) + if !ok { + panic("expected *bool for mod.State()") + } + *removeThisTurn = true + + // "Custom event" + rm, _ := mod.Engine().CharacterInfo(mod.Source()) + delayAmt := mod.Engine().Stats(mod.Source()).BreakEffect()*0.2 + 0.1 + mult := ultBreakDamage[rm.UltLevelIndex()] + maxStanceMult := ((mod.Engine().MaxStance(mod.Owner()) / 30) + 2) / 4 + mod.Engine().SetCurrentGaugeCost(info.ModifyCurrentGaugeCost{ + Key: UltDebuff, + Source: mod.Source(), + Amount: -1, + }) + mod.Engine().InsertAbility(info.Insert{ Key: UltDebuff, Execute: func() { - + mod.Engine().Attack(info.Attack{ + Key: UltDebuff, + Targets: []key.TargetID{mod.Owner()}, + Source: mod.Source(), + DamageType: model.DamageType_ICE, + AttackType: model.AttackType_ELEMENT_DAMAGE, + BaseDamage: info.DamageMap{model.DamageFormula_BY_BREAK_DAMAGE: mult * maxStanceMult}, + AsPureDamage: true, + }) }, Source: mod.Source(), Priority: info.CharInsertAttackSelf, }) + + mod.Engine().ModifyGaugeNormalized(info.ModifyAttribute{ + Key: UltDebuff, + Target: mod.Owner(), + Source: mod.Source(), + Amount: delayAmt, + }) } -// Need to do logic for "..._Count" dynamic value (declare inside UltDebuff) +func removeCD(mod *modifier.Instance) { + mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) +} From ac3ced40c5ff604e02c121774038964740aba869 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 20 Oct 2024 22:39:31 +0200 Subject: [PATCH 05/10] Change to `AllDamagePEN` --- internal/character/ruanmei/ult.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/character/ruanmei/ult.go b/internal/character/ruanmei/ult.go index 7ea4df92..a7e054dc 100644 --- a/internal/character/ruanmei/ult.go +++ b/internal/character/ruanmei/ult.go @@ -149,16 +149,15 @@ func (c *char) Ult(target key.TargetID, state info.ActionState) { } } -// Missing AllDamagePEN func addResPen(mod *modifier.Instance) { for _, trg := range mod.Engine().Characters() { if trg == mod.Owner() { - mod.AddProperty(prop.IcePEN, mod.State().(float64)) + mod.AddProperty(prop.AllDamagePEN, mod.State().(float64)) } else { mod.Engine().AddModifier(trg, info.Modifier{ Name: UltResPenAlly, Source: mod.Owner(), - Stats: info.PropMap{prop.IcePEN: mod.State().(float64)}, + Stats: info.PropMap{prop.AllDamagePEN: mod.State().(float64)}, }) } } From 143f028cd869077f9c1a5fc69ea2518446a0e3a2 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sat, 23 Nov 2024 22:21:39 +0100 Subject: [PATCH 06/10] Update `UltDebuff` and E4 * add `OnDispel` listener * add `BreakExtend` listener * add `AbortFlags` to ult insert * set `CanDispel` to true --- internal/character/ruanmei/eidolon.go | 2 +- internal/character/ruanmei/technique.go | 4 +-- internal/character/ruanmei/ult.go | 38 +++++++++++-------------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/internal/character/ruanmei/eidolon.go b/internal/character/ruanmei/eidolon.go index c05ee323..12d547de 100644 --- a/internal/character/ruanmei/eidolon.go +++ b/internal/character/ruanmei/eidolon.go @@ -32,7 +32,7 @@ func init() { modifier.Register(E4, modifier.Config{ Stacking: modifier.ReplaceBySource, StatusType: model.StatusType_STATUS_BUFF, - // CanDispel: true, + CanDispel: true, }) // Causes known bug (?) of the breaking hit not benefitting from E4 as this should apply with OnBeforeBeingBreak modifier.Register(E4Listener, modifier.Config{ diff --git a/internal/character/ruanmei/technique.go b/internal/character/ruanmei/technique.go index cda2ad79..0218bd66 100644 --- a/internal/character/ruanmei/technique.go +++ b/internal/character/ruanmei/technique.go @@ -5,6 +5,4 @@ import ( "github.com/simimpact/srsim/pkg/key" ) -func (c *char) Technique(target key.TargetID, state info.ActionState) { - -} +func (c *char) Technique(target key.TargetID, state info.ActionState) {} diff --git a/internal/character/ruanmei/ult.go b/internal/character/ruanmei/ult.go index a7e054dc..ea0472d1 100644 --- a/internal/character/ruanmei/ult.go +++ b/internal/character/ruanmei/ult.go @@ -38,13 +38,14 @@ func init() { StatusType: model.StatusType_STATUS_BUFF, }) modifier.Register(UltDebuff, modifier.Config{ + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_BREAK_EXTEND}, + CanDispel: true, Listeners: modifier.Listeners{ // OnAllowAction: removeReset, (unknown mechanic, will be ignored) - OnHPChange: removeCDAndSelf, - OnEndBreak: doUltImprintWithRemove, - // OnDispel: removeCD, (Missing OnDispel listener) - // OnBreakExtendAnim: doUltImprint, (Missing OnBreakExtend listener) - // Missing BreakExtend flag implementation + OnHPChange: removeCDAndSelf, + OnEndBreak: doUltImprintWithRemove, + OnDispel: removeCD, + OnBreakExtend: doUltImprint, }, }) modifier.Register(UltDebuffCD, modifier.Config{ @@ -101,10 +102,7 @@ func (c *char) initUlt() { if c.engine.HasModifierFromSource(trg, c.id, UltDebuff) { // Get UltDebuff's dynamic value mod := c.engine.GetModifiers(trg, UltDebuff)[0] - removeThisTurn, ok := mod.State.(*bool) - if !ok { - panic("expected *bool for State") - } + removeThisTurn := mod.State.(*bool) if *removeThisTurn { *removeThisTurn = false c.engine.RemoveModifier(trg, UltDebuff) @@ -173,11 +171,15 @@ func removeUltMods(mod *modifier.Instance) { func removeCDAndSelf(mod *modifier.Instance, e event.HPChange) { if mod.Engine().HPRatio(mod.Owner()) == 0 { - mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) + removeCD(mod) mod.RemoveSelf() } } +func removeCD(mod *modifier.Instance) { + mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) +} + func doUltImprintWithRemove(mod *modifier.Instance) { doUltImprint(mod) mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) @@ -185,13 +187,10 @@ func doUltImprintWithRemove(mod *modifier.Instance) { } func doUltImprint(mod *modifier.Instance) { - removeThisTurn, ok := mod.State().(*bool) - if !ok { - panic("expected *bool for mod.State()") - } + removeThisTurn := mod.State().(*bool) *removeThisTurn = true - // "Custom event" + // Assumed to happen before insert rm, _ := mod.Engine().CharacterInfo(mod.Source()) delayAmt := mod.Engine().Stats(mod.Source()).BreakEffect()*0.2 + 0.1 mult := ultBreakDamage[rm.UltLevelIndex()] @@ -215,8 +214,9 @@ func doUltImprint(mod *modifier.Instance) { AsPureDamage: true, }) }, - Source: mod.Source(), - Priority: info.CharInsertAttackSelf, + Source: mod.Source(), + AbortFlags: nil, + Priority: info.CharInsertAttackSelf, }) mod.Engine().ModifyGaugeNormalized(info.ModifyAttribute{ @@ -226,7 +226,3 @@ func doUltImprint(mod *modifier.Instance) { Amount: delayAmt, }) } - -func removeCD(mod *modifier.Instance) { - mod.Engine().RemoveModifier(mod.Owner(), UltDebuffCD) -} From c4ec6dd40d1889a40be5b39c27055d2c78b38220 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sat, 23 Nov 2024 22:42:11 +0100 Subject: [PATCH 07/10] Fix `TalentSpdBuff` to use correct value --- internal/character/ruanmei/talent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/character/ruanmei/talent.go b/internal/character/ruanmei/talent.go index 6c2452ab..95801b09 100644 --- a/internal/character/ruanmei/talent.go +++ b/internal/character/ruanmei/talent.go @@ -54,7 +54,7 @@ func (c *char) initTalent() { c.engine.AddModifier(trg, info.Modifier{ Name: TalentSpdBuff, Source: c.id, - Stats: info.PropMap{prop.SPDPercent: 0.1}, + Stats: info.PropMap{prop.SPDPercent: talentSpd[c.info.TalentLevelIndex()]}, }) if c.info.Traces["101"] { c.engine.AddModifier(trg, info.Modifier{ From 6b9d5dbb0f64d12516934239d1ff456939627eaf Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 24 Nov 2024 13:08:40 +0100 Subject: [PATCH 08/10] Update E2 and E4 * change E2 to check for `Break` flag * change `E4Listener` to listen to `OnBeforeBeingBreak` * refactor E4 application to helper function --- internal/character/ruanmei/eidolon.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/internal/character/ruanmei/eidolon.go b/internal/character/ruanmei/eidolon.go index 12d547de..54293cc6 100644 --- a/internal/character/ruanmei/eidolon.go +++ b/internal/character/ruanmei/eidolon.go @@ -37,14 +37,7 @@ func init() { // Causes known bug (?) of the breaking hit not benefitting from E4 as this should apply with OnBeforeBeingBreak modifier.Register(E4Listener, modifier.Config{ Listeners: modifier.Listeners{ - OnBeingBreak: func(mod *modifier.Instance) { - mod.Engine().AddModifier(mod.Source(), info.Modifier{ - Name: E4, - Source: mod.Source(), - Stats: info.PropMap{prop.BreakEffect: 1}, - Duration: 3, - }) - }, + OnBeforeBeingBreak: addE4, }, }) } @@ -54,8 +47,16 @@ func applyE1(mod *modifier.Instance, e event.HitStart) { } func applyE2(mod *modifier.Instance, e event.HitStart) { - // Needs to check for Break flag (uses workaround for now) - if mod.Engine().Stance(mod.Owner()) == 0 { + if mod.Engine().HasBehaviorFlag(e.Defender, model.BehaviorFlag_BREAK) { e.Hit.Attacker.AddProperty(E2, prop.ATKPercent, 0.4) } } + +func addE4(mod *modifier.Instance) { + mod.Engine().AddModifier(mod.Source(), info.Modifier{ + Name: E4, + Source: mod.Source(), + Stats: info.PropMap{prop.BreakEffect: 1}, + Duration: 3, + }) +} From f9c862ffb6cf1fc24e3a11631dcff1f6855fe03a Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 24 Nov 2024 13:35:20 +0100 Subject: [PATCH 09/10] Update mods with RWSD flag * update `TalentBreakListener` and `UltDebuff` to use `RemoveWhenSourceDead` flag instead of doing subscription to `TargetDeath` event --- internal/character/ruanmei/talent.go | 12 ++---------- internal/character/ruanmei/ult.go | 9 +-------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/internal/character/ruanmei/talent.go b/internal/character/ruanmei/talent.go index 95801b09..d673ff8b 100644 --- a/internal/character/ruanmei/talent.go +++ b/internal/character/ruanmei/talent.go @@ -30,7 +30,8 @@ func init() { BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_STAT_SPEED_UP}, }) modifier.Register(TalentBreakListener, modifier.Config{ - Stacking: modifier.ReplaceBySource, + Stacking: modifier.ReplaceBySource, + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_REMOVE_WHEN_SOURCE_DEAD}, Listeners: modifier.Listeners{ OnBeingBreak: doTalentBreakDamage, }, @@ -91,15 +92,6 @@ func (c *char) initTalent() { }, ) - // Remove TalentBreakListener from all enemies when Ruan Mei dies - c.engine.Events().TargetDeath.Subscribe(func(event event.TargetDeath) { - if event.Target == c.id { - for _, trg := range c.engine.Enemies() { - c.engine.RemoveModifierFromSource(trg, c.id, TalentBreakListener) - } - } - }) - // Add mods on BattleStart c.engine.Events().BattleStart.Subscribe(func(event event.BattleStart) { if c.info.Eidolon >= 2 { diff --git a/internal/character/ruanmei/ult.go b/internal/character/ruanmei/ult.go index ea0472d1..68128305 100644 --- a/internal/character/ruanmei/ult.go +++ b/internal/character/ruanmei/ult.go @@ -49,6 +49,7 @@ func init() { }, }) modifier.Register(UltDebuffCD, modifier.Config{ + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_REMOVE_WHEN_SOURCE_DEAD}, Listeners: modifier.Listeners{ OnLimboWaitHeal: func(mod *modifier.Instance) bool { mod.RemoveSelf() @@ -110,14 +111,6 @@ func (c *char) initUlt() { } } }) - // Remove UltDebuff when RM dies - c.engine.Events().TargetDeath.Subscribe(func(event event.TargetDeath) { - if event.Target == c.id { - for _, trg := range c.engine.Enemies() { - c.engine.RemoveModifierFromSource(trg, c.id, UltDebuff) - } - } - }) } func (c *char) Ult(target key.TargetID, state info.ActionState) { From 6e0b68fb522b1470ffadad8c482df96c605e4299 Mon Sep 17 00:00:00 2001 From: kdovtdc Date: Sun, 24 Nov 2024 18:37:30 +0100 Subject: [PATCH 10/10] Fix `E4Listener` to use correct Stacking and Flag --- internal/character/ruanmei/eidolon.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/character/ruanmei/eidolon.go b/internal/character/ruanmei/eidolon.go index 54293cc6..d7852c36 100644 --- a/internal/character/ruanmei/eidolon.go +++ b/internal/character/ruanmei/eidolon.go @@ -36,6 +36,8 @@ func init() { }) // Causes known bug (?) of the breaking hit not benefitting from E4 as this should apply with OnBeforeBeingBreak modifier.Register(E4Listener, modifier.Config{ + Stacking: modifier.ReplaceBySource, + BehaviorFlags: []model.BehaviorFlag{model.BehaviorFlag_REMOVE_WHEN_SOURCE_DEAD}, Listeners: modifier.Listeners{ OnBeforeBeingBreak: addE4, },