Files
bl/logic/service/fight/fightc.go

571 lines
14 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 fight
import (
"blazing/common/data/xmlres"
"blazing/logic/service/common"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/player"
"fmt"
"math/rand"
"sort"
"time"
"github.com/jinzhu/copier"
)
type FightC struct {
Info info.NoteReadyToFightInfo
ownerID uint32 // 战斗发起者ID
Our *input.Input //始终等于房主ID
Opp *input.Input //对手ID
Switch []*ActiveSwitchAction
rand *rand.Rand
StartTime time.Time
actionChan chan BattleActionI // 所有操作统一从这里进入
Round int //回合数
First *input.Input
Second *input.Input
closefight bool
}
func (f *FightC) Ownerid() uint32 {
return f.ownerID
}
func (f *FightC) GetInputByPlayer(c common.PlayerI, isOpposite bool) *input.Input {
// 判断当前玩家是否为我方玩家
isOurPlayer := c.GetInfo().UserID == f.ownerID
// 当isOurPlayer与isOpposite值不同时返回我方相同时返回对方
if isOurPlayer != isOpposite {
return f.Our
}
return f.Opp
}
func (f *FightC) GetInputByAction(c BattleActionI, isOpposite bool) *input.Input {
// 判断动作所属玩家是否为我方
isOurAction := c.GetPlayerID() == f.Our.Player.GetInfo().UserID
// 根据isOpposite决定是否返回相反方向的输入
if isOurAction == !isOpposite {
return f.Our
}
return f.Opp
}
// 玩家使用技能
func (f *FightC) GetCurrPET(c common.PlayerI) *info.BattlePetEntity {
if f.Our.Player.GetInfo().UserID == c.GetInfo().UserID {
return f.Our.CurrentPet
} else {
return f.Opp.CurrentPet
}
}
// 获取随机数
func (f *FightC) GetRand() *rand.Rand {
return f.rand
}
func (f *FightC) initplayer(c common.PlayerI, opp bool) {
temp := input.NewInput(f, c)
temp.AllPet = make([]*info.BattlePetEntity, 0)
temp.InitAttackValue()
for i := 0; i < len(c.GetInfo().PetList); i++ {
if f.Info.MAXPET == 0 || i < int(f.Info.MAXPET) {
temp.AllPet = append(temp.AllPet, info.CreateBattlePetEntity(&c.GetInfo().PetList[i], f.rand))
}
}
sort.Slice(temp.AllPet, func(i, j int) bool {
x, y := temp.AllPet[i], temp.AllPet[j]
// 若x血量>0且y血量=0则x排在前
if x.Info.Hp > 0 && y.Info.Hp <= 0 {
return true
}
// 若x血量=0且y血量>0则x排在后
if x.Info.Hp <= 0 && y.Info.Hp > 0 {
return false
}
// 同类型(都>0或都=0保持原有顺序
return i < j
})
if opp {
f.Opp = temp //这里是对方的
copier.Copy(&f.Info.OpponentInfo, f.Opp.Player.GetInfo())
f.Info.OpponentPetList = make([]info.ReadyFightPetInfo, len(temp.AllPet))
for i := 0; i < len(temp.AllPet); i++ {
err := copier.CopyWithOption(&f.Info.OpponentPetList[i], temp.AllPet[i].Info, copier.Option{IgnoreEmpty: true, DeepCopy: true})
if err != nil {
panic(err)
}
}
} else {
f.Our = temp
copier.Copy(&f.Info.OurInfo, f.Our.Player.GetInfo())
f.Info.OurPetList = make([]info.ReadyFightPetInfo, len(temp.AllPet))
for i := 0; i < len(temp.AllPet); i++ {
err := copier.CopyWithOption(&f.Info.OurPetList[i], &temp.AllPet[i].Info, copier.Option{IgnoreEmpty: true, DeepCopy: true})
if err != nil {
panic(err)
}
}
}
for _, v := range temp.AllPet {
if v.Info.Hp == 0 {
v.NotAlive = true
}
}
temp.CurrentPet = temp.AllPet[0]
}
// 创建新战斗,邀请方和被邀请方,或者玩家和野怪方
func NewFight(i info.EnumBattleMode, p1 common.PlayerI, p2 common.PlayerI) *FightC {
f := &FightC{}
f.ownerID = p1.GetInfo().UserID
f.Info.FightId = i //房主
switch i {
case info.BattleMode.PVP_6V6:
f.Info.MAXPET = 6
case info.BattleMode.PVP_1V1:
f.Info.MAXPET = 1
}
seed := f.StartTime.UnixNano() ^ int64(p1.GetInfo().UserID) ^ int64(p2.GetInfo().UserID) // ^ int64(f.Round) // 用异或运算混合多维度信息
f.rand = rand.New(rand.NewSource(seed))
f.Info = info.NoteReadyToFightInfo{
FightId: i,
}
f.initplayer(p1, false)
f.initplayer(p2, true)
defer func() {
rr := Fightpool.Submit(f.battleLoop)
if rr != nil {
panic(rr)
}
f.Broadcast(func(ff *input.Input) {
ff.Player.SendNoteReadyToFightInfo(f.Info)
})
}()
//go f.battleLoop() // 起战斗循环
return f
}
// 被击败的ID
func (b *FightC) IsWin(c *input.Input, cache uint32) bool {
var tt []*info.BattlePetEntity
bbb := b.Our.AllPet
if c.Player.GetInfo().UserID == b.ownerID { //如果是房主
bbb = b.Opp.AllPet
}
for _, v := range bbb {
if v.Info.CatchTime == cache {
v.NotAlive = true
}
tt = append(tt, v)
}
for _, v := range tt {
if !v.NotAlive { //如果存活
return false
}
}
return true
}
// 广播,并是否结束回合
func (f *FightC) Broadcast(t func(ff *input.Input)) {
t(f.Our)
t(f.Opp)
}
// 战斗回合循环
func (f *FightC) battleLoop() {
f.StartTime = time.Now()
f.actionChan = make(chan BattleActionI, 2) // 初始化全局操作通道
fmt.Println("战斗开始精灵", f.Our.Player.GetInfo().PetList[0].CatchTime)
//战斗开始前操作
for {
if f.closefight { //回合数超过250,战斗平局结束f.Round > 250 ||
close(f.actionChan)
break
}
f.Round++ //回合数自增
actions := make(map[uint32]BattleActionI) // 每个玩家一条记录
timeout := time.After(60 * time.Second)
for len(actions) < 2 {
select {
case action, ok := <-f.actionChan:
// 只接受有效玩家 ID\
if action == nil {
continue
}
if !ok {
fmt.Println("战斗结束")
break
}
if action.GetPlayerID() != f.Our.Player.GetInfo().UserID && action.GetPlayerID() != f.Opp.Player.GetInfo().UserID {
continue
}
if a, isExpelled := action.(*ActiveSwitchAction); isExpelled {
f.Broadcast(func(ff *input.Input) {
if ff.Player.GetInfo().UserID == a.PlayerID { //先给自身广播
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).GetAction(f.Our)
}
// 如果该玩家已经提交过,就忽略重复动作
if _, exists := actions[uint32(action.GetPlayerID())]; exists {
fmt.Printf("玩家%d 已经提交过动作,忽略重复\n", action.GetPlayerID())
continue
}
actions[uint32(action.GetPlayerID())] = action
fmt.Println("玩家 执行动作", action.GetPlayerID(), action.Priority())
case <-timeout:
if _, exists := actions[f.Our.Player.GetInfo().UserID]; !exists {
f.Over(f.Opp.Player, info.BattleOverReason.PlayerOVerTime)
}
if _, exists := actions[f.Opp.Player.GetInfo().UserID]; !exists {
f.Over(f.Opp.Player, info.BattleOverReason.PlayerOVerTime)
}
}
}
// 双方动作齐了,取出来结算
//todo 如果一方没有选择,实际上就是后端判断PP是否还有,前端是直接发的
p1Action := actions[f.Our.Player.GetInfo().UserID]
p2Action := actions[f.Opp.Player.GetInfo().UserID]
fmt.Println("开始结算回合")
// 统一赋值,减少重复代码
var BattleActionI [2]BattleActionI
BattleActionI[0], BattleActionI[1] = f.Compare(p1Action, p2Action)
switch faction := BattleActionI[0].(type) {
case *PlayerOfflineAction: //单方掉线
f.Broadcast(func(ff *input.Input) {
ff.Player.SendFightEndInfo(faction.Reason) //广播逃跑原因
})
f.closefight = true
case *EscapeAction: //优先逃跑
f.Broadcast(func(ff *input.Input) {
ff.Player.SendFightEndInfo(faction.Reason) //广播逃跑原因
})
f.closefight = true
case *ActiveSwitchAction: //切换上场的,切换方放弃出手
f.enterturn(BattleActionI[1], &SystemGiveUpAction{BaseAction: NewBaseAction(BattleActionI[0].GetPlayerID())}) //切换,相当于后手直接出手
case *UseItemAction: //使用道具
//fmt.Println(faction.ItemID)
switch {
case faction.ItemID >= 30001 && faction.ItemID <= 300010: //胶囊
//todo 将血量和技能pp传回enterturn
tt, ok := f.Our.Player.(*player.Player)
mo, ism := f.Opp.Player.(*player.AI_player)
if ok && ism && mo.CanCapture { //如果获取玩家
ok, _ := f.Our.Capture(f.Opp.CurrentPet, faction.ItemID, -1)
if ok { //todo 待补充
tt.Service.PetAdd(*f.Opp.CurrentPet.Info)
tt.CatchPetInfo(info.CatchMonsterOutboundInfo{
CatchTime: uint32(f.Opp.CurrentPet.Info.CatchTime),
PetId: uint32(f.Opp.CurrentPet.ID),
})
tt.SendFightEndInfo(info.FightOverInfo{
WinnerId: f.ownerID,
})
f.closefight = true
} else {
tt.CatchPetInfo(info.CatchMonsterOutboundInfo{})
}
} else { //说明不是可以捕捉的
tt.CatchPetInfo(info.CatchMonsterOutboundInfo{})
}
// 当 ItemID 在 30001-300010 之间时执行的逻辑
fmt.Println("ItemID 在范围内")
case faction.ItemID == 300001:
// 原来的单个值判断(如果还需要保留)
fmt.Println("ItemID 是 300001")
default:
// 其他情况
fmt.Println("ItemID 不在指定范围内")
}
f.enterturn(BattleActionI[1], &SystemGiveUpAction{BaseAction: NewBaseAction(BattleActionI[0].GetPlayerID())}) //切换,相当于后手直接出手
default: //选择技能或者放弃出手
//回合前操作,比如挂载buff
f.enterturn(BattleActionI[0], BattleActionI[1])
//回合后操作
}
}
}
// 解析并 施加effect
func (f *FightC) parseskill(attacker, defender *input.Input, id *SelectSkillAction) {
temparg := id.Skill.SideEffectArgS
for _, v := range id.Skill.SideEffectS {
t := input.GetSkillEffect(v)
args := xmlres.EffectArgs[v]
t.Effect.SetArgs(temparg[:args]...) //设置入参
temparg = temparg[args:]
if t.Effect.GetOwner() { //如果取反,说明是给对方添加的回合效果
//实际上,owner永远为反,说明是对方给我添加的
defender.AddEffect(t)
} else {
attacker.AddEffect(t)
}
}
}
func (f *FightC) initAttackers(fattack BattleActionI) {
// 伤害值
// 根据攻击方归属设置当前战斗的主/次攻击方属性
if fattack.GetPlayerID() == f.ownerID {
f.First, f.Second = f.Our, f.Opp // 攻击方为我方时,主攻击方是我方
} else {
f.First, f.Second = f.Opp, f.Our // 攻击方为对方时,主攻击方是对方
}
fmt.Println("先手", f.First.CurrentPet.Info.CatchTime, "后手", f.Second.CurrentPet.Info.CatchTime)
// TODO: 在这里调用技能结算逻辑
}
// 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *SelectSkillAction) {
// 记录技能信息
attacker.AttackValue.SkillID = uint32(a.Skill.ID) //获取技能ID
attacker.Exec(func(t input.Effect) bool { //计算命中
t.IsHit(defender, a.Skill) //相当于先调整基础命中
return attacker.AttackTime == 0 //等于0,继续处理
})
defender.Exec(func(t input.Effect) bool { //计算闪避
t.TakeHit(attacker, a.Skill)
return attacker.AttackTime > 0 //
})
attacker.AttackValue.AttackTime = a.Skill.AttackTime
if attacker.AttackValue.AttackTime > 0 { //如果命中
f.parseskill(attacker, defender, a) //命中后解析effect
attacker.Exec(func(t input.Effect) bool {
t.OnSkill(defender, a.Skill) //调用伤害计算
return true
})
attacker.Exec(func(t input.Effect) bool {
t.SkillUseEnd(defender)
return true
})
// 扣减防御方血量
} //todo 处理未命中效果
}
//回合有先手方和后手方,同时有攻击方和被攻击方
func (f *FightC) enterturn(fattack, sattack BattleActionI) {
if f.closefight { //战斗结束
return
}
f.initAttackers(fattack) //初始化先后手
var attacker, defender *input.Input
//开始回合操作
for i := 0; i < 2; i++ {
var attackeraction BattleActionI
if i == 0 { //
attacker, defender = f.First, f.Second
attackeraction = fattack
attacker.First = true //先手技能
} else {
attacker, defender = f.Second, f.First
attackeraction = sattack
attacker.First = false //先手技能
}
skill, ok := attackeraction.(*SelectSkillAction)
if !ok || attacker.CurrentPet.Info.Hp <= 0 { //还有系统选择放弃出手的
continue
}
attacker.Exec(func(t input.Effect) bool { //回合开始前
//结算状态
t.OnTurnStart(defender)
return true
})
canuseskill := attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
return t.UseSkill(defender) //返回本身结算,如果false,说明不能使用技能了
})
if canuseskill { //可以使用技能
f.processSkillAttack(attacker, defender, skill)
skill.Skill.Info.PP-- //减少PP
}
fmt.Println(i,
"玩家技能伤害:", attacker.GetDamageEffect(0),
"自身剩余血量:", attacker.CurrentPet.Info.Hp,
"对手剩余血量:", defender.CurrentPet.Info.Hp,
)
if defender.CurrentPet.Info.Hp == 0 {
defender.CanChange = true //被打死就可以切精灵了
if f.IsWin(attacker, defender.CurrentPet.Info.CatchTime) { //然后检查是否战斗结束
var WinnerId uint32
if i == 0 {
WinnerId = f.First.Player.GetInfo().UserID
} else {
WinnerId = f.Second.Player.GetInfo().UserID
}
defer f.Broadcast(func(ff *input.Input) {
//todo 将血量和技能pp传回enterturn
ff.Player.SendFightEndInfo(info.FightOverInfo{
WinnerId: WinnerId,
})
})
defer func() {
f.closefight = true
}()
}
}
}
attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
t.TurnEnd(defender) //返回本身结算,如果false,说明不能使用技能了
return true
})
defender.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
t.TurnEnd(defender) //返回本身结算,如果false,说明不能使用技能了
return true
})
ret := info.AttackValueS{
FAttack: *f.First.AttackValue,
SAttack: *f.Second.AttackValue,
}
for i := 0; i < 20; i++ { //堆叠状态剩余回合
ate, ok := attacker.GetStatusEffect(i)
if ok {
ret.FAttack.Status[i] = int8(ate.Duration())
}
dte, ok := defender.GetStatusEffect(i)
if ok {
ret.FAttack.Status[i] = int8(dte.Duration())
}
}
f.Broadcast(func(ff *input.Input) {
for _, v := range f.Switch {
if ff.Player.GetInfo().UserID != v.PlayerID {
ff.Player.SendChangePet(v.Reason)
}
}
f.Switch = []*ActiveSwitchAction{}
ff.Player.SendAttackValue(ret)
})
}