Files
bl/logic/service/fightc.go
昔念 9d2de92dd6 feat(fight): 实现精灵切换功能并优化战斗逻辑
- 新增 ChangePet 方法实现精灵切换
- 优化战斗循环逻辑,支持精灵切换
- 修复一些战斗相关的 bug
- 优化代码结构,提高可维护性
2025-09-07 00:23:28 +08:00

369 lines
8.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"blazing/common/data/xmlres"
"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 {
ID() uint32
MapID() uint32
GetAction()
GetPetInfo() []*model.PetInfo
End() //结束战斗
SendPack(b []byte) error
SendReadyToFightInfo(info.FightStartOutboundInfo)
SendNoteReadyToFightInfo(info.NoteReadyToFightInfo)
SendFightEndInfo(info.FightOverInfo)
SendAttackValue(info.AttackValueS)
SendChangePet(info.ChangePetInfo)
}
type FightC struct {
Info *info.NoteReadyToFightInfo
Our PlayerI
OurCurrentPet *info.BattlePetEntity //我方精灵
Opp PlayerI
OppCurrentPet *info.BattlePetEntity //对方当前精灵
MAXPET uint32 // 最大精灵数
OwnerID uint32 // 战斗发起者ID
AFinished bool
BFinished bool
//random *rand.Rand //随机数种子
StartTime time.Time
actionChan chan info.BattleActionI // 所有操作统一从这里进入
Round int //回合数
EffectS info.NodeManager //effects容器
}
// 玩家逃跑
func (f *FightC) Escape(c PlayerI) {
ret := &info.EscapeAction{
PlayerID: c.ID(),
Reason: info.FightOverInfo{
Reason: uint32(info.BattleOverReason.PlayerEscape),
},
}
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{
PlayerID: c.ID(),
}
if c == f.Our {
ret.PetInfo = f.OurCurrentPet //存入自己的精灵信息
} else {
ret.PetInfo = f.OppCurrentPet
}
for _, v := range ret.PetInfo.Skills {
if v != nil && v.ID == int(id) {
ret.Skill = v
}
}
f.actionChan <- ret
}
// 战斗准备
func (f *FightC) ReadyFight(c PlayerI) {
rett := info.FightStartOutboundInfo{}
copier.Copy(&rett.Info1, &f.Info.OurPetList[0]) // 复制自己的信息
copier.Copy(&rett.Info2, &f.Info.OpponentPetList[0])
rett.Info1.UserID = f.Info.OurInfo.UserID //
rett.Info2.UserID = f.Info.OpponentInfo.UserID
rrsult := func() { //传回函数
f.Our.SendReadyToFightInfo(rett)
f.Opp.SendReadyToFightInfo(rett)
}
switch f.Info.FightId {
case 1: // 1v1
if c == f.Our {
f.AFinished = true
if f.BFinished {
rrsult()
}
} else {
f.Opp = c
f.OppCurrentPet = info.CreateBattlePetEntity(c.GetPetInfo()[0], f.Random())
f.BFinished = true
if f.AFinished {
rrsult()
}
}
case 3: // 野怪战斗
//判断捕捉率大于0
if gconv.Int(xmlres.PetMAP[int(f.Info.OpponentPetList[0].ID)].CatchRate) > 0 {
rett.Info2.Catchable = 1
}
rrsult()
}
}
func (f *FightC) Random() *rand.Source {
//先产生战斗的随机数
// 组合「时间戳(纳秒精度)+ 双方ID + 回合数」生成种子
seed := f.StartTime.UnixNano() ^ int64(f.OwnerID) ^ int64(f.Our.ID()) ^ int64(f.Round) // 用异或运算混合多维度信息
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) {
f.Our = plays
f.Info = i
f.StartTime = time.Now()
f.actionChan = make(chan info.BattleActionI, 2) // 初始化全局操作通道
f.OurCurrentPet = info.CreateBattlePetEntity(plays.GetPetInfo()[0], f.Random())
if mo != nil {
f.OppCurrentPet = info.CreateBattlePetEntity(mo, f.Random())
}
switch i.FightId {
case 1:
// 1v1等双方进入
case 3: // 野怪战斗
f.Opp = &AI_player{fightinfo: *i, FightC: f} // 创建虚拟对手
plays.SendNoteReadyToFightInfo(*i)
}
rr := Fightpool.Submit(f.battleLoop)
if rr != nil {
panic(rr)
}
//go f.battleLoop() // 起战斗循环
}
// 广播,并是否结束回合
func (f *FightC) Broadcast(t info.BattleActionI) bool {
switch ff := t.(type) {
case *info.EscapeAction:
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:
}
return false
}
func (f *FightC) BroadcastSkill(t info.AttackValueS) {
f.Our.SendAttackValue(t)
f.Opp.SendAttackValue(t)
}
// 战斗回合循环
func (f *FightC) battleLoop() {
for {
f.Round++ //回合数自增
// f.AFinished = false
// f.BFinished = false
if f.Round > 250 { //回合数超过250,战斗平局结束
}
actions := make(map[uint32]info.BattleActionI) // 每个玩家一条记录
timeout := time.After(60 * time.Second)
for len(actions) < 2 {
select {
case action := <-f.actionChan:
// 只接受有效玩家 ID
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的动作
}
// 如果该玩家已经提交过,就忽略重复动作
if _, exists := actions[uint32(action.GetPlayerID())]; exists {
fmt.Printf("玩家%d 已经提交过动作,忽略重复\n", action.GetPlayerID())
continue
}
actions[uint32(action.GetPlayerID())] = action
fmt.Println("玩家%d 执行动作", action.GetPlayerID())
case <-timeout:
fmt.Println("回合操作超时")
if _, exists := actions[f.Our.ID()]; !exists {
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.Opp.ID()} //系统选择出手
}
}
}
// 双方动作齐了,取出来结算
p1Action := actions[f.Our.ID()]
p2Action := actions[f.Opp.ID()]
fmt.Println("开始结算回合")
var BattleActionI [2]info.BattleActionI
BattleActionI[0], BattleActionI[1] = info.Compare(p1Action, p2Action)
if BattleActionI[0].Priority() > 0 { //如果优先级大于0,说明是在技能之上的
t := f.Broadcast(BattleActionI[0])
if t {
break
}
}
if BattleActionI[0].Priority() < 0 && BattleActionI[1].Priority() < 0 { //双方都空过
break
}
var p_skill [2]*info.SelectSkillAction
for i := 0; i < 2; i++ {
// TODO: 在这里调用技能结算逻辑
p_skill[i] = BattleActionI[i].(*info.SelectSkillAction)
temparg := p_skill[i].Skill.SideEffectArgS
for _, v := range p_skill[i].Skill.SideEffectS {
t, ok := info.NodeM[v]
if ok { //获取成功
args := t.GetArgSize()
t.SetArgs(temparg[:args]) //设置入参
//如果不是是房主方,说明施加的对象是反的,比如本来是false,实际上是给邀请方施加的
//所以这里要对target取反
if BattleActionI[i].GetPlayerID() != f.OwnerID {
t.SetTarget(!t.Target())
}
temparg = temparg[args:]
f.EffectS.AddEffect(deepcopy.Copy(t).(info.Effect))
}
}
//开始计算技能效果battleLoop
p_skill[i].Attack = info.AttackValue{
p_skill[i].PlayerID,
uint32(p_skill[i].Skill.ID),
1,
200,
0,
int32(f.OurCurrentPet.Info.MaxHp),
f.OurCurrentPet.Info.MaxHp,
0,
0,
[]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,
})
}
close(f.actionChan) //关闭战斗通道
//取消战斗信息
f.Our.End()
f.Opp.End()
}