Files
bl/logic/service/fight/fightc.go
1 35935549bd refactor(effect): 重构效果节点生命周期管理及属主控制逻辑
- 将Alive()方法改为Alive(bool)可设置方法,替代NotALive()
- 将GetOwner()改为Owner(bool)可设置方法
- 修复效果初始化时默认激活状态
- 优化效果叠层和取消逻辑
- 修正超时处理日志输出
2025-11-03 14:46:33 +00:00

590 lines
15 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/utils"
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/blazing/model"
"fmt"
"math/rand"
"reflect"
"sort"
"time"
"github.com/barkimedes/go-deepcopy"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
)
type FightC struct {
Info info.NoteReadyToFightInfo
IsReady bool
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 {
return f.First.Player == play
}
// 加载进度
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) bool {
if len(c.GetInfo().PetList) == 0 {
return false
}
temp := input.NewInput(f, c)
temp.AllPet = make([]*info.BattlePetEntity, 0)
temp.InitAttackValue()
for i := 0; i < len(c.GetInfo().PetList); i++ {
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
})
switch f.Info.Mode {
case info.BattleMode.SINGLE_MODE:
temp.AllPet = temp.AllPet[:1]
default:
}
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]
return true
}
// 创建新战斗,邀请方和被邀请方,或者玩家和野怪方
func NewFight(mode, status info.EnumBattleMode, p1 common.PlayerI, p2 common.PlayerI) *FightC {
f := &FightC{}
f.ownerID = p1.GetInfo().UserID
f.StartTime = time.Now()
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.Info.Status = status //房主
f.Info.Mode = mode
ok := f.initplayer(p1, false)
if !ok {
return nil
}
ok = f.initplayer(p2, true)
if !ok {
return nil
}
defer func() {
rr := Fightpool.Submit(f.battleLoop)
if rr != nil {
panic(rr)
}
f.Broadcast(func(ff *input.Input) {
ff.Player.SendNoteReadyToFightInfo(f.Info)
})
}()
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) processSkillAttack(attacker, defender *input.Input, a *action.SelectSkillAction) {
attacker.Exec(func(t input.Effect) bool { //计算命中 miss改命中
t.Skill_Hit_Pre(input.Ctx{ //调基础命中
Input: defender,
SelectSkillAction: a,
}) //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
a.AttackTimeC(attacker.GetProp(5, true)) //计算命中
defender.Exec(func(t input.Effect) bool { //计算闪避 ,然后修改对方命中),同时相当于计算属性无效这种
t.Skill_Hit_to(input.Ctx{ //计算命中后,我方强制改命中效果
Input: attacker,
SelectSkillAction: a,
})
return true
})
attacker.AttackValue.AttackTime = a.AttackTime //是否命中赋值
attacker.Exec(func(t input.Effect) bool { //计算命中 miss改命中
t.Skill_Hit(input.Ctx{ //计算变威力
Input: attacker,
SelectSkillAction: a,
}) //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
//技能命中+效果失效 这里就是修改效果命中为false
//技能miss+效果生效 这里属于强制改命中效果,但是正常来说,技能miss掉后效果也应该失效
//技能失效+效果失效
// 记录技能信息
attacker.AttackValue.SkillID = uint32(a.ID) //获取技能ID
if attacker.AttackValue.AttackTime > 0 { //如果命中
attacker.UseSkill(defender, a) //暴击计算
attacker.AttackValue.IsCritical = a.Crit
attacker.Exec(func(t input.Effect) bool { //计算命中 miss改命中
t.Calculate_Pre(input.Ctx{ //计算视为效果
Input: defender,
SelectSkillAction: a,
}) //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
attacker.DamageZone.Damage = attacker.CalculatePower(defender, a.SkillEntity)
//睡眠受击消除
if a.SkillEntity.Category() != info.Category.STATUS {
t := defender.GetEffect(input.EffectType.Status, int(info.PetStatus.Sleep))
if t != nil {
t.Alive(false)
}
}
if attacker.AttackValue.IsCritical == 1 {
//暴击破防
if a.SkillEntity.Category() == info.Category.PHYSICAL {
defender.Prop[1] = 0
} else if a.SkillEntity.Category() == info.Category.SPECIAL {
defender.Prop[3] = 0
}
//暴击翻倍
attacker.DamageZone.Damage = attacker.DamageZone.Damage.Mul(decimal.NewFromInt(2))
}
}
for _, e := range attacker.EffectCache {
//这里实现应该参考本地技能是否命中,然后
e.Hit(a.AttackTime != 0) //我方效果命中
}
for _, t := range defender.EffectCache {
if t.GetInput() == attacker { //如果取反,说明是给对方添加的回合效果
t.Hit(a.AttackTime != 0)
}
}
// 扣减防御方血量
attacker.Exec(func(t input.Effect) bool {
t.OnSkill(input.Ctx{
Input: defender,
SelectSkillAction: a,
}) //调用伤害计算
return true
})
defender.Damage(input.Ctx{
Input: attacker,
SelectSkillAction: a,
DamageZone: &info.DamageZone{
Type: info.DamageType.Red,
Damage: attacker.DamageZone.Damage,
},
})
//这里其实是受到致死伤害
//然后先触发死亡效果消除所有buff
//然后触发回神效果
}
func IsNil(x interface{}) bool {
if x == nil {
return true
}
rv := reflect.ValueOf(x)
return rv.Kind() == reflect.Ptr && rv.IsNil()
}
func copyskill(t *action.SelectSkillAction) *action.SelectSkillAction {
oldskill, _ := deepcopy.Anything(t) //备份技能
return oldskill.(*action.SelectSkillAction)
}
//回合有先手方和后手方,同时有攻击方和被攻击方
func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
if f.closefight { //战斗结束
return
}
// 伤害值
// 根据攻击方归属设置当前战斗的主/次攻击方属性
if fattack != nil {
if fattack.GetPlayerID() == f.ownerID {
f.First, f.Second = f.Our, f.Opp // 攻击方为我方时,主攻击方是我方
} else {
f.First, f.Second = f.Opp, f.Our // 攻击方为对方时,主攻击方是对方
}
} else {
f.First, f.Second = f.Our, f.Opp
}
f.First.ResetAttackValue()
f.Second.ResetAttackValue()
switch {
case sattack != nil:
f.Second.Parseskill(f.Second, sattack) //解析到临时数据
f.Second.Exec(func(t input.Effect) bool { //回合开始前
//结算状态
t.Compare_Pre(fattack, sattack) //先结算技能的优先级
return true
})
switch {
case fattack != nil: //房主也放弃出手
//是否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.SkillEntity.Priority < sattack.SkillEntity.Priority:
fattack, sattack = sattack, fattack //互换先手权
f.First, f.Second = f.Second, f.First
case fattack.SkillEntity.Priority == sattack.SkillEntity.Priority:
if f.Second.GetProp(4, false) > f.First.GetProp(4, false) {
fattack, sattack = sattack, fattack //互换先手权
f.First, f.Second = f.Second, f.First
}
}
default: //房主放弃出手
fattack, sattack = sattack, fattack //互换先手权
f.First, f.Second = f.Second, f.First
}
}
var attacker, defender *input.Input
f.First.Exec(func(t input.Effect) bool { //回合开始前
//结算状态
t.Turn_Start(input.Ctx{Input: f.First})
return true
})
f.Second.Exec(func(t input.Effect) bool { //回合开始前
//结算状态
t.Turn_Start(input.Ctx{Input: f.Second})
return true
})
//开始回合操作
for i := 0; i < 2; i++ {
var oldskill *action.SelectSkillAction //原始技能
var currentskill *action.SelectSkillAction //当前技能
if i == 0 { //
attacker, defender = f.First, f.Second
oldskill = copyskill(fattack)
currentskill = fattack
} else {
attacker, defender = f.Second, f.First
oldskill = copyskill(sattack)
currentskill = sattack
}
canuseskill := true
// 实际上攻击方 还有系统选择放弃出手的
if IsNil(currentskill) || attacker.CurrentPet.Info.Hp <= 0 {
// attacker.AttackValue.SkillID = 0
canuseskill = false
} else {
if !action.CanUse(currentskill) {
// attacker.AttackValue.SkillID = 0
canuseskill = false
}
}
canuseskillok := attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
//然后这里还可以处理自爆类
return t.Skill_Hit_Pre(input.Ctx{
Input: defender,
SelectSkillAction: currentskill,
}) //返回本身结算,如果false,说明不能使用技能了
})
if canuseskill && canuseskillok { //可以使用技能
f.processSkillAttack(attacker, defender, currentskill)
currentskill = oldskill
_, skill, ok := utils.FindWithIndex(attacker.CurrentPet.Info.SkillList, func(item model.SkillInfo) bool {
return item.ID == currentskill.Info.ID
})
if ok {
skill.PP--
}
}
//技能使用后
defender.Exec(func(t input.Effect) bool {
t.Skill_Use(input.Ctx{Input: attacker})
return true
})
//技能使用后
attacker.Exec(func(t input.Effect) bool { //技能使用后的我方效果
t.Skill_Useed(input.Ctx{Input: defender, SelectSkillAction: currentskill})
return true
})
fmt.Println(i,
// "玩家技能:", oldskill.(*info.SkillEntity).ID,
"玩家技能伤害:", attacker.DamageZone.Damage,
"自身剩余血量:", attacker.CurrentPet.Info.Hp,
"对手剩余血量:", defender.CurrentPet.Info.Hp,
)
if defender.CurrentPet.Info.Hp == 0 {
// defender.AttackValue.SkillID = 0
//todo 解耦成战斗循环defer
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,
})
})
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.First.AttackValue.SkillList = f.First.CurrentPet.Info.SkillList
f.Second.AttackValue.SkillList = f.Second.CurrentPet.Info.SkillList
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 != nil { //状态都是叠层类的
ret.FAttack.Status[i] = int8(t.Duration())
}
t = f.Second.GetEffect(input.EffectType.Status, i)
if t != nil {
ret.SAttack.Status[i] = int8(t.Duration())
}
}
f.Broadcast(func(ff *input.Input) {
for _, v := range f.Switch {
if ff.Player.GetInfo().UserID != v.PlayerID {
ff.Player.SendChangePet(v.Reason)
}
}
ff.Player.SendAttackValue(ret)
})
f.Switch = []*action.ActiveSwitchAction{}
}