Skip to content

Commit

Permalink
Merge pull request #679 from k0l11/kuki-frames
Browse files Browse the repository at this point in the history
Kuki frames update and hitlag
  • Loading branch information
shizukayuki authored Aug 12, 2022
2 parents 10faf90 + e4343ec commit f9c6cd4
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 66 deletions.
2 changes: 2 additions & 0 deletions internal/characters/kuki/asc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/genshinsim/gcsim/pkg/modifier"
)

// A1:
// When Shinobu's HP is not higher than 50%, her Healing Bonus is increased by 15%.
func (c *char) a1() {
m := make([]float64, attributes.EndStatType)
m[attributes.Heal] = .15
Expand Down
41 changes: 26 additions & 15 deletions internal/characters/kuki/attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,43 @@ import (
)

var attackFrames [][]int
var attackHitmarks = []int{13, 26, 30, 55}
var attackHitmarks = []int{12, 13, 13, 23}
var attackHitlagHaltFrame = []float64{0.03, 0.03, 0.06, 0.10}

const normalHitNum = 4

func init() {
attackFrames = make([][]int, normalHitNum)

attackFrames[0] = frames.InitNormalCancelSlice(attackHitmarks[0], 14)
attackFrames[1] = frames.InitNormalCancelSlice(attackHitmarks[1], 27)
attackFrames[2] = frames.InitNormalCancelSlice(attackHitmarks[2], 31)
attackFrames[3] = frames.InitNormalCancelSlice(attackHitmarks[3], 56)
attackFrames[3][action.ActionCharge] = 500 //TODO: this action is illegal; need better way to handle it
attackFrames[0] = frames.InitNormalCancelSlice(attackHitmarks[0], 23) // N1 -> CA
attackFrames[0][action.ActionAttack] = 19 // N1 -> N2

attackFrames[1] = frames.InitNormalCancelSlice(attackHitmarks[1], 27) // N2 -> CA
attackFrames[1][action.ActionAttack] = 17 // N2 -> N3

attackFrames[2] = frames.InitNormalCancelSlice(attackHitmarks[2], 50) // N3 -> CA
attackFrames[2][action.ActionAttack] = 42 // N3 -> N4

attackFrames[3] = frames.InitNormalCancelSlice(attackHitmarks[3], 57) // N4 -> N1
attackFrames[3][action.ActionCharge] = 500 // N4 -> CA, TODO: this action is illegal; need better way to handle it
}

func (c *char) Attack(p map[string]int) action.ActionInfo {
ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: fmt.Sprintf("Normal %v", c.NormalCounter),
AttackTag: combat.AttackTagNormal,
ICDTag: combat.ICDTagNormalAttack,
ICDGroup: combat.ICDGroupDefault,
StrikeType: combat.StrikeTypeSlash,
Element: attributes.Physical,
Durability: 25,
Mult: auto[c.NormalCounter][c.TalentLvlAttack()],
ActorIndex: c.Index,
Abil: fmt.Sprintf("Normal %v", c.NormalCounter),
AttackTag: combat.AttackTagNormal,
ICDTag: combat.ICDTagNormalAttack,
ICDGroup: combat.ICDGroupDefault,
StrikeType: combat.StrikeTypeSlash,
Element: attributes.Physical,
Durability: 25,
Mult: auto[c.NormalCounter][c.TalentLvlAttack()],
HitlagFactor: 0.01,
HitlagHaltFrames: attackHitlagHaltFrame[c.NormalCounter] * 60,
CanBeDefenseHalted: true,
}
// no multihits so no need for char queue here
c.Core.QueueAttack(
ai,
combat.NewCircleHit(c.Core.Combat.Player(), .3, false, combat.TargettableEnemy),
Expand Down
15 changes: 9 additions & 6 deletions internal/characters/kuki/burst.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import (

var burstFrames []int

const burstStart = 78
const burstStart = 50

func init() {
burstFrames = frames.InitAbilSlice(78)
burstFrames = frames.InitAbilSlice(63) // Q -> D/J
burstFrames[action.ActionAttack] = 62 // Q -> N1
burstFrames[action.ActionSkill] = 62 // Q -> E
burstFrames[action.ActionSwap] = 62 // Q -> Swap
}

func (c *char) Burst(p map[string]int) action.ActionInfo {
Expand Down Expand Up @@ -42,17 +45,17 @@ func (c *char) Burst(p map[string]int) action.ActionInfo {
r = 3.5
}

for i := 0; i < count*interval; i += interval {
for i := burstStart; i < count*interval+burstStart; i += interval {
c.Core.QueueAttackWithSnap(ai, snap, combat.NewCircleHit(c.Core.Combat.Player(), r, false, combat.TargettableEnemy), i)
}

c.ConsumeEnergy(55) //TODO: Check if she can be pre-funneled
c.SetCDWithDelay(action.ActionBurst, 900, 55)
c.ConsumeEnergy(4)
c.SetCD(action.ActionBurst, 900) // 15s * 60

return action.ActionInfo{
Frames: frames.NewAbilFunc(burstFrames),
AnimationLength: burstFrames[action.InvalidAction],
CanQueueAfter: burstFrames[action.InvalidAction],
CanQueueAfter: burstFrames[action.ActionAttack], // earliest cancel
State: action.BurstState,
}
}
37 changes: 22 additions & 15 deletions internal/characters/kuki/charge.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,35 @@ import (
)

var chargeFrames []int
var chargeHitmarks = []int{72, 82}
var chargeHitmarks = []int{14, 25}
var chargeHitlagHaltFrame = []float64{0, 0.10}
var chargeDefHalt = []bool{false, true}

func init() {
chargeFrames = frames.InitAbilSlice(87)
chargeFrames[action.ActionDash] = chargeHitmarks[len(chargeHitmarks)-1]
chargeFrames[action.ActionJump] = chargeHitmarks[len(chargeHitmarks)-1]
chargeFrames = frames.InitAbilSlice(35) // CA -> N1/E/Q
chargeFrames[action.ActionDash] = 31 // CA -> D
chargeFrames[action.ActionJump] = 31 // CA -> J
chargeFrames[action.ActionSwap] = 29 // CA -> Swap
}

func (c *char) ChargeAttack(p map[string]int) action.ActionInfo {
ai := combat.AttackInfo{
ActorIndex: c.Index,
AttackTag: combat.AttackTagExtra,
ICDTag: combat.ICDTagExtraAttack,
ICDGroup: combat.ICDGroupDefault,
StrikeType: combat.StrikeTypeSlash,
Element: attributes.Physical,
Durability: 25,
}

for i, mult := range charge {
ai.Mult = mult[c.TalentLvlAttack()]
ai.Abil = fmt.Sprintf("Charge %v", i)
ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: fmt.Sprintf("Charge %v", i),
AttackTag: combat.AttackTagExtra,
ICDTag: combat.ICDTagExtraAttack,
ICDGroup: combat.ICDGroupDefault,
StrikeType: combat.StrikeTypeSlash,
Element: attributes.Physical,
Durability: 25,
Mult: mult[c.TalentLvlAttack()],
HitlagFactor: 0.01,
HitlagHaltFrames: chargeHitlagHaltFrame[i] * 60,
CanBeDefenseHalted: chargeDefHalt[i],
}
// only the last multihit has hitlag so no need for char queue here
c.Core.QueueAttack(ai, combat.NewCircleHit(c.Core.Combat.Player(), 0.5, false, combat.TargettableEnemy), chargeHitmarks[i], chargeHitmarks[i])
}

Expand Down
31 changes: 17 additions & 14 deletions internal/characters/kuki/cons.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import (
"github.com/genshinsim/gcsim/pkg/modifier"
)

//When the Normal, Charged, or Plunging Attacks of the character affected by Shinobu's Grass Ring of Sanctification hit opponents,
// a Thundergrass Mark will land on the opponent's position and deal AoE Electro DMG based on 9.7% of Shinobu's Max HP.
//This effect can occur once every 5s.
// C4:
// When the Normal, Charged, or Plunging Attacks of the character affected by Shinobu's Grass Ring of Sanctification hit opponents,
// a Thundergrass Mark will land on the opponent's position and deal AoE Electro DMG based on 9.7% of Shinobu's Max HP.
// This effect can occur once every 5s.
func (c *char) c4() {
//TODO: idk if the damage is instant or not
const c4IcdKey = "kuki-c4-icd"
c.Core.Events.Subscribe(event.OnDamage, func(args ...interface{}) bool {
ae := args[1].(*combat.AttackEvent)
//ignore if C4 on icd
if c.c4ICD > c.Core.F {
if c.StatusIsActive(c4IcdKey) {
return false
}
//On normal,charge and plunge attack
Expand All @@ -27,14 +29,15 @@ func (c *char) c4() {
if ae.Info.ActorIndex != c.Core.Player.Active() {
return false
}
if c.Core.Status.Duration("kukibell") == 0 {
if c.Core.Status.Duration("kuki-e") == 0 {
return false
}
c.AddStatus(c4IcdKey, 300, true) // 5s * 60

//TODO:frames for this and ICD tag
ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: "C4 proc",
Abil: "Thundergrass Mark",
AttackTag: combat.AttackTagElementalArt,
ICDTag: combat.ICDTagNone,
ICDGroup: combat.ICDGroupDefault,
Expand All @@ -49,19 +52,20 @@ func (c *char) c4() {
if c.Core.Rand.Float64() < .45 {
c.Core.QueueParticle("kuki", 1, attributes.Electro, 100) // TODO: idk the particle timing yet fml (or probability)
}
c.c4ICD = c.Core.F + 300 //5 sec icd
return false
}, "kuki-c4")
}

//When Kuki Shinobu takes lethal DMG, this instance of DMG will not take her down.
//This effect will automatically trigger when her HP reaches 1 and will trigger once every 60s.
//When Shinobu's HP drops below 25%, she will gain 150 Elemental Mastery for 15s. This effect will trigger once every 60s.
// C6:
// When Kuki Shinobu takes lethal DMG, this instance of DMG will not take her down.
// This effect will automatically trigger when her HP reaches 1 and will trigger once every 60s.
// When Shinobu's HP drops below 25%, she will gain 150 Elemental Mastery for 15s. This effect will trigger once every 60s.
func (c *char) c6() {
m := make([]float64, attributes.EndStatType)
m[attributes.EM] = 150
const c6IcdKey = "kuki-c6-icd"
c.Core.Events.Subscribe(event.OnCharacterHurt, func(_ ...interface{}) bool {
if c.Core.F < c.c6ICD {
if c.StatusIsActive(c6IcdKey) {
return false
}
//check if hp less than 25%
Expand All @@ -72,18 +76,17 @@ func (c *char) c6() {
if c.HPCurrent <= -1 {
c.HPCurrent = 1
}
c.AddStatus(c6IcdKey, 3600, true) // 60s * 60

//increase EM by 150 for 15s
c.AddStatMod(character.StatMod{
Base: modifier.NewBase("kuki-c6", 900),
Base: modifier.NewBaseWithHitlag("kuki-c6", 900),
AffectedStat: attributes.EM,
Amount: func() ([]float64, bool) {
return m, true
},
})

c.c6ICD = c.Core.F + 3600

return false
}, "kuki-c6")
}
5 changes: 0 additions & 5 deletions internal/characters/kuki/kuki.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ func init() {
type char struct {
*tmpl.Character
bellActiveUntil int
c4ICD int
c6ICD int
}

func NewChar(s *core.Core, w *character.CharWrapper, _ profile.CharacterProfile) error {
Expand All @@ -28,9 +26,6 @@ func NewChar(s *core.Core, w *character.CharWrapper, _ profile.CharacterProfile)
c.BurstCon = 5
c.SkillCon = 3

c.c4ICD = -1
c.c6ICD = -1

w.Character = &c

return nil
Expand Down
30 changes: 19 additions & 11 deletions internal/characters/kuki/skill.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import (

var skillFrames []int

const skillHitmark = 58
const skillHitmark = 11 // Initial Hit

func init() {
skillFrames = frames.InitAbilSlice(58)
skillFrames = frames.InitAbilSlice(52) // E -> Q
skillFrames[action.ActionAttack] = 50 // E -> N1
skillFrames[action.ActionDash] = 12 // E -> D
skillFrames[action.ActionJump] = 11 // E -> J
skillFrames[action.ActionSwap] = 50 // E -> Swap
}

func (c *char) Skill(p map[string]int) action.ActionInfo {
Expand All @@ -24,7 +28,6 @@ func (c *char) Skill(p map[string]int) action.ActionInfo {
} else if (c.HPCurrent / c.MaxHP()) > 0.2 { //check if below 20%
c.HPCurrent = 0.2 * c.MaxHP()
}
//TODO: damage frame

ai := combat.AttackInfo{
ActorIndex: c.Index,
Expand All @@ -46,19 +49,24 @@ func (c *char) Skill(p map[string]int) action.ActionInfo {
skilldur = 900 //12+3s
}

c.SetCD(action.ActionSkill, skillHitmark+15*60) // what's the diff between f and a again? Nice question Yakult
c.Core.Tasks.Add(c.bellTick(), 90) //Assuming this executes every 90 frames-1.5s
c.bellActiveUntil = c.Core.F + skilldur
c.Core.Log.NewEvent("Bell activated", glog.LogCharacterEvent, c.Index).
Write("expected end", c.bellActiveUntil).
Write("next expected tick", c.Core.F+90)
// this gets executed before kuki can expierence hitlag so no need for char queue
// ring duration starts after hitmark
c.Core.Tasks.Add(func() {
// E duration and ticks are not affected by hitlag
c.Core.Status.Add("kuki-e", skilldur)
c.Core.Tasks.Add(c.bellTick(), 90) // Assuming this executes every 90 frames = 1.5s
c.bellActiveUntil = c.Core.F + skilldur
c.Core.Log.NewEvent("Bell activated", glog.LogCharacterEvent, c.Index).
Write("expected end", c.bellActiveUntil).
Write("next expected tick", c.Core.F+90)
}, 23)

c.Core.Status.Add("kukibell", skilldur)
c.SetCDWithDelay(action.ActionSkill, 90, 7)

return action.ActionInfo{
Frames: frames.NewAbilFunc(skillFrames),
AnimationLength: skillFrames[action.InvalidAction],
CanQueueAfter: skillFrames[action.InvalidAction],
CanQueueAfter: skillFrames[action.ActionJump], // earliest cancel
State: action.SkillState,
}
}
Expand Down

0 comments on commit f9c6cd4

Please sign in to comment.