diff --git a/internal/characters/iansan/asc.go b/internal/characters/iansan/asc.go
new file mode 100644
index 0000000000..1ccfddbed8
--- /dev/null
+++ b/internal/characters/iansan/asc.go
@@ -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)
+}
diff --git a/internal/characters/iansan/attack.go b/internal/characters/iansan/attack.go
new file mode 100644
index 0000000000..d581bef1c4
--- /dev/null
+++ b/internal/characters/iansan/attack.go
@@ -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
+}
diff --git a/internal/characters/iansan/burst.go b/internal/characters/iansan/burst.go
new file mode 100644
index 0000000000..0cba87fc97
--- /dev/null
+++ b/internal/characters/iansan/burst.go
@@ -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
+}
diff --git a/internal/characters/iansan/charge.go b/internal/characters/iansan/charge.go
new file mode 100644
index 0000000000..04fb7477ad
--- /dev/null
+++ b/internal/characters/iansan/charge.go
@@ -0,0 +1,105 @@
+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"
+)
+
+var (
+ chargeFrames []int
+ swiftFrames []int
+)
+
+const (
+ chargeHitmark = 24
+ swiftHitmark = 14
+)
+
+func init() {
+ chargeFrames = frames.InitAbilSlice(55)
+ chargeFrames[action.ActionAttack] = 49
+ chargeFrames[action.ActionSkill] = 49
+ chargeFrames[action.ActionBurst] = 50
+ chargeFrames[action.ActionDash] = chargeHitmark
+ chargeFrames[action.ActionJump] = chargeHitmark
+ chargeFrames[action.ActionSwap] = 49
+
+ swiftFrames = frames.InitAbilSlice(52)
+ swiftFrames[action.ActionAttack] = 51
+ swiftFrames[action.ActionSkill] = 51
+ swiftFrames[action.ActionJump] = 50
+ swiftFrames[action.ActionSwap] = 49
+}
+
+func (c *char) ChargeAttack(p map[string]int) (action.Info, error) {
+ if c.nightsoulState.HasBlessing() || c.StatusIsActive(fastSkill) {
+ c.DeleteStatus(fastSkill)
+ return c.chargedSwift(), nil
+ }
+
+ ai := combat.AttackInfo{
+ ActorIndex: c.Index,
+ Abil: "Charged Attack",
+ AttackTag: attacks.AttackTagExtra,
+ ICDTag: attacks.ICDTagExtraAttack,
+ ICDGroup: attacks.ICDGroupPoleExtraAttack,
+ StrikeType: attacks.StrikeTypeSpear,
+ Element: attributes.Physical,
+ Durability: 25,
+ HitlagFactor: 0.01,
+ CanBeDefenseHalted: true,
+ IsDeployable: true,
+ Mult: charged[c.TalentLvlAttack()],
+ }
+
+ c.Core.QueueAttack(
+ ai,
+ combat.NewCircleHit(
+ c.Core.Combat.Player(),
+ c.Core.Combat.PrimaryTarget(),
+ nil,
+ 0.8,
+ ),
+ chargeHitmark,
+ chargeHitmark,
+ )
+
+ return action.Info{
+ Frames: frames.NewAbilFunc(chargeFrames),
+ AnimationLength: chargeFrames[action.InvalidAction],
+ CanQueueAfter: chargeHitmark,
+ State: action.ChargeAttackState,
+ }, nil
+}
+
+func (c *char) chargedSwift() action.Info {
+ ai := combat.AttackInfo{
+ ActorIndex: c.Index,
+ Abil: "Swift Stormflight",
+ AdditionalTags: []attacks.AdditionalTag{attacks.AdditionalTagNightsoul},
+ AttackTag: attacks.AttackTagExtra,
+ ICDTag: attacks.ICDTagNone,
+ ICDGroup: attacks.ICDGroupDefault,
+ StrikeType: attacks.StrikeTypeDefault,
+ Element: attributes.Electro,
+ Durability: 25,
+ Mult: swift[c.TalentLvlSkill()],
+ }
+ c.Core.QueueAttack(
+ ai,
+ combat.NewCircleHit(c.Core.Combat.Player(), c.Core.Combat.PrimaryTarget(), nil, 6),
+ swiftHitmark,
+ swiftHitmark,
+ c.makeA1CB(),
+ )
+
+ return action.Info{
+ Frames: frames.NewAbilFunc(swiftFrames),
+ AnimationLength: swiftFrames[action.InvalidAction],
+ CanQueueAfter: swiftFrames[action.ActionSwap],
+ State: action.ChargeAttackState,
+ }
+}
diff --git a/internal/characters/iansan/config.yml b/internal/characters/iansan/config.yml
new file mode 100644
index 0000000000..8fcb68fa5f
--- /dev/null
+++ b/internal/characters/iansan/config.yml
@@ -0,0 +1,27 @@
+package_name: iansan
+genshin_id: 10000110
+key: iansan
+skill_data_mapping:
+ attack:
+ attack_1:
+ - 0 # 1-Hit DMG|{param1:F1P}
+ attack_2:
+ - 1 # 2-Hit DMG|{param2:F1P}
+ attack_3:
+ - 2 # 3-Hit DMG|{param3:F1P}
+ charged:
+ - 3 # Charged Attack DMG|{param4:F1P}
+ swift:
+ - 4 # Swift Stormflight DMG|{param5:F1P}
+ skill:
+ skill:
+ - 0 # Skill DMG|{param1:F1P}
+ burst:
+ burst:
+ - 0 # Skill DMG|{param1:F1P}
+ highATK:
+ - 1 # High Nightsoul Points ATK Conversion Rate|{param2:P} ATK
+ lowATK:
+ - 2 # Low Nightsoul Points ATK Conversion Rate|{param3:F1P} ATK / Nightsoul Point
+ maxATK:
+ - 3 # Max ATK Bonus|{param4:F1}
diff --git a/internal/characters/iansan/cons.go b/internal/characters/iansan/cons.go
new file mode 100644
index 0000000000..1889b1dea9
--- /dev/null
+++ b/internal/characters/iansan/cons.go
@@ -0,0 +1,90 @@
+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/player/character"
+ "github.com/genshinsim/gcsim/pkg/modifier"
+)
+
+const (
+ c1ICD = "iansan-c1"
+ c6Status = "iansan-c6"
+)
+
+func (c *char) c1(points float64) {
+ if c.Base.Cons < 1 {
+ return
+ }
+ if c.StatusIsActive(c1ICD) {
+ return
+ }
+ c.c1Points += points
+ if c.c1Points < 6 {
+ return
+ }
+ c.AddEnergy("iansan-c1", 15)
+ c.AddStatus(c1ICD, 18*60, true)
+}
+
+func (c *char) c2ATKBuff(char *character.CharWrapper) {
+ c.burstBuff[attributes.ATKP] = 0
+ if c.Base.Cons < 2 {
+ return
+ }
+ if c.Base.Ascension < 1 {
+ return
+ }
+ if c.Index == c.Core.Player.Active() {
+ return
+ }
+ if char.Index != c.Core.Player.Active() {
+ return
+ }
+ c.burstBuff[attributes.ATKP] = 0.3
+}
+
+func (c *char) c4() {
+ if c.Base.Cons < 4 {
+ return
+ }
+
+ c.Core.Events.Subscribe(event.OnBurst, func(args ...interface{}) bool {
+ if !c.StatusIsActive(burstStatus) {
+ return false
+ }
+ if c.Index == c.Core.Player.Active() {
+ return false
+ }
+ c.c4Stacks = 2
+ return false
+ }, "iansan-c4")
+}
+
+func (c *char) c4Points() float64 {
+ if c.Base.Cons < 4 {
+ return 0.0
+ }
+ points := c.pointsOverflow * 0.5
+ if c.c4Stacks > 0 {
+ points += 4.0
+ }
+ return points
+}
+
+func (c *char) c6() {
+ if c.Base.Cons < 6 {
+ return
+ }
+ m := make([]float64, attributes.EndStatType)
+ m[attributes.DmgP] = 0.25
+
+ active := c.Core.Player.ActiveChar()
+ active.AddAttackMod(character.AttackMod{
+ Base: modifier.NewBaseWithHitlag(c6Status, 3*60),
+ Amount: func(atk *combat.AttackEvent, t combat.Target) ([]float64, bool) {
+ return m, true
+ },
+ })
+}
diff --git a/internal/characters/iansan/data_gen.textproto b/internal/characters/iansan/data_gen.textproto
new file mode 100644
index 0000000000..e5e7ec0471
--- /dev/null
+++ b/internal/characters/iansan/data_gen.textproto
@@ -0,0 +1,151 @@
+id: 10000110
+key: "iansan"
+rarity: QUALITY_PURPLE
+body: BODY_LOLI
+region: ASSOC_TYPE_NATLAN
+element: Electric
+weapon_class: WEAPON_POLE
+icon_name: "UI_AvatarIcon_Iansan"
+stats: {
+ base_hp: 893.5552
+ base_atk: 21.54768
+ base_def: 53.505375
+ 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_ATTACK_PERCENT
+ }
+ }
+ promo_data: {
+ max_level: 40
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_HP
+ value: 667.5315
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_DEFENSE
+ value: 39.97125
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_ATTACK
+ value: 16.097597
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_ATTACK_PERCENT
+ }
+ }
+ promo_data: {
+ max_level: 50
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_HP
+ value: 1141.8302
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_DEFENSE
+ value: 68.37187
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_ATTACK
+ value: 27.535364
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_ATTACK_PERCENT
+ value: 0.06
+ }
+ }
+ promo_data: {
+ max_level: 60
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_HP
+ value: 1774.2284
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_DEFENSE
+ value: 106.23937
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_ATTACK
+ value: 42.78572
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_ATTACK_PERCENT
+ value: 0.12
+ }
+ }
+ promo_data: {
+ max_level: 70
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_HP
+ value: 2248.527
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_DEFENSE
+ value: 134.64
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_ATTACK
+ value: 54.223488
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_ATTACK_PERCENT
+ value: 0.12
+ }
+ }
+ promo_data: {
+ max_level: 80
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_HP
+ value: 2722.8257
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_DEFENSE
+ value: 163.04062
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_ATTACK
+ value: 65.661255
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_ATTACK_PERCENT
+ value: 0.18
+ }
+ }
+ promo_data: {
+ max_level: 90
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_HP
+ value: 3197.1245
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_DEFENSE
+ value: 191.44125
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_BASE_ATTACK
+ value: 77.09902
+ }
+ add_props: {
+ prop_type: FIGHT_PROP_ATTACK_PERCENT
+ value: 0.24
+ }
+ }
+}
+skill_details: {
+ skill: 11102
+ burst: 11105
+ attack: 11101
+ burst_energy_cost: 70
+}
+name_text_hash_map: 3120492514
diff --git a/internal/characters/iansan/iansan.go b/internal/characters/iansan/iansan.go
new file mode 100644
index 0000000000..e801843ca6
--- /dev/null
+++ b/internal/characters/iansan/iansan.go
@@ -0,0 +1,76 @@
+package iansan
+
+import (
+ tmpl "github.com/genshinsim/gcsim/internal/template/character"
+ "github.com/genshinsim/gcsim/internal/template/nightsoul"
+ "github.com/genshinsim/gcsim/pkg/core"
+ "github.com/genshinsim/gcsim/pkg/core/action"
+ "github.com/genshinsim/gcsim/pkg/core/attributes"
+ "github.com/genshinsim/gcsim/pkg/core/info"
+ "github.com/genshinsim/gcsim/pkg/core/keys"
+ "github.com/genshinsim/gcsim/pkg/core/player/character"
+)
+
+func init() {
+ core.RegisterCharFunc(keys.Iansan, NewChar)
+}
+
+type char struct {
+ *tmpl.Character
+ nightsoulState *nightsoul.State
+
+ nightsoulSrc int
+ particleGenerated bool
+ burstSrc int
+ burstBuff []float64
+ burstRestoreNS int
+ pointsOverflow float64
+
+ a1Increase bool
+ a4Src int
+
+ c1Points float64
+ c4Stacks int
+}
+
+func NewChar(s *core.Core, w *character.CharWrapper, _ info.CharacterProfile) error {
+ c := char{}
+ c.Character = tmpl.NewWithWrapper(s, w)
+
+ c.EnergyMax = 70
+ c.SkillCon = 3
+ c.BurstCon = 5
+ c.NormalHitNum = normalHitNum
+
+ c.nightsoulState = nightsoul.New(s, w)
+ c.nightsoulState.MaxPoints = 54
+
+ w.Character = &c
+
+ return nil
+}
+
+func (c *char) Init() error {
+ c.burstBuff = make([]float64, attributes.EndStatType)
+
+ c.a1()
+ c.a4()
+
+ return nil
+}
+
+func (c *char) ActionStam(a action.Action, p map[string]int) float64 {
+ if a == action.ActionCharge && c.StatusIsActive(fastSkill) {
+ return 0
+ }
+ return c.Character.ActionStam(a, p)
+}
+
+func (c *char) Condition(fields []string) (any, error) {
+ switch fields[0] {
+ case "nightsoul":
+ return c.nightsoulState.Condition(fields)
+ default:
+ return c.Character.Condition(fields)
+ }
+}
diff --git a/internal/characters/iansan/iansan_gen.go b/internal/characters/iansan/iansan_gen.go
new file mode 100644
index 0000000000..a99aad7a88
--- /dev/null
+++ b/internal/characters/iansan/iansan_gen.go
@@ -0,0 +1,216 @@
+// Code generated by "pipeline"; DO NOT EDIT.
+package iansan
+
+import (
+ _ "embed"
+
+ "github.com/genshinsim/gcsim/pkg/model"
+ "google.golang.org/protobuf/encoding/prototext"
+)
+
+//go:embed data_gen.textproto
+var pbData []byte
+var base *model.AvatarData
+
+func init() {
+ base = &model.AvatarData{}
+ err := prototext.Unmarshal(pbData, base)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func (x *char) Data() *model.AvatarData {
+ return base
+}
+
+var (
+ attack = [][]float64{
+ attack_1,
+ attack_2,
+ attack_3,
+ }
+)
+
+var (
+ // attack: attack_1 = [0]
+ attack_1 = []float64{
+ 0.469758,
+ 0.507994,
+ 0.54623,
+ 0.600853,
+ 0.639089,
+ 0.682787,
+ 0.742873,
+ 0.802958,
+ 0.863043,
+ 0.928591,
+ 0.994139,
+ 1.059686,
+ 1.125234,
+ 1.190781,
+ 1.256329,
+ }
+ // attack: attack_2 = [1]
+ attack_2 = []float64{
+ 0.427644,
+ 0.462452,
+ 0.49726,
+ 0.546986,
+ 0.581794,
+ 0.621575,
+ 0.676274,
+ 0.730972,
+ 0.785671,
+ 0.845342,
+ 0.905013,
+ 0.964684,
+ 1.024356,
+ 1.084027,
+ 1.143698,
+ }
+ // attack: attack_3 = [2]
+ attack_3 = []float64{
+ 0.643882,
+ 0.696291,
+ 0.7487,
+ 0.82357,
+ 0.875979,
+ 0.935875,
+ 1.018232,
+ 1.100589,
+ 1.182946,
+ 1.27279,
+ 1.362634,
+ 1.452478,
+ 1.542322,
+ 1.632166,
+ 1.72201,
+ }
+ // attack: charged = [3]
+ charged = []float64{
+ 1.00276,
+ 1.08438,
+ 1.166,
+ 1.2826,
+ 1.36422,
+ 1.4575,
+ 1.58576,
+ 1.71402,
+ 1.84228,
+ 1.9822,
+ 2.12212,
+ 2.26204,
+ 2.40196,
+ 2.54188,
+ 2.6818,
+ }
+ // attack: swift = [4]
+ swift = []float64{
+ 0.84194,
+ 0.91047,
+ 0.979,
+ 1.0769,
+ 1.14543,
+ 1.22375,
+ 1.33144,
+ 1.43913,
+ 1.54682,
+ 1.6643,
+ 1.78178,
+ 1.89926,
+ 2.01674,
+ 2.13422,
+ 2.2517,
+ }
+ // skill: skill = [0]
+ skill = []float64{
+ 2.864,
+ 3.0788,
+ 3.2936,
+ 3.58,
+ 3.7948,
+ 4.0096,
+ 4.296,
+ 4.5824,
+ 4.8688,
+ 5.1552,
+ 5.4416,
+ 5.728,
+ 6.086,
+ 6.444,
+ 6.802,
+ }
+ // burst: burst = [0]
+ burst = []float64{
+ 4.304,
+ 4.6268,
+ 4.9496,
+ 5.38,
+ 5.7028,
+ 6.0256,
+ 6.456,
+ 6.8864,
+ 7.3168,
+ 7.7472,
+ 8.1776,
+ 8.608,
+ 9.146,
+ 9.684,
+ 10.222,
+ }
+ // burst: highATK = [1]
+ highATK = []float64{
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ 0.27,
+ }
+ // burst: lowATK = [2]
+ lowATK = []float64{
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ 0.005,
+ }
+ // burst: maxATK = [3]
+ maxATK = []float64{
+ 330,
+ 370,
+ 410,
+ 450,
+ 490,
+ 530,
+ 570,
+ 610,
+ 650,
+ 690,
+ 730,
+ 770,
+ 810,
+ 850,
+ 890,
+ }
+)
diff --git a/internal/characters/iansan/skill.go b/internal/characters/iansan/skill.go
new file mode 100644
index 0000000000..906e169090
--- /dev/null
+++ b/internal/characters/iansan/skill.go
@@ -0,0 +1,124 @@
+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/targets"
+)
+
+var (
+ skillHitmark = 21
+ skillFrames []int
+
+ fastSkill = "fast-skill"
+)
+
+func init() {
+ skillFrames = frames.InitAbilSlice(43)
+ skillFrames[action.ActionDash] = 31
+ skillFrames[action.ActionJump] = 32
+ skillFrames[action.ActionSwap] = 42
+}
+
+func (c *char) Skill(p map[string]int) (action.Info, error) {
+ ai := combat.AttackInfo{
+ ActorIndex: c.Index,
+ Abil: "Thunderbolt Rush",
+ AdditionalTags: []attacks.AdditionalTag{attacks.AdditionalTagNightsoul},
+ AttackTag: attacks.AttackTagElementalArt,
+ ICDTag: attacks.ICDTagNone,
+ ICDGroup: attacks.ICDGroupDefault,
+ StrikeType: attacks.StrikeTypeSlash,
+ Element: attributes.Electro,
+ Durability: 25,
+ Mult: skill[c.TalentLvlSkill()],
+ }
+
+ c.Core.QueueAttack(
+ ai,
+ combat.NewCircleHit(
+ c.Core.Combat.Player(),
+ c.Core.Combat.PrimaryTarget(),
+ nil,
+ 1,
+ ),
+ skillHitmark,
+ skillHitmark,
+ c.particleCB,
+ )
+
+ c.AddStatus(fastSkill, 5*60, true)
+ c.enterNightsoul(c.nightsoulState.MaxPoints)
+ c.particleGenerated = false
+ c.SetCD(action.ActionSkill, 16*60)
+
+ return action.Info{
+ Frames: frames.NewAbilFunc(skillFrames),
+ AnimationLength: skillFrames[action.InvalidAction],
+ CanQueueAfter: skillFrames[action.ActionDash], // earliest cancel
+ State: action.SkillState,
+ }, nil
+}
+
+func (c *char) particleCB(a combat.AttackCB) {
+ if a.Target.Type() != targets.TargettableEnemy {
+ return
+ }
+ if c.particleGenerated {
+ return
+ }
+ c.particleGenerated = true
+
+ count := 4.0
+ c.Core.QueueParticle(c.Base.Key.String(), count, attributes.Electro, c.ParticleDelay)
+}
+
+func (c *char) enterNightsoul(points float64) {
+ c.nightsoulSrc = c.Core.F
+ c.nightsoulState.EnterBlessing(points)
+ c.nightsoulPointReduceTask(c.nightsoulSrc)
+ c.setNightsoulExitTimer(16 * 60)
+}
+
+func (c *char) exitNightsoul() {
+ c.nightsoulSrc = -1
+ c.nightsoulState.ExitBlessing()
+ c.DeleteStatus(burstStatus)
+ c.DeleteStatus(a1Status)
+}
+
+func (c *char) nightsoulPointReduceTask(src int) {
+ // reduce 0.6 point every 6f, which is 6 per second
+ const tickInterval = .1
+
+ c.QueueCharTask(func() {
+ if c.nightsoulSrc != src {
+ return
+ }
+
+ points := 0.6
+ c.nightsoulState.ConsumePoints(points)
+ c.c1(points)
+ c.updateATKBuff()
+ if c.nightsoulState.Points() < 0.001 {
+ c.exitNightsoul()
+ return
+ }
+
+ c.nightsoulPointReduceTask(src)
+ }, 60*tickInterval)
+}
+
+func (c *char) setNightsoulExitTimer(duration int) {
+ src := c.nightsoulSrc
+ c.QueueCharTask(func() {
+ if c.nightsoulSrc != src {
+ return
+ }
+ c.nightsoulState.ClearPoints()
+ c.exitNightsoul()
+ }, duration)
+}
diff --git a/internal/services/assets/avatars_gen.go b/internal/services/assets/avatars_gen.go
index f451a2e918..42bdd193ee 100644
--- a/internal/services/assets/avatars_gen.go
+++ b/internal/services/assets/avatars_gen.go
@@ -37,6 +37,7 @@ var avatarMap = map[string]string{
"gorou": "UI_AvatarIcon_Gorou",
"heizou": "UI_AvatarIcon_Heizo",
"hutao": "UI_AvatarIcon_Hutao",
+ "iansan": "UI_AvatarIcon_Iansan",
"itto": "UI_AvatarIcon_Itto",
"jean": "UI_AvatarIcon_Qin",
"kaeya": "UI_AvatarIcon_Kaeya",
diff --git a/pkg/core/keys/keys_char_gen.go b/pkg/core/keys/keys_char_gen.go
index 42950ecb0a..1b7a98bcdd 100644
--- a/pkg/core/keys/keys_char_gen.go
+++ b/pkg/core/keys/keys_char_gen.go
@@ -42,6 +42,7 @@ const (
Gorou
Heizou
Hutao
+ Iansan
Itto
Jean
Kaeya
@@ -242,6 +243,10 @@ func init() {
charPrettyName[Hutao] = "Hutao"
CharKeyToEle[Hutao] = attributes.Pyro
+ charNames[Iansan] = "iansan"
+ charPrettyName[Iansan] = "Iansan"
+ CharKeyToEle[Iansan] = attributes.Electro
+
charNames[Itto] = "itto"
charPrettyName[Itto] = "Itto"
CharKeyToEle[Itto] = attributes.Geo
diff --git a/pkg/gcs/validation/validate.go b/pkg/gcs/validation/validate.go
index 5927197434..3af05c5c92 100644
--- a/pkg/gcs/validation/validate.go
+++ b/pkg/gcs/validation/validate.go
@@ -1,15 +1,31 @@
package validation
import (
+ "slices"
+
"github.com/genshinsim/gcsim/pkg/core/action"
"github.com/genshinsim/gcsim/pkg/core/keys"
)
+// generic params that can be used for any character
+var ignoreParams = []string{
+ // iansan burst
+ "movement",
+}
+
func ValidateCharParamKeys(c keys.Char, a action.Action, keys []string) error {
f, ok := charValidParamKeys[c]
if !ok {
// all is ok if no validation function registered
return nil
}
- return f(a, keys)
+
+ filtered := make([]string, 0, len(keys))
+ for _, v := range keys {
+ if !slices.Contains(ignoreParams, v) {
+ filtered = append(filtered, v)
+ }
+ }
+
+ return f(a, filtered)
}
diff --git a/pkg/shortcut/characters.go b/pkg/shortcut/characters.go
index 58c1c4e83f..1431e2de02 100644
--- a/pkg/shortcut/characters.go
+++ b/pkg/shortcut/characters.go
@@ -180,4 +180,5 @@ var CharNameToKey = map[string]keys.Char{
"olorun": keys.Ororon,
"chasca": keys.Chasca,
"lanyan": keys.Lanyan,
+ "iansan": keys.Iansan,
}
diff --git a/pkg/simulation/imports_char_gen.go b/pkg/simulation/imports_char_gen.go
index e4cc45dfba..54a97dd93a 100644
--- a/pkg/simulation/imports_char_gen.go
+++ b/pkg/simulation/imports_char_gen.go
@@ -37,6 +37,7 @@ import (
_ "github.com/genshinsim/gcsim/internal/characters/gorou"
_ "github.com/genshinsim/gcsim/internal/characters/heizou"
_ "github.com/genshinsim/gcsim/internal/characters/hutao"
+ _ "github.com/genshinsim/gcsim/internal/characters/iansan"
_ "github.com/genshinsim/gcsim/internal/characters/itto"
_ "github.com/genshinsim/gcsim/internal/characters/jean"
_ "github.com/genshinsim/gcsim/internal/characters/kaeya"
diff --git a/ui/packages/db/src/Data/char_data.generated.json b/ui/packages/db/src/Data/char_data.generated.json
index c393b1f739..406c833929 100644
--- a/ui/packages/db/src/Data/char_data.generated.json
+++ b/ui/packages/db/src/Data/char_data.generated.json
@@ -685,6 +685,23 @@
},
"name_text_hash_map ": "1940919994"
},
+ "iansan": {
+ "id": 10000110,
+ "key": "iansan",
+ "rarity": "QUALITY_PURPLE",
+ "body": "BODY_LOLI",
+ "region": "ASSOC_TYPE_NATLAN",
+ "element": "Electric",
+ "weapon_class": "WEAPON_POLE",
+ "icon_name": "UI_AvatarIcon_Iansan",
+ "skill_details": {
+ "skill": 11102,
+ "burst": 11105,
+ "attack": 11101,
+ "burst_energy_cost": 70
+ },
+ "name_text_hash_map ": "3120492514"
+ },
"itto": {
"id": 10000057,
"key": "itto",
diff --git a/ui/packages/docs/docs/reference/characters/iansan.md b/ui/packages/docs/docs/reference/characters/iansan.md
new file mode 100644
index 0000000000..4e6fe2527c
--- /dev/null
+++ b/ui/packages/docs/docs/reference/characters/iansan.md
@@ -0,0 +1,44 @@
+---
+title: Iansan
+---
+
+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/localization/src/locales/names.generated.json b/ui/packages/localization/src/locales/names.generated.json
index b9ffa309cd..ee0eea3c24 100644
--- a/ui/packages/localization/src/locales/names.generated.json
+++ b/ui/packages/localization/src/locales/names.generated.json
@@ -41,6 +41,7 @@
"gorou": "五郎",
"heizou": "鹿野院平藏",
"hutao": "胡桃",
+ "iansan": "伊安珊",
"itto": "荒泷一斗",
"jean": "琴",
"kaeya": "凯亚",
@@ -165,6 +166,7 @@
"filletblade": "吃虎鱼刀",
"finaleofthedeep": "海渊终曲",
"fleuvecendreferryman": "灰河渡手",
+ "flowerwreathedfeathers": "缀花之翎",
"flowingpurity": "纯水流华",
"fluteofezpitzal": "息燧之笛",
"footprintoftherainbow": "虹的行迹",
@@ -272,6 +274,7 @@
"swordofdescension": "降临之剑",
"swordofnarzissenkreuz": "水仙十字之剑",
"talkingstick": "聊聊棒",
+ "tamayurateinoohanashi": "且住亭御咄",
"thealleyflash": "暗巷闪光",
"thebell": "钟剑",
"theblacksword": "黑剑",
@@ -742,6 +745,7 @@
"gorou": "Gorou",
"heizou": "Shikanoin Heizou",
"hutao": "Hu Tao",
+ "iansan": "Iansan",
"itto": "Arataki Itto",
"jean": "Jean",
"kaeya": "Kaeya",
@@ -866,6 +870,7 @@
"filletblade": "Fillet Blade",
"finaleofthedeep": "Finale of the Deep",
"fleuvecendreferryman": "Fleuve Cendre Ferryman",
+ "flowerwreathedfeathers": "Flower-Wreathed Feathers",
"flowingpurity": "Flowing Purity",
"fluteofezpitzal": "Flute of Ezpitzal",
"footprintoftherainbow": "Footprint of the Rainbow",
@@ -973,6 +978,7 @@
"swordofdescension": "Sword of Descension",
"swordofnarzissenkreuz": "Sword of Narzissenkreuz",
"talkingstick": "Talking Stick",
+ "tamayurateinoohanashi": "Tamayuratei no Ohanashi",
"thealleyflash": "The Alley Flash",
"thebell": "The Bell",
"theblacksword": "The Black Sword",
@@ -1443,6 +1449,7 @@
"gorou": "Gorou",
"heizou": "Shikanoin Heizou",
"hutao": "Hu Tao",
+ "iansan": "Iansan",
"itto": "Arataki Itto",
"jean": "Jean",
"kaeya": "Kaeya",
@@ -1567,6 +1574,7 @@
"filletblade": "Filetiermesser",
"finaleofthedeep": "Finale in der Tiefe",
"fleuvecendreferryman": "Schiffer des Fleuve Cendré",
+ "flowerwreathedfeathers": "Mit Blumen gebundene Federn",
"flowingpurity": "Fließende Reinheit",
"fluteofezpitzal": "Ezpitzal-Flöte",
"footprintoftherainbow": "Spuren des Regenbogens",
@@ -1674,6 +1682,7 @@
"swordofdescension": "Schwert der Niederkunft",
"swordofnarzissenkreuz": "Schwert des Narzissenkreuzes",
"talkingstick": "Lass uns reden",
+ "tamayurateinoohanashi": "Tamayuratei no Ohanashi",
"thealleyflash": "Gassenleuchte",
"thebell": "Glocke",
"theblacksword": "Schwarzes Schwert",
@@ -2144,6 +2153,7 @@
"gorou": "ゴロー",
"heizou": "鹿野院平蔵",
"hutao": "胡桃",
+ "iansan": "イアンサ",
"itto": "荒瀧一斗",
"jean": "ジン",
"kaeya": "ガイア",
@@ -2268,6 +2278,7 @@
"filletblade": "チ虎魚の刀",
"finaleofthedeep": "海淵のフィナーレ",
"fleuvecendreferryman": "サーンドルの渡し守",
+ "flowerwreathedfeathers": "花飾りの羽",
"flowingpurity": "純水流華",
"fluteofezpitzal": "エズピツァルの笛",
"footprintoftherainbow": "虹の行方",
@@ -2375,6 +2386,7 @@
"swordofdescension": "降臨の剣",
"swordofnarzissenkreuz": "水仙十字の剣",
"talkingstick": "話死合い棒",
+ "tamayurateinoohanashi": "玉響停の御噺",
"thealleyflash": "ダークアレイの閃光",
"thebell": "鐘の剣",
"theblacksword": "黒剣",
@@ -2844,6 +2856,7 @@
"gorou": "고로",
"heizou": "시카노인 헤이조",
"hutao": "호두",
+ "iansan": "얀사",
"itto": "아라타키 이토",
"jean": "진",
"kaeya": "케이아",
@@ -2968,6 +2981,7 @@
"filletblade": "흘호 생선회칼",
"finaleofthedeep": "해연의 피날레",
"fleuvecendreferryman": "잿빛의 강 뱃사공",
+ "flowerwreathedfeathers": "꽃장식 깃",
"flowingpurity": "순수한 달빛 물결",
"fluteofezpitzal": "에스피찰의 피리",
"footprintoftherainbow": "무지개의 행적",
@@ -3075,6 +3089,7 @@
"swordofdescension": "강림의 검",
"swordofnarzissenkreuz": "수선화 십자검",
"talkingstick": "대화봉",
+ "tamayurateinoohanashi": "쉼터의 이야기꾼",
"thealleyflash": "뒷골목의 섬광",
"thebell": "시간의 검",
"theblacksword": "칠흑검",
@@ -3545,6 +3560,7 @@
"gorou": "Горо",
"heizou": "Хэйдзо",
"hutao": "Ху Тао",
+ "iansan": "Иансан",
"itto": "Итто",
"jean": "Джинн",
"kaeya": "Кэйа",
@@ -3669,6 +3685,7 @@
"filletblade": "Филейный нож",
"finaleofthedeep": "Грандиозный финал глубин",
"fleuvecendreferryman": "Перевозчик Флёв Сандр",
+ "flowerwreathedfeathers": "Украшенные цветами перья",
"flowingpurity": "Сверкание чистых вод",
"fluteofezpitzal": "Флейта Эспицаль",
"footprintoftherainbow": "След радуги",
@@ -3776,6 +3793,7 @@
"swordofdescension": "Меч нисхождения",
"swordofnarzissenkreuz": "Меч Нарциссенкрейца",
"talkingstick": "Говорящая палица",
+ "tamayurateinoohanashi": "Тамаюратэй но оханаси",
"thealleyflash": "Вспышка во тьме",
"thebell": "Меч-колокол",
"theblacksword": "Чёрный меч",
@@ -4246,6 +4264,7 @@
"gorou": "Gorou",
"heizou": "Shikanoin Heizou",
"hutao": "Hu Tao",
+ "iansan": "Iansán",
"itto": "Arataki Itto",
"jean": "Jean",
"kaeya": "Kaeya",
@@ -4370,6 +4389,7 @@
"filletblade": "Hoja de Filetear",
"finaleofthedeep": "Réquiem Abisal",
"fleuvecendreferryman": "Vado del Río Ceniciento",
+ "flowerwreathedfeathers": "Penacho Engalanado",
"flowingpurity": "Fluencia Impoluta",
"fluteofezpitzal": "Flauta de Ezpitzal",
"footprintoftherainbow": "Estela Iridiscente",
@@ -4477,6 +4497,7 @@
"swordofdescension": "Espada del Descenso",
"swordofnarzissenkreuz": "Espada Cruz de los Narcisos",
"talkingstick": "Garrote del Diálogo",
+ "tamayurateinoohanashi": "Charla en el Pabellón",
"thealleyflash": "Destello en la Oscuridad",
"thebell": "Espada del Tiempo",
"theblacksword": "Espada Negra",
diff --git a/ui/packages/ui/src/Data/char_data.generated.json b/ui/packages/ui/src/Data/char_data.generated.json
index c393b1f739..406c833929 100644
--- a/ui/packages/ui/src/Data/char_data.generated.json
+++ b/ui/packages/ui/src/Data/char_data.generated.json
@@ -685,6 +685,23 @@
},
"name_text_hash_map ": "1940919994"
},
+ "iansan": {
+ "id": 10000110,
+ "key": "iansan",
+ "rarity": "QUALITY_PURPLE",
+ "body": "BODY_LOLI",
+ "region": "ASSOC_TYPE_NATLAN",
+ "element": "Electric",
+ "weapon_class": "WEAPON_POLE",
+ "icon_name": "UI_AvatarIcon_Iansan",
+ "skill_details": {
+ "skill": 11102,
+ "burst": 11105,
+ "attack": 11101,
+ "burst_energy_cost": 70
+ },
+ "name_text_hash_map ": "3120492514"
+ },
"itto": {
"id": 10000057,
"key": "itto",