package battle import ( "encoding/json" "math" ) // DamageType 伤害类型:物理 / 法术 / 真实 type DamageType string const ( DamageTypePhysical DamageType = "physical" DamageTypeMagical DamageType = "magical" DamageTypeTrue DamageType = "true" ) // TargetType 技能目标类型 type TargetType string const ( TargetSingle TargetType = "single" TargetSelf TargetType = "self" TargetAlly TargetType = "ally" ) // BaseStats 角色六维基础素质(与 DB base_stats JSONB 对齐) type BaseStats struct { Str float64 `json:"str"` Vit float64 `json:"vit"` Wis float64 `json:"wis"` Agi float64 `json:"agi"` Spi float64 `json:"spi"` Luk float64 `json:"luk"` // Blood 为巫族特殊属性,可替换灵 Blood float64 `json:"blood,omitempty"` } // SkillInstance 战斗中的一次技能实例(包含运行时 CD) type SkillInstance struct { InstanceID string `json:"instance_id"` SkillID string `json:"skill_id"` Name string `json:"name"` DamageType DamageType `json:"damage_type"` Element string `json:"element"` // fire/water/lightning/earth/nature/yin/yang/chaos/none Alignment string `json:"alignment"` // light/dark/neutral ScalingAttr string `json:"scaling_attr"` // str/vit/wis/agi/spi/luk/blood BaseCoef float64 `json:"base_coef"` CD int `json:"cd"` // 总 CD(ticks) Cost float64 `json:"cost"` // 内力消耗 TriggerRate float64 `json:"trigger_rate"` // 基础触发率,1.0=100% TargetType TargetType `json:"target_type"` // 运行时字段 CDRemaining int `json:"-"` } // IsNormal 判断是否为普通攻击兜底技能 func (s *SkillInstance) IsNormal() bool { return s == nil || s.SkillID == "normal_attack" } // Status 简易战斗状态 type Status struct { Type string Stacks int Duration int } // Fighter 战斗实体(玩家或怪物) type Fighter struct { ID string Name string RaceID string Job string Side string // attacker / defender IsPlayer bool // 基础素质 Base BaseStats // 派生战斗属性 HPMax float64 HP float64 PhysAtk float64 MagAtk float64 Speed float64 CritRate float64 EvadeRate float64 PhysDR float64 MagDR float64 // 战斗资源(内力) Mana float64 ManaMax float64 // 阵营 / 元素 WorldTier int RealmTier int Alignment string Element string // 技能 Skills []*SkillInstance // 状态 Stunned bool StunRemaining int SlowRemaining int // ATB Gauge float64 ActionCount int } // IsAlive 是否存活 func (f *Fighter) IsAlive() bool { return f != nil && f.HP > 0 } // EffectiveSpeed 计算当前有效速度(含减速) func (f *Fighter) EffectiveSpeed() float64 { speed := f.Speed if f.SlowRemaining > 0 { slow := cfgFloat("combat.cc.slow_value", 0.2) speed *= (1 - slow) } if speed < 1 { speed = 1 } return speed } // ResetGauge 行动后清空行动条 func (f *Fighter) ResetGauge() { f.Gauge = 0 } // ReduceCooldowns 每 tick 减少技能 CD func (f *Fighter) ReduceCooldowns() { for _, s := range f.Skills { if s.CDRemaining > 0 { s.CDRemaining-- } } } // AddCooldown 为指定技能进入 CD func (f *Fighter) AddCooldown(s *SkillInstance) { if s == nil || s.CD <= 0 { return } for _, sk := range f.Skills { if sk.InstanceID == s.InstanceID && sk.SkillID == s.SkillID { sk.CDRemaining = s.CD return } } } // RegenMana 每 tick 回复内力 func (f *Fighter) RegenMana() { regen := cfgFloat("combat.mana.regen_per_tick", 0.15) f.Mana += regen if f.Mana > f.ManaMax { f.Mana = f.ManaMax } } // ManaRegenAfterAction 行动后额外回复内力 func (f *Fighter) ManaRegenAfterAction() { regen := cfgFloat("combat.mana.regen_per_action", 3) f.Mana += regen if f.Mana > f.ManaMax { f.Mana = f.ManaMax } } // CanSpendMana 是否有足够内力释放技能 func (f *Fighter) CanSpendMana(cost float64) bool { return f.Mana >= cost } // SpendMana 消耗内力 func (f *Fighter) SpendMana(cost float64) { f.Mana -= cost if f.Mana < 0 { f.Mana = 0 } } // TakeDamage 受到伤害 func (f *Fighter) TakeDamage(dmg float64) { f.HP -= dmg if f.HP < 0 { f.HP = 0 } } // AddSkill 添加技能 func (f *Fighter) AddSkill(s *SkillInstance) { if s == nil { return } f.Skills = append(f.Skills, s) } // ActiveSkillIDs 返回用于战报展示的技能 ID 列表 func (f *Fighter) ActiveSkillIDs() []string { ids := make([]string, 0, len(f.Skills)) for _, s := range f.Skills { if s.SkillID != "normal_attack" { ids = append(ids, s.SkillID) } } return ids } // NewFighter 通过基础属性创建一个战斗实体 func NewFighter(id, name, raceID, side string, worldTier, realmTier int, base BaseStats, alignment, element string, isPlayer bool) *Fighter { f := &Fighter{ ID: id, Name: name, RaceID: raceID, Side: side, IsPlayer: isPlayer, Base: base, WorldTier: worldTier, RealmTier: realmTier, Alignment: alignment, Element: element, } f.recalcDerived() f.ManaMax = cfgFloat("combat.mana.max", 100) f.Mana = f.ManaMax return f } // NewFighterFromJSON 从 DB JSONB 解析基础属性后创建实体 func NewFighterFromJSON(id, name, raceID, side string, worldTier, realmTier int, data []byte, alignment, element string, isPlayer bool) *Fighter { var base BaseStats _ = json.Unmarshal(data, &base) return NewFighter(id, name, raceID, side, worldTier, realmTier, base, alignment, element, isPlayer) } // recalcDerived 根据基础素质重算战斗属性 func (f *Fighter) recalcDerived() { hpCoef := hpCoefForTier(f.WorldTier) f.HPMax = f.Base.Vit*hpCoef + realmBaseHP(f.RealmTier) f.HP = f.HPMax atkCoef := cfgFloat("combat.atk.coefficient", 1.0) magAtkCoef := cfgFloat("combat.magic_atk.coefficient", 1.0) f.PhysAtk = f.Base.Str*atkCoef f.MagAtk = f.Base.Spi*magAtkCoef if f.Base.Blood > 0 { // 巫族可用血属性部分替代灵 f.MagAtk += f.Base.Blood * magAtkCoef * 0.5 f.PhysAtk += f.Base.Blood * atkCoef * 0.5 } f.Speed = f.Base.Agi critCap := cfgFloat("combat.crit.rate_cap", 0.6) f.CritRate = math.Min(critCap, f.Base.Luk*0.0015) evaCap := cfgFloat("combat.evade.rate_cap", 0.5) f.EvadeRate = math.Min(evaCap, f.Base.Agi*0.001) defCoef := cfgFloat("combat.defense.coefficient", 0.5) defConst := defenseConstantForTier(f.WorldTier) f.PhysDR = math.Min(0.75, f.Base.Vit*defCoef/(f.Base.Vit*defCoef+defConst)) f.MagDR = math.Min(0.75, f.Base.Spi*defCoef/(f.Base.Spi*defCoef+defConst)) } func realmBaseHP(realmTier int) float64 { switch realmTier { case 1: return 100 case 2: return 300 case 3: return 700 case 4: return 1500 case 5: return 3000 case 6: return 6000 default: return 100 } } func hpCoefForTier(worldTier int) float64 { defaults := map[int]float64{ 1: 10, 2: 12, 3: 15, 4: 20, 5: 28, 6: 38, } key := "combat.hp.coefficient_tier_" + tierKey(worldTier) if v, ok := cfgGet(key); ok { return toFloat(v, defaults[worldTier]) } return defaults[worldTier] } func defenseConstantForTier(worldTier int) float64 { defaults := map[int]float64{ 1: 100, 2: 150, 3: 220, 4: 320, 5: 460, 6: 640, } key := "combat.defense.constant_tier_" + tierKey(worldTier) if v, ok := cfgGet(key); ok { return toFloat(v, defaults[worldTier]) } return defaults[worldTier] } func tierKey(tier int) string { switch tier { case 1: return "1" case 2: return "2" case 3: return "3" case 4: return "4" case 5: return "5" case 6: return "6" default: return "1" } }