Files
bl/logic/service/fightc.go

637 lines
16 KiB
Go
Raw Normal View History

package service
import (
"blazing/common/data/xmlres"
"blazing/common/utils"
"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"
"github.com/shopspring/decimal"
)
type PlayerI interface {
ID() uint32
MapID() uint32
GetAction()
GetPetInfo() []model.PetInfo
SendPack(b []byte) error
SendReadyToFightInfo(info.FightStartOutboundInfo)
SendNoteReadyToFightInfo(info.NoteReadyToFightInfo)
SendFightEndInfo(info.FightOverInfo)
SendAttackValue(info.AttackValueS)
SendChangePet(info.ChangePetInfo)
SetFightC(*FightC)
}
type Input struct {
CanChange bool //是否可以死亡切换CanChange
CurrentPet *info.BattlePetEntity //当前精灵
AllPet []*info.BattlePetEntity
Player PlayerI
Finished bool //是否加载完成
}
func (i *Input) GetPetInfo() *info.BattlePetEntity {
return i.CurrentPet
}
type FightC struct {
Info info.NoteReadyToFightInfo
2025-09-07 05:58:47 +08:00
OwnerID uint32 // 战斗发起者ID
Our *Input //始终等于房主ID
Opp *Input //对手ID
rand *rand.Rand
StartTime time.Time
actionChan chan info.BattleActionI // 所有操作统一从这里进入
Round int //回合数
EffectS info.NodeManager //effects容器
First *BPET
Second *BPET
}
func (f *FightC) GetInputByPlayer(c PlayerI, isOpposite bool) *Input {
// 判断当前玩家是否为我方玩家
isOurPlayer := c == f.Our.Player
// 逻辑简化:当"是否为我方玩家"与"是否需要对方"状态一致时,返回对方输入,否则返回我方输入
if isOurPlayer == isOpposite {
return f.Opp
}
return f.Our
}
func (f *FightC) GetInputByAction(c info.BattleActionI, isOpposite bool) *Input {
// 判断动作所属玩家是否为我方
isOurAction := c.GetPlayerID() == f.Our.Player.ID()
// 根据isOpposite决定是否返回相反方向的输入
if isOurAction == !isOpposite {
return f.Our
}
return f.Opp
}
// 玩家逃跑
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) Offline(c PlayerI) {
ret := &info.PlayerOfflineAction{
PlayerID: c.ID(),
Reason: info.FightOverInfo{
Reason: uint32(info.BattleOverReason.PlayerOffline),
},
}
f.actionChan <- ret
}
// 切换精灵 主动和被驱逐
func (f *FightC) ChangePet(c PlayerI, id int32) {
ret := &info.ActiveSwitchAction{
PlayerID: c.ID(),
}
rett := func(t *Input) *info.BattlePetEntity {
for _, v := range t.AllPet {
if v.Info.CatchTime == uint32(id) {
copier.Copy(&ret.Reason, &v.Info)
ret.Reason.UserId = c.ID()
return v
}
}
2025-09-07 05:58:47 +08:00
return nil
}
f.GetInputByPlayer(c, false).CurrentPet = rett(f.GetInputByPlayer(c, false))
f.actionChan <- ret
}
// 玩家使用技能
func (f *FightC) UseSkill(c PlayerI, id int32) {
if id == 0 {
f.actionChan <- &info.SystemGiveUpAction{PlayerID: c.ID()}
return
}
ret := &info.SelectSkillAction{
PlayerID: c.ID(),
}
ret.PetInfo = f.GetInputByPlayer(c, false).CurrentPet
for _, v := range ret.PetInfo.Skills {
if v != nil && v.ID == int(id) {
ret.Skill = v
}
}
f.actionChan <- ret
}
// 玩家使用技能
func (f *FightC) Capture(c PlayerI, id uint32) {
f.actionChan <- &info.UseItemAction{PlayerID: c.ID(), ItemID: id}
}
// 战斗准备
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.Player.SendReadyToFightInfo(rett)
f.Opp.Player.SendReadyToFightInfo(rett)
}
switch f.Info.FightId {
case 1: // 1v1
f.GetInputByPlayer(c, false).Finished = true
if f.GetInputByPlayer(c, true).Finished {
rrsult()
}
case 3: // 野怪战斗
//判断捕捉率大于0
if gconv.Int(xmlres.PetMAP[int(f.Info.OpponentPetList[0].ID)].CatchRate) > 0 {
rett.Info2.Catchable = 1
t, _ := f.Opp.Player.(*AI_player)
t.CanCapture = true
}
rrsult()
}
}
var Fightpool *ants.Pool
func init() {
Fightpool, _ = ants.NewPool(math.MaxInt32)
//defer p.Release()
}
// 创建新战斗,邀请方和被邀请方,或者玩家和野怪方
func NewFight(i info.NoteReadyToFightInfo, p1 PlayerI, p2 PlayerI) *FightC {
f := &FightC{}
f.OwnerID = p1.ID()
f.Info = i //房主
seed := f.StartTime.UnixNano() ^ int64(p1.ID()) ^ int64(p2.ID()) // ^ int64(f.Round) // 用异或运算混合多维度信息
f.rand = rand.New(rand.NewSource(seed))
f.Our = &Input{
CurrentPet: info.CreateBattlePetEntity(&p1.GetPetInfo()[0], f.rand),
Player: p1,
}
for k, v := range p1.GetPetInfo() {
if i.MAXPET == 0 || k < int(i.MAXPET) { //todo 待测试
f.Our.AllPet = append(f.Our.AllPet, info.CreateBattlePetEntity(&v, f.rand))
}
}
2025-09-07 05:58:47 +08:00
f.Opp = &Input{
CurrentPet: info.CreateBattlePetEntity(&p2.GetPetInfo()[0], f.rand),
Player: p2,
2025-09-07 05:58:47 +08:00
}
for k, v := range p2.GetPetInfo() {
if i.MAXPET == 0 || k < int(i.MAXPET) {
f.Opp.AllPet = append(f.Opp.AllPet, info.CreateBattlePetEntity(&v, f.rand))
}
}
p1.SetFightC(f) //给我方追加战斗容器
p2.SetFightC(f) //给对方增加战斗容器
defer func() {
rr := Fightpool.Submit(f.battleLoop)
if rr != nil {
panic(rr)
}
f.Broadcast(func(ff *Input) {
ff.Player.SendNoteReadyToFightInfo(i)
})
}()
//go f.battleLoop() // 起战斗循环
return f
}
// 广播,并是否结束回合
func (f *FightC) Broadcast(t func(ff *Input)) {
2025-09-07 05:58:47 +08:00
t(f.Our)
2025-09-07 05:58:47 +08:00
t(f.Opp)
2025-09-07 05:58:47 +08:00
}
// 战斗回合循环
func (f *FightC) battleLoop() {
f.StartTime = time.Now()
f.actionChan = make(chan info.BattleActionI, 2) // 初始化全局操作通道
fmt.Println("战斗开始精灵", f.Our.Player.GetPetInfo()[0].CatchTime)
//战斗开始前操作
for {
2025-09-07 05:58:47 +08:00
f.Round++ //回合数自增
2025-09-07 05:58:47 +08:00
if f.actionChan == nil { //回合数超过250,战斗平局结束f.Round > 250 ||
break
}
actions := make(map[uint32]info.BattleActionI) // 每个玩家一条记录
timeout := time.After(60 * time.Second)
for len(actions) < 2 {
select {
case action := <-f.actionChan:
// 只接受有效玩家 ID\
if action == nil {
continue
}
if action.GetPlayerID() != f.Our.Player.ID() && action.GetPlayerID() != f.Opp.Player.ID() {
continue
}
if a, isExpelled := action.(*info.ActiveSwitchAction); isExpelled {
2025-09-07 05:58:47 +08:00
//fmt.Println("对方死亡切换")
f.Broadcast(func(ff *Input) {
ff.Player.SendChangePet(a.Reason)
})
if f.GetInputByAction(action, false).CanChange {
//如果是被动切换,不计入回合结算
f.GetInputByAction(action, false).CanChange = false
continue
}
}
if action.GetPlayerID() != 0 && f.Info.FightId == 3 {
f.GetInputByAction(action, true).Player.GetAction()
}
// 如果该玩家已经提交过,就忽略重复动作
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.Player.ID()]; !exists {
actions[f.Our.Player.ID()] = &info.SystemGiveUpAction{PlayerID: f.Our.Player.ID()} //系统选择出手
}
if _, exists := actions[f.Opp.Player.ID()]; !exists {
actions[f.Opp.Player.ID()] = &info.SystemGiveUpAction{PlayerID: f.Opp.Player.ID()} //系统选择出手
}
}
}
// 双方动作齐了,取出来结算
//todo 如果一方没有选择,实际上就是后端判断PP是否还有,前端是直接发的
p1Action := actions[f.Our.Player.ID()]
p2Action := actions[f.Opp.Player.ID()]
fmt.Println("开始结算回合")
var BattleActionI [2]info.BattleActionI
BattleActionI[0], BattleActionI[1] = info.Compare(p1Action, p2Action)
switch faction := BattleActionI[0].(type) {
case *info.EscapeAction: //优先逃跑
f.Broadcast(func(ff *Input) {
ff.Player.SendFightEndInfo(faction.Reason) //广播逃跑原因
})
break
case *info.PlayerOfflineAction: //单方掉线
f.Broadcast(func(ff *Input) {
ff.Player.SendFightEndInfo(faction.Reason) //广播逃跑原因
})
break
case *info.ActiveSwitchAction: //切换上场的
f.enterturn(BattleActionI[1], nil) //切换,相当于后手直接出手
case *info.UseItemAction: //使用道具
fmt.Println(faction.ItemID)
switch {
case faction.ItemID >= 30001 && faction.ItemID <= 300010: //胶囊
f.Broadcast(func(ff *Input) {
//todo 将血量和技能pp传回enterturn
tt, ok := ff.Player.(*Player)
mo, ism := f.Opp.Player.(*AI_player)
if ok { //如果获取玩家
if ism && mo.CanCapture { //如果获取到IA
tt.Service.PetAdd(*f.Opp.CurrentPet.Info)
tt.CatchPetInfo(info.CatchMonsterOutboundInfo{
CatchTime: uint32(f.Opp.CurrentPet.Info.CatchTime),
PetId: uint32(f.Opp.CurrentPet.ID),
})
} else { //说明不是可以捕捉的
tt.CatchPetInfo(info.CatchMonsterOutboundInfo{})
}
}
ff.Player.SendFightEndInfo(info.FightOverInfo{
WinnerId: f.OwnerID,
})
})
// 当 ItemID 在 30001-300010 之间时执行的逻辑
fmt.Println("ItemID 在范围内")
case faction.ItemID == 300001:
// 原来的单个值判断(如果还需要保留)
fmt.Println("ItemID 是 300001")
default:
// 其他情况
fmt.Println("ItemID 不在指定范围内")
}
case *info.SelectSkillAction: //选择技能
//回合前操作,比如挂载buff
f.enterturn(BattleActionI[0], BattleActionI[1])
//回合后操作
}
}
}
type BPET struct {
*Input
2025-09-07 05:58:47 +08:00
//*info.SelectSkillAction //技能实体
*info.BattlePetEntity //精灵实体
*info.AttackValue
*FightC
info.BattleActionI
Damage decimal.Decimal //造成伤害
}
2025-09-07 05:58:47 +08:00
// 被击败的ID
func (b *BPET) IsWin(cache uint32) bool {
fmt.Println("当前回合", b.FightC.Round, "开始检查战斗是否结束")
var tt []info.ReadyFightPetInfo
bbb := b.FightC.Info.OurPetList
if b.Input.Player.ID() == b.FightC.OwnerID { //如果是房主
bbb = b.FightC.Info.OpponentPetList
} else {
bbb = b.FightC.Info.OurPetList
}
for _, v := range bbb {
if v.CatchTime == cache {
v.NotAlive = true
}
tt = append(tt, v)
}
for _, v := range tt {
if !v.NotAlive { //如果存活
return false
}
}
return true
}
// 解析并 施加effect
func (f *FightC) parseskill(id *info.SelectSkillAction) {
temparg := id.Skill.SideEffectArgS
for _, v := range id.Skill.SideEffectS {
t, ok := info.NodeM[v]
if ok { //获取成功
args := t.GetArgSize()
t.SetArgs(temparg[:args]) //设置入参
//如果不是是房主方,说明施加的对象是反的,比如本来是false,实际上是给邀请方施加的
//所以这里要对target取反
if id.GetPlayerID() != f.OwnerID {
t.SetOwner(!t.GetOwner())
}
temparg = temparg[args:]
f.EffectS.AddEffect(deepcopy.Copy(t).(info.Effect))
}
}
}
// 创建BPET实例的辅助函数
func (f *FightC) newBPET(input *Input) *BPET {
return &BPET{
FightC: f,
Input: input,
BattlePetEntity: input.CurrentPet,
AttackValue: info.NewAttackValue(input.Player.ID()),
}
}
func (f *FightC) initAttackers(fattack, sattack info.BattleActionI) {
// 伤害值
// 根据攻击方归属设置当前战斗的主/次攻击方属性
var first, second *Input // 定义临时变量存储主/次攻击方
if fattack.GetPlayerID() == f.OwnerID {
first, second = f.Our, f.Opp // 攻击方为我方时,主攻击方是我方
} else {
first, second = f.Opp, f.Our // 攻击方为对方时,主攻击方是对方
}
// 统一赋值,减少重复代码
f.First = f.newBPET(first)
f.Second = f.newBPET(second)
fmt.Println("先手", f.First.CurrentPet.Info.CatchTime, "后手", f.Second.CurrentPet.Info.CatchTime)
// TODO: 在这里调用技能结算逻辑
}
// 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *BPET, skill *info.SelectSkillAction) {
f.parseskill(skill) //解析effect
// 记录技能信息
attacker.AttackValue.SkillID = uint32(skill.Skill.ID) //获取技能ID
attacker.AttackValue.AttackTime = skill.Skill.AttackTime() //计算命中
f.EffectS.Exec(func(t info.Effect) bool { //计算闪避 闪避就是命中率重新计算的结果
//闪避本质上是算对方的命中重写函数?
if attacker.UserID == f.Our.Player.ID() {
}
if !t.GetOwner() { //先获取对方的
return t.AttackTime()
}
return true
})
if attacker.AttackValue.AttackTime == 1 { //如果命中
spower := skill.Skill.CalculatePower(defender.BattlePetEntity)
attacker.Damage = spower
CritRate := utils.Max(skill.Skill.CritRate, 1)
CritRateR := f.rand.Int31n(16)
//CritAtkFirst: 先出手时必定致命一击; 默认: 0
if skill.Skill.CritAtkFirst != 0 && attacker == f.First {
CritRate = 16
}
//CritAtkSecond: 后出手时必定致命一击; 默认: 0
if skill.Skill.CritAtkSecond != 0 && attacker != f.First {
CritRate = 16
}
// CritSelfHalfHp: 自身体力低于一半时必定致命一击; 默认: 0
if skill.Skill.CritSelfHalfHp != 0 && (attacker.CurrentPet.HP < int(attacker.CurrentPet.Info.MaxHp)/2) {
CritRate = 16
}
// CritFoeHalfHp: 对方体力低于一半时必定致命一击; 默认: 0
if skill.Skill.CritSelfHalfHp != 0 && (defender.CurrentPet.HP < int(defender.CurrentPet.Info.MaxHp)/2) {
CritRate = 16
}
//todo 暴击伤害
if CritRateR <= int32(CritRate) {
attacker.AttackValue.IsCritical = 1
}
if attacker.AttackValue.IsCritical == 1 {
attacker.Damage.Mul(decimal.NewFromInt(2)) //暴击翻倍
}
if uint32(attacker.Damage.IntPart()) > defender.CurrentPet.Info.Hp {
defender.CurrentPet.Info.Hp = 0
} else {
defender.CurrentPet.Info.Hp = defender.CurrentPet.Info.Hp - uint32(attacker.Damage.IntPart())
}
// 扣减防御方血量
} //todo 处理未命中效果
}
func (f *FightC) enterturn(fattack, sattack info.BattleActionI) {
f.initAttackers(fattack, sattack) //初始化先后手
var attacker, defender *BPET
for i := 0; i < 2; i++ {
if i == 0 { //
attacker, defender = f.First, f.Second
attacker.BattleActionI, defender.BattleActionI = fattack, sattack
} else {
attacker, defender = f.Second, f.First
}
skill, ok := attacker.BattleActionI.(*info.SelectSkillAction)
if !ok { //还有系统选择放弃出手的
continue
2025-09-07 05:58:47 +08:00
}
if attacker.CurrentPet.Info.Hp <= 0 { //攻击方死亡
continue
}
f.processSkillAttack(attacker, defender, skill)
fmt.Println(i,
"玩家技能伤害:", attacker.Damage,
"自身剩余血量:", attacker.CurrentPet.Info.Hp,
"对手剩余血量:", defender.CurrentPet.Info.Hp,
)
skill.Skill.Info.PP-- //减少PP
if defender.CurrentPet.Info.Hp == 0 {
defender.CanChange = true //被打死就可以切精灵了
if attacker.IsWin(defender.CurrentPet.Info.CatchTime) { //然后检查是否战斗结束
var WinnerId uint32
if i == 0 {
WinnerId = f.First.Player.ID()
} else {
WinnerId = f.Second.Player.ID()
}
defer f.Broadcast(func(ff *Input) {
//todo 将血量和技能pp传回enterturn
ff.Player.SendFightEndInfo(info.FightOverInfo{
WinnerId: WinnerId,
})
})
defer close(f.actionChan)
2025-09-07 05:58:47 +08:00
}
}
}
f.Broadcast(func(ff *Input) {
ret := info.AttackValueS{
FAttack: *f.First.AttackValue,
SAttack: *f.Second.AttackValue,
}
ret.FAttack.RemainHp = int32(f.First.CurrentPet.Info.Hp)
ret.FAttack.LostHp = uint32(f.First.Damage.IntPart()) //先手方造成血量
ret.SAttack.RemainHp = int32(f.Second.CurrentPet.Info.Hp)
ret.SAttack.LostHp = uint32(f.Second.Damage.IntPart()) //后手方造成血量
//ret.SAttack.Status.Poisoned_1 = 1
ff.Player.SendAttackValue(ret)
})
}