From 5ba81e2f90883f287071d4b2097b45cb7bec0f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E5=BF=B5?= <1@72wo.cn> Date: Mon, 25 Aug 2025 04:23:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor(fight):=20=E9=87=8D=E6=9E=84=E6=88=98?= =?UTF-8?q?=E6=96=97=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除未使用的战斗模式枚举和相关代码 - 更新 BurnEffect 结构,增加生命周期管理 - 删除多余的 Skill 结构和 Effect 相关代码 - 调整 NoteReadyToFightInfo 结构的位置 --- common/data/xml/skill/monster_refresh_test.go | 20 ++ common/data/xml/skill/skill.go | 117 ++++++++++ logic/controller/fight.go | 11 +- logic/main.go | 1 + logic/service/fight/battle/action/Compare.go | 59 +++++ .../battle/container/BattleStateMachine.go | 23 ++ .../fight/battle/{ => container}/battle.go | 41 +--- .../fight/battle/container/battle1v1.go | 12 + logic/service/fight/battle/random/random.go | 143 ++++++++++++ .../battle/skill/effect/base/BurnEffect.go | 25 +- .../fight/battle/skill/effect/base/base.go | 36 +++ logic/service/fight/battle/skill/effect/c.go | 59 +++++ .../fight/battle/skill/effect/effect.go | 215 ------------------ .../fight/battle/skill/effect/effect_test.go | 40 ---- .../fight/battle/skill/effect/effecti.go | 117 ++++++++++ logic/service/fight/battle/skill/skill.go | 11 - logic/service/fight/fight.go | 64 ++++++ logic/service/fight/fightcmd.go | 125 ---------- .../fight/info/BattleInputSourceEntity.go | 10 + logic/service/fight/info/BattlePetEntity.go | 87 +++++++ logic/service/fight/info/BattleSkillEntity.go | 159 +++++++++++++ .../fight/info/NoteReadyToFightInfo.go | 59 +++++ 22 files changed, 997 insertions(+), 437 deletions(-) create mode 100644 common/data/xml/skill/monster_refresh_test.go create mode 100644 common/data/xml/skill/skill.go create mode 100644 logic/service/fight/battle/action/Compare.go create mode 100644 logic/service/fight/battle/container/BattleStateMachine.go rename logic/service/fight/battle/{ => container}/battle.go (52%) create mode 100644 logic/service/fight/battle/container/battle1v1.go create mode 100644 logic/service/fight/battle/random/random.go create mode 100644 logic/service/fight/battle/skill/effect/base/base.go create mode 100644 logic/service/fight/battle/skill/effect/c.go delete mode 100644 logic/service/fight/battle/skill/effect/effect.go delete mode 100644 logic/service/fight/battle/skill/effect/effect_test.go create mode 100644 logic/service/fight/battle/skill/effect/effecti.go delete mode 100644 logic/service/fight/battle/skill/skill.go create mode 100644 logic/service/fight/fight.go delete mode 100644 logic/service/fight/fightcmd.go create mode 100644 logic/service/fight/info/BattleInputSourceEntity.go create mode 100644 logic/service/fight/info/BattlePetEntity.go create mode 100644 logic/service/fight/info/BattleSkillEntity.go create mode 100644 logic/service/fight/info/NoteReadyToFightInfo.go diff --git a/common/data/xml/skill/monster_refresh_test.go b/common/data/xml/skill/monster_refresh_test.go new file mode 100644 index 000000000..f7709cdc8 --- /dev/null +++ b/common/data/xml/skill/monster_refresh_test.go @@ -0,0 +1,20 @@ +package skill + +import ( + "fmt" + "testing" + + "github.com/ECUST-XX/xml" +) + +func Test_main(t *testing.T) { + + // 解析XML到结构体 + var maps MovesTbl + t1, _ := getxml() + xml.Unmarshal(t1, &maps) + + //tf, _ := xml.MarshalIndentShortForm(tt, " ", " ") + fmt.Println(maps.Moves[4].SideEffect) + fmt.Println(maps.Moves[4].SideEffectArg) +} diff --git a/common/data/xml/skill/skill.go b/common/data/xml/skill/skill.go new file mode 100644 index 000000000..65e5b0f89 --- /dev/null +++ b/common/data/xml/skill/skill.go @@ -0,0 +1,117 @@ +package skill + +import ( + "encoding/xml" + "fmt" + "io" + "log" + "net/http" + "time" +) + +// MovesTbl 定义 XML 根元素 +type MovesTbl struct { + XMLName xml.Name `xml:"MovesTbl"` + Moves []Move `xml:"Moves>Move"` + EFF []SideEffect `xml:"SideEffects>SideEffect"` +} +type MovesMap struct { + XMLName xml.Name `xml:"MovesTbl"` + Moves map[int64]Move + EFF []SideEffect `xml:"SideEffects>SideEffect"` +} + +// Move 定义单个技能的结构 +type Move struct { + ID int `xml:"ID,attr"` + Name string `xml:"Name,attr"` + Category int `xml:"Category,attr"` + Type int `xml:"Type,attr"` + Power int `xml:"Power,attr"` + MaxPP int `xml:"MaxPP,attr"` + Accuracy int `xml:"Accuracy,attr"` + CritRate int `xml:"CritRate,attr,omitempty"` + Priority int `xml:"Priority,attr,omitempty"` + MustHit int `xml:"MustHit,attr,omitempty"` + SwapElemType int `xml:"SwapElemType,attr,omitempty"` + CopyElemType int `xml:"CopyElemType,attr,omitempty"` + CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty"` + CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty"` + CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty"` + CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty"` + DmgBindLv int `xml:"DmgBindLv,attr,omitempty"` + PwrBindDv int `xml:"PwrBindDv,attr,omitempty"` + PwrDouble int `xml:"PwrDouble,attr,omitempty"` + + SideEffect string `xml:"SideEffect,attr,omitempty"` + SideEffectArg string `xml:"SideEffectArg,attr,omitempty"` + AtkNum int `xml:"AtkNum,attr,omitempty"` + Url string `xml:"Url,attr,omitempty"` + + Info string `xml:"info,attr,omitempty"` + + CD int `xml:"CD,attr"` +} +type SideEffect struct { + ID int `xml:"ID,attr"` + Help string `xml:"help,attr"` + Des string `xml:"des,attr"` +} + +// ReadHTTPFile 通过HTTP GET请求获取远程文件内容 +// url: 远程文件的URL地址 +// 返回文件内容字节流和可能的错误 +func ReadHTTPFile(url string) ([]byte, error) { + // 创建HTTP客户端并设置超时时间(避免无限等待) + client := &http.Client{ + Timeout: 30 * time.Second, // 30秒超时 + } + + // 发送GET请求 + resp, err := client.Get(url) + if err != nil { + return nil, fmt.Errorf("请求失败: %w", err) + } + defer resp.Body.Close() // 确保响应体被关闭 + + // 检查响应状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("请求返回非成功状态码: %d", resp.StatusCode) + } + + // 读取响应体内容 + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取内容失败: %w", err) + } + + return content, nil +} +func getxml() ([]byte, error) { + + // 读取整个文件内容,返回字节切片和错误 + content, err := ReadHTTPFile("http://127.0.0.1:8080/assets/227.xml") + if err != nil { + // 处理错误(文件不存在、权限问题等) + log.Fatalf("无法读取文件: %v", err) + } + return content, nil + +} +func getMoves() MovesMap { + + // 解析XML到结构体 + var maps MovesTbl + t1, _ := getxml() + xml.Unmarshal(t1, &maps) + var mapss MovesMap + mapss.Moves = make(map[int64]Move, 0) + for _, v := range maps.Moves { + mapss.Moves[int64(v.ID)] = v + } + + return mapss +} + +// 全局函数配置 +var MovesConfig = getMoves() diff --git a/logic/controller/fight.go b/logic/controller/fight.go index 7bf509ef4..efef721be 100644 --- a/logic/controller/fight.go +++ b/logic/controller/fight.go @@ -5,18 +5,19 @@ import ( "blazing/common/socket/errorcode" "blazing/common/socket/handler" "blazing/logic/service/fight" + "blazing/logic/service/fight/info" "blazing/modules/blazing/model" ) func (h Controller) OnPlayerFightNpcMonster(data *fight.FightNpcMonsterInboundInfo, c *entity.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { c.IsFighting = true t1 := handler.NewTomeeHeader(2503, c.UserID) - ttt := fight.NoteReadyToFightInfo{ + ttt := info.NoteReadyToFightInfo{ FightId: 3, } - ttt.OurInfo = fight.FightUserInfo{UserID: c.UserID} + ttt.OurInfo = info.FightUserInfo{UserID: c.UserID} - ttt.OurPetList = []fight.ReadyFightPetInfo{{ID: 300, + ttt.OurPetList = []info.ReadyFightPetInfo{{ID: 300, Level: 100, MaxHp: 100, @@ -27,8 +28,8 @@ func (h Controller) OnPlayerFightNpcMonster(data *fight.FightNpcMonsterInboundIn for i := 0; i < 4; i++ { ttt.OurPetList[0].SkillList[i] = model.SkillInfo{ID: 10001, Pp: 1} } - ttt.OpponentInfo = fight.FightUserInfo{UserID: 0} - ttt.OpponentPetList = []fight.ReadyFightPetInfo{{ID: 1, + ttt.OpponentInfo = info.FightUserInfo{UserID: 0} + ttt.OpponentPetList = []info.ReadyFightPetInfo{{ID: 1, Level: 100, MaxHp: 100, SkillListLen: 4, diff --git a/logic/main.go b/logic/main.go index 1e1c9a019..559ffc21b 100644 --- a/logic/main.go +++ b/logic/main.go @@ -29,6 +29,7 @@ func main() { go Start(cool.Config.PortBL) //注入service fmt.Println("Process start, pid:", os.Getpid()) + gproc.AddSigHandlerShutdown( signalHandlerForMain, diff --git a/logic/service/fight/battle/action/Compare.go b/logic/service/fight/battle/action/Compare.go new file mode 100644 index 000000000..c66673d14 --- /dev/null +++ b/logic/service/fight/battle/action/Compare.go @@ -0,0 +1,59 @@ +package action + +import ( + "blazing/logic/service/fight/battle/random" + "blazing/logic/service/fight/info" +) + +type BattleAction struct { + Priority int + ramdom *random.RandomXS128 + Skill *info.BattleSkillEntity + Pet *info.BattlePetEntity +} + +// NewBattle1v1ActionComparator 创建1v1战斗动作比较器实例 +// 入参为1v1战斗实例,从战斗中获取随机数生成器(对应原 Java 构造函数) +func NewBattleAction(seek *random.RandomXS128) *BattleAction { + + return &BattleAction{ + + ramdom: seek, + } +} + +// Compare 比较两个1v1战斗动作的执行优先级(核心逻辑) +// 完全遵循原 Java 比较逻辑:优先级 → 技能优先级 → 速度 → AI拼速 → 玩家随机拼速 +func (c *BattleAction) Compare(o2 *BattleAction) *BattleAction { + // 1. 第一步:比较动作本身的优先级(o2 - o1,优先级高的先执行) + p1 := o2.Priority - c.Priority + if p1 > 0 { //说明对手快 + return o2 + } else if p1 < 0 { + return c + } + + // 2. 第二步:如果是“使用技能”类型的动作,额外比较技能优先级和速度 + // 类型断言:判断 o1 是否为 Use1v1SkillAction(对应原 Java instanceof) + + if o2.Skill != nil && c.Skill != nil { + p2 := o2.Skill.Priority - c.Skill.Priority // 假设 Use1v1SkillAction 有 SkillPriority() 方法 + if p2 > 0 { + return o2 + } else if p2 < 0 { + return c + } + } + + // 2.2 比较动作所有者的主力宠物速度(o2 - o1,速度快的先执行) + // 假设:Action.Owner() 返回动作所有者(玩家/AI),Owner.MainPet() 返回主力宠物,Pet.SpeedValue() 返回速度值 + + p2 := int(o2.Pet.GetSpeed()) - int(c.Pet.GetSpeed()) // 假设 Use1v1SkillAction 有 SkillPriority() 方法 + if p2 > 0 { + return o2 + } else if p2 < 0 { + return c + } + + return c // 3.3 速度相同时 ,发起方优先 +} diff --git a/logic/service/fight/battle/container/BattleStateMachine.go b/logic/service/fight/battle/container/BattleStateMachine.go new file mode 100644 index 000000000..7ea00ffe1 --- /dev/null +++ b/logic/service/fight/battle/container/BattleStateMachine.go @@ -0,0 +1,23 @@ +package battle + +const ( + StateStart BattleState = iota + StatePlayerTurn + StateEnemyTurn + StateEnd +) + +type BattleStateMachine struct { + State BattleState +} + +func (fsm *BattleStateMachine) Next() { + switch fsm.State { + case StateStart: + fsm.State = StatePlayerTurn + case StatePlayerTurn: + fsm.State = StateEnemyTurn + case StateEnemyTurn: + fsm.State = StatePlayerTurn + } +} diff --git a/logic/service/fight/battle/battle.go b/logic/service/fight/battle/container/battle.go similarity index 52% rename from logic/service/fight/battle/battle.go rename to logic/service/fight/battle/container/battle.go index 39ae6670e..b807bb068 100644 --- a/logic/service/fight/battle/battle.go +++ b/logic/service/fight/battle/container/battle.go @@ -1,6 +1,9 @@ package battle -import "math/rand/v2" +import ( + "blazing/logic/service/fight/battle/skill/effect" + "math/rand/v2" +) type BattleState int @@ -13,7 +16,7 @@ type BattleUnit struct { Def int MaxHP int HP int - Buffs map[string]Effect + Buffs map[string]effect.Effect IsCritical bool } @@ -26,20 +29,10 @@ func NewBattleUnit(name string, level, atk, def, maxHP int) *BattleUnit { Def: def, MaxHP: maxHP, HP: maxHP, - Buffs: make(map[string]Effect), + Buffs: make(map[string]effect.Effect), } } -// ==== 技能类型 ==== - -const ( - SkillTypePhysical SkillType = iota + 1 - SkillTypeSpecial - SkillTypeGrass - SkillTypeWater - SkillTypeFire -) - // ==== 战斗上下文 ==== type BattleContext struct { @@ -57,25 +50,3 @@ type Skill struct { Defender *BattleUnit Effects []Effect } - -const ( - StateStart BattleState = iota - StatePlayerTurn - StateEnemyTurn - StateEnd -) - -type BattleStateMachine struct { - State BattleState -} - -func (fsm *BattleStateMachine) Next() { - switch fsm.State { - case StateStart: - fsm.State = StatePlayerTurn - case StatePlayerTurn: - fsm.State = StateEnemyTurn - case StateEnemyTurn: - fsm.State = StatePlayerTurn - } -} diff --git a/logic/service/fight/battle/container/battle1v1.go b/logic/service/fight/battle/container/battle1v1.go new file mode 100644 index 000000000..3e4e27334 --- /dev/null +++ b/logic/service/fight/battle/container/battle1v1.go @@ -0,0 +1,12 @@ +package battle + +type BattleContainer struct { + FightUserInfo +} + b.Turn++ + fmt.Printf("=== 回合 %d 开始 ===\n", b.Turn) + b.PublishTrigger(EffectTrigger.TurnStart, containers) + fmt.Println("=== 玩家操作阶段 ===") + b.PublishTrigger(EffectTrigger.TurnEnd, containers) + fmt.Printf("=== 回合 %d 结束 ===\n\n", b.Turn) +} \ No newline at end of file diff --git a/logic/service/fight/battle/random/random.go b/logic/service/fight/battle/random/random.go new file mode 100644 index 000000000..1437b270f --- /dev/null +++ b/logic/service/fight/battle/random/random.go @@ -0,0 +1,143 @@ +package random + +import ( + "math/rand" + "time" +) + +// RandomXS128 实现 xorshift128+ 伪随机数生成器 +type RandomXS128 struct { + seed0 uint64 // 内部状态前半部分 + seed1 uint64 // 内部状态后半部分 +} + +// 归一化常数 +const ( + normDouble = 1.0 / (1 << 53) + normFloat = 1.0 / (1 << 24) +) + +// NewRandomXS128 创建一个新的随机数生成器,自动生成初始种子 +func NewRandomXS128() *RandomXS128 { + // 用系统随机数初始化种子(模拟 Java 中 new Random().nextLong() 的行为) + // 实际使用中可替换为更安全的种子源(如 crypto/rand) + source := rand.NewSource(time.Now().UnixNano()) // 基于时间的种子源 + r := rand.New(source) // 用种子源创建 Rand 实例 + + seed := uint64(r.Int())<<32 | uint64(r.Int()) + return NewRandomXS128WithSeed(seed) +} + +// NewRandomXS128WithSeed 用单个 uint64 种子创建生成器 +func NewRandomXS128WithSeed(seed uint64) *RandomXS128 { + seed0 := murmurHash3(seed) + seed1 := murmurHash3(seed0) + return &RandomXS128{seed0: seed0, seed1: seed1} +} + +// NewRandomXS128WithTwoSeeds 用两个 uint64 种子直接设置状态 +func NewRandomXS128WithTwoSeeds(seed0, seed1 uint64) *RandomXS128 { + return &RandomXS128{seed0: seed0, seed1: seed1} +} + +// NextLong 生成下一个 64 位随机整数 +func (r *RandomXS128) NextLong() uint64 { + s1 := r.seed0 + s0 := r.seed1 + r.seed0 = s0 + s1 ^= s1 << 23 + r.seed1 = s1 ^ s0 ^ (s1 >> 17) ^ (s0 >> 26) + return r.seed1 + s0 +} + +// NextInt 生成下一个 32 位随机整数 +func (r *RandomXS128) NextInt() int { + return int(r.NextLong() & 0xFFFFFFFF) +} + +// NextIntN 生成 [0, n) 范围内的随机整数 +func (r *RandomXS128) NextIntN(n int) int { + if n <= 0 { + panic("n must be positive") + } + for { + bits := int(r.NextLong() >> 1) + value := int(bits % n) + if bits-value+n-1 >= 0 { + return value + } + } +} + +// NextLongN 生成 [0, n) 范围内的随机 64 位整数 +func (r *RandomXS128) NextLongN(n uint64) uint64 { + if n <= 0 { + panic("n must be positive") + } + for { + bits := r.NextLong() >> 1 + value := bits % n + if bits-value+(n-1) >= 0 { + return value + } + } +} + +// NextDouble 生成 [0.0, 1.0) 范围内的随机浮点数 +func (r *RandomXS128) NextDouble() float64 { + return float64(r.NextLong()>>11) * normDouble +} + +// NextFloat 生成 [0.0, 1.0) 范围内的随机单精度浮点数 +func (r *RandomXS128) NextFloat() float32 { + return float32(float64(r.NextLong()>>40) * normFloat) +} + +// NextBoolean 生成随机布尔值 +func (r *RandomXS128) NextBoolean() bool { + return (r.NextLong() & 1) != 0 +} + +// NextBytes 填充字节数组为随机字节 +func (r *RandomXS128) NextBytes(b []byte) { + i := len(b) + for i > 0 { + n := i + if n > 8 { + n = 8 + } + bits := r.NextLong() + for j := 0; j < n; j++ { + b[i-1-j] = byte(bits >> (8 * j)) + } + i -= n + } +} + +// SetSeed 用单个种子重置生成器状态 +func (r *RandomXS128) SetSeed(seed uint64) { + seed0 := murmurHash3(seed) + r.seed0 = seed0 + r.seed1 = murmurHash3(seed0) +} + +// SetState 直接设置内部状态 +func (r *RandomXS128) SetState(seed0, seed1 uint64) { + r.seed0 = seed0 + r.seed1 = seed1 +} + +// GetState 获取内部状态 +func (r *RandomXS128) GetState() (seed0, seed1 uint64) { + return r.seed0, r.seed1 +} + +// murmurHash3 用于种子扩展的哈希函数 +func murmurHash3(x uint64) uint64 { + x ^= x >> 33 + x *= 0xff51afd7ed558ccd + x ^= x >> 33 + x *= 0xc4ceb9fe1a85ec53 + x ^= x >> 33 + return x +} diff --git a/logic/service/fight/battle/skill/effect/base/BurnEffect.go b/logic/service/fight/battle/skill/effect/base/BurnEffect.go index 57a71e43b..2e5fcd300 100644 --- a/logic/service/fight/battle/skill/effect/base/BurnEffect.go +++ b/logic/service/fight/battle/skill/effect/base/BurnEffect.go @@ -5,15 +5,28 @@ import ( "fmt" ) -// 灼烧效果 -type BurnEffect struct{} +type BurnEffect struct { + life LifeCycle +} + +func NewBurnEffect(turns int) *BurnEffect { + return &BurnEffect{life: NewTurnLife(turns)} +} func (b *BurnEffect) Trigger() node.EnumEffectTrigger { - return node.EffectTrigger.OnHit + return node.EffectTrigger.TurnEnd // 每回合结束触发掉血 } func (b *BurnEffect) Apply(ctx *node.EffectContext, next func()) { - fmt.Printf("[%s] 命中 [%s],触发灼烧效果!\n", ctx.Actor, ctx.Target) - ctx.Extra["Burn"] = true - next() // 继续执行后续效果 + fmt.Printf("[%s] 在回合结束时受灼烧伤害!\n", ctx.Target) + b.life.Use() // 消耗一次触发次数(如果你定义为按次数存活) + next() +} + +func (b *BurnEffect) Tick() { + b.life.Tick() +} + +func (b *BurnEffect) Alive() bool { + return b.life.Alive() } diff --git a/logic/service/fight/battle/skill/effect/base/base.go b/logic/service/fight/battle/skill/effect/base/base.go new file mode 100644 index 000000000..02f3e57a8 --- /dev/null +++ b/logic/service/fight/battle/skill/effect/base/base.go @@ -0,0 +1,36 @@ +package base + +// 生命周期管理器 +type LifeCycle struct { + remainTurn int // 剩余回合(-1 表示不限回合) + remainCount int // 剩余次数(-1 表示不限次数) +} + +// 创建一个回合持续类效果 +func NewTurnLife(turns int) LifeCycle { + return LifeCycle{remainTurn: turns, remainCount: -1} +} + +// 创建一个次数持续类效果 +func NewCountLife(count int) LifeCycle { + return LifeCycle{remainTurn: -1, remainCount: count} +} + +// 每回合 tick +func (lc *LifeCycle) Tick() { + if lc.remainTurn > 0 { + lc.remainTurn-- + } +} + +// 使用一次 +func (lc *LifeCycle) Use() { + if lc.remainCount > 0 { + lc.remainCount-- + } +} + +// 是否还活跃 +func (lc *LifeCycle) Alive() bool { + return (lc.remainTurn != 0) && (lc.remainCount != 0) +} diff --git a/logic/service/fight/battle/skill/effect/c.go b/logic/service/fight/battle/skill/effect/c.go new file mode 100644 index 000000000..0574001a3 --- /dev/null +++ b/logic/service/fight/battle/skill/effect/c.go @@ -0,0 +1,59 @@ +package effect + +import ( + "sort" + + "github.com/badu/bus" +) + +type EffectContainer struct { + ID string + Effects []Effect + Subs []*bus.Listener[*EffectContext] + Battle *Battle + Parent ContextType +} + +func NewEffectContainer(id string, effects []Effect, battle *Battle, parent ContextType) *EffectContainer { + c := &EffectContainer{ + ID: id, + Effects: effects, + Battle: battle, + Parent: parent, + } + // 自动订阅 triggers + for _, e := range effects { + for _, trig := range e.Triggers() { + sub := battle.GetTopic(trig).Sub(func(ctx *EffectContext) { + if ctx.Available && e.Apply != nil { + ctx.Effect = e + c.executeWithPriority([]*EffectContext{ctx}) + } + }) + c.Subs = append(c.Subs, sub) + } + } + return c +} + +// done/next 执行队列,按优先级 +func (c *EffectContainer) executeWithPriority(queue []*EffectContext) { + sort.SliceStable(queue, func(i, j int) bool { + return queue[i].Effect.Priority() > queue[j].Effect.Priority() + }) + var runNext func(idx int) + runNext = func(idx int) { + if idx >= len(queue) { + return + } + ctx := queue[idx] + if ctx.Available { + ctx.Effect.Apply(ctx, func() { + runNext(idx + 1) + }) + } else { + runNext(idx + 1) + } + } + runNext(0) +} diff --git a/logic/service/fight/battle/skill/effect/effect.go b/logic/service/fight/battle/skill/effect/effect.go deleted file mode 100644 index 6bf724769..000000000 --- a/logic/service/fight/battle/skill/effect/effect.go +++ /dev/null @@ -1,215 +0,0 @@ -package effect - -import ( - "fmt" - "sort" - - "github.com/badu/bus" - "github.com/tnnmigga/enum" -) - -// ======================== -// BattleMode 枚举 -// ======================== - -type EnumBattleMode string - -var BattleMode = enum.New[struct { - PVE EnumBattleMode `enum:"3"` - PVP EnumBattleMode `enum:"1"` -}]() - -// ======================== -// Effect 框架 -// ======================== - -type ContextType interface { - SourceID() string -} - -type EffectContext struct { - Parent ContextType - Trigger EnumEffectTrigger - Container *EffectContainer - Effect *Effect - Available bool - Success bool - Done bool -} - -func (c *EffectContext) Stop() { c.Done = true } -func (c *EffectContext) SourceID() string { - if c.Container != nil { - return c.Container.ID - } - return "unknown" -} - -type EffectFunc func(ctx *EffectContext, next func()) - -type Effect struct { - ID string - Priority int - Triggers []EnumEffectTrigger - Condition func(*EffectContext) bool - Apply EffectFunc -} - -type EffectContainer struct { - ID string - Effects []*Effect - Subs []*bus.Listener[*EffectContext] - Battle *Battle - Parent ContextType -} - -func NewEffectContainer(id string, effects []*Effect, battle *Battle, parent ContextType) *EffectContainer { - c := &EffectContainer{ - ID: id, - Effects: effects, - Battle: battle, - Parent: parent, - } - // 自动订阅 triggers - for _, e := range effects { - for _, trig := range e.Triggers { - sub := battle.GetTopic(trig).Sub(func(ctx *EffectContext) { - if ctx.Available && e.Apply != nil { - ctx.Effect = e - c.executeWithPriority([]*EffectContext{ctx}) - } - }) - c.Subs = append(c.Subs, sub) - } - } - return c -} - -// done/next 执行队列,按优先级 -func (c *EffectContainer) executeWithPriority(queue []*EffectContext) { - sort.SliceStable(queue, func(i, j int) bool { - return queue[i].Effect.Priority > queue[j].Effect.Priority - }) - var runNext func(idx int) - runNext = func(idx int) { - if idx >= len(queue) { - return - } - ctx := queue[idx] - if ctx.Available { - ctx.Effect.Apply(ctx, func() { - runNext(idx + 1) - }) - } else { - runNext(idx + 1) - } - } - runNext(0) -} - -// ======================== -// Battle -// ======================== - -type Battle struct { - Turn int - topics map[EnumEffectTrigger]*bus.Topic[*EffectContext] -} - -func NewBattle() *Battle { - allTriggers := []EnumEffectTrigger{ - EffectTrigger.OnBattleStart, - EffectTrigger.BeforeSort, - EffectTrigger.BeforeUseSkillCheck, - EffectTrigger.AfterUseSkillCheck, - EffectTrigger.BeforeMultiHit, - EffectTrigger.BeforeHit, - EffectTrigger.OnCritPreDamage, - EffectTrigger.PreDamage, - EffectTrigger.OnHit, - EffectTrigger.OnMiss, - EffectTrigger.AfterAttacked, - EffectTrigger.OnDefeat, - EffectTrigger.SkillUseEnd, - EffectTrigger.OnBeforeCalculateDamage, - EffectTrigger.OnDamage, - EffectTrigger.Shield, - EffectTrigger.PostDamage, - EffectTrigger.OnCritPostDamage, - EffectTrigger.OnTransform, - EffectTrigger.OnTransformEnd, - EffectTrigger.BeforeTransform, - EffectTrigger.AfterTransform, - EffectTrigger.TurnStart, - EffectTrigger.TurnEnd, - EffectTrigger.OnBeforeAddMark, - EffectTrigger.OnAnyMarkAdded, - EffectTrigger.OnRemoveMark, - EffectTrigger.OnMarkCreated, - EffectTrigger.OnMarkDestroy, - EffectTrigger.OnMarkDurationEnd, - EffectTrigger.OnStackBefore, - EffectTrigger.OnStack, - EffectTrigger.OnBeforeConsumeStack, - EffectTrigger.OnConsumeStack, - EffectTrigger.OnBeforeHeal, - EffectTrigger.OnHeal, - EffectTrigger.BeforeRageGain, - EffectTrigger.BeforeRageLoss, - EffectTrigger.OnRageGain, - EffectTrigger.OnRageLoss, - EffectTrigger.OnSwitchIn, - EffectTrigger.OnSwitchOut, - EffectTrigger.OnOwnerSwitchIn, - EffectTrigger.OnOwnerSwitchOut, - EffectTrigger.BeforeEffect, - EffectTrigger.AfterEffect, - } - topics := make(map[EnumEffectTrigger]*bus.Topic[*EffectContext]) - for _, trig := range allTriggers { - topics[trig] = bus.NewTopic[*EffectContext]() - } - return &Battle{Turn: 0, topics: topics} -} - -func (b *Battle) GetTopic(trig EnumEffectTrigger) *bus.Topic[*EffectContext] { - return b.topics[trig] -} - -func (b *Battle) NextTurn(containers []*EffectContainer) { - b.Turn++ - fmt.Printf("=== 回合 %d 开始 ===\n", b.Turn) - b.PublishTrigger(EffectTrigger.TurnStart, containers) - fmt.Println("=== 玩家操作阶段 ===") - b.PublishTrigger(EffectTrigger.TurnEnd, containers) - fmt.Printf("=== 回合 %d 结束 ===\n\n", b.Turn) -} - -func (b *Battle) PublishTrigger(trigger EnumEffectTrigger, containers []*EffectContainer) { - for _, c := range containers { - for _, e := range c.Effects { - for _, t := range e.Triggers { - if t == trigger { - ctx := &EffectContext{ - Parent: c.Parent, - Trigger: trigger, - Container: c, - Effect: e, - Available: true, - } - b.GetTopic(trigger).Pub(ctx) - } - } - } - } -} - -// ======================== -// 示例 -// ======================== - -type Player struct { - ID string -} - -func (p *Player) SourceID() string { return p.ID } diff --git a/logic/service/fight/battle/skill/effect/effect_test.go b/logic/service/fight/battle/skill/effect/effect_test.go deleted file mode 100644 index 1002acbcd..000000000 --- a/logic/service/fight/battle/skill/effect/effect_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package effect - -import ( - "fmt" - "testing" - "time" -) - -func Test_mainTT(t *testing.T) { - battle := NewBattle() - player := &Player{ID: "player1"} - - container := NewEffectContainer("container1", []*Effect{ - { - ID: "startEffect", - Priority: 10, - Triggers: []EnumEffectTrigger{EffectTrigger.TurnStart}, - Apply: func(ctx *EffectContext, next func()) { - fmt.Println("回合开始效果: 增加护盾") - next() - }, - }, - { - ID: "endEffect", - Priority: 5, - Triggers: []EnumEffectTrigger{EffectTrigger.TurnEnd}, - Apply: func(ctx *EffectContext, next func()) { - fmt.Println("回合结束效果: 恢复怒气") - next() - }, - }, - }, battle, player) - - for i := 0; i < 2; i++ { - battle.NextTurn([]*EffectContainer{container}) - time.Sleep(500 * time.Millisecond) - } - - fmt.Println("测试 BattleMode 枚举:", BattleMode.PVE, BattleMode.PVP) -} diff --git a/logic/service/fight/battle/skill/effect/effecti.go b/logic/service/fight/battle/skill/effect/effecti.go new file mode 100644 index 000000000..5dba9e8c1 --- /dev/null +++ b/logic/service/fight/battle/skill/effect/effecti.go @@ -0,0 +1,117 @@ +package effect + +import ( + "blazing/logic/service/fight/battle/node" + "sort" +) + +type Effect interface { + Trigger() node.EnumEffectTrigger + Apply(ctx *EffectContext, next func()) + Alive() bool // 是否还有效 + Tick() // 每回合开始/结束时调用,用来减少回合数或次数 + Priority() int // 优先级 +} + +// ======================== +// 上下文:一次效果执行环境 +// ======================== +type EffectContext struct { + Parent string // 上下文来源(比如 "Skill"、"Buff"、"Passive") + Trigger EnumEffectTrigger // 当前触发的节点 + Container *EffectContainer // 效果容器(通常挂在 Actor 身上) + Effect *Effect // 当前正在执行的 Effect + Available bool // 是否可用 + Success bool // 是否执行成功 + Done bool // 是否中止后续执行 +} + +// ======================== +// Effect: 单个效果 +// ======================== +type Effect struct { + ID string // 唯一标识 + Trigger EnumEffectTrigger // 触发节点 + Priority int // 执行优先级,数值越大越先执行 + Duration int // 持续回合(0 = 即时生效,>0 = 回合数) + Stacks int // 当前层数 + MaxStack int // 最大叠加层数 + Apply func(ctx *EffectContext) bool // 执行逻辑,返回 true 表示继续保留 +} + +// ======================== +// 容器:存放多个效果 +// ======================== +type EffectContainer struct { + Effects []*Effect +} + +// 添加效果 +func (c *EffectContainer) AddEffect(e *Effect) { + // 如果已有同 ID 的效果,尝试叠加 + for _, eff := range c.Effects { + if eff.ID == e.ID { + if eff.Stacks < eff.MaxStack { + eff.Stacks++ + } + return + } + } + // 否则新加入 + c.Effects = append(c.Effects, e) +} + +// 触发执行 +func (c *EffectContainer) Trigger(trigger EnumEffectTrigger, ctx *EffectContext) { + var candidates []*Effect + for _, eff := range c.Effects { + if eff.Trigger == trigger { + candidates = append(candidates, eff) + } + } + + // 按优先级排序 + sort.SliceStable(candidates, func(i, j int) bool { + return candidates[i].Priority > candidates[j].Priority + }) + + // 执行 + for _, eff := range candidates { + ctx.Effect = eff + keep := eff.Apply(ctx) + + if !keep { + // 持续回合结束 / 返回 false 的 effect 删除 + c.removeEffect(eff) + } + + if ctx.Done { + break // 被拦截 + } + } +} + +// 每回合结束时调用,用于处理持续时间 +func (c *EffectContainer) Tick() { + var remain []*Effect + for _, eff := range c.Effects { + if eff.Duration > 0 { + eff.Duration-- + } + if eff.Duration != 0 { // 保留 (负数表示永久) + remain = append(remain, eff) + } + } + c.Effects = remain +} + +// 删除 +func (c *EffectContainer) removeEffect(e *Effect) { + var remain []*Effect + for _, eff := range c.Effects { + if eff != e { + remain = append(remain, eff) + } + } + c.Effects = remain +} diff --git a/logic/service/fight/battle/skill/skill.go b/logic/service/fight/battle/skill/skill.go deleted file mode 100644 index 91f4793b5..000000000 --- a/logic/service/fight/battle/skill/skill.go +++ /dev/null @@ -1,11 +0,0 @@ -package skill - -type Skill struct { - Name string - Type SkillType - Power int - IsAttack bool - Attacker *BattleUnit - Defender *BattleUnit - Effects []Effect -} diff --git a/logic/service/fight/fight.go b/logic/service/fight/fight.go new file mode 100644 index 000000000..7387a6dfe --- /dev/null +++ b/logic/service/fight/fight.go @@ -0,0 +1,64 @@ +package fight + +import ( + "blazing/common/socket/handler" +) + +// 野怪对战包 +type FightNpcMonsterInboundInfo struct { + Head handler.TomeeHeader `cmd:"2408" struc:"[0]pad"` + // Number 地图刷新怪物结构体对应的序号(1-9的位置序号) + // 对应Java的@UInt long类型,使用uint32保持无符号特性 + Number uint32 `fieldDesc:"地图刷新怪物结构体对应的序号 1 - 9 的位置序号" ` +} + +type NullOutboundInfo struct { +} + +// 准备战斗包 +type ReadyToFightInboundInfo struct { + Head handler.TomeeHeader `cmd:"2404" struc:"[0]pad"` +} + +type FightStartOutboundInfo struct { + // 对应Java的@UInt long类型 + IsCanAuto uint32 `fieldDesc:"是否自动 默认给0 怀疑是自动战斗器使用的" ` + + // 当前战斗精灵信息1(前端通过userid判断是否为我方) + Info1 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"` + + // 当前战斗精灵信息2(前端通过userid判断是否为我方) + Info2 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"` +} + +// FightPetInfo 战斗精灵信息结构体,对应Java的FightPetInfo类 +type FightPetInfo struct { + // 用户ID(野怪为0),对应Java的@UInt long + UserID uint32 `fieldDesc:"用户ID 野怪为0" ` + + // 当前对战精灵ID,对应Java的@UInt long + PetID uint32 `fieldDesc:"当前对战精灵ID" ` + + // 空的16字节byte,对应Java的固定长度字符串 + // 使用[16]byte匹配固定长度16字节的要求 + PetName [16]byte `fieldDesc:"空的16字节byte" serialize:"fixedLength=16,type=byteArray"` + + // 精灵的捕获时间,对应Java的@UInt long + CatchTime uint32 `fieldDesc:"精灵的捕获时间" ` + + // 当前HP,对应Java的@UInt long + Hp uint32 `fieldDesc:"当前HP" ` + + // 最大HP,对应Java的@UInt long + MaxHp uint32 `fieldDesc:"最大HP" ` + + // 当前等级,对应Java的@UInt long + Level uint32 `fieldDesc:"当前等级" ` + + // 精灵是否能捕捉(1为能捕捉,0为不能捕捉),对应Java的@UInt long + Catchable uint32 `fieldDesc:"精灵是否能捕捉. 1为能捕捉 0为不能捕捉" ` + + // 战斗属性等级数组(6个单字节) + // 对应Java的byte[],固定长度6,存储buff等级、攻击、速度等属性 + BattleLV [6]byte `fieldDesc:"这里实际上应该是6个单字节byte, 内容为buff等级 攻击 速度 特攻 防御 特防 命中等.但具体顺序未知可能需要测试. 具体数值为1-6等级" serialize:"fixedLength=6,type=byteArray"` +} diff --git a/logic/service/fight/fightcmd.go b/logic/service/fight/fightcmd.go deleted file mode 100644 index fa9e71ca3..000000000 --- a/logic/service/fight/fightcmd.go +++ /dev/null @@ -1,125 +0,0 @@ -package fight - -import ( - "blazing/common/socket/handler" - "blazing/modules/blazing/model" -) - -type FightNpcMonsterInboundInfo struct { - Head handler.TomeeHeader `cmd:"2408" struc:"[0]pad"` //玩家登录 - // Number 地图刷新怪物结构体对应的序号(1-9的位置序号) - // 对应Java的@UInt long类型,使用uint32保持无符号特性 - Number uint32 `fieldDesc:"地图刷新怪物结构体对应的序号 1 - 9 的位置序号" ` -} -type NullOutboundInfo struct { -} - -type ReadyToFightInboundInfo struct { - Head handler.TomeeHeader `cmd:"2404" struc:"[0]pad"` //玩家登录 - -} - -type FightStartOutboundInfo struct { - // 对应Java的@UInt long类型 - IsCanAuto uint32 `fieldDesc:"是否自动 默认给0 怀疑是自动战斗器使用的" ` - - // 当前战斗精灵信息1(前端通过userid判断是否为我方) - Info1 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"` - - // 当前战斗精灵信息2(前端通过userid判断是否为我方) - Info2 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"` -} - -// FightPetInfo 战斗精灵信息结构体,对应Java的FightPetInfo类 -type FightPetInfo struct { - // 用户ID(野怪为0),对应Java的@UInt long - UserID uint32 `fieldDesc:"用户ID 野怪为0" ` - - // 当前对战精灵ID,对应Java的@UInt long - PetID uint32 `fieldDesc:"当前对战精灵ID" ` - - // 空的16字节byte,对应Java的固定长度字符串 - // 使用[16]byte匹配固定长度16字节的要求 - PetName [16]byte `fieldDesc:"空的16字节byte" serialize:"fixedLength=16,type=byteArray"` - - // 精灵的捕获时间,对应Java的@UInt long - CatchTime uint32 `fieldDesc:"精灵的捕获时间" ` - - // 当前HP,对应Java的@UInt long - Hp uint32 `fieldDesc:"当前HP" ` - - // 最大HP,对应Java的@UInt long - MaxHp uint32 `fieldDesc:"最大HP" ` - - // 当前等级,对应Java的@UInt long - Level uint32 `fieldDesc:"当前等级" ` - - // 精灵是否能捕捉(1为能捕捉,0为不能捕捉),对应Java的@UInt long - Catchable uint32 `fieldDesc:"精灵是否能捕捉. 1为能捕捉 0为不能捕捉" ` - - // 战斗属性等级数组(6个单字节) - // 对应Java的byte[],固定长度6,存储buff等级、攻击、速度等属性 - BattleLV [6]byte `fieldDesc:"这里实际上应该是6个单字节byte, 内容为buff等级 攻击 速度 特攻 防御 特防 命中等.但具体顺序未知可能需要测试. 具体数值为1-6等级" serialize:"fixedLength=6,type=byteArray"` -} - -// NoteReadyToFightInfo 战斗准备就绪消息结构体,对应Java的NoteReadyToFightInfo -type NoteReadyToFightInfo struct { - // 战斗类型ID(与野怪战斗为3,与人战斗为1,前端似乎未使用) - // 对应Java的@UInt long - FightId uint32 `fieldDesc:"战斗类型ID 但前端好像没有用到 与野怪战斗为3,与人战斗似乎是1" ` - - // 我方信息 - OurInfo FightUserInfo `fieldDesc:"我方信息" serialize:"struct"` - OurPetListLen uint32 `struc:"sizeof=OurPetList"` - // 我方携带精灵的信息 - // 对应Java的ArrayList,使用切片模拟动态列表 - OurPetList []ReadyFightPetInfo `fieldDesc:"我方携带精灵的信息" serialize:"lengthFirst,lengthType=uint16,type=structArray"` - - // 对方信息 - OpponentInfo FightUserInfo `fieldDesc:"对方信息" serialize:"struct"` - OpponentPetListLen uint32 `struc:"sizeof=OpponentPetList"` - // 敌方的精灵信息 - // 野怪战斗时:客户端接收此包前已生成精灵PetInfo,将部分信息写入该列表 - OpponentPetList []ReadyFightPetInfo `fieldDesc:"敌方的精灵信息 如果是野怪 那么再给客户端发送这个包体时就提前生成好了这只精灵的PetInfo,然后把从PetInfo中把部分信息写入到这个敌方的精灵信息中再发送这个包结构体" serialize:"lengthFirst,lengthType=uint16,type=structArray"` -} -type FightUserInfo struct { - // 用户ID(野怪为0),对应Java的@UInt long - UserID uint32 `fieldDesc:"userID 如果为野怪则为0" ` - - // 玩家名称(野怪为UTF-8的'-',固定16字节) - // 使用[16]byte存储固定长度的字节数组 - Nickname [16]byte ` ` -} - -// ReadyFightPetInfo 准备战斗的精灵信息结构体,对应Java的ReadyFightPetInfo类 -type ReadyFightPetInfo struct { - // 精灵ID,对应Java的@UInt long - ID uint32 `fieldDesc:"精灵ID" ` - - // 精灵等级,对应Java的@UInt long - Level uint32 `fieldDesc:"精灵等级" ` - - // 精灵当前HP,对应Java的@UInt long - Hp uint32 `fieldDesc:"精灵HP" ` - - // 精灵最大HP,对应Java的@UInt long - MaxHp uint32 `fieldDesc:"最大HP" ` - SkillListLen uint32 - // 技能信息列表(固定4个元素,技能ID和剩余PP,无技能则为0) - // 对应Java的List,初始化容量为4 - SkillList [4]model.SkillInfo `fieldDesc:"技能信息 技能ID跟剩余PP 固定32字节 没有给0" serialize:"fixedLength=4,type=structArray"` - - // 精灵捕获时间,对应Java的@UInt long - CatchTime uint32 `fieldDesc:"精灵捕获时间" ` - - // 捕捉地图(固定给0),对应Java的@UInt long - CatchMap uint32 `fieldDesc:"捕捉地图 给0" ` - - // 固定给0,对应Java的@UInt long - CatchRect uint32 `fieldDesc:"给0" ` - - // 固定给0,对应Java的@UInt long - CatchLevel uint32 `fieldDesc:"给0" ` - SkinID uint32 `fieldDesc:"精灵皮肤ID" ` - Shiny uint32 `fieldDesc:"精灵是否闪" ` -} diff --git a/logic/service/fight/info/BattleInputSourceEntity.go b/logic/service/fight/info/BattleInputSourceEntity.go new file mode 100644 index 000000000..0a49e274f --- /dev/null +++ b/logic/service/fight/info/BattleInputSourceEntity.go @@ -0,0 +1,10 @@ +package info + +type FightUserInfo struct { + // 用户ID(野怪为0),对应Java的@UInt long + UserID uint32 `fieldDesc:"userID 如果为野怪则为0" ` + + // 玩家名称(野怪为UTF-8的'-',固定16字节) + // 使用[16]byte存储固定长度的字节数组 + Nickname [16]byte ` ` +} diff --git a/logic/service/fight/info/BattlePetEntity.go b/logic/service/fight/info/BattlePetEntity.go new file mode 100644 index 000000000..2b1e376c2 --- /dev/null +++ b/logic/service/fight/info/BattlePetEntity.go @@ -0,0 +1,87 @@ +package info + +import ( + "blazing/modules/blazing/model" + "sync" +) + +type BattlePetEntity struct { + // /host *BattleInputSourceEntity // 宠物的主人(输入源) + //uuid string // 唯一标识 + *model.PetInfo + + GainHp int64 // 获得的生命值 + + Capturable bool // 是否可捕获 + + // 状态条件(如中毒、烧伤等) + statusConditions sync.Map // key: StatusCondition, value: int (剩余回合) + + // 状态变化值 + attackStat int + defenseStat int + specialAttackStat int + specialDefenseStat int + speedStat int + accuracyStat int + + skills [4]*BattleSkillEntity // 技能槽(最多4个技能) + + // 特殊属性 + Perseverance int // 毅力值:抵消致命伤 + Stubborn bool // 顽强特性 + StubbornProbability int // 顽强触发概率 + Revival bool // 回神特性 + RevivalProbability int // 回神触发概率 + + Definitely int // 必定命中概率 + Dodge int // 闪避概率 + + // 护盾相关 + MaxShield int64 // 最大护盾值 + Shield int64 // 当前护盾值 + CountShield int // 次数盾 + + // 属性变化回合 + //petAttributeRound map[BattleRoundStarEffect]int // 属性变化类型 -> 剩余回合 + petImmunityEffectIds map[int]int // 免疫效果ID -> 剩余回合 (-1表示永久) + petImmunityBuffs map[string]int // 免疫buff效果ID -> 剩余回合 (-1表示永久) + + IsDead bool // 是否死亡 + + // 战斗开始时拥有的特殊buff + //battleStartHavingBuffs []buff.BattleBuffInterface +} + +func (b *BattlePetEntity) GetSpeed() uint32 { + + b.calculateRealValue(int64(b.Speed), b.speedStat) + + return 0 + +} + +// calculateRealValue 计算实际属性值根据状态变化计算实际值 +// value 基础属性值 +// stat 状态变化值(可正可负) +// 返回// 返回计算后的实际属性值,确保结果至少为1 +func (b *BattlePetEntity) calculateRealValue(value int64, stat int) int64 { + if stat == 0 { + if value <= 0 { + return 1 + } + return value + } else if stat > 0 { + r := int64(float64(value) * (float64(stat+2) / 2.0)) + if r <= 0 { + return 1 + } + return r + } else { + r := int64(float64(value) * (2.0 / float64(2-stat))) + if r <= 0 { + return 1 + } + return r + } +} diff --git a/logic/service/fight/info/BattleSkillEntity.go b/logic/service/fight/info/BattleSkillEntity.go new file mode 100644 index 000000000..9dc6a6ad1 --- /dev/null +++ b/logic/service/fight/info/BattleSkillEntity.go @@ -0,0 +1,159 @@ +package info + +import ( + "blazing/common/data/xml/skill" + "context" + "fmt" + "strconv" + "strings" + + "github.com/gogf/gf/v2/os/glog" + "github.com/tnnmigga/enum" +) + +// EnumSkillType 技能类型枚举基础类型 +type EnumSkillType int + +// SkillType 技能类型枚举实例(使用enum包创建) +// 与原Java枚举保持相同的数值映射:PHYSICAL=1, SPECIAL=2, STATUS=4 +var SkillType = enum.New[struct { + PHYSICAL EnumSkillType `enum:"1"` // 物理攻击 + SPECIAL EnumSkillType `enum:"2"` // 特殊攻击 + STATUS EnumSkillType `enum:"4"` // 状态技能 +}]() + +// BattleSkillEntity 战斗技能实体 +// 实现了战斗中技能的所有属性和行为,包括PP管理、技能使用、属性获取等 +type BattleSkillEntity struct { + skill.Move + SideEffects []int + SideEffectArgs []int + PP int + InfinityPP bool + // 技能类型属性 + SkillType EnumSkillType // 技能类型(物理/特殊/状态) + +} + +// CreateBattleSkill 创建战斗技能实例 +func CreateBattleSkill(id int64, pp int) *BattleSkillEntity { + return CreateBattleSkillWithInfinity(id, pp, false) +} + +// CreateBattleSkillWithInfinity 创建战斗技能实例(可指定是否无限PP) +func CreateBattleSkillWithInfinity(id int64, pp int, infinityPP bool) *BattleSkillEntity { + // ID小于10001的视为无效技能 + if id < 10001 { + return nil + } + + // 从资源仓库获取技能数据 + move, ok := skill.MovesConfig.Moves[id] + if !ok { + glog.Error(context.Background(), "技能ID无效", "id", id) + } + var ret BattleSkillEntity + // 解析副作用参数 + sideEffectArgs := parseSideEffectArgs(move.SideEffectArg) + tt := strings.Split(move.SideEffect, " ") + rf, err := strSliceToIntSlice(tt) + if err == nil { + ret.SideEffects = rf + } + + ret.SideEffectArgs = sideEffectArgs + + return &ret +} + +// 将字符串切片转换为整数切片 +func strSliceToIntSlice(strs []string) ([]int, error) { + intSlice := make([]int, 0, len(strs)) // 预分配容量,提高性能 + + for _, s := range strs { + // 将字符串转换为整数 + num, err := strconv.Atoi(s) + if err != nil { + // 转换失败时返回错误(包含具体的错误信息和失败的字符串) + return nil, fmt.Errorf("无法将字符串 '%s' 转换为整数: %v", s, err) + } + intSlice = append(intSlice, num) + } + + return intSlice, nil +} + +// CanUse 检查技能是否可以使用(PP是否充足) +func (s *BattleSkillEntity) CanUse() bool { + return s.PP > 0 +} + +// UseOnce 使用一次技能(消耗1点PP) +func (s *BattleSkillEntity) UseOnce() *BattleSkillEntity { + if !s.InfinityPP { + s.PP-- + s.correctPP() + } + return s +} + +// AddPP 增加PP值 +func (s *BattleSkillEntity) AddPP(value int) *BattleSkillEntity { + if !s.InfinityPP { + s.PP += value + s.correctPP() + } + return s +} + +// MinusPP 减少PP值 +func (s *BattleSkillEntity) MinusPP(value int) *BattleSkillEntity { + if !s.InfinityPP { + s.PP -= value + s.correctPP() + } + return s +} + +// 修正PP值,确保在0到MaxPP之间 +func (s *BattleSkillEntity) correctPP() { + if s.PP < 0 { + s.PP = 0 + } else if s.PP > s.MaxPP { + s.PP = s.MaxPP + } +} + +// 解析副作用参数字符串为整数列表 +func parseSideEffectArgs(argsStr string) []int { + if argsStr == "" { + return []int{} + } + + parts := strings.Fields(argsStr) + args := make([]int, 0, len(parts)) + + for _, part := range parts { + if num, err := strconv.Atoi(part); err == nil { + args = append(args, num) + } + } + + return args +} + +// 获取技能名称,为空时使用ID +func getSkillName(move *BattleSkillEntity) string { + if move.Name == "" { + return strconv.FormatInt(int64(move.ID), 10) + } + return move.Name +} + +// 获取副作用列表,处理空值情况 +func getSideEffects(move *BattleSkillEntity) []int { + if move.SideEffect == "" { + return []int{} + } + return move.SideEffects +} diff --git a/logic/service/fight/info/NoteReadyToFightInfo.go b/logic/service/fight/info/NoteReadyToFightInfo.go new file mode 100644 index 000000000..efe8b914c --- /dev/null +++ b/logic/service/fight/info/NoteReadyToFightInfo.go @@ -0,0 +1,59 @@ +package info + +import ( + "blazing/modules/blazing/model" +) + +// NoteReadyToFightInfo 战斗准备就绪消息结构体,对应Java的NoteReadyToFightInfo +type NoteReadyToFightInfo struct { + // 战斗类型ID(与野怪战斗为3,与人战斗为1,前端似乎未使用) + // 对应Java的@UInt long + FightId uint32 `fieldDesc:"战斗类型ID 但前端好像没有用到 与野怪战斗为3,与人战斗似乎是1" ` + + // 我方信息 + OurInfo FightUserInfo `fieldDesc:"我方信息" serialize:"struct"` + OurPetListLen uint32 `struc:"sizeof=OurPetList"` + // 我方携带精灵的信息 + // 对应Java的ArrayList,使用切片模拟动态列表 + OurPetList []ReadyFightPetInfo `fieldDesc:"我方携带精灵的信息" serialize:"lengthFirst,lengthType=uint16,type=structArray"` + + // 对方信息 + OpponentInfo FightUserInfo `fieldDesc:"对方信息" serialize:"struct"` + OpponentPetListLen uint32 `struc:"sizeof=OpponentPetList"` + // 敌方的精灵信息 + // 野怪战斗时:客户端接收此包前已生成精灵PetInfo,将部分信息写入该列表 + OpponentPetList []ReadyFightPetInfo `fieldDesc:"敌方的精灵信息 如果是野怪 那么再给客户端发送这个包体时就提前生成好了这只精灵的PetInfo,然后把从PetInfo中把部分信息写入到这个敌方的精灵信息中再发送这个包结构体" serialize:"lengthFirst,lengthType=uint16,type=structArray"` +} + +// ReadyFightPetInfo 准备战斗的精灵信息结构体,对应Java的ReadyFightPetInfo类 +type ReadyFightPetInfo struct { + // 精灵ID,对应Java的@UInt long + ID uint32 `fieldDesc:"精灵ID" ` + + // 精灵等级,对应Java的@UInt long + Level uint32 `fieldDesc:"精灵等级" ` + + // 精灵当前HP,对应Java的@UInt long + Hp uint32 `fieldDesc:"精灵HP" ` + + // 精灵最大HP,对应Java的@UInt long + MaxHp uint32 `fieldDesc:"最大HP" ` + SkillListLen uint32 + // 技能信息列表(固定4个元素,技能ID和剩余PP,无技能则为0) + // 对应Java的List,初始化容量为4 + SkillList [4]model.SkillInfo `fieldDesc:"技能信息 技能ID跟剩余PP 固定32字节 没有给0" serialize:"fixedLength=4,type=structArray"` + + // 精灵捕获时间,对应Java的@UInt long + CatchTime uint32 `fieldDesc:"精灵捕获时间" ` + + // 捕捉地图(固定给0),对应Java的@UInt long + CatchMap uint32 `fieldDesc:"捕捉地图 给0" ` + + // 固定给0,对应Java的@UInt long + CatchRect uint32 `fieldDesc:"给0" ` + + // 固定给0,对应Java的@UInt long + CatchLevel uint32 `fieldDesc:"给0" ` + SkinID uint32 `fieldDesc:"精灵皮肤ID" ` + Shiny uint32 `fieldDesc:"精灵是否闪" ` +}