Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions internal/characters/iansan/asc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package iansan

import (
"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/combat"
"github.com/genshinsim/gcsim/pkg/core/event"
"github.com/genshinsim/gcsim/pkg/core/info"
"github.com/genshinsim/gcsim/pkg/core/player/character"
"github.com/genshinsim/gcsim/pkg/core/targets"
"github.com/genshinsim/gcsim/pkg/modifier"
)

const (
a1Status = "precise-movement"
a1ICD = "precise-movement-icd"
a4Status = "warming-up"
)

func (c *char) a1() {
if c.Base.Ascension < 1 {
return
}

cb := func(args ...interface{}) bool {
idx := args[0].(int)
if idx != c.Core.Player.Active() {
return false
}
if !c.StatModIsActive(a1Status) {
return false
}
if c.StatusIsActive(a1ICD) {
return false
}
c.AddStatus(a1ICD, 2.8*60, true)
c.a1Increase = true

return false
}
c.Core.Events.Subscribe(event.OnNightsoulGenerate, cb, "iansan-a1-generate")
c.Core.Events.Subscribe(event.OnNightsoulConsume, cb, "iansan-a1-consume")
}

func (c *char) a1ATK() {
m := make([]float64, attributes.EndStatType)
m[attributes.ATKP] = 0.2
c.AddStatMod(character.StatMod{
Base: modifier.NewBaseWithHitlag(a1Status, 15*60),
Amount: func() ([]float64, bool) {
return m, true
},
})
}

func (c *char) makeA1CB() func(_ combat.AttackCB) {
if c.Base.Ascension < 1 {
return nil
}

done := false
return func(a combat.AttackCB) {
if done {
return
}
if a.Target.Type() != targets.TargettableEnemy {
return
}
c.a1ATK()
done = true
}
}

func (c *char) a1Points() float64 {
if c.Base.Ascension < 1 {
return 0.0
}
if !c.StatModIsActive(a1Status) {
return 0.0
}
if c.a1Increase {
c.a1Increase = false
return 4.0
}
return 1.0
}

func (c *char) a4() {
if c.Base.Ascension < 4 {
return
}

c.Core.Events.Subscribe(event.OnNightsoulBurst, func(args ...interface{}) bool {
c.AddStatus(a4Status, 10*60, true)
c.a4Src = c.Core.F
c.a4Task(c.a4Src)

return false
}, "iansan-a4")
}

func (c *char) a4Task(src int) {
c.QueueCharTask(func() {
if c.a4Src != src {
return
}

c.Core.Player.Heal(info.HealInfo{
Caller: c.Index,
Target: c.Core.Player.Active(),
Message: "Warming Up",
Src: c.TotalAtk() * 0.6,
Bonus: c.Stat(attributes.Heal),
})

c.nightsoulState.GeneratePoints(1)
c.a4Task(src)
}, 2.8*60)
}
86 changes: 86 additions & 0 deletions internal/characters/iansan/attack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package iansan

import (
"fmt"

"github.com/genshinsim/gcsim/internal/frames"
"github.com/genshinsim/gcsim/pkg/core/action"
"github.com/genshinsim/gcsim/pkg/core/attacks"
"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/combat"
"github.com/genshinsim/gcsim/pkg/core/geometry"
)

var (
attackFrames [][]int
attackHitmarks = []int{13, 16, 12}
attackHitlagHaltFrame = []float64{0.06, 0.06, 0}
attackHitboxes = [][]float64{{1.2, 3}, {2}, {2}, {2.2}}
attackOffsets = []float64{0, 0.5, 0.5, 0.5, 1.5}
attackFanAngles = []float64{360, 270, 270, 360}
)

const normalHitNum = 3

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

attackFrames[0] = frames.InitNormalCancelSlice(attackHitmarks[0], 28)
attackFrames[0][action.ActionAttack] = 22
attackFrames[0][action.ActionCharge] = 22

attackFrames[1] = frames.InitNormalCancelSlice(attackHitmarks[1], 31)
attackFrames[1][action.ActionAttack] = 22
attackFrames[1][action.ActionCharge] = 25

attackFrames[2] = frames.InitNormalCancelSlice(attackHitmarks[2], 51)
attackFrames[2][action.ActionAttack] = 41
attackFrames[2][action.ActionCharge] = 500 //TODO: this action is illegal; need better way to handle it
}

func (c *char) Attack(p map[string]int) (action.Info, error) {
if c.StatusIsActive(fastSkill) {
// TODO: or c.Core.Player.Exec(action.ActionCharge, c.Base.Key, nil)
return c.ChargeAttack(p)
}

ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: fmt.Sprintf("Normal %v", c.NormalCounter),
Mult: attack[c.NormalCounter][c.TalentLvlAttack()],
AttackTag: attacks.AttackTagNormal,
ICDTag: attacks.ICDTagNormalAttack,
ICDGroup: attacks.ICDGroupDefault,
StrikeType: attacks.StrikeTypeSlash,
Element: attributes.Physical,
Durability: 25,
HitlagFactor: 0.01,
HitlagHaltFrames: attackHitlagHaltFrame[c.NormalCounter] * 60,
CanBeDefenseHalted: true,
}
ap := combat.NewCircleHitOnTargetFanAngle(
c.Core.Combat.Player(),
geometry.Point{Y: attackOffsets[c.NormalCounter]},
attackHitboxes[c.NormalCounter][0],
attackFanAngles[c.NormalCounter],
)
if c.NormalCounter == 0 {
ap = combat.NewBoxHitOnTarget(
c.Core.Combat.Player(),
geometry.Point{Y: attackOffsets[c.NormalCounter]},
attackHitboxes[c.NormalCounter][0],
attackHitboxes[c.NormalCounter][1],
)
}

c.Core.QueueAttack(ai, ap, attackHitmarks[c.NormalCounter], attackHitmarks[c.NormalCounter])

defer c.AdvanceNormalIndex()

return action.Info{
Frames: frames.NewAttackFunc(c.Character, attackFrames),
AnimationLength: attackFrames[c.NormalCounter][action.InvalidAction],
CanQueueAfter: attackHitmarks[c.NormalCounter],
State: action.NormalAttackState,
}, nil
}
142 changes: 142 additions & 0 deletions internal/characters/iansan/burst.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package iansan

import (
"github.com/genshinsim/gcsim/internal/frames"
"github.com/genshinsim/gcsim/pkg/core/action"
"github.com/genshinsim/gcsim/pkg/core/attacks"
"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/combat"
"github.com/genshinsim/gcsim/pkg/core/event"
"github.com/genshinsim/gcsim/pkg/core/player/character"
"github.com/genshinsim/gcsim/pkg/modifier"
)

var burstFrames []int

func init() {
burstFrames = frames.InitAbilSlice(72)
burstFrames[action.ActionAttack] = 71
burstFrames[action.ActionSkill] = 71
burstFrames[action.ActionJump] = 70
burstFrames[action.ActionSwap] = 69
}

const (
burstHitmark = 34

burstStatus = "kinetic-energy"
burstBuffStatus = "iansan-burst-buff"
)

func (c *char) Burst(p map[string]int) (action.Info, error) {
ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: "The Three Principles of Power",
AdditionalTags: []attacks.AdditionalTag{attacks.AdditionalTagNightsoul},
AttackTag: attacks.AttackTagElementalBurst,
ICDTag: attacks.ICDTagNone,
ICDGroup: attacks.ICDGroupDefault,
StrikeType: attacks.StrikeTypeDefault,
Element: attributes.Electro,
Durability: 25,
Mult: burst[c.TalentLvlBurst()],
}
c.Core.QueueAttack(
ai,
combat.NewCircleHit(c.Core.Combat.Player(), c.Core.Combat.PrimaryTarget(), nil, 6),
burstHitmark,
burstHitmark,
)

c.Core.Tasks.Add(func() {
if !c.nightsoulState.HasBlessing() {
c.enterNightsoul(15)
} else {
c.nightsoulState.GeneratePoints(15)
}
}, burstHitmark)

c.burstSrc = c.Core.F
c.burstRestoreNS = 0
c.updateATKBuff()
c.applyBuffTask(c.burstSrc)
c.Core.Events.Subscribe(event.OnActionExec, c.burstMovementRestore, burstBuffStatus)

if c.Base.Cons >= 2 {
c.a1ATK()
}
c.c4Stacks = 0

duration := 12 * 60
if c.Base.Cons >= 6 {
duration += 3.0
}
c.AddStatus(burstStatus, duration, false) // TODO: hitlag affected?
c.SetCD(action.ActionBurst, 18*60)
c.ConsumeEnergy(3)

return action.Info{
Frames: frames.NewAbilFunc(burstFrames),
AnimationLength: burstFrames[action.InvalidAction],
CanQueueAfter: burstFrames[action.ActionSwap],
State: action.BurstState,
}, nil
}

func (c *char) applyBuffTask(src int) {
c.Core.Tasks.Add(func() {
if c.burstSrc != src {
return
}
if !c.StatusIsActive(burstStatus) {
c.c4Stacks = 0
return
}

points := float64(c.burstRestoreNS) + c.a1Points() + c.c4Points()
c.burstRestoreNS = 0
c.pointsOverflow = max(c.nightsoulState.Points()+points-c.nightsoulState.MaxPoints, 0.0)
if c.pointsOverflow > 0 {
c.c6()
}
if points > 0.0 {
c.nightsoulState.GeneratePoints(points)
}

active := c.Core.Player.ActiveChar()
active.AddStatMod(character.StatMod{
Base: modifier.NewBaseWithHitlag(burstBuffStatus, 1*60),
Amount: func() ([]float64, bool) {
c.c2ATKBuff(active)
return c.burstBuff, true
},
})
c.applyBuffTask(src)
}, 0.5*60) // TODO: refresh rate?
}

func (c *char) updateATKBuff() {
if !c.StatusIsActive(burstStatus) {
c.burstBuff[attributes.ATK] = 0
return
}

rate := highATK[c.TalentLvlBurst()]
if c.nightsoulState.Points() < 42 {
rate = lowATK[c.TalentLvlBurst()] * c.nightsoulState.Points()
}
c.burstBuff[attributes.ATK] = min(c.TotalAtk()*rate, maxATK[c.TalentLvlBurst()])
}

func (c *char) burstMovementRestore(args ...interface{}) bool {
if !c.StatusIsActive(burstStatus) {
return true
}

param := args[2].(map[string]int)
movement, ok := param["movement"]
if ok {
c.burstRestoreNS += movement
}
return false
}
Loading
Loading