diff --git a/internal/characters/lisa/abil.go b/internal/characters/lisa/abil.go index d14690110..b8d15be2d 100644 --- a/internal/characters/lisa/abil.go +++ b/internal/characters/lisa/abil.go @@ -6,6 +6,8 @@ import ( "github.com/genshinsim/gcsim/pkg/core" ) +var hitmarks = []int{26, 18, 17, 31} + func (c *char) Attack(p map[string]int) (int, int) { f, a := c.ActionFrames(core.ActionAttack, p) @@ -20,7 +22,8 @@ func (c *char) Attack(p map[string]int) (int, int) { Mult: attack[c.NormalCounter][c.TalentLvlAttack()], } - c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(1, core.TargettableEnemy), 0, f-1) + //todo: Does it really snapshot immediately? + c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(1, core.TargettableEnemy), 0, hitmarks[c.NormalCounter]) c.AdvanceNormalIndex() @@ -57,11 +60,13 @@ func (c *char) ChargeAttack(p map[string]int) (int, int) { done = true } - c.Core.Combat.QueueAttack(ai, core.NewDefCircHit(0.1, false, core.TargettableEnemy), 0, f-1, cb) + c.Core.Combat.QueueAttack(ai, core.NewDefCircHit(0.1, false, core.TargettableEnemy), 0, f, cb) return f, a } +var skillHitmarks = []int{22, 117} + //p = 0 for no hold, p = 1 for hold func (c *char) Skill(p map[string]int) (int, int) { hold := p["hold"] @@ -97,13 +102,13 @@ func (c *char) skillPress(p map[string]int) (int, int) { done = true } - c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(1, core.TargettableEnemy), 0, f-1, cb) + c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(1, core.TargettableEnemy), 0, skillHitmarks[0], cb) if c.Core.Rand.Float64() < 0.5 { c.QueueParticle("Lisa", 1, core.Electro, f+100) } - c.SetCD(core.ActionSkill, 60) + c.SetCDWithDelay(core.ActionSkill, 60, 17) return f, a } @@ -146,7 +151,7 @@ func (c *char) skillHold(p map[string]int) (int, int) { //[8:31 PM] ArchedNosi | Lisa Unleashed: yeah 4-5 50/50 with Hold //[9:13 PM] ArchedNosi | Lisa Unleashed: @gimmeabreak actually wait, xd i noticed i misread my sheet, Lisa Hold E always gens 5 orbs - c.Core.Combat.QueueAttack(ai, core.NewDefCircHit(3, false, core.TargettableEnemy), 0, f, c1cb) + c.Core.Combat.QueueAttack(ai, core.NewDefCircHit(3, false, core.TargettableEnemy), 0, skillHitmarks[1], c1cb) // count := 4 // if c.Core.Rand.Float64() < 0.5 { @@ -154,8 +159,8 @@ func (c *char) skillHold(p map[string]int) (int, int) { // } c.QueueParticle("Lisa", 5, core.Electro, f+100) - // c.CD[def.SkillCD] = c.Core.F + 960 //16seconds - c.SetCD(core.ActionSkill, 960) + // c.CD[def.SkillCD] = c.Core.F + 960 //16seconds, starts after 114 frames + c.SetCDWithDelay(core.ActionSkill, 960, 114) return f, a } @@ -178,7 +183,7 @@ func (c *char) Burst(p map[string]int) (int, int) { c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(targ, core.TargettableEnemy), f, f, a4cb) //duration is 15 seconds, tick every .5 sec - //30 zaps once every 30 frame, starting at f + //30 zaps once every 30 frame, starting at 119 ai = core.AttackInfo{ ActorIndex: c.Index, @@ -191,7 +196,7 @@ func (c *char) Burst(p map[string]int) (int, int) { Mult: burst[c.TalentLvlBurst()], } - for i := 30; i <= 900; i += 30 { + for i := 119; i <= 119+900; i += 30 { //first tick at 119 var cb core.AttackCBFunc if c.Base.Cons >= 4 { @@ -209,12 +214,12 @@ func (c *char) Burst(p map[string]int) (int, int) { } } - c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(1, core.TargettableEnemy), f-1, f+i, cb, a4cb) + c.Core.Combat.QueueAttack(ai, core.NewDefSingleTarget(1, core.TargettableEnemy), f-1, i, cb, a4cb) } //add a status for this just in case someone cares c.AddTask(func() { - c.Core.Status.AddStatus("lisaburst", 900) + c.Core.Status.AddStatus("lisaburst", 119+900) }, "lisa burst status", f) //on lisa c4 @@ -225,11 +230,11 @@ func (c *char) Burst(p map[string]int) (int, int) { //[8:11 PM] gimmeabreak: i guess single target it does nothing then? //[8:12 PM] ArchedNosi | Lisa Unleashed: yeah single does nothing - //burst cd starts 52 frames after executed - //energy consumed the same time as the initial hit (64 frames) - c.ConsumeEnergy(64) + //burst cd starts 53 frames after executed + //energy usually consumed after 63 frames + c.ConsumeEnergy(63) // c.CD[def.BurstCD] = c.Core.F + 1200 - c.SetCDWithDelay(core.ActionBurst, 1200, 52) + c.SetCDWithDelay(core.ActionBurst, 1200, 53) return f, a } diff --git a/internal/characters/lisa/frames.go b/internal/characters/lisa/frames.go index 41f1c560d..e7e3157be 100644 --- a/internal/characters/lisa/frames.go +++ b/internal/characters/lisa/frames.go @@ -6,32 +6,115 @@ func (c *char) ActionFrames(a core.ActionType, p map[string]int) (int, int) { switch a { case core.ActionAttack: f := 0 + a := 0 switch c.NormalCounter { //TODO: need to add atkspd mod case 0: - f = 25 + f = 15 + a = 30 case 1: - f = 46 - 25 + f = 12 + a = 20 case 2: - f = 70 - 46 + f = 17 + a = 34 case 3: - f = 114 - 70 + f = 31 + a = 57 } f = int(float64(f) / (1 + c.Stat(core.AtkSpd))) - return f, f + return f, a case core.ActionCharge: - return 95, 95 + return 70, 91 case core.ActionSkill: hold := p["hold"] if hold == 0 { - return 21, 21 //no hold + return 20, 38 //no hold } //yes hold - return 116, 116 + return 114, 141 case core.ActionBurst: - return 64, 64 + return 56, 86 + case core.ActionDash: + return 22, 22 + case core.ActionJump: + return 33, 33 default: c.Core.Log.NewEventBuildMsg(core.LogActionEvent, c.Index, "unknown action (invalid frames): ", a.String()) return 0, 0 } } + +func (c *char) InitCancelFrames() { + + //normal cancels + c.SetNormalCancelFrames(0, core.ActionAttack, 30-15) //n1 -> next attack + c.SetNormalCancelFrames(0, core.ActionCharge, 15-15) //n1 -> charge + c.SetNormalCancelFrames(0, core.ActionSkill, 26-15) //only CA can be done before hitmark + c.SetNormalCancelFrames(0, core.ActionBurst, 26-15) + c.SetNormalCancelFrames(0, core.ActionDash, 26-15) + c.SetNormalCancelFrames(0, core.ActionJump, 26-15) + c.SetNormalCancelFrames(0, core.ActionSwap, 26-15) + + c.SetNormalCancelFrames(1, core.ActionAttack, 20-12) //n2 -> next attack + c.SetNormalCancelFrames(1, core.ActionCharge, 12-12) //n2 -> charge + c.SetNormalCancelFrames(1, core.ActionSkill, 18-12) //only CA can be done before hitmark + c.SetNormalCancelFrames(1, core.ActionBurst, 18-12) + c.SetNormalCancelFrames(1, core.ActionDash, 18-12) + c.SetNormalCancelFrames(1, core.ActionJump, 18-12) + c.SetNormalCancelFrames(1, core.ActionSwap, 18-12) + + c.SetNormalCancelFrames(2, core.ActionAttack, 34-17) //n3 -> n4 + c.SetNormalCancelFrames(2, core.ActionCharge, 26-17) //n3 -> charge + //CA is after hitmark this time, so no need for the rest + + c.SetNormalCancelFrames(3, core.ActionAttack, 57-31) //n4 -> n1 + //Missing N4->CA - it's extremely long though. + + c.SetAbilCancelFrames(core.ActionCharge, core.ActionAttack, 86-70) //charge -> n1 + c.SetAbilCancelFrames(core.ActionCharge, core.ActionCharge, 90-70) //charge -> charge + c.SetAbilCancelFrames(core.ActionCharge, core.ActionSkill, 94-70) //charge -> skill + c.SetAbilCancelFrames(core.ActionCharge, core.ActionBurst, 93-70) //charge -> burst + c.SetAbilCancelFrames(core.ActionCharge, core.ActionSwap, 90-70) //charge -> swap + + c.SetAbilCancelFrames(core.ActionBurst, core.ActionAttack, 86-56) //burst -> n1 + c.SetAbilCancelFrames(core.ActionBurst, core.ActionCharge, 86-56) //burst -> charge + c.SetAbilCancelFrames(core.ActionBurst, core.ActionSkill, 87-56) //burst -> skill + c.SetAbilCancelFrames(core.ActionBurst, core.ActionDash, 88-56) //burst -> dash + c.SetAbilCancelFrames(core.ActionBurst, core.ActionJump, 57-56) //burst -> jump + c.SetAbilCancelFrames(core.ActionBurst, core.ActionSwap, 56-56) //burst -> swap + + c.SetAbilCancelFrames(core.ActionSkill, core.ActionAttack, 37-20) + c.SetAbilCancelFrames(core.ActionSkill, core.ActionCharge, 38-20) + c.SetAbilCancelFrames(core.ActionSkill, core.ActionBurst, 40-20) + c.SetAbilCancelFrames(core.ActionSkill, core.ActionDash, 35-20) + c.SetAbilCancelFrames(core.ActionSkill, core.ActionJump, 20-20) + c.SetAbilCancelFrames(core.ActionSkill, core.ActionSwap, 23-20) +} + +func (c *char) ActionInterruptableDelay(next core.ActionType, p map[string]int) int { + // Provide a custom override for Lisa's Hold E + if c.Core.LastAction.Typ == core.ActionSkill && + c.Core.LastAction.Param["hold"] == 1 { + return SkillHoldFrames(next) + } + //otherwise use default implementation + return c.Tmpl.ActionInterruptableDelay(next, p) +} + +func SkillHoldFrames(next core.ActionType) int { + switch next { + case core.ActionAttack: + return 143 - 114 + case core.ActionCharge: + return 125 - 114 + case core.ActionBurst: + return 138 - 114 + case core.ActionDash: + return 116 - 114 + case core.ActionJump: + return 117 - 114 + default: + return 114 - 114 + } +} diff --git a/internal/characters/lisa/lisa.go b/internal/characters/lisa/lisa.go index f747d449b..24da5ab6f 100644 --- a/internal/characters/lisa/lisa.go +++ b/internal/characters/lisa/lisa.go @@ -39,6 +39,7 @@ func NewChar(s *core.Core, p core.CharacterProfile) (core.Character, error) { func (c *char) Init() { c.Tmpl.Init() + c.InitCancelFrames() c.skillHoldMult() diff --git a/internal/characters/lisa/lisa_test.go b/internal/characters/lisa/lisa_test.go index 7aec3c3ee..c6ba2c85c 100644 --- a/internal/characters/lisa/lisa_test.go +++ b/internal/characters/lisa/lisa_test.go @@ -28,7 +28,7 @@ func TestCD(t *testing.T) { t.Error(err) t.FailNow() } - err = testhelper.TestSkillCDSingleCharge(c, x, 60) + err = testhelper.TestSkillCDSingleCharge(c, x, 60+17) //17 frames for CD to start if err != nil { t.Error(err) }