Skip to content

Commit

Permalink
mavuika from wfpsim & ange1o5
Browse files Browse the repository at this point in the history
  • Loading branch information
kengzzzz committed Nov 29, 2024
1 parent 458e269 commit d0ce684
Show file tree
Hide file tree
Showing 19 changed files with 2,006 additions and 0 deletions.
60 changes: 60 additions & 0 deletions internal/characters/mavuika/asc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package mavuika

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/player/character"
"github.com/genshinsim/gcsim/pkg/modifier"
)

const (
a1Key = "mavuika-a1"
)

func (c *char) a1() {
if c.Base.Ascension < 1 {
return
}
m := make([]float64, attributes.EndStatType)
m[attributes.ATKP] = 0.35
c.Core.Events.Subscribe(event.OnNightsoulBurst, func(args ...interface{}) bool {
c.AddStatMod(character.StatMod{
Base: modifier.NewBaseWithHitlag(a1Key, 10*60),
Amount: func() ([]float64, bool) {
return m, true
},
})
return false
}, a1Key)
}

func (c *char) a4Init() {
if c.Base.Ascension < 4 {
return
}
c.a4buff = make([]float64, attributes.EndStatType)
}

func (c *char) a4() {
if c.Base.Ascension < 4 {
return
}
started := c.Core.F
for _, char := range c.Core.Player.Chars() {
this := char
this.AddAttackMod(character.AttackMod{
Base: modifier.NewBase("mavuika-a4", 20*60),
Amount: func(_ *combat.AttackEvent, _ combat.Target) ([]float64, bool) {
// char must be active
if c.Core.Player.Active() != this.Index {
return nil, false
}
dmg := c.burstStacks * 0.0025
dmg *= 1.0 - float64(c.Core.F-started)*c.c4DecayRate()
c.a4buff[attributes.DmgP] = dmg
return c.a4buff, true
},
})
}
}
135 changes: 135 additions & 0 deletions internal/characters/mavuika/attack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package mavuika

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{{22}, {24, 26}, {22, 24, 26}, {41}}
attackPoiseDMG = []float64{93.33, 92.72, 115.14, 143.17}
attackHitlagHaltFrame = []float64{0.09, 0.10, 0.08, .12}
attackHitboxes = []float64{2.2, 2.3, 1.8, 3}
attackOffsets = []float64{0.5, -1.3, 0.5, -0.8}

bikeAttackFrames [][]int
bikeAttackHitmarks = []int{22, 26, 26, 30, 41}
bikeAttackPoiseDMG = []float64{76.6, 79.1, 93.6, 93.2, 121.7}
bikeAttackHitlagHaltFrame = []float64{0.09, 0.08, 0.04, 0.03, 0.0}
bikeAttackHitboxes = [][]float64{{3.7}, {4}, {3.7}, {5.5, 4.5}, {4.7}}
bikeAttackOffsets = []float64{0.5, -1.3, 0.5, -0.8, 1}
)

const normalHitNum = 4
const bikeHitNum = 5

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

attackFrames[0] = frames.InitNormalCancelSlice(attackHitmarks[0][0], 31) // N1 -> N2
attackFrames[1] = frames.InitNormalCancelSlice(attackHitmarks[1][1], 34) // N2 -> N3
attackFrames[2] = frames.InitNormalCancelSlice(attackHitmarks[2][2], 43) // N3 -> N4
attackFrames[3] = frames.InitNormalCancelSlice(attackHitmarks[3][0], 85) // N4 -> N1

bikeAttackFrames = make([][]int, bikeHitNum)

bikeAttackFrames[0] = frames.InitNormalCancelSlice(bikeAttackHitmarks[0], 31) // N1 -> N2
bikeAttackFrames[1] = frames.InitNormalCancelSlice(bikeAttackHitmarks[1], 34) // N2 -> N3
bikeAttackFrames[2] = frames.InitNormalCancelSlice(bikeAttackHitmarks[2], 43) // N3 -> N4
bikeAttackFrames[3] = frames.InitNormalCancelSlice(bikeAttackHitmarks[3], 30) // N4 -> N5
bikeAttackFrames[4] = frames.InitNormalCancelSlice(bikeAttackHitmarks[4], 85) // N5 -> N1
}

func (c *char) Attack(p map[string]int) (action.Info, error) {
if c.armamentState == bike && c.nightsoulState.HasBlessing() {
return c.bikeAttack(), nil
}
ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: fmt.Sprintf("Normal %v", c.NormalCounter),
AttackTag: attacks.AttackTagNormal,
ICDTag: attacks.ICDTagNormalAttack,
ICDGroup: attacks.ICDGroupDefault,
StrikeType: attacks.StrikeTypeBlunt,
PoiseDMG: attackPoiseDMG[c.NormalCounter],
Element: attributes.Physical,
Durability: 25,
Mult: attack[c.NormalCounter][c.TalentLvlAttack()],
HitlagFactor: 0.01,
HitlagHaltFrames: attackHitlagHaltFrame[c.NormalCounter] * 60,
}
ap := combat.NewCircleHitOnTarget(
c.Core.Combat.Player(),
geometry.Point{Y: attackOffsets[c.NormalCounter]},
attackHitboxes[c.NormalCounter],
)

for _, delay := range attackHitmarks[c.NormalCounter] {
c.Core.QueueAttack(ai, ap, delay, delay)
}

defer c.AdvanceNormalIndex()

return action.Info{
Frames: frames.NewAttackFunc(c.Character, attackFrames),
AnimationLength: attackFrames[c.NormalCounter][action.InvalidAction],
CanQueueAfter: attackHitmarks[c.NormalCounter][len(attackHitmarks[c.NormalCounter])-1],
State: action.NormalAttackState,
}, nil
}

func (c *char) bikeAttack() action.Info {
delay := bikeAttackHitmarks[c.NormalCounter]
ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: fmt.Sprintf("Flamestrider Normal %v", c.NormalCounter),
AttackTag: attacks.AttackTagNormal,
AdditionalTags: []attacks.AdditionalTag{attacks.AdditionalTagNightsoul},
ICDTag: attacks.ICDTagMavuikaFlamestrider,
ICDGroup: attacks.ICDGroupDefault,
StrikeType: attacks.StrikeTypeBlunt,
PoiseDMG: bikeAttackPoiseDMG[c.NormalCounter],
Element: attributes.Pyro,
Durability: 25,
Mult: skillAttack[c.NormalCounter][c.TalentLvlAttack()],
HitlagFactor: 0.01,
HitlagHaltFrames: bikeAttackHitlagHaltFrame[c.NormalCounter] * 60,
IgnoreInfusion: true,
}

ap := combat.NewCircleHitOnTarget(
c.Core.Combat.Player(),
geometry.Point{Y: bikeAttackOffsets[c.NormalCounter]},
bikeAttackHitboxes[c.NormalCounter][0],
)

if c.NormalCounter == 3 {
ap = combat.NewBoxHitOnTarget(
c.Core.Combat.Player(),
geometry.Point{Y: bikeAttackOffsets[c.NormalCounter]},
bikeAttackHitboxes[c.NormalCounter][0],
bikeAttackHitboxes[c.NormalCounter][1],
)
}
c.QueueCharTask(func() {
ai.FlatDmg = c.burstBuffNA() + c.c2BikeNA()
c.Core.QueueAttack(ai, ap, 0, 0)
c.reduceNightsoulPoints(1)
}, delay)

defer c.AdvanceNormalIndex()

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

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/geometry"
"github.com/genshinsim/gcsim/pkg/enemy"
)

const (
burstKey = "mavuika-burst"
energyNAICDKey = "mavuika-fighting-spirit-na-icd"
burstDuration = 7.0 * 60
burstHitmark = 118
)

var (
burstFrames []int
)

func (c *char) nightsoulConsumptionMul() float64 {
if c.StatusIsActive(burstKey) {
return 0.0
}
return 1.0
}

func init() {
burstFrames = frames.InitAbilSlice(123) // Q -> N1
burstFrames[action.ActionCharge] = 135
burstFrames[action.ActionSwap] = burstHitmark
}

func (c *char) Burst(p map[string]int) (action.Info, error) {
c.burstStacks = c.fightingSpirit
c.armamentState = bike
c.fightingSpirit = 0

c.QueueCharTask(func() {
c.enterNightsoulOrRegenerate(10)
c.AddStatus(burstKey, burstDuration, true)
}, 95)

c.QueueCharTask(func() {
c.a4()

ai := combat.AttackInfo{
ActorIndex: c.Index,
Abil: "Sunfell Slice",
AttackTag: attacks.AttackTagElementalBurst,
ICDTag: attacks.ICDTagNone,
AdditionalTags: []attacks.AdditionalTag{attacks.AdditionalTagNightsoul},
ICDGroup: attacks.ICDGroupDefault,
StrikeType: attacks.StrikeTypeBlunt,
PoiseDMG: 150,
Element: attributes.Pyro,
Durability: 25,
Mult: burst[c.TalentLvlBurst()],
FlatDmg: c.burstBuffSunfell() + c.c2BikeQ(),
}
ap := combat.NewCircleHitOnTarget(
c.Core.Combat.Player(),
geometry.Point{Y: 1.0},
6,
)
c.Core.QueueAttack(ai, ap, 0, 0)
}, burstHitmark)

c.SetCDWithDelay(action.ActionBurst, 18*60, 0)

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

func (c *char) burstBuffCA() float64 {
if !c.StatusIsActive(burstKey) {
return 0.0
}
return c.burstStacks * burstCABonus[c.TalentLvlBurst()] * c.TotalAtk()
}

func (c *char) burstBuffNA() float64 {
if !c.StatusIsActive(burstKey) {
return 0.0
}
return c.burstStacks * burstNABonus[c.TalentLvlBurst()] * c.TotalAtk()
}

func (c *char) burstBuffSunfell() float64 {
if !c.StatusIsActive(burstKey) {
return 0.0
}
return c.burstStacks * burstQBonus[c.TalentLvlBurst()] * c.TotalAtk()
}

func (c *char) gainFightingSpirit(val float64) {
c.fightingSpirit += val * c.c1FightingSpiritEff()
if c.fightingSpirit > 200 {
c.fightingSpirit = 200
}
c.c1OnFightingSpirit()
}

func (c *char) burstInit() {
c.fightingSpirit = 200
c.Core.Events.Subscribe(event.OnNightsoulConsume, func(args ...interface{}) bool {
amount := args[1].(float64)
if amount < 0.0000001 {
return false
}
c.gainFightingSpirit(amount)
return false
}, "mavuika-fighting-spirit-ns")

c.Core.Events.Subscribe(event.OnEnemyDamage, func(args ...interface{}) bool {
ae := args[1].(*combat.AttackEvent)
_, ok := args[0].(*enemy.Enemy)
if !ok {
return false
}
if ae.Info.AttackTag != attacks.AttackTagNormal {
return false
}
if c.StatusIsActive(energyNAICDKey) {
return false
}
c.AddStatus(energyNAICDKey, 0.1*60, true)
c.gainFightingSpirit(1.5)
return false
}, "mavuika-fighting-spirit-na")
}
Loading

0 comments on commit d0ce684

Please sign in to comment.