feat(fight): 实现精灵切换功能并优化战斗逻辑

- 新增 ChangePet 方法实现精灵切换
- 优化战斗循环逻辑,支持精灵切换
- 修复一些战斗相关的 bug
- 优化代码结构,提高可维护性
This commit is contained in:
2025-09-07 00:23:28 +08:00
parent 6376d94487
commit 9d2de92dd6
12 changed files with 193 additions and 48 deletions

View File

@@ -83,3 +83,10 @@ func (h Controller) Escape(data *fight.EscapeFightInboundInfo, c *service.Player
c.FightC.Escape(c)
return nil, 0
}
// 切换精灵
func (h Controller) ChangePet(data *fight.ChangePetInboundInfo, c *service.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
c.FightC.ChangePet(c, int32(data.CatchTime))
return nil, 0
}

View File

@@ -60,6 +60,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=

View File

@@ -8,6 +8,7 @@ import (
"github.com/gogf/gf/v2/os/gproc"
_ "blazing/contrib/drivers/pgsql"
"blazing/logic/service"
"blazing/cool"
@@ -17,6 +18,7 @@ import (
)
func signalHandlerForMain(sig os.Signal) {
service.Fightpool.Release()
fmt.Println("MainProcess is shutting down due to signal:", sig.String())
}

View File

@@ -62,3 +62,6 @@ func (p *AI_player) GetPetInfo() []*model.PetInfo {
func (p *AI_player) SendAttackValue(info.AttackValueS) {
}
func (p *AI_player) SendChangePet(info.ChangePetInfo) {
}

View File

@@ -48,3 +48,8 @@ type UseSkillInboundInfo struct {
// 技能id
SkillId uint32
}
type ChangePetInboundInfo struct {
Head service.TomeeHeader `cmd:"2407" struc:"[0]pad"`
// CatchTime 捕捉时间
CatchTime uint32 `json:"catchTime"`
}

View File

@@ -11,12 +11,13 @@ type EnumPlayerOperation int
// 定义读秒倒计时期间玩家可执行的操作枚举
var PlayerOperations = enum.New[struct {
SystemGiveUp EnumPlayerOperation `enum:"-1"` // 系统选择放弃出手(比如没有PP)
SelectSkill EnumPlayerOperation `enum:"0"` // 选择技能-6到6
ActiveSwitch EnumPlayerOperation `enum:"2"` // 主动切换(中切)
UsePotion EnumPlayerOperation `enum:"3"` // 使用药剂(捕捉、逃跑等)
Escape EnumPlayerOperation `enum:"4"` // 逃跑(等级最高,以及掉线)
PlayerOffline EnumPlayerOperation `enum:"5"` // 玩家掉线
SystemGiveUp EnumPlayerOperation `enum:"-1"` // 系统选择放弃出手(比如没有PP)
SelectSkill EnumPlayerOperation `enum:"0"` // 选择技能-6到6
ActiveSwitch EnumPlayerOperation `enum:"2"` // 主动切换(中切)
UsePotion EnumPlayerOperation `enum:"3"` // 使用药剂(捕捉、逃跑等)
Escape EnumPlayerOperation `enum:"4"` // 逃跑(等级最高,以及掉线)
PlayerOffline EnumPlayerOperation `enum:"5"` // 玩家掉线
BeExpelledSwitch EnumPlayerOperation `enum:"6"` // 被驱逐切换
}]()
// Compare 比较两个1v1战斗动作的执行优先级核心逻辑
@@ -46,7 +47,7 @@ func Compare(a, b BattleActionI) (BattleActionI, BattleActionI) {
}
// 比较宠物相关属性(假设Value(4)返回速度相关值)
p2 = int(bskill.PetInfo.Value(4)) - int(askill.PetInfo.Value(4))
p2 = int(bskill.PetInfo.Speed()) - int(askill.PetInfo.Speed())
if p2 > 0 {
return b, a
} else if p2 < 0 {
@@ -82,15 +83,20 @@ func (s *SelectSkillAction) Priority() int {
// ActiveSwitchAction 主动切换宠物的战斗动作
type ActiveSwitchAction struct {
PlayerID uint32 // 玩家ID
CurrentPet BattlePetEntity // 当前在场宠物
TargetPet BattlePetEntity // 要切换上场的宠物
SwitchReason string // 切换原因
PlayerID uint32 // 玩家ID
Type bool //是否主动切换
Reason ChangePetInfo
// CurrentPet BattlePetEntity // 当前在场宠物
// TargetPet BattlePetEntity // 要切换上场的宠物
// SwitchReason string // 切换原因
}
// Priority 返回动作优先级
func (a *ActiveSwitchAction) Priority() int {
return int(PlayerOperations.ActiveSwitch)
if a.Type {
return int(PlayerOperations.ActiveSwitch)
}
return int(PlayerOperations.BeExpelledSwitch)
}
// Broadcast 广播切换宠物的动作信息
@@ -99,6 +105,12 @@ func (a *ActiveSwitchAction) Broadcast() {
// // a.PlayerID, a.CurrentPet.Name, a.TargetPet.Name, a.SwitchReason)
}
func (a *ActiveSwitchAction) GetPlayerID() uint32 {
return a.PlayerID
// fmt.Printf("玩家[%d]主动切换宠物:从%s切换到%s原因%s\n",
// // a.PlayerID, a.CurrentPet.Name, a.TargetPet.Name, a.SwitchReason)
}
// UsePotionAction 使用药剂的战斗动作
type UsePotionAction struct {
PlayerID uint32 // 玩家ID
@@ -115,12 +127,6 @@ func (u *UsePotionAction) Priority() int {
return int(PlayerOperations.UsePotion)
}
// Broadcast 广播使用药剂的动作信息
func (u *UsePotionAction) Broadcast() {
// fmt.Printf("玩家[%d]使用药剂:%sID%d目标%s\n",
// u.PlayerID, u.PotionName, u.PotionID, u.TargetPet.Name)
}
// EscapeAction 逃跑的战斗动作
type EscapeAction struct {
PlayerID uint32 // 玩家ID

View File

@@ -4,6 +4,8 @@ import (
element "blazing/common/data/Element"
"blazing/common/data/xmlres"
"blazing/modules/blazing/model"
"fmt"
"math/rand"
"sync"
"unsafe"
)
@@ -23,19 +25,25 @@ func (a *BattlePetEntity) Value(tt uint32) uint32 {
offsetAtk := unsafe.Offsetof(a.Info.Attack) // c字段的偏移量通常为4+16=20
// 2. 将结构体指针转换为原始内存地址uintptr
baseAddr := uintptr(unsafe.Pointer(&offsetAtk))
fmt.Println(*(*uint32)(unsafe.Pointer(&baseAddr)))
addrA := unsafe.Pointer(baseAddr + 4*uintptr(tt)) //根据0是攻击
offsetAtkP := unsafe.Offsetof(a.Prop.Attack) // c字段的偏移量通常为4+16=20
// 2. 将结构体指针转换为原始内存地址uintptr
baseAddrp := uintptr(unsafe.Pointer(&offsetAtkP))
fmt.Println(*(*uint32)(unsafe.Pointer(&offsetAtkP)))
addrB := unsafe.Pointer(baseAddrp + 4*uintptr(tt)) //根据0是攻击
return uint32(calculateRealValue(int64(*(*uint32)(addrA)), int(*(*byte)(addrB))))
fmt.Println(*(*uint32)(addrA))
ret := uint32(calculateRealValue(int64(*(*uint32)(addrA)), int(*(*byte)(addrB))))
return ret
}
func (a *BattlePetEntity) Speed() uint32 {
return uint32(calculateRealValue(int64(a.Info.Speed), int(a.Prop.Speed)))
}
type BattlePetEntity struct {
xmlres.PetInfo
Info *model.PetInfo //通过偏移赋值
Info model.PetInfo //通过偏移赋值
statusConditions sync.Map // key: StatusCondition, value: int (剩余回合)
Skills [4]*BattleSkillEntity // 技能槽最多4个技能
@@ -45,13 +53,14 @@ type BattlePetEntity struct {
}
// 创建精灵实例
func CreateBattlePetEntity(info *model.PetInfo) *BattlePetEntity {
func CreateBattlePetEntity(info *model.PetInfo, rand *rand.Source) *BattlePetEntity {
ret := &BattlePetEntity{}
ret.PetInfo = xmlres.PetMAP[int(info.ID)] //注入精灵信息
ret.Info = info
ret.Info = *info
for i := 0; i < 4; i++ {
ret.Skills[i] = CreateBattleSkillWithInfinity(info.SkillList[i].ID)
//todo 技能信息应该每回合进行深拷贝,保证每次的技能效果都是不一样的
ret.Skills[i] = CreateBattleSkillWithInfinity(&info.SkillList[i], rand)
}
return ret

View File

@@ -3,6 +3,8 @@ package info
import (
element "blazing/common/data/Element"
"blazing/common/data/xmlres"
"blazing/modules/blazing/model"
"math/rand"
"context"
"fmt"
@@ -39,26 +41,26 @@ type BattleSkillEntity struct {
PP int
DamageZone map[EnumCategory]map[EnumsZoneType]map[EnumsZoneType][]float64 // 三维map 伤害类型-》增还是减-》加还是乘-》值
DamageValue decimal.Decimal // 伤害值
Rand rand.Source
// 技能类型属性
//SkillType EnumCategory // 技能类型(物理/特殊/状态)
}
// CreateBattleSkillWithInfinity 创建战斗技能实例可指定是否无限PP
func CreateBattleSkillWithInfinity(id uint32) *BattleSkillEntity {
func CreateBattleSkillWithInfinity(skill *model.SkillInfo, rand *rand.Source) *BattleSkillEntity {
//如果PP是-1 ,那就是无限PP
// ID小于10001的视为无效技能
if id < 10001 {
if skill.ID < 10001 {
return nil
}
var ret BattleSkillEntity
// 从资源仓库获取技能数据
move, ok := xmlres.SkillMap[int(id)]
move, ok := xmlres.SkillMap[int(skill.ID)]
if !ok {
glog.Error(context.Background(), "技能ID无效", "id", id)
glog.Error(context.Background(), "技能ID无效", "id", skill.ID)
}
ret.Move = move
// // 解析副作用参数
@@ -68,7 +70,7 @@ func CreateBattleSkillWithInfinity(id uint32) *BattleSkillEntity {
// if err == nil {
// ret.SideEffects = rf
// }
ret.PP = int(skill.PP)
// ret.SideEffectArgs = sideEffectArgs
ret.DamageZone = make(map[EnumCategory]map[EnumsZoneType]map[EnumsZoneType][]float64) //初始化第一层类型

View File

@@ -5,6 +5,22 @@ import (
"fmt"
)
type ChangePetInfo struct {
// UserId 米米号野怪为0
UserId uint32 `json:"userId"`
// PetId 切换上场的精灵编号
ID uint32 `fieldDesc:"当前对战精灵ID" `
// PetName 精灵名字固定16字节长度
Name string `struc:"[16]byte"`
// Level 切换上场的精灵等级
Level uint32 `json:"level"`
// Hp 切换上场的精灵当前生命值
Hp uint32 `json:"hp"`
// MaxHp 切换上场的精灵最大生命值
MaxHp uint32 `json:"maxHp"`
CatchTime uint32 `fieldDesc:"捕捉时间" `
}
// FightPetInfo 战斗精灵信息结构体FightPetInfo类
type FightPetInfo struct {
// 用户ID野怪为0@UInt long
@@ -42,8 +58,8 @@ type AttackValue struct {
SkillID uint32 `json:"skillId" fieldDescription:"使用技能的id"`
AttackTime uint32 `json:"attackTime" fieldDescription:"是否击中 如果为0 则miss 如果为1 则击中"`
LostHp uint32 `json:"lostHp" fieldDescription:"我方造成的伤害"`
GainHp uint32 `json:"gainHp" fieldDescription:"我方获得血量"`
RemainHp uint32 `json:"remainHp" fieldDescription:"我方剩余血量"`
GainHp int32 `json:"gainHp" fieldDescription:"我方获得血量"`
RemainHp int32 `json:"remainHp" fieldDescription:"我方剩余血量"`
MaxHp uint32 `json:"maxHp" fieldDescription:"我方最大血量"`
State uint32 `json:"state" fieldDescription:"固定值0 需要后续测试"`
SkillListLen uint32 `struc:"sizeof=SkillList"`
@@ -76,7 +92,7 @@ type StatusDict struct {
ImmuneToStatDrop_17 byte // 17: 免疫能力下降
ImmuneToAbnormal_18 byte // 18: 免疫异常状态
Paralyzed_19 byte // 19: 瘫痪
Blind_20 byte // 20: 失明
//Blind_20 byte // 20: 失明
}
// 精灵的能力提升

View File

@@ -5,12 +5,14 @@ import (
"blazing/logic/service/fight/info"
"blazing/modules/blazing/model"
"fmt"
"math"
"math/rand"
"time"
"github.com/gogf/gf/v2/util/gconv"
"github.com/jinzhu/copier"
"github.com/mohae/deepcopy"
"github.com/panjf2000/ants/v2"
)
type PlayerI interface {
@@ -24,6 +26,7 @@ type PlayerI interface {
SendNoteReadyToFightInfo(info.NoteReadyToFightInfo)
SendFightEndInfo(info.FightOverInfo)
SendAttackValue(info.AttackValueS)
SendChangePet(info.ChangePetInfo)
}
type FightC struct {
@@ -56,6 +59,33 @@ func (f *FightC) Escape(c PlayerI) {
f.actionChan <- ret
}
// 切换精灵 主动和被驱逐
func (f *FightC) ChangePet(c PlayerI, id int32) {
ret := &info.ActiveSwitchAction{
PlayerID: c.ID(),
}
rett := func(t PlayerI) {
for _, v := range t.GetPetInfo() {
if v.CatchTime == uint32(id) {
f.OurCurrentPet = info.CreateBattlePetEntity(v, f.Random()) //存入自己的精灵信息
copier.Copy(&ret.Reason, &v)
ret.Reason.UserId = c.ID()
}
}
}
if c == f.Our {
rett(f.Our)
} else {
rett(f.Opp)
}
f.actionChan <- ret
}
// 玩家使用技能
func (f *FightC) UseSkill(c PlayerI, id int32) {
ret := &info.SelectSkillAction{
@@ -106,7 +136,7 @@ func (f *FightC) ReadyFight(c PlayerI) {
}
} else {
f.Opp = c
f.OppCurrentPet = info.CreateBattlePetEntity(c.GetPetInfo()[0])
f.OppCurrentPet = info.CreateBattlePetEntity(c.GetPetInfo()[0], f.Random())
f.BFinished = true
if f.AFinished {
rrsult()
@@ -122,15 +152,23 @@ func (f *FightC) ReadyFight(c PlayerI) {
rrsult()
}
}
func (f *FightC) Random() {
func (f *FightC) Random() *rand.Source {
//先产生战斗的随机数
// 组合「时间戳(纳秒精度)+ 双方ID + 回合数」生成种子
seed := f.StartTime.UnixNano() ^ int64(f.OwnerID) ^ int64(f.Our.ID()) ^ int64(f.Round) // 用异或运算混合多维度信息
rand.Seed(seed)
ret := rand.NewSource(seed)
return &ret
}
var Fightpool *ants.Pool
func init() {
Fightpool, _ = ants.NewPool(math.MaxInt32)
//defer p.Release()
}
// 创建新战斗
func (f *FightC) NewFight(i *info.NoteReadyToFightInfo, plays PlayerI, mo *model.PetInfo) {
@@ -138,9 +176,9 @@ func (f *FightC) NewFight(i *info.NoteReadyToFightInfo, plays PlayerI, mo *model
f.Info = i
f.StartTime = time.Now()
f.actionChan = make(chan info.BattleActionI, 2) // 初始化全局操作通道
f.OurCurrentPet = info.CreateBattlePetEntity(plays.GetPetInfo()[0])
f.OurCurrentPet = info.CreateBattlePetEntity(plays.GetPetInfo()[0], f.Random())
if mo != nil {
f.OppCurrentPet = info.CreateBattlePetEntity(mo)
f.OppCurrentPet = info.CreateBattlePetEntity(mo, f.Random())
}
switch i.FightId {
@@ -152,7 +190,11 @@ func (f *FightC) NewFight(i *info.NoteReadyToFightInfo, plays PlayerI, mo *model
plays.SendNoteReadyToFightInfo(*i)
}
go f.battleLoop() // 起战斗循环
rr := Fightpool.Submit(f.battleLoop)
if rr != nil {
panic(rr)
}
//go f.battleLoop() // 起战斗循环
}
// 广播,并是否结束回合
@@ -164,6 +206,12 @@ func (f *FightC) Broadcast(t info.BattleActionI) bool {
f.Our.SendFightEndInfo(ff.Reason)
f.Opp.SendFightEndInfo(ff.Reason)
return true
case *info.ActiveSwitchAction: //切换精灵
f.Our.SendChangePet(ff.Reason)
f.Opp.SendChangePet(ff.Reason)
default:
}
@@ -177,7 +225,9 @@ func (f *FightC) BroadcastSkill(t info.AttackValueS) {
// 战斗回合循环
func (f *FightC) battleLoop() {
for {
f.Round++ //回合数自增
f.Round++ //回合数自增
// f.AFinished = false
// f.BFinished = false
if f.Round > 250 { //回合数超过250,战斗平局结束
}
@@ -191,7 +241,14 @@ func (f *FightC) battleLoop() {
if action.GetPlayerID() != f.Our.ID() && action.GetPlayerID() != f.Opp.ID() {
continue
}
if a, isExpelled := action.(*info.ActiveSwitchAction); isExpelled {
fmt.Println("对方死亡切换")
f.Broadcast(a)
if !a.Type {
continue
}
}
if action.GetPlayerID() == f.Our.ID() && f.Info.FightId == 3 {
go f.Opp.GetAction() //获取AI的动作
@@ -204,7 +261,7 @@ func (f *FightC) battleLoop() {
}
actions[uint32(action.GetPlayerID())] = action
fmt.Printf("玩家%d 执行动作", action.GetPlayerID())
fmt.Println("玩家%d 执行动作", action.GetPlayerID())
case <-timeout:
fmt.Println("回合操作超时")
@@ -212,7 +269,7 @@ func (f *FightC) battleLoop() {
actions[f.Our.ID()] = &info.SystemGiveUpAction{PlayerID: f.Our.ID()} //系统选择出手
}
if _, exists := actions[f.Opp.ID()]; !exists {
actions[f.Opp.ID()] = &info.SystemGiveUpAction{PlayerID: f.Our.ID()} //系统选择出手
actions[f.Opp.ID()] = &info.SystemGiveUpAction{PlayerID: f.Opp.ID()} //系统选择出手
}
}
}
@@ -236,6 +293,7 @@ func (f *FightC) battleLoop() {
break
}
var p_skill [2]*info.SelectSkillAction
for i := 0; i < 2; i++ {
@@ -271,19 +329,33 @@ func (f *FightC) battleLoop() {
uint32(p_skill[i].Skill.ID),
1,
200,
200,
100,
0,
int32(f.OurCurrentPet.Info.MaxHp),
f.OurCurrentPet.Info.MaxHp,
0,
0,
f.OurCurrentPet.Info.SkillList[:],
[]model.SkillInfo{},
1,
info.StatusDict{},
info.PropDict{},
}
}
p_skill[1].Attack = info.AttackValue{
p_skill[1].PlayerID,
0,
0,
0,
0,
0,
0,
0,
0,
[]model.SkillInfo{},
0,
info.StatusDict{},
info.PropDict{},
}
f.BroadcastSkill(info.AttackValueS{
p_skill[0].Attack, p_skill[1].Attack,
})

View File

@@ -154,6 +154,14 @@ func (p *Player) SendAttackValue(b info.AttackValueS) {
p.SendPack(t1.Pack(&b)) //准备包由各自发,因为协议不一样
}
func (p *Player) SendChangePet(b info.ChangePetInfo) {
t1 := NewTomeeHeader(2407, p.Info.UserID)
p.SendPack(t1.Pack(&b)) //准备包由各自发,因为协议不一样
}
func (p *Player) Cheak(b error) {
if b != nil {
g.Log().Error(context.Background(), "出现错误", p.Info.UserID, b.Error())

View File

@@ -3,6 +3,7 @@ package model
import (
"blazing/common/data/xmlres"
"blazing/cool"
"math"
"math/rand"
"time"
@@ -23,6 +24,17 @@ type Pet struct {
Data string `gorm:"type:text;not null;comment:'精灵全部数据'" json:"data"`
}
func GetGaussRandomNum(min, max int64) int64 {
σ := (float64(min) + float64(max)) / 2
μ := (float64(max) - σ) / 3
rand.Seed(time.Now().UnixNano())
x := rand.Float64()
x1 := rand.Float64()
a := math.Cos(2*math.Pi*x) * math.Sqrt((-2)*math.Log(x1))
result := a*μ + σ
return int64(result)
}
// RandomInRange 从切片表示的范围中返回对应结果:
// - 切片包含1个元素时返回该元素本身
// - 切片包含2个元素时从[min, max]闭区间随机生成一个整数自动调整min和max顺序
@@ -41,7 +53,8 @@ func RandomInRange(rangeSlice []int) int {
min, max = max, min
}
// 生成 [min, max] 区间的随机整数
return (rand.Intn(int(max-min+1)) + int(min))
return int(GetGaussRandomNum(int64(min), int64(max))) //(rand.Intn(int(max-min+1)) + int(min))
default:
// 其他长度返回0
return 0