diff --git a/internal/characters/sethos/aimed.go b/internal/characters/sethos/aimed.go new file mode 100644 index 0000000000..f403db41c6 --- /dev/null +++ b/internal/characters/sethos/aimed.go @@ -0,0 +1,191 @@ +package sethos + +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" + "github.com/genshinsim/gcsim/pkg/core/glog" +) + +var aimedFrames [][]int + +var aimedHitmarks = []int{16, 74, 368} +var startCharge = aimedHitmarks[0] + +const shadowPierceShotAil = "Shadowpiercing Shot" + +func init() { + // outside of E status + aimedFrames = make([][]int, 3) + + // Aimed Shot + aimedFrames[0] = frames.InitAbilSlice(26) + aimedFrames[0][action.ActionDash] = aimedHitmarks[0] + aimedFrames[0][action.ActionJump] = aimedHitmarks[0] + + // Fully-Charged Aimed Shot + aimedFrames[1] = frames.InitAbilSlice(83) + aimedFrames[1][action.ActionDash] = aimedHitmarks[1] + aimedFrames[1][action.ActionJump] = aimedHitmarks[1] + + // Shadowpiercing Shot + aimedFrames[2] = frames.InitAbilSlice(379) + aimedFrames[2][action.ActionDash] = aimedHitmarks[2] + aimedFrames[2][action.ActionJump] = aimedHitmarks[2] +} + +func (c *char) Aimed(p map[string]int) (action.Info, error) { + if c.StatusIsActive(burstBuffKey) { + return action.Info{}, fmt.Errorf("%v: Cannot aim while in burst", c.Base.Key) + } + + hold, ok := p["hold"] + if !ok { + // is this a good default? it's gonna take 6s to do without energy + hold = attacks.AimParamLv2 + } + switch hold { + case attacks.AimParamPhys: + case attacks.AimParamLv1: + case attacks.AimParamLv2: + return c.ShadowPierce(p) + default: + return action.Info{}, fmt.Errorf("invalid hold param supplied, got %v", hold) + } + + skip, energy := c.a1Calc() + if skip > aimedHitmarks[hold]-startCharge { + skip = aimedHitmarks[hold] - startCharge + } + + travel, ok := p["travel"] + if !ok { + travel = 10 + } + weakspot := p["weakspot"] + + c.QueueCharTask(func() { + ai := combat.AttackInfo{ + ActorIndex: c.Index, + Abil: "Fully-Charged Aimed Shot", + AttackTag: attacks.AttackTagExtra, + ICDTag: attacks.ICDTagNone, + ICDGroup: attacks.ICDGroupDefault, + StrikeType: attacks.StrikeTypePierce, + Element: attributes.Electro, + Durability: 25, + Mult: fullaim[c.TalentLvlAttack()], + HitWeakPoint: weakspot == 1, + HitlagHaltFrames: 0.12 * 60, + HitlagFactor: 0.01, + HitlagOnHeadshotOnly: true, + IsDeployable: true, + } + if hold < attacks.AimParamLv1 { + ai.Abil = "Aimed Shot" + ai.Element = attributes.Physical + ai.Mult = aim[c.TalentLvlAttack()] + } + + c.Core.QueueAttack( + ai, + combat.NewBoxHit( + c.Core.Combat.Player(), + c.Core.Combat.PrimaryTarget(), + geometry.Point{Y: -0.5}, + 0.1, + 1, + ), + 0, + travel, + ) + c.a1Consume(energy, hold) + }, aimedHitmarks[hold]-skip) + + return action.Info{ + Frames: func(next action.Action) int { return aimedFrames[hold][next] - skip }, + AnimationLength: aimedFrames[hold][action.InvalidAction] - skip, + CanQueueAfter: aimedHitmarks[hold] - skip, + State: action.AimState, + }, nil +} + +func (c *char) ShadowPierce(p map[string]int) (action.Info, error) { + travel, ok := p["travel"] + if !ok { + travel = 10 + } + weakspot := p["weakspot"] + + skip, energy := c.a1Calc() + if skip > aimedHitmarks[2]-startCharge { + skip = aimedHitmarks[2] - startCharge + } + hitHaltFrames := 0.0 + if weakspot == 1 { + hitHaltFrames = 0.12 * 60 + } + + c.QueueCharTask(func() { + em := c.Stat(attributes.EM) + ai := combat.AttackInfo{ + ActorIndex: c.Index, + Abil: shadowPierceShotAil, + AttackTag: attacks.AttackTagExtra, + ICDTag: attacks.ICDTagNone, + ICDGroup: attacks.ICDGroupDefault, + StrikeType: attacks.StrikeTypeDefault, + Element: attributes.Electro, + Durability: 50, + Mult: shadowpierceAtk[c.TalentLvlAttack()], + HitWeakPoint: weakspot == 1, + HitlagHaltFrames: hitHaltFrames, + HitlagFactor: 0.01, + HitlagOnHeadshotOnly: true, + IsDeployable: true, + FlatDmg: shadowpierceEM[c.TalentLvlAttack()] * em, + } + + if c.StatusIsActive(a4Key) { + ai.FlatDmg += 7 * em + c.Core.Log.NewEvent("Sethos A4 proc dmg add", glog.LogPreDamageMod, c.Index). + Write("em", em). + Write("ratio", 7.0). + Write("addition", 7*em) + } + + deltaPos := c.Core.Combat.Player().Pos().Sub(c.Core.Combat.PrimaryTarget().Pos()) + dist := deltaPos.Magnitude() + + // simulate piercing. Extends 15 units from player + ap := combat.NewBoxHit( + c.Core.Combat.Player(), + c.Core.Combat.PrimaryTarget(), + geometry.Point{Y: -dist}, + 0.1, + 15, + ) + c.a1Consume(energy, attacks.AimParamLv2) + c.Core.QueueAttack( + ai, + ap, + 0, + travel, + c.makeA4cb(), + c.makeC4cb(), + c.makeC6cb(energy), + ) + }, aimedHitmarks[2]-skip) + + return action.Info{ + Frames: func(next action.Action) int { return aimedFrames[2][next] - skip }, + AnimationLength: aimedFrames[2][action.InvalidAction] - skip, + CanQueueAfter: aimedHitmarks[2] - skip, + State: action.AimState, + }, nil +} diff --git a/internal/characters/sethos/asc.go b/internal/characters/sethos/asc.go new file mode 100644 index 0000000000..626714e4e9 --- /dev/null +++ b/internal/characters/sethos/asc.go @@ -0,0 +1,81 @@ +package sethos + +import ( + "github.com/genshinsim/gcsim/pkg/core/attacks" + "github.com/genshinsim/gcsim/pkg/core/combat" + "github.com/genshinsim/gcsim/pkg/core/targets" +) + +const a1Key = "sethos-a1" + +// returns the amount of time to save, and the amount of energy considered +func (c *char) a1Calc() (int, float64) { + if c.Base.Ascension < 1 { + return 0, 0 + } + energy := min(c.Energy, 20) + // floor or round the skip? + return int(0.285 * energy * 60), energy +} + +func (c *char) a1Consume(energy float64, holdLevel int) { + switch holdLevel { + default: + return + case attacks.AimParamLv1: + c.AddEnergy(a1Key, -energy*0.5) + case attacks.AimParamLv2: + c.AddEnergy(a1Key, -energy) + } + c.c2AddStack(c2ConsumingKey) +} + +const a4Key = "sethos-a4" +const a4IcdKey = "sethos-a4-icd" +const a4Icd = 15 * 60 + +func (c *char) a4() { + if c.Base.Ascension < 4 { + return + } + // buff stays active until a4 is proc'd + c.AddStatus(a4Key, -1, true) + c.a4Count = 0 +} + +func (c *char) makeA4cb() combat.AttackCBFunc { + if c.Base.Ascension < 4 { + return nil + } + + done := false + return func(a combat.AttackCB) { + if a.Target.Type() != targets.TargettableEnemy { + return + } + if done { + return + } + if !c.StatusIsActive(a4Key) { + return + } + done = true + if c.a4Count == 0 { + // overwrite the expiry of the a4 buff to be 5s after + c.AddStatus(a4Key, 5*60, true) + c.startA4Icd() + } + c.a4Count += 1 + if c.a4Count >= 4 { + c.DeleteStatus(a4Key) + } + } +} + +func (c *char) startA4Icd() { + if c.StatusIsActive(a4IcdKey) { + return + } + c.AddStatus(a4IcdKey, a4Icd, true) + c.QueueCharTask(c.a4, a4Icd) +} diff --git a/internal/characters/sethos/attack.go b/internal/characters/sethos/attack.go new file mode 100644 index 0000000000..c10803071a --- /dev/null +++ b/internal/characters/sethos/attack.go @@ -0,0 +1,103 @@ +package sethos + +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" +) + +const normalHitNum = 3 + +var ( + attackFrames [][]int + attackHitmarks = [][]int{{10}, {12, 12 + 3}, {39}} +) + +func init() { + attackFrames = make([][]int, normalHitNum) + + attackFrames[0] = frames.InitNormalCancelSlice(attackHitmarks[0][0], 19) // N1 -> Walk + attackFrames[0][action.ActionAttack] = 17 + attackFrames[0][action.ActionAim] = 15 + + attackFrames[1] = frames.InitNormalCancelSlice(attackHitmarks[1][1], 52-17) // N2 -> Walk + attackFrames[1][action.ActionAttack] = 32 + attackFrames[1][action.ActionAim] = 45 - 17 + + attackFrames[2] = frames.InitNormalCancelSlice(attackHitmarks[2][0], 118-32-17) // N3 -> Walk + attackFrames[2][action.ActionAttack] = 63 +} + +func (c *char) Attack(p map[string]int) (action.Info, error) { + travel, ok := p["travel"] + if !ok { + travel = 10 + } + + ai := combat.AttackInfo{ + ActorIndex: c.Index, + Abil: fmt.Sprintf("Normal %v", c.NormalCounter), + AttackTag: attacks.AttackTagNormal, + ICDTag: attacks.ICDTagNone, + ICDGroup: attacks.ICDGroupDefault, + StrikeType: attacks.StrikeTypePierce, + Element: attributes.Physical, + Durability: 25, + } + + ap := combat.NewBoxHit( + c.Core.Combat.Player(), + c.Core.Combat.PrimaryTarget(), + geometry.Point{Y: -0.5}, + 0.1, + 1, + ) + + for i, mult := range attack[c.NormalCounter] { + c.QueueCharTask(func() { + var c4cb combat.AttackCBFunc + if c.StatusIsActive(burstBuffKey) { + ai.Abil = fmt.Sprintf("Dusk Bolt %v", c.NormalCounter) + ai.AttackTag = attacks.AttackTagExtra + ai.ICDTag = attacks.ICDTagElementalBurst + ai.Element = attributes.Electro + ai.FlatDmg += burstEM[c.TalentLvlBurst()] * c.Stat(attributes.EM) + + deltaPos := c.Core.Combat.Player().Pos().Sub(c.Core.Combat.PrimaryTarget().Pos()) + dist := deltaPos.Magnitude() + + // simulate piercing. Extends 15 units from player + ap = combat.NewBoxHit( + c.Core.Combat.Player(), + c.Core.Combat.PrimaryTarget(), + geometry.Point{Y: -dist}, + 0.1, + 15, + ) + c4cb = c.makeC4cb() + } + ai.Mult = mult[c.TalentLvlAttack()] + c.Core.QueueAttack( + ai, + ap, + 0, + travel, + c4cb, + ) + }, attackHitmarks[c.NormalCounter][i]) + } + + 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 +} diff --git a/internal/characters/sethos/burst.go b/internal/characters/sethos/burst.go new file mode 100644 index 0000000000..10a0a3a110 --- /dev/null +++ b/internal/characters/sethos/burst.go @@ -0,0 +1,29 @@ +package sethos + +import ( + "github.com/genshinsim/gcsim/internal/frames" + "github.com/genshinsim/gcsim/pkg/core/action" +) + +var burstFrames []int + +const burstBuffKey = "sethos-burst" + +func init() { + burstFrames = frames.InitAbilSlice(50) +} + +func (c *char) Burst(p map[string]int) (action.Info, error) { + c.AddStatus(burstBuffKey, 8*60, true) + c.c2AddStack(c2BurstKey) + + c.SetCD(action.ActionBurst, 15*60) + c.ConsumeEnergy(7) + + return action.Info{ + Frames: frames.NewAbilFunc(burstFrames), + AnimationLength: burstFrames[action.InvalidAction], + CanQueueAfter: burstFrames[action.ActionDash], // earliest cancel + State: action.BurstState, + }, nil +} diff --git a/internal/characters/sethos/config.yml b/internal/characters/sethos/config.yml new file mode 100644 index 0000000000..506608ba2d --- /dev/null +++ b/internal/characters/sethos/config.yml @@ -0,0 +1,35 @@ +package_name: sethos +genshin_id: 10000097 +key: sethos +action_param_keys: + attack: + - param: "travel" + aim: + - param: "hold" + - param: "travel" + - param: "weakspot" +skill_data_mapping: + attack: # Normal Attack: Royal Reed Bowmanship + attack_1: + - 0 # 1-Hit DMG|{param0:F1P} + attack_2: + - 1 # 2-Hit DMG|{param1:F1P}+{param2:F1P} + - 2 # 2-Hit DMG|{param1:F1P}+{param2:F1P} + attack_3: + - 3 # 3-Hit DMG|{param3:F1P} + aim: + - 4 # Aimed Shot|{param4:F1P} + fullaim: + - 5 # Aimed Shot Charge Level 1|{param5:F1P} + shadowpierceAtk: + - 6 # Shadowpiercing Shot DMG|{param6:P} ATK+{param7:P} Elemental Mastery + shadowpierceEM: + - 7 # Shadowpiercing Shot DMG|{param6:P} ATK+{param7:P} Elemental Mastery + skill: # Ancient Rite: Thunderous Roar of Sand + skill: + - 0 # Skill DMG|{param0:F1P} + skillEnergyRegen: + - 1 # Energy Regeneration|{param1:I} + burst: # Secret Rite: Twilight Shadowpiercer + burstEM: + - 0 # Dusk Bolt DMG Increase|{param0:F1P} Elemental Mastery diff --git a/internal/characters/sethos/cons.go b/internal/characters/sethos/cons.go new file mode 100644 index 0000000000..c53b3d1516 --- /dev/null +++ b/internal/characters/sethos/cons.go @@ -0,0 +1,145 @@ +package sethos + +import ( + "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/player/character" + "github.com/genshinsim/gcsim/pkg/core/targets" + "github.com/genshinsim/gcsim/pkg/modifier" +) + +const ( + c2Key = "sethos-c2" + c2ConsumingKey = "sethos-c2-consuming" + c2RegainingKey = "sethos-c2-regaining" + c2BurstKey = "sethos-c2-burst" + + c2Dur = 10 * 60 +) + +const c4Key = "sethos-c4" +const c4Dur = 10 * 60 + +const c6Key = "sethos-c6" +const c6IcdKey = "sethos-c6-icd" +const c6IcdDur = 15 * 60 + +func (c *char) c1() { + if c.Base.Cons < 1 { + return + } + m := make([]float64, attributes.EndStatType) + m[attributes.CR] = 0.15 + c.AddAttackMod(character.AttackMod{ + Base: modifier.NewBase("sethos-c1", -1), + Amount: func(atk *combat.AttackEvent, t combat.Target) ([]float64, bool) { + if atk.Info.AttackTag != attacks.AttackTagExtra { + return nil, false + } + if atk.Info.Abil != shadowPierceShotAil { + return nil, false + } + return m, true + }, + }) +} + +func (c *char) c2() { + if c.Base.Cons < 2 { + return + } + mElectro := make([]float64, attributes.EndStatType) + c.AddStatMod(character.StatMod{ + Base: modifier.NewBase(c2Key, -1), + Amount: func() ([]float64, bool) { + stackCount := c.c2Stacks() + if stackCount == 0 { + return nil, false + } + mElectro[attributes.ElectroP] = 0.15 * float64(stackCount) + return mElectro, true + }, + }) +} + +func (c *char) c2AddStack(name string) { + if c.Base.Cons < 2 { + return + } + c.AddStatus(name, c2Dur, true) +} + +func (c *char) c2Stacks() int { + stacks := 0 + if c.StatusIsActive(c2ConsumingKey) { + stacks++ + } + if c.StatusIsActive(c2RegainingKey) { + stacks++ + } + if c.StatusIsActive(c2BurstKey) { + stacks++ + } + return min(stacks, 2) +} + +func (c *char) c4() { + if c.Base.Cons < 4 { + return + } + c.c4Buff = make([]float64, attributes.EndStatType) + c.c4Buff[attributes.EM] = 80 +} + +func (c *char) makeC4cb() combat.AttackCBFunc { + if c.Base.Cons < 4 { + return nil + } + count := 0 + return func(a combat.AttackCB) { + if a.Target.Type() != targets.TargettableEnemy { + return + } + if count >= 2 { + return + } + count += 1 + if count == 2 { + for _, char := range c.Core.Player.Chars() { + char.AddStatMod(character.StatMod{ + Base: modifier.NewBaseWithHitlag(c4Key, c4Dur), + AffectedStat: attributes.EM, + Amount: func() ([]float64, bool) { + return c.c4Buff, true + }, + }) + } + } + } +} + +func (c *char) makeC6cb(energy float64) combat.AttackCBFunc { + if c.Base.Cons < 6 { + return nil + } + if c.Base.Ascension < 1 { + return nil + } + + done := false + return func(a combat.AttackCB) { + if a.Target.Type() != targets.TargettableEnemy { + return + } + if done { + return + } + if c.StatusIsActive(c6IcdKey) { + return + } + done = true + c.AddStatus(c6IcdKey, c6IcdDur, true) + c.AddEnergy(c6Key, energy) + } +} diff --git a/internal/characters/sethos/data_gen.textproto b/internal/characters/sethos/data_gen.textproto new file mode 100644 index 0000000000..38f3db3446 --- /dev/null +++ b/internal/characters/sethos/data_gen.textproto @@ -0,0 +1,151 @@ +id: 10000097 +key: "sethos" +rarity: QUALITY_PURPLE +body: BODY_BOY +region: ASSOC_TYPE_SUMERU +element: Electric +weapon_class: WEAPON_BOW +icon_name: "UI_AvatarIcon_Sethos" +stats: { + base_hp: 820.6119 + base_atk: 19.05456 + base_def: 46.9245 + hp_curve: GROW_CURVE_HP_S4 + atk_curve: GROW_CURVE_ATTACK_S4 + def_cruve: GROW_CURVE_HP_S4 + promo_data: { + max_level: 20 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + } + } + promo_data: { + max_level: 40 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + value: 613.0391 + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + value: 35.055 + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + value: 14.235066 + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + } + } + promo_data: { + max_level: 50 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + value: 1048.6195 + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + value: 59.9625 + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + value: 24.349455 + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + value: 24 + } + } + promo_data: { + max_level: 60 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + value: 1629.3934 + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + value: 93.1725 + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + value: 37.835308 + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + value: 48 + } + } + promo_data: { + max_level: 70 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + value: 2064.9739 + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + value: 118.08 + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + value: 47.949696 + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + value: 48 + } + } + promo_data: { + max_level: 80 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + value: 2500.5542 + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + value: 142.9875 + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + value: 58.064083 + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + value: 72 + } + } + promo_data: { + max_level: 90 + add_props: { + prop_type: FIGHT_PROP_BASE_HP + value: 2936.1348 + } + add_props: { + prop_type: FIGHT_PROP_BASE_DEFENSE + value: 167.895 + } + add_props: { + prop_type: FIGHT_PROP_BASE_ATTACK + value: 68.178474 + } + add_props: { + prop_type: FIGHT_PROP_ELEMENT_MASTERY + value: 96 + } + } +} +skill_details: { + skill: 10972 + burst: 10975 + attack: 10971 + burst_energy_cost: 60 +} +name_text_hash_map: 1431322346 diff --git a/internal/characters/sethos/sethos.go b/internal/characters/sethos/sethos.go new file mode 100644 index 0000000000..c0c357b304 --- /dev/null +++ b/internal/characters/sethos/sethos.go @@ -0,0 +1,58 @@ +package sethos + +import ( + tmpl "github.com/genshinsim/gcsim/internal/template/character" + "github.com/genshinsim/gcsim/pkg/core" + "github.com/genshinsim/gcsim/pkg/core/info" + "github.com/genshinsim/gcsim/pkg/core/keys" + "github.com/genshinsim/gcsim/pkg/core/player/character" + "github.com/genshinsim/gcsim/pkg/model" +) + +func init() { + core.RegisterCharFunc(keys.Sethos, NewChar) +} + +type char struct { + *tmpl.Character + lastSkillFrame int + a4Count int + c4Buff []float64 +} + +func NewChar(s *core.Core, w *character.CharWrapper, _ info.CharacterProfile) error { + c := char{} + + c.Character = tmpl.NewWithWrapper(s, w) + + c.EnergyMax = 60 + c.NormalHitNum = normalHitNum + c.BurstCon = 5 + c.NormalCon = 3 + + c.lastSkillFrame = -1 + + w.Character = &c + + return nil +} + +func (c *char) Init() error { + c.skillRefundHook() + c.a4() + c.c1() + c.c2() + c.c4() + return nil +} + +func (c *char) AnimationStartDelay(k model.AnimationDelayKey) int { + switch k { + case model.AnimationXingqiuN0StartDelay: + return 5 + case model.AnimationYelanN0StartDelay: + return 4 + default: + return c.Character.AnimationStartDelay(k) + } +} diff --git a/internal/characters/sethos/sethos_gen.go b/internal/characters/sethos/sethos_gen.go new file mode 100644 index 0000000000..7648407f47 --- /dev/null +++ b/internal/characters/sethos/sethos_gen.go @@ -0,0 +1,258 @@ +// Code generated by "pipeline"; DO NOT EDIT. +package sethos + +import ( + _ "embed" + + "fmt" + "github.com/genshinsim/gcsim/pkg/core/action" + "github.com/genshinsim/gcsim/pkg/core/keys" + "github.com/genshinsim/gcsim/pkg/gcs/validation" + "github.com/genshinsim/gcsim/pkg/model" + "google.golang.org/protobuf/encoding/prototext" + "slices" +) + +//go:embed data_gen.textproto +var pbData []byte +var base *model.AvatarData +var paramKeysValidation = map[action.Action][]string{ + 3: {"travel"}, + 7: {"rr_cancel", "hold", "travel", "weakspot"}, +} + +func init() { + base = &model.AvatarData{} + err := prototext.Unmarshal(pbData, base) + if err != nil { + panic(err) + } + validation.RegisterCharParamValidationFunc(keys.Sethos, ValidateParamKeys) +} + +func ValidateParamKeys(a action.Action, keys []string) error { + valid, ok := paramKeysValidation[a] + if !ok { + return nil + } + for _, v := range keys { + if !slices.Contains(valid, v) { + return fmt.Errorf("key %v is invalid for action %v", v, a.String()) + } + } + return nil +} + +func (x *char) Data() *model.AvatarData { + return base +} + +var ( + attack = [][][]float64{ + {attack_1}, + attack_2, + {attack_3}, + } +) + +var ( + // attack: aim = [4] + aim = []float64{ + 0.4386, + 0.4743, + 0.51, + 0.561, + 0.5967, + 0.6375, + 0.6936, + 0.7497, + 0.8058, + 0.867, + 0.9282, + 0.9894, + 1.0506, + 1.1118, + 1.173, + } + // attack: attack_1 = [0] + attack_1 = []float64{ + 0.526139, + 0.568965, + 0.61179, + 0.672969, + 0.715794, + 0.764737, + 0.832034, + 0.899331, + 0.966628, + 1.040043, + 1.113458, + 1.186873, + 1.260287, + 1.333702, + 1.407117, + } + // attack: attack_2 = [1 2] + attack_2 = [][]float64{ + { + 0.237962, + 0.257331, + 0.2767, + 0.30437, + 0.323739, + 0.345875, + 0.376312, + 0.406749, + 0.437186, + 0.47039, + 0.503594, + 0.536798, + 0.570002, + 0.603206, + 0.63641, + }, + { + 0.266084, + 0.287742, + 0.3094, + 0.34034, + 0.361998, + 0.38675, + 0.420784, + 0.454818, + 0.488852, + 0.52598, + 0.563108, + 0.600236, + 0.637364, + 0.674492, + 0.71162, + }, + } + // attack: attack_3 = [3] + attack_3 = []float64{ + 0.739867, + 0.800088, + 0.86031, + 0.946341, + 1.006563, + 1.075387, + 1.170022, + 1.264656, + 1.35929, + 1.462527, + 1.565764, + 1.669001, + 1.772239, + 1.875476, + 1.978713, + } + // attack: fullaim = [5] + fullaim = []float64{ + 1.24, + 1.333, + 1.426, + 1.55, + 1.643, + 1.736, + 1.86, + 1.984, + 2.108, + 2.232, + 2.356, + 2.48, + 2.635, + 2.79, + 2.945, + } + // attack: shadowpierceAtk = [6] + shadowpierceAtk = []float64{ + 1.4, + 1.505, + 1.61, + 1.75, + 1.855, + 1.96, + 2.1, + 2.24, + 2.38, + 2.52, + 2.66, + 2.8, + 2.975, + 3.15, + 3.325, + } + // attack: shadowpierceEM = [7] + shadowpierceEM = []float64{ + 1.3456, + 1.44652, + 1.54744, + 1.682, + 1.78292, + 1.88384, + 2.0184, + 2.15296, + 2.28752, + 2.42208, + 2.55664, + 2.6912, + 2.8594, + 3.0276, + 3.1958, + } + // skill: skill = [0] + skill = []float64{ + 1.156, + 1.2427, + 1.3294, + 1.445, + 1.5317, + 1.6184, + 1.734, + 1.8496, + 1.9652, + 2.0808, + 2.1964, + 2.312, + 2.4565, + 2.601, + 2.7455, + } + // skill: skillEnergyRegen = [1] + skillEnergyRegen = []float64{ + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + } + // burst: burstEM = [0] + burstEM = []float64{ + 1.9616, + 2.10872, + 2.25584, + 2.452, + 2.59912, + 2.74624, + 2.9424, + 3.13856, + 3.33472, + 3.53088, + 3.72704, + 3.9232, + 4.1684, + 4.4136, + 4.6588, + } +) diff --git a/internal/characters/sethos/skill.go b/internal/characters/sethos/skill.go new file mode 100644 index 0000000000..353357dc13 --- /dev/null +++ b/internal/characters/sethos/skill.go @@ -0,0 +1,97 @@ +package sethos + +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/targets" +) + +var skillFrames []int + +const skillParticleICDKey = "sethos-particle-icd" + +func init() { + skillFrames = frames.InitAbilSlice(38) // E -> Charge + skillFrames[action.ActionAttack] = 28 + skillFrames[action.ActionSkill] = 32 + skillFrames[action.ActionBurst] = 28 + skillFrames[action.ActionDash] = 27 + skillFrames[action.ActionJump] = 27 + skillFrames[action.ActionWalk] = 25 + skillFrames[action.ActionSwap] = 30 +} + +func (c *char) skillRefundHook() { + refundCB := func(args ...interface{}) bool { + // TODO: Check if Sethos E filters by enemy + // a := args[0].(combat.Target) + // if a.Type() != targets.TargettableEnemy { + // return false + // } + ae := args[1].(*combat.AttackEvent) + if ae.Info.ActorIndex != c.Index { + return false + } + if ae.Info.AttackTag != attacks.AttackTagElementalArt { + return false + } + // to avoid procing twice in aoe + if c.lastSkillFrame == ae.SourceFrame { + return false + } + c.lastSkillFrame = ae.SourceFrame + c.AddEnergy("sethos-skill", skillEnergyRegen[c.TalentLvlSkill()]) + c.c2AddStack(c2RegainingKey) + + return false + } + + c.Core.Events.Subscribe(event.OnOverload, refundCB, "sethos-e-refund") + c.Core.Events.Subscribe(event.OnElectroCharged, refundCB, "sethos-e-refund") + c.Core.Events.Subscribe(event.OnSuperconduct, refundCB, "sethos-e-refund") + c.Core.Events.Subscribe(event.OnSwirlElectro, refundCB, "sethos-e-refund") + c.Core.Events.Subscribe(event.OnHyperbloom, refundCB, "sethos-e-refund") + c.Core.Events.Subscribe(event.OnQuicken, refundCB, "sethos-e-refund") + c.Core.Events.Subscribe(event.OnAggravate, refundCB, "sethos-e-refund") +} + +func (c *char) Skill(p map[string]int) (action.Info, error) { + ai := combat.AttackInfo{ + ActorIndex: c.Index, + Abil: "Ancient Rite: Thunderous Roar of Sand", + AttackTag: attacks.AttackTagElementalArt, + ICDTag: attacks.ICDTagNone, + ICDGroup: attacks.ICDGroupDefault, + StrikeType: attacks.StrikeTypeDefault, + Element: attributes.Electro, + Durability: 25, + Mult: skill[c.TalentLvlSkill()], + } + + ap := combat.NewCircleHitOnTarget(c.Core.Combat.Player(), nil, 4.5) + c.Core.QueueAttack(ai, ap, 0, 13, c.particleCB) + + c.SetCDWithDelay(action.ActionSkill, 8*60, 10) + + return action.Info{ + Frames: frames.NewAbilFunc(skillFrames), + AnimationLength: skillFrames[action.InvalidAction], + CanQueueAfter: skillFrames[action.ActionSwap], // earliest cancel + State: action.SkillState, + }, nil +} + +func (c *char) particleCB(a combat.AttackCB) { + if a.Target.Type() != targets.TargettableEnemy { + return + } + if c.StatusIsActive(skillParticleICDKey) { + return + } + c.AddStatus(skillParticleICDKey, 0.5*60, true) + c.Core.QueueParticle(c.Base.Key.String(), 2, attributes.Electro, c.ParticleDelay) +} diff --git a/pkg/core/keys/keys_char_gen.go b/pkg/core/keys/keys_char_gen.go index 26d39c3dc5..7e52b4342b 100644 --- a/pkg/core/keys/keys_char_gen.go +++ b/pkg/core/keys/keys_char_gen.go @@ -70,6 +70,7 @@ const ( Rosaria Sara Sayu + Sethos Shenhe Sucrose Tartaglia @@ -346,6 +347,10 @@ func init() { charPrettyName[Sayu] = "Sayu" CharKeyToEle[Sayu] = attributes.Anemo + charNames[Sethos] = "sethos" + charPrettyName[Sethos] = "Sethos" + CharKeyToEle[Sethos] = attributes.Electro + charNames[Shenhe] = "shenhe" charPrettyName[Shenhe] = "Shenhe" CharKeyToEle[Shenhe] = attributes.Cryo diff --git a/pkg/model/enemy_gen.go b/pkg/model/enemy_gen.go index 752c06f7aa..19266dc692 100644 --- a/pkg/model/enemy_gen.go +++ b/pkg/model/enemy_gen.go @@ -3020,6 +3020,36 @@ var EnemyMap = map[int]*MonsterData{ }, }, }, + 22120201: { + Id: 22120201, + Key: "tenebrousmimiflora", + NameTextHashMap: 2988972984, + BaseStats: &MonsterStatsData{ + BaseHp: 46.1856, + HpCurve: MonsterCurveType_GROW_CURVE_HP_2, + Resist: &MonsterResistData{ + FireResist: 0.1, + GrassResist: 0.1, + WaterResist: 0.1, + ElectricResist: 0.1, + WindResist: 0.1, + IceResist: 0.1, + RockResist: 0.1, + PhysicalResist: 0.1, + }, + FreezeResist: 0, + HpDrop: []*MonsterHPDrop{ + { + DropId: 22010010, + HpPercent: 0.6, + }, + { + DropId: 22010010, + HpPercent: 0, + }, + }, + }, + }, 21030101: { Id: 21030101, Key: "hydrosamachurl", @@ -4383,6 +4413,44 @@ var EnemyMap = map[int]*MonsterData{ }, }, }, + 24090201: { + Id: 24090201, + Key: "secretsourceautomatonconfigurationdevice", + NameTextHashMap: 2834509656, + BaseStats: &MonsterStatsData{ + BaseHp: 353.184, + HpCurve: MonsterCurveType_GROW_CURVE_HP_2, + Resist: &MonsterResistData{ + FireResist: 0.1, + GrassResist: 0.1, + WaterResist: 0.1, + ElectricResist: 0.1, + WindResist: 0.1, + IceResist: 0.1, + RockResist: 0.1, + PhysicalResist: 0.1, + }, + FreezeResist: 1, + HpDrop: []*MonsterHPDrop{ + { + DropId: 22010030, + HpPercent: 0.75, + }, + { + DropId: 22010030, + HpPercent: 0.5, + }, + { + DropId: 22010030, + HpPercent: 0.25, + }, + { + DropId: 22010030, + HpPercent: 0, + }, + }, + }, + }, 24810101: { Id: 24810101, Key: "basicdefensivepyrotower", diff --git a/pkg/shortcut/characters.go b/pkg/shortcut/characters.go index 5f6f0d0373..6f7ea0d847 100644 --- a/pkg/shortcut/characters.go +++ b/pkg/shortcut/characters.go @@ -169,4 +169,5 @@ var CharNameToKey = map[string]keys.Char{ "clorinde": keys.Clorinde, "emilie": keys.Emilie, "mualani": keys.Mualani, + "sethos": keys.Sethos, } diff --git a/pkg/shortcut/enemies_gen.go b/pkg/shortcut/enemies_gen.go index 5e475af8e3..b841934603 100644 --- a/pkg/shortcut/enemies_gen.go +++ b/pkg/shortcut/enemies_gen.go @@ -96,6 +96,7 @@ var MonsterNameToID = map[string]int{ "shatterstonebreacherprimus": 22110201, "largeovergrownbreacherprimus": 22110301, "overgrownbreacherprimus": 22110402, + "tenebrousmimiflora": 22120201, "hydrosamachurl": 21030101, "dendrosamachurl": 21030201, "anemosamachurl": 21030301, @@ -137,6 +138,7 @@ var MonsterNameToID = map[string]int{ "icewindsuite": 24070101, "experimentalfieldgenerator": 24080101, "secretsourceautomatonhunterseeker": 24090101, + "secretsourceautomatonconfigurationdevice": 24090201, "basicdefensivepyrotower": 24810101, "advanceddefensivepyrotower": 24810201, "basicdefensiveelectrotower": 24810301, diff --git a/pkg/simulation/imports_char_gen.go b/pkg/simulation/imports_char_gen.go index 4f341d2256..d78bce1906 100644 --- a/pkg/simulation/imports_char_gen.go +++ b/pkg/simulation/imports_char_gen.go @@ -65,6 +65,7 @@ import ( _ "github.com/genshinsim/gcsim/internal/characters/rosaria" _ "github.com/genshinsim/gcsim/internal/characters/sara" _ "github.com/genshinsim/gcsim/internal/characters/sayu" + _ "github.com/genshinsim/gcsim/internal/characters/sethos" _ "github.com/genshinsim/gcsim/internal/characters/shenhe" _ "github.com/genshinsim/gcsim/internal/characters/sucrose" _ "github.com/genshinsim/gcsim/internal/characters/tartaglia" diff --git a/ui/packages/components/src/Editor/mode-gcsim.js b/ui/packages/components/src/Editor/mode-gcsim.js index ca13be0c5f..918b13cdf2 100644 --- a/ui/packages/components/src/Editor/mode-gcsim.js +++ b/ui/packages/components/src/Editor/mode-gcsim.js @@ -79,7 +79,7 @@ ace.define( 'let|while|if|else|fn|switch|case|default|break|continue|fallthrough|return|for|' + 'options|add|char|stats|weapon|set|lvl|refine|cons|talent|count|active|target|particle_threshold|particle_drop_count|resist|energy|hurt'; var gcsimAvatars = - 'traveleranemo|travelergeo|travelerelectro|travelerdendro|travelerhydro|travelerpyro|travelercryo|aether-anemo|lumine-anemo|aether-geo|lumine-geo|aether-electro|lumine-electro|aether-dendro|lumine-dendro|aether-hydro|lumine-hydro|aether-pyro|lumine-pyro|aether-cryo|lumine-cryo|aetheranemo|lumineanemo|aethergeo|luminegeo|aetherelectro|lumineelectro|aetherdendro|luminedendro|aetherhydro|luminehydro|aetherpyro|luminepyro|aethercryo|luminecryo|albedo|aloy|amber|barbara|barb|beidou|bennett|charlotte|chongyun|chong|cyno|diluc|diona|eula|fischl|fish|amy|ganyu|hutao|tao|ht|jean|kaedeharakazuha|kazuha|kaz|kaeya|kamisatoayaka|ayaka|kamisatoayato|ayato|keqing|keq|klee|kujousara|kujosara|sara|lisa|mona|ningguang|ning|noelle|qiqi|raidenshogun|raiden|herexcellencythealmightynarukamiogoshogodofthunder|razor|rosaria|rosa|sangonomiyakokomi|kokomi|koko|sayu|sucrose|tartaglia|childe|thoma|venti|xiangling|xl|xianyun|cloudretainer|liuyun|xiao|xingqiu|xq|xinyan|yanfei|yoimiya|yoi|yunjin|zhongli|zhong|zl|gorou|aratakiitto|itto|aratakitheoneandoniitto|shenhe|yae|yaemiko|yelan|kukishinobu|kuki|shikanoinheizou|heizou|tighnari|collei|dori|candace|nilou|kusanali|lesserlordkusanali|nahida|layla|faruzan|faru|wanderer|scaramouche|scara|kunikuzushi|kuni|kabukimono|alhaitham|haitham|baizhu|dehya|yaoyao|mika|kaveh|kirara|lyney|lynette|neuvillette|neuv|chiefjusticeoffontaine|freminet|furina|furinadefontaine|navia|demoiselle|wriothesley|wrio|chevreuse|chev|gaming|chiori|arlecchino|arle|mualani'; + 'traveleranemo|travelergeo|travelerelectro|travelerdendro|travelerhydro|travelerpyro|travelercryo|aether-anemo|lumine-anemo|aether-geo|lumine-geo|aether-electro|lumine-electro|aether-dendro|lumine-dendro|aether-hydro|lumine-hydro|aether-pyro|lumine-pyro|aether-cryo|lumine-cryo|aetheranemo|lumineanemo|aethergeo|luminegeo|aetherelectro|lumineelectro|aetherdendro|luminedendro|aetherhydro|luminehydro|aetherpyro|luminepyro|aethercryo|luminecryo|albedo|aloy|amber|barbara|barb|beidou|bennett|charlotte|chongyun|chong|cyno|diluc|diona|eula|fischl|fish|amy|ganyu|hutao|tao|ht|jean|kaedeharakazuha|kazuha|kaz|kaeya|kamisatoayaka|ayaka|kamisatoayato|ayato|keqing|keq|klee|kujousara|kujosara|sara|lisa|mona|ningguang|ning|noelle|qiqi|raidenshogun|raiden|herexcellencythealmightynarukamiogoshogodofthunder|razor|rosaria|rosa|sangonomiyakokomi|kokomi|koko|sayu|sucrose|tartaglia|childe|thoma|venti|xiangling|xl|xianyun|cloudretainer|liuyun|xiao|xingqiu|xq|xinyan|yanfei|yoimiya|yoi|yunjin|zhongli|zhong|zl|gorou|aratakiitto|itto|aratakitheoneandoniitto|shenhe|yae|yaemiko|yelan|kukishinobu|kuki|shikanoinheizou|heizou|tighnari|collei|dori|candace|nilou|kusanali|lesserlordkusanali|nahida|layla|faruzan|faru|wanderer|scaramouche|scara|kunikuzushi|kuni|kabukimono|alhaitham|haitham|baizhu|dehya|yaoyao|mika|kaveh|kirara|lyney|lynette|neuvillette|neuv|chiefjusticeoffontaine|freminet|furina|furinadefontaine|navia|demoiselle|wriothesley|wrio|chevreuse|chev|gaming|chiori|arlecchino|arle|mualani|sethos'; var gcsimAbilities = 'attack|charge|aim|skill|burst|low_plunge|high_plunge|dash|jump|walk|swap'; var gcsimStats = 'hp(?:%)?|atk(?:%)?|def(?:%)?|er|em|cr|cd|heal|phys%'; diff --git a/ui/packages/db/src/Data/char_data.generated.json b/ui/packages/db/src/Data/char_data.generated.json index b172bd636f..7795dbcef8 100644 --- a/ui/packages/db/src/Data/char_data.generated.json +++ b/ui/packages/db/src/Data/char_data.generated.json @@ -1251,6 +1251,23 @@ }, "name_text_hash_map ": "2388785242" }, + "sethos": { + "id": 10000097, + "key": "sethos", + "rarity": "QUALITY_PURPLE", + "body": "BODY_BOY", + "region": "ASSOC_TYPE_SUMERU", + "element": "Electric", + "weapon_class": "WEAPON_BOW", + "icon_name": "UI_AvatarIcon_Sethos", + "skill_details": { + "skill": 10972, + "burst": 10975, + "attack": 10971, + "burst_energy_cost": 60 + }, + "name_text_hash_map ": "1431322346" + }, "shenhe": { "id": 10000063, "key": "shenhe", diff --git a/ui/packages/docs/docs/reference/characters/sethos.md b/ui/packages/docs/docs/reference/characters/sethos.md new file mode 100644 index 0000000000..25b985bf4f --- /dev/null +++ b/ui/packages/docs/docs/reference/characters/sethos.md @@ -0,0 +1,44 @@ +--- +title: Sethos +--- + +import HitlagTable from "@site/src/components/Hitlag/HitlagTable"; +import FieldsTable from "@site/src/components/Fields/FieldsTable"; +import ParamsTable from "@site/src/components/Params/ParamsTable"; +import FramesTable from "@site/src/components/Frames/FramesTable"; +import IssuesTable from "@site/src/components/Issues/IssuesTable"; +import AoETable from "@site/src/components/AoE/AoETable"; +import NamesList from "@site/src/components/Names/NamesList"; +import ActionsTable from "@site/src/components/Actions/ActionsTable"; + +## Frames + + + +## Hitlag Data + + + +## AoE Data + + + +## Known issues + + + +## Names + + + +## Legal Actions + + + +## Params + + + +## Fields + + diff --git a/ui/packages/docs/src/components/Actions/character_data.json b/ui/packages/docs/src/components/Actions/character_data.json index 06de48834b..3335011f52 100644 --- a/ui/packages/docs/src/components/Actions/character_data.json +++ b/ui/packages/docs/src/components/Actions/character_data.json @@ -2303,6 +2303,40 @@ "legal": true } ], + "sethos": [ + { + "ability": "attack", + "legal": true + }, + { + "ability": "aim", + "legal": true + }, + { + "ability": "skill", + "legal": true + }, + { + "ability": "burst", + "legal": true + }, + { + "ability": "dash", + "legal": true + }, + { + "ability": "jump", + "legal": true + }, + { + "ability": "walk", + "notes": "Only aim followed by walk has proper frames." + }, + { + "ability": "swap", + "legal": true + } + ], "shenhe": [ { "ability": "attack", diff --git a/ui/packages/docs/src/components/AoE/character_data.json b/ui/packages/docs/src/components/AoE/character_data.json index cad4573122..7bb6480271 100644 --- a/ui/packages/docs/src/components/AoE/character_data.json +++ b/ui/packages/docs/src/components/AoE/character_data.json @@ -7389,6 +7389,132 @@ } ] }, + "sethos": { + "normal": [ + { + "ability": "N1", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "N2-1", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "N2-2", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "N3", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "Q-N1", + "shape": "Box", + "center": "Player", + "boxX": 0.1, + "boxY": 15, + "notes": "This is an approximation because we do not actually know the real AoE. This attack is a line going from the Player to the direction of the Primary Target." + }, + { + "ability": "Q-N2-1", + "shape": "Box", + "center": "Player", + "boxX": 0.1, + "boxY": 15, + "notes": "This is an approximation because we do not actually know the real AoE. This attack is a line going from the Player to the direction of the Primary Target." + }, + { + "ability": "Q-N2-2", + "shape": "Box", + "center": "Player", + "boxX": 0.1, + "boxY": 15, + "notes": "This is an approximation because we do not actually know the real AoE. This attack is a line going from the Player to the direction of the Primary Target." + }, + { + "ability": "Q-N3", + "shape": "Box", + "center": "Player", + "boxX": 0.1, + "boxY": 15, + "notes": "This is an approximation because we do not actually know the real AoE. This attack is a line going from the Player to the direction of the Primary Target." + } + ], + "aim": [ + { + "ability": "Aim", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "Aim-Head", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "FullAim", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "FullAim-Head", + "shape": "Box", + "center": "PrimaryTarget", + "offsetY": -0.5, + "boxX": 0.1, + "boxY": 1 + }, + { + "ability": "ShadowpiercingShot", + "shape": "Box", + "center": "Player", + "boxX": 0.1, + "boxY": 15, + "notes": "This is an approximation because we do not actually know the real AoE. This attack is a line going from the Player to the direction of the Primary Target." + }, + { + "ability": "ShadowpiercingShot-Head", + "shape": "Box", + "center": "Player", + "boxX": 0.1, + "boxY": 15, + "notes": "This is an approximation because we do not actually know the real AoE. This attack is a line going from the Player to the direction of the Primary Target." + } + ], + "skill": [ + { + "ability": "E", + "shape": "Circle", + "center": "Player", + "radius": 4.5 + } + ] + }, "shenhe": { "normal": [ { diff --git a/ui/packages/docs/src/components/Fields/character_data.json b/ui/packages/docs/src/components/Fields/character_data.json index ff58a6aeba..94cd4dffdb 100644 --- a/ui/packages/docs/src/components/Fields/character_data.json +++ b/ui/packages/docs/src/components/Fields/character_data.json @@ -163,14 +163,6 @@ "desc": "Number of Prop Surplus stacks." } ], - "mualani": [ - { - "fields": [ - ".mualani.momentum" - ], - "desc": "Number of Wave Momentum stacks." - } - ], "navia": [ { "fields": [ diff --git a/ui/packages/docs/src/components/Frames/character_data.json b/ui/packages/docs/src/components/Frames/character_data.json index 5399f17669..569c386b2c 100644 --- a/ui/packages/docs/src/components/Frames/character_data.json +++ b/ui/packages/docs/src/components/Frames/character_data.json @@ -1125,6 +1125,14 @@ "count": "https://docs.google.com/spreadsheets/d/1vLH6ZiAhb7G-f5ecYy-sPLoCdouA5krMixtgh9ITX2Q/edit?usp=sharing" } ], + "sethos": [ + { + "vid_credit": "wh1t3snak", + "count_credit": "wh1t3snak", + "vid": "https://youtu.be/MYXatbTDbBo", + "count": "https://docs.google.com/spreadsheets/d/1Fxk8KZtVJjY2P9BX5IIy4oNHIcPqCTi6zqrdAn335dY/edit?usp=sharing" + } + ], "shenhe": [ { "vid_credit": "Kolibri#7675 (special thanks to Xreejan#1180)", diff --git a/ui/packages/docs/src/components/Hitlag/character_data.json b/ui/packages/docs/src/components/Hitlag/character_data.json index 1ee6979e23..840f02f00c 100644 --- a/ui/packages/docs/src/components/Hitlag/character_data.json +++ b/ui/packages/docs/src/components/Hitlag/character_data.json @@ -2845,6 +2845,31 @@ } ] }, + "sethos": { + "aim": [ + { + "ability": "Aim-Head", + "hitHaltTime": 0.12, + "hitHaltTimeScale": 0.01, + "canBeDefenseHalt": false, + "deployable": true + }, + { + "ability": "FullAim-Head", + "hitHaltTime": 0.12, + "hitHaltTimeScale": 0.01, + "canBeDefenseHalt": false, + "deployable": true + }, + { + "ability": "ShadowpiercingShot-Head", + "hitHaltTime": 0.12, + "hitHaltTimeScale": 0.01, + "canBeDefenseHalt": false, + "deployable": true + } + ] + }, "shenhe": { "normal": [ { diff --git a/ui/packages/docs/src/components/Names/character_data.json b/ui/packages/docs/src/components/Names/character_data.json index b65e2ef72d..0b3c65db84 100644 --- a/ui/packages/docs/src/components/Names/character_data.json +++ b/ui/packages/docs/src/components/Names/character_data.json @@ -117,6 +117,7 @@ "koko" ], "sayu": [], + "sethos": [], "shenhe": [], "heizou": [ "shikanoinheizou" diff --git a/ui/packages/docs/src/components/Params/character_data.json b/ui/packages/docs/src/components/Params/character_data.json index 1bf3ab85eb..bac80c28b7 100644 --- a/ui/packages/docs/src/components/Params/character_data.json +++ b/ui/packages/docs/src/components/Params/character_data.json @@ -961,6 +961,28 @@ "desc": "0 for Tap (default), value between 1 and 600 for Hold. The number determines the E duration in frames." } ], + "sethos": [ + { + "ability": "attack", + "param": "travel", + "desc": "Projectile travel time. Default 10 frames." + }, + { + "ability": "aim", + "param": "hold", + "desc": "0 for Physical Aimed Shot, 1 for Fully-Charged Aimed Shot (default), 2 for Shadowpiercing Shot." + }, + { + "ability": "aim", + "param": "travel", + "desc": "Projectile travel time. Default 10 frames." + }, + { + "ability": "aim", + "param": "weakspot", + "desc": "Hit weakspot with aimed shot. Default 0 (false), 1 for true." + } + ], "shenhe": [ { "ability": "skill", diff --git a/ui/packages/localization/src/locales/names.generated.json b/ui/packages/localization/src/locales/names.generated.json index f3805480ca..a75b4cd502 100644 --- a/ui/packages/localization/src/locales/names.generated.json +++ b/ui/packages/localization/src/locales/names.generated.json @@ -74,6 +74,7 @@ "rosaria": "罗莎莉亚", "sara": "九条裟罗", "sayu": "早柚", + "sethos": "赛索斯", "shenhe": "申鹤", "sucrose": "砂糖", "tartaglia": "达达利亚", @@ -578,6 +579,7 @@ "rulerofthechizhangmountainsyiji": "赤璋巡岳府君·移即", "samachurlboss": "(test)丘丘萨满BOSS", "sangonomiyacohort": "珊瑚宫众", + "secretsourceautomatonconfigurationdevice": "秘源机兵·构型械", "secretsourceautomatonhunterseeker": "秘源机兵·寻捕械", "setekhwenut": "风蚀沙虫", "shadowyhuskdefender": "黯色空壳·近卫", @@ -605,6 +607,7 @@ "swiftstepstormscout": "疾迅勇士·荡风斥候", "taintedwatersplittingphantasm": "浊水粉碎幻灵", "taintedwaterspoutingphantasm": "浊水喷吐幻灵", + "tenebrousmimiflora": "深邃拟覆叶", "tepetlisaurus": "嵴锋龙", "tepetlisaurustribechief": "嵴锋龙部族首领", "tepetlisauruswarriorrockbreakerblade": "嵴锋龙武士·破岩锐刃", @@ -734,6 +737,7 @@ "rosaria": "Rosaria", "sara": "Kujou Sara", "sayu": "Sayu", + "sethos": "Sethos", "shenhe": "Shenhe", "sucrose": "Sucrose", "tartaglia": "Tartaglia", @@ -1238,6 +1242,7 @@ "rulerofthechizhangmountainsyiji": "Ruler of the Chizhang Mountains: Yiji", "samachurlboss": "Samachurl Boss", "sangonomiyacohort": "Sangonomiya Cohort", + "secretsourceautomatonconfigurationdevice": "Secret Source Automaton: Configuration Device", "secretsourceautomatonhunterseeker": "Secret Source Automaton: Hunter-Seeker", "setekhwenut": "Setekh Wenut", "shadowyhuskdefender": "Shadowy Husk: Defender", @@ -1265,6 +1270,7 @@ "swiftstepstormscout": "Swiftstep Storm Scout", "taintedwatersplittingphantasm": "Tainted Water-Splitting Phantasm", "taintedwaterspoutingphantasm": "Tainted Water-Spouting Phantasm", + "tenebrousmimiflora": "Tenebrous Mimiflora", "tepetlisaurus": "Tepetlisaurus", "tepetlisaurustribechief": "Tepetlisaurus Tribe Chief", "tepetlisauruswarriorrockbreakerblade": "Tepetlisaurus Warrior: Rockbreaker Blade", @@ -1394,6 +1400,7 @@ "rosaria": "Rosaria", "sara": "Kujou Sara", "sayu": "Sayu", + "sethos": "Sethos", "shenhe": "Shenhe", "sucrose": "Saccharose", "tartaglia": "Tartaglia", @@ -1898,6 +1905,7 @@ "rulerofthechizhangmountainsyiji": "Streifenherrscher der Berge von Chizhang – Yiji", "samachurlboss": "Samachurl-Boss", "sangonomiyacohort": "Sangonomiya-Kohorte", + "secretsourceautomatonconfigurationdevice": "Geheimquellen-Automaton – Konfigurationsgerät", "secretsourceautomatonhunterseeker": "Geheimquellen-Automaton – Jäger-Sucher", "setekhwenut": "Setekh Wenut", "shadowyhuskdefender": "Hülle der Finsternis – Garde", @@ -1925,6 +1933,7 @@ "swiftstepstormscout": "Recke des schnellen Schritts – Sturmspäher", "taintedwatersplittingphantasm": "Wasser spaltender Okeanospuk", "taintedwaterspoutingphantasm": "Wasser speiender Okeanospuk", + "tenebrousmimiflora": "Abgrundimitator", "tepetlisaurus": "Tepetlisaurier", "tepetlisaurustribechief": "Häuptling des Tepetlisaurierstamms", "tepetlisauruswarriorrockbreakerblade": "Tepetlisaurierkrieger – Felsbrecherklinge", @@ -2054,6 +2063,7 @@ "rosaria": "ロサリア", "sara": "九条裟羅", "sayu": "早柚", + "sethos": "セトス", "shenhe": "申鶴", "sucrose": "スクロース", "tartaglia": "タルタリヤ", @@ -2557,6 +2567,7 @@ "rulerofthechizhangmountainstianyu": "赤璋巡岳府君·天虞", "rulerofthechizhangmountainsyiji": "赤璋巡岳府君·移即", "sangonomiyacohort": "珊瑚宮衆", + "secretsourceautomatonconfigurationdevice": "秘源機兵・機巧デバイス", "secretsourceautomatonhunterseeker": "秘源機兵·ハンターシーカー", "setekhwenut": "風蝕ウェネト", "shadowyhuskdefender": "シャドウハスク·守衛", @@ -2584,6 +2595,7 @@ "swiftstepstormscout": "迅疾の勇士·ストームスカウト", "taintedwatersplittingphantasm": "濁水粉砕の幻霊", "taintedwaterspoutingphantasm": "濁水噴出の幻霊", + "tenebrousmimiflora": "深遠なるミミックフローラ", "tepetlisaurus": "テペトル竜", "tepetlisaurustribechief": "テペトル竜の部族の長", "tepetlisauruswarriorrockbreakerblade": "テペトル竜戦士·砕岩のブレード", @@ -2713,6 +2725,7 @@ "rosaria": "로자리아", "sara": "쿠죠 사라", "sayu": "사유", + "sethos": "세토스", "shenhe": "신학", "sucrose": "설탕", "tartaglia": "타르탈리아", @@ -3217,6 +3230,7 @@ "rulerofthechizhangmountainsyiji": "적장순악부군·이즉", "samachurlboss": "츄츄 샤먼BOSS", "sangonomiyacohort": "산호궁 사람들", + "secretsourceautomatonconfigurationdevice": "비밀근원 기계·구축기", "secretsourceautomatonhunterseeker": "비밀근원 기계·포획기", "setekhwenut": "바람 침식 모래 벌레", "shadowyhuskdefender": "암흑의 빈 갑주·근위", @@ -3244,6 +3258,7 @@ "swiftstepstormscout": "날쌘 용사·바람 척후병", "taintedwatersplittingphantasm": "탁한 물의 분쇄 환령", "taintedwaterspoutingphantasm": "탁한 물의 분사 환령", + "tenebrousmimiflora": "그윽한 의태 잎뭉치", "tepetlisaurus": "산룡", "tepetlisaurustribechief": "산룡 부족 족장", "tepetlisauruswarriorrockbreakerblade": "산룡 무사·바위 파쇄검", @@ -3373,6 +3388,7 @@ "rosaria": "Розария", "sara": "Сара", "sayu": "Саю", + "sethos": "Сетос", "shenhe": "Шэнь Хэ", "sucrose": "Сахароза", "tartaglia": "Тарталья", @@ -3877,6 +3893,7 @@ "rulerofthechizhangmountainsyiji": "Владыка гор Чичжан: Ицзи", "samachurlboss": "Босс шамачурлов", "sangonomiyacohort": "Сторонник Сангономии", + "secretsourceautomatonconfigurationdevice": "Автоматон таинственного источника: Конфигуратор", "secretsourceautomatonhunterseeker": "Автоматон таинственного источника: Охотник-ищейка", "setekhwenut": "Унут Сетеха", "shadowyhuskdefender": "Чёрный доспех: Гвардеец", @@ -3904,6 +3921,7 @@ "swiftstepstormscout": "Стремительный воин: Разведчик ветра", "taintedwatersplittingphantasm": "Дробящий воду фантазм", "taintedwaterspoutingphantasm": "Извергающий воду фантазм", + "tenebrousmimiflora": "Сумрачная мимифлора", "tepetlisaurus": "Тепетлизавр", "tepetlisaurustribechief": "Вождь племени тепетлизавров", "tepetlisauruswarriorrockbreakerblade": "Тепетлизавр-воин: Камнедробитель", @@ -4033,6 +4051,7 @@ "rosaria": "Rosaria", "sara": "Kujou Sara", "sayu": "Sayu", + "sethos": "Sethos", "shenhe": "Shenhe", "sucrose": "Sacarosa", "tartaglia": "Tartaglia", @@ -4537,6 +4556,7 @@ "rulerofthechizhangmountainsyiji": "Patrullamontañas de Chizhang - Yiji", "samachurlboss": "Samachurl jefe", "sangonomiyacohort": "Secuaz de Sangonomiya", + "secretsourceautomatonconfigurationdevice": "Autómata Fuentenigma - Máquina configuracional", "secretsourceautomatonhunterseeker": "Autómata Fuentenigma - Rastreador", "setekhwenut": "Gusano de Sutej", "shadowyhuskdefender": "Soldado del Vacío - Defensor", @@ -4564,6 +4584,7 @@ "swiftstepstormscout": "Guerrero Raudo - Exploracéfiros", "taintedwatersplittingphantasm": "Ánima Aplastalodo", "taintedwaterspoutingphantasm": "Ánima Escupelodo", + "tenebrousmimiflora": "Licopodio Abismático", "tepetlisaurus": "Tepetlisaurio", "tepetlisaurustribechief": "Jefe de la tribu de los Tepetlisaurios", "tepetlisauruswarriorrockbreakerblade": "Soldado Tepetlisaurio - Destrozarrocas", diff --git a/ui/packages/ui/src/Data/char_data.generated.json b/ui/packages/ui/src/Data/char_data.generated.json index b172bd636f..7795dbcef8 100644 --- a/ui/packages/ui/src/Data/char_data.generated.json +++ b/ui/packages/ui/src/Data/char_data.generated.json @@ -1251,6 +1251,23 @@ }, "name_text_hash_map ": "2388785242" }, + "sethos": { + "id": 10000097, + "key": "sethos", + "rarity": "QUALITY_PURPLE", + "body": "BODY_BOY", + "region": "ASSOC_TYPE_SUMERU", + "element": "Electric", + "weapon_class": "WEAPON_BOW", + "icon_name": "UI_AvatarIcon_Sethos", + "skill_details": { + "skill": 10972, + "burst": 10975, + "attack": 10971, + "burst_energy_cost": 60 + }, + "name_text_hash_map ": "1431322346" + }, "shenhe": { "id": 10000063, "key": "shenhe", diff --git a/ui/packages/ui/src/Data/weapon_data.generated.json b/ui/packages/ui/src/Data/weapon_data.generated.json index 830850e627..337c94a864 100644 --- a/ui/packages/ui/src/Data/weapon_data.generated.json +++ b/ui/packages/ui/src/Data/weapon_data.generated.json @@ -1561,4 +1561,4 @@ "name_text_hash_map ": "143051931" } } -} \ No newline at end of file +} diff --git a/ui/packages/ui/src/util/mode-gcsim.js b/ui/packages/ui/src/util/mode-gcsim.js index c05ff28f0e..0cf4efe39c 100644 --- a/ui/packages/ui/src/util/mode-gcsim.js +++ b/ui/packages/ui/src/util/mode-gcsim.js @@ -79,7 +79,7 @@ ace.define( 'let|while|if|else|fn|switch|case|default|break|continue|fallthrough|return|for|' + 'options|add|char|stats|weapon|set|lvl|refine|cons|talent|count|active|target|particle_threshold|particle_drop_count|resist|energy|hurt'; var gcsimAvatars = - 'traveleranemo|travelergeo|travelerelectro|travelerdendro|travelerhydro|travelerpyro|travelercryo|aether-anemo|lumine-anemo|aether-geo|lumine-geo|aether-electro|lumine-electro|aether-dendro|lumine-dendro|aether-hydro|lumine-hydro|aether-pyro|lumine-pyro|aether-cryo|lumine-cryo|aetheranemo|lumineanemo|aethergeo|luminegeo|aetherelectro|lumineelectro|aetherdendro|luminedendro|aetherhydro|luminehydro|aetherpyro|luminepyro|aethercryo|luminecryo|albedo|aloy|amber|barbara|barb|beidou|bennett|charlotte|chongyun|chong|cyno|diluc|diona|eula|fischl|fish|amy|ganyu|hutao|tao|ht|jean|kaedeharakazuha|kazuha|kaz|kaeya|kamisatoayaka|ayaka|kamisatoayato|ayato|keqing|keq|klee|kujousara|kujosara|sara|lisa|mona|ningguang|ning|noelle|qiqi|raidenshogun|raiden|herexcellencythealmightynarukamiogoshogodofthunder|razor|rosaria|rosa|sangonomiyakokomi|kokomi|koko|sayu|sucrose|tartaglia|childe|thoma|venti|xiangling|xl|xianyun|cloudretainer|liuyun|xiao|xingqiu|xq|xinyan|yanfei|yoimiya|yoi|yunjin|zhongli|zhong|zl|gorou|aratakiitto|itto|aratakitheoneandoniitto|shenhe|yae|yaemiko|yelan|kukishinobu|kuki|shikanoinheizou|heizou|tighnari|collei|dori|candace|nilou|kusanali|lesserlordkusanali|nahida|layla|faruzan|faru|wanderer|scaramouche|scara|kunikuzushi|kuni|kabukimono|alhaitham|haitham|baizhu|dehya|yaoyao|mika|kaveh|kirara|lyney|lynette|neuvillette|neuv|chiefjusticeoffontaine|freminet|furina|furinadefontaine|navia|demoiselle|wriothesley|wrio|chevreuse|chev|gaming|chiori|arlecchino|arle|clorinde|emilie|mualani'; + 'traveleranemo|travelergeo|travelerelectro|travelerdendro|travelerhydro|travelerpyro|travelercryo|aether-anemo|lumine-anemo|aether-geo|lumine-geo|aether-electro|lumine-electro|aether-dendro|lumine-dendro|aether-hydro|lumine-hydro|aether-pyro|lumine-pyro|aether-cryo|lumine-cryo|aetheranemo|lumineanemo|aethergeo|luminegeo|aetherelectro|lumineelectro|aetherdendro|luminedendro|aetherhydro|luminehydro|aetherpyro|luminepyro|aethercryo|luminecryo|albedo|aloy|amber|barbara|barb|beidou|bennett|charlotte|chongyun|chong|cyno|diluc|diona|eula|fischl|fish|amy|ganyu|hutao|tao|ht|jean|kaedeharakazuha|kazuha|kaz|kaeya|kamisatoayaka|ayaka|kamisatoayato|ayato|keqing|keq|klee|kujousara|kujosara|sara|lisa|mona|ningguang|ning|noelle|qiqi|raidenshogun|raiden|herexcellencythealmightynarukamiogoshogodofthunder|razor|rosaria|rosa|sangonomiyakokomi|kokomi|koko|sayu|sucrose|tartaglia|childe|thoma|venti|xiangling|xl|xianyun|cloudretainer|liuyun|xiao|xingqiu|xq|xinyan|yanfei|yoimiya|yoi|yunjin|zhongli|zhong|zl|gorou|aratakiitto|itto|aratakitheoneandoniitto|shenhe|yae|yaemiko|yelan|kukishinobu|kuki|shikanoinheizou|heizou|tighnari|collei|dori|candace|nilou|kusanali|lesserlordkusanali|nahida|layla|faruzan|faru|wanderer|scaramouche|scara|kunikuzushi|kuni|kabukimono|alhaitham|haitham|baizhu|dehya|yaoyao|mika|kaveh|kirara|lyney|lynette|neuvillette|neuv|chiefjusticeoffontaine|freminet|furina|furinadefontaine|navia|demoiselle|wriothesley|wrio|chevreuse|chev|gaming|chiori|arlecchino|arle|clorinde|emilie|mualani|sethos'; var gcsimAbilities = 'attack|charge|aim|skill|burst|low_plunge|high_plunge|dash|jump|walk|swap'; var gcsimStats = 'hp(?:%)?|atk(?:%)?|def(?:%)?|er|em|cr|cd|heal|phys%';