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

664 lines
17 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/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/player"
"fmt"
"math/rand"
"sort"
"time"
"github.com/barkimedes/go-deepcopy"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
)
type FightC struct {
Info info.NoteReadyToFightInfo
ownerID uint32 // 战斗发起者ID
Our *input.Input //始终等于房主ID
Opp *input.Input //对手ID
Switch []*action.ActiveSwitchAction
rand *rand.Rand
StartTime time.Time
actionChan chan action.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 action.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) IsFirst(play common.PlayerI) bool {
if f.First.Player == play {
return true
}
return false
}
// 加载进度
func (f *FightC) LoadPercent(c common.PlayerI, percent int32) {
f.GetInputByPlayer(c, true).Player.SendLoadPercent(info.LoadPercentOutboundInfo{
Id: c.GetInfo().UserID,
Percent: uint32(percent),
})
}
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(mode, status info.EnumBattleMode, p1 common.PlayerI, p2 common.PlayerI) *FightC {
f := &FightC{}
f.ownerID = p1.GetInfo().UserID
f.Info.Status = status //房主
f.Info.Mode = mode
switch mode {
case info.BattleMode.SINGLE_MODE:
f.Info.MAXPET = 1
default:
}
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{
Status: status,
}
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 action.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]action.BattleActionI) // 每个玩家一条记录
timeout := time.After(60 * time.Second)
for len(actions) < 2 {
select {
case paction, ok := <-f.actionChan:
// 只接受有效玩家 ID\
if paction == nil {
continue
}
if !ok {
fmt.Println("战斗结束")
break
}
if paction.GetPlayerID() != f.Our.Player.GetInfo().UserID && paction.GetPlayerID() != f.Opp.Player.GetInfo().UserID {
continue
}
if a, isExpelled := paction.(*action.ActiveSwitchAction); isExpelled {
f.Broadcast(func(ff *input.Input) {
if ff.Player.GetInfo().UserID == a.PlayerID { //先给自身广播
ff.Player.SendChangePet(a.Reason)
}
})
if f.GetInputByAction(paction, false).CanChange {
//如果是被动切换,不计入回合结算
f.GetInputByAction(paction, false).CanChange = false
continue
}
}
if paction.GetPlayerID() != 0 && f.Info.Status == 3 {
//AI的action实质上就是放技能如果阻止掉比如中毒那就也不能逃跑
f.GetInputByAction(paction, true).GetAction(f.Our)
}
// 如果该玩家已经提交过,就忽略重复动作
if _, exists := actions[uint32(paction.GetPlayerID())]; exists {
fmt.Printf("玩家%d 已经提交过动作,忽略重复\n", paction.GetPlayerID())
continue
}
actions[uint32(paction.GetPlayerID())] = paction
fmt.Println("玩家 执行动作", paction.GetPlayerID(), paction.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]action.BattleActionI
BattleActionI[0], BattleActionI[1] = f.Compare(p1Action, p2Action)
switch faction := BattleActionI[0].(type) {
case *action.PlayerOfflineAction: //单方掉线
f.Broadcast(func(ff *input.Input) {
ff.Player.SendFightEndInfo(faction.Reason) //广播逃跑原因
})
f.closefight = true
case *action.EscapeAction: //优先逃跑
f.Broadcast(func(ff *input.Input) {
ff.Player.SendFightEndInfo(faction.Reason) //广播逃跑原因
})
f.closefight = true
case *action.ActiveSwitchAction: //切换上场的,切换方放弃出手
f.enterturn(BattleActionI[1].(*action.SelectSkillAction),
&action.SelectSkillAction{BaseAction: action.NewBaseAction(BattleActionI[0].GetPlayerID())}) //切换,相当于后手直接出手
case *action.UseItemAction: //使用道具
//fmt.Println(faction.ItemID)
switch {
case faction.ItemID >= 30001 && faction.ItemID <= 300010: //胶囊
tt, ok := f.Our.Player.(*player.Player)
mo, ism := f.Opp.Player.(*player.AI_player)
if ok && ism && mo.CanCapture { //如果获取玩家
ok, res := f.Our.Capture(f.Opp.CurrentPet, faction.ItemID, -1)
if ok { //todo 待补充
fmt.Println(res)
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].(*action.SelectSkillAction), &action.SelectSkillAction{BaseAction: action.NewBaseAction(BattleActionI[0].GetPlayerID())}) //切换,相当于后手直接出手
default: //选择技能或者放弃出手
//回合前操作,比如挂载buff
f.enterturn(BattleActionI[0].(*action.SelectSkillAction), BattleActionI[1].(*action.SelectSkillAction))
//回合后操作
}
}
}
// 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *action.SelectSkillAction) {
a.Skill.AttackTimeC(attacker.GetProp(5, true)) //计算命中
defender.Exec(func(t input.Effect) bool { //计算闪避 ,然后修改对方命中),同时相当于计算属性无效这种
t.Skill_Hit_to(input.Ctx{
Input: attacker,
SkillEntity: a.Skill, //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
})
return true
})
attacker.Exec(func(t input.Effect) bool { //计算命中 miss改命中
t.Skill_Hit(input.Ctx{ //计算变威力
Input: attacker,
SkillEntity: a.Skill,
}) //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
a.Skill.AttackTimeC(attacker.GetProp(5, true)) //计算命中
defender.Exec(func(t input.Effect) bool { //计算闪避 ,然后修改对方命中),同时相当于计算属性无效这种
t.Skill_Hit_to(input.Ctx{ //计算命中后,我方强制改命中效果
Input: attacker,
SkillEntity: a.Skill,
})
return true
})
f.parseskill(attacker, defender, a) //是否miss都应该施加解析effect
// 记录技能信息
attacker.AttackValue.SkillID = uint32(a.Skill.ID) //获取技能ID
attacker.UseSkill(defender, a.Skill) //攻击方计算技能使用
attacker.AttackValue.AttackTime = a.Skill.AttackTime
if attacker.AttackValue.AttackTime > 0 { //如果命中
attacker.DamageZone.Damage = attacker.CalculatePower(defender, a.Skill)
attacker.AttackValue.IsCritical = a.Skill.Crit
if attacker.AttackValue.IsCritical == 1 {
//暴击翻倍
attacker.DamageZone.Damage = attacker.DamageZone.Damage.Mul(decimal.NewFromInt(2))
}
attacker.Exec(func(t input.Effect) bool {
//这里实现应该参考本地技能是否命中,然后
t.Hit(true) //我方效果命中
return true
})
defender.Exec(func(t input.Effect) bool {
if t.GetInput() == attacker { //如果取反,说明是给对方添加的回合效果
t.Hit(true)
}
return true
})
// 扣减防御方血量
}
attacker.Exec(func(t input.Effect) bool {
t.OnSkill(input.Ctx{
Input: defender,
SkillEntity: a.Skill,
}) //调用伤害计算
return true
})
defender.Damage(input.Ctx{
Input: attacker,
SkillEntity: a.Skill,
DamageZone: &info.DamageZone{
Type: info.DamageType.Red,
Damage: attacker.DamageZone.Damage,
},
})
//这里其实是受到致死伤害
//然后先触发死亡效果消除所有buff
//然后触发回神效果
}
//回合有先手方和后手方,同时有攻击方和被攻击方
func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
if f.closefight { //战斗结束
return
}
// 伤害值
// 根据攻击方归属设置当前战斗的主/次攻击方属性
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)
//是否miss都应该施加解析effect
f.First.Parseskill(f.Second, fattack)
f.First.Exec(func(t input.Effect) bool { //回合开始前
//结算状态
t.Compare_Pre(fattack, sattack) //先结算技能的优先级
return true
})
switch {
case fattack.Skill.Priority < sattack.Skill.Priority:
fattack, sattack = sattack, fattack //互换先手权
case fattack.Skill.Priority == sattack.Skill.Priority:
if f.Second.GetProp(4, false) > f.First.GetProp(4, false) {
fattack, sattack = sattack, fattack //互换先手权
}
}
var attacker, defender *input.Input
//开始回合操作
for i := 0; i < 2; i++ {
if i == 0 { //
attacker, defender = f.First, f.Second
} else {
attacker, defender = f.Second, f.First
}
attacker.Exec(func(t input.Effect) bool { //回合开始前
//结算状态
t.Turn_Start(input.Ctx{Input: attacker})
return true
})
canuseskill := true
// 实际上攻击方 还有系统选择放弃出手的
if fattack.Skill == nil || attacker.CurrentPet.Info.Hp <= 0 {
attacker.AttackValue.SkillID = 0
canuseskill = false
}
var oldskill interface{}
if ok {
oldskill, _ = deepcopy.Anything(skill.Skill) //备份技能
canuseskillok := attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
//然后这里还可以处理自爆类
return t.Skill_Hit_Pre(input.Ctx{
Input: attacker,
SkillEntity: skill.Skill,
}) //返回本身结算,如果false,说明不能使用技能了
})
if !canuseskill || !canuseskillok { //可以使用技能
f.processSkillAttack(attacker, defender, skill)
skill.Skill = oldskill.(*info.SkillEntity) //还原技能效果
}
skill.Skill.Info.PP-- //减少PP
}
//技能使用后
defender.Exec(func(t input.Effect) bool {
t.Skill_Use(input.Ctx{Input: attacker})
return true
})
fmt.Println(i,
"玩家技能伤害:", attacker.DamageZone.Damage,
"自身剩余血量:", attacker.CurrentPet.Info.Hp,
"对手剩余血量:", defender.CurrentPet.Info.Hp,
)
if defender.CurrentPet.Info.Hp == 0 {
defender.AttackValue.SkillID = 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
}()
}
}
}
f.First.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
t.Turn_End(input.Ctx{
Input: f.Second,
}) //返回本身结算,如果false,说明不能使用技能了
return true
})
f.Second.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
t.Turn_End(input.Ctx{
Input: f.First,
})
return true
})
f.First.AttackValue.RemainHp = int32(f.First.CurrentPet.Info.Hp)
f.Second.AttackValue.RemainHp = int32(f.Second.CurrentPet.Info.Hp)
ret := info.AttackValueS{
FAttack: *f.First.AttackValue,
SAttack: *f.Second.AttackValue,
}
for i := 0; i < 20; i++ { //堆叠状态剩余回合
t := f.First.GetEffect(input.EffectType.Status, i)
if t.ID != 0 {
ret.FAttack.Status[i] = int8(t.Effect.Duration())
}
t = f.Second.GetEffect(input.EffectType.Status, i)
if t.ID != 0 {
ret.FAttack.Status[i] = int8(t.Effect.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 = []*action.ActiveSwitchAction{}
ff.Player.SendAttackValue(ret)
})
}