refactor(fight): 重构战斗相关代码
- 移除未使用的战斗模式枚举和相关代码 - 更新 BurnEffect 结构,增加生命周期管理 - 删除多余的 Skill 结构和 Effect 相关代码 - 调整 NoteReadyToFightInfo 结构的位置
This commit is contained in:
20
common/data/xml/skill/monster_refresh_test.go
Normal file
20
common/data/xml/skill/monster_refresh_test.go
Normal file
@@ -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)
|
||||
}
|
||||
117
common/data/xml/skill/skill.go
Normal file
117
common/data/xml/skill/skill.go
Normal file
@@ -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()
|
||||
@@ -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,
|
||||
|
||||
@@ -29,6 +29,7 @@ func main() {
|
||||
go Start(cool.Config.PortBL) //注入service
|
||||
|
||||
fmt.Println("Process start, pid:", os.Getpid())
|
||||
|
||||
gproc.AddSigHandlerShutdown(
|
||||
|
||||
signalHandlerForMain,
|
||||
|
||||
59
logic/service/fight/battle/action/Compare.go
Normal file
59
logic/service/fight/battle/action/Compare.go
Normal file
@@ -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 速度相同时 ,发起方优先
|
||||
}
|
||||
23
logic/service/fight/battle/container/BattleStateMachine.go
Normal file
23
logic/service/fight/battle/container/BattleStateMachine.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
12
logic/service/fight/battle/container/battle1v1.go
Normal file
12
logic/service/fight/battle/container/battle1v1.go
Normal file
@@ -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)
|
||||
}
|
||||
143
logic/service/fight/battle/random/random.go
Normal file
143
logic/service/fight/battle/random/random.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
36
logic/service/fight/battle/skill/effect/base/base.go
Normal file
36
logic/service/fight/battle/skill/effect/base/base.go
Normal file
@@ -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)
|
||||
}
|
||||
59
logic/service/fight/battle/skill/effect/c.go
Normal file
59
logic/service/fight/battle/skill/effect/c.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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)
|
||||
}
|
||||
117
logic/service/fight/battle/skill/effect/effecti.go
Normal file
117
logic/service/fight/battle/skill/effect/effecti.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package skill
|
||||
|
||||
type Skill struct {
|
||||
Name string
|
||||
Type SkillType
|
||||
Power int
|
||||
IsAttack bool
|
||||
Attacker *BattleUnit
|
||||
Defender *BattleUnit
|
||||
Effects []Effect
|
||||
}
|
||||
64
logic/service/fight/fight.go
Normal file
64
logic/service/fight/fight.go
Normal file
@@ -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"`
|
||||
}
|
||||
@@ -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<ReadyFightPetInfo>,使用切片模拟动态列表
|
||||
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<SkillInfo>,初始化容量为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:"精灵是否闪" `
|
||||
}
|
||||
10
logic/service/fight/info/BattleInputSourceEntity.go
Normal file
10
logic/service/fight/info/BattleInputSourceEntity.go
Normal file
@@ -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 ` `
|
||||
}
|
||||
87
logic/service/fight/info/BattlePetEntity.go
Normal file
87
logic/service/fight/info/BattlePetEntity.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
159
logic/service/fight/info/BattleSkillEntity.go
Normal file
159
logic/service/fight/info/BattleSkillEntity.go
Normal file
@@ -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
|
||||
}
|
||||
59
logic/service/fight/info/NoteReadyToFightInfo.go
Normal file
59
logic/service/fight/info/NoteReadyToFightInfo.go
Normal file
@@ -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<ReadyFightPetInfo>,使用切片模拟动态列表
|
||||
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<SkillInfo>,初始化容量为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:"精灵是否闪" `
|
||||
}
|
||||
Reference in New Issue
Block a user