Files
bl/logic/service/fight/fightc.go
昔念 0051ac0be8
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat(fight): 添加旧组队协议支持并优化战斗系统

- 实现了旧组队协议相关功能,包括GroupReadyFightFinish、GroupUseSkill、
  GroupUseItem、GroupChangePet和GroupEscape方法
- 新增组队战斗相关的入站信息结构体定义
- 实现了组队BOSS战斗逻辑,添加groupBossSlotLimit常量
- 重构宠物技能设置逻辑,调整金币消耗时机
- 优化战斗循环逻辑,添加对无行动槽位的处理
- 改进AI行动逻辑,增加多位置目标选择
2026-04-08 01:28:55 +08:00

485 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/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/player/model"
"reflect"
"github.com/alpacahq/alpacadecimal"
"github.com/barkimedes/go-deepcopy"
"github.com/gogf/gf/v2/util/grand"
)
// processSkillAttack 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *info.SkillEntity) {
if attacker == nil || defender == nil || skill == nil {
return
}
skill.AttackTimeC(attacker.Prop[5]) //计算命中
defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //计算闪避,然后修改对方命中),同时相当于计算属性无效这种
f.setEffectSkillContext(effect, skill, defender)
effect.SkillHit_ex()
return true
})
attacker.ExecWithOpponent(defender, func(effect input.Effect) bool {
//计算变威力
f.setEffectSkillContext(effect, skill, defender)
effect.SkillHit() //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
var originalProps [2][6]int8
var originalPetInfo [2]model.PetInfo
attackerPet := attacker.CurrentPet()
defenderPet := defender.CurrentPet()
if attackerPet == nil || defenderPet == nil {
return
}
//复制属性
originalProps[0], originalProps[1] = attacker.Prop, defender.Prop
originalPetInfo[0], originalPetInfo[1] = attackerPet.Info, defenderPet.Info
attacker.ExecWithOpponent(defender, func(effect input.Effect) bool {
//计算变威力
f.setEffectSkillContext(effect, skill, defender)
effect.CalculatePre() //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
//技能命中+效果失效 这里就是修改效果命中为false
//技能miss+效果生效 这里属于强制改命中效果,但是正常来说,技能miss掉后效果也应该失效
//技能失效+效果失效
attacker.AttackTime = skill.AttackTime
attacker.SkillID = uint32(skill.XML.ID) //获取技能ID
var SumDamage alpacadecimal.Decimal
if skill.AttackTime != 0 { //如果命中
SumDamage = attacker.CalculatePower(defender, skill)
attacker.CalculateCrit(defender, skill) //暴击计算
attacker.IsCritical = skill.Crit
}
//还原属性
attacker.Prop, defender.Prop = originalProps[0], originalProps[1]
attackerPet.Info, defenderPet.Info = originalPetInfo[0], originalPetInfo[1]
if attacker.IsCritical == 1 { //命中了才有暴击
//暴击破防
if skill.Category() == info.Category.PHYSICAL && defender.Prop[1] > 0 {
defender.Prop[1] = 0
} else if skill.Category() == info.Category.SPECIAL && defender.Prop[3] > 0 {
defender.Prop[3] = 0
}
//暴击翻倍
SumDamage = SumDamage.Mul(alpacadecimal.NewFromInt(2))
}
//到这里已经是强制miss或者命中,所以根本不存在强制miss改命中的情况,因为miss的时候不会执行到这里
if !skill.Hit {
for _, effect := range attacker.EffectCache {
effect.Alive(false) //我方效果命中
}
//这时候将被覆盖的效果全部装回来enterturn
for _, effect := range attacker.EffectLost {
if effect.Duration() > 0 || effect.Duration() == -1 {
//effect.Alive(true)
attacker.AddEffect(effect.GetInput(), effect)
}
}
}
// 扣减防御方血量
attacker.ExecWithOpponent(defender, func(effect input.Effect) bool {
f.setEffectSkillContext(effect, skill, defender)
effect.OnSkill() //调用伤害计算
return true
})
defender.Damage(attacker, &info.DamageZone{
Damage: SumDamage,
Type: info.DamageType.Red,
})
}
// IsNil 检查值是否为nil
func IsNil(x interface{}) bool {
if x == nil {
return true
}
rv := reflect.ValueOf(x)
return rv.Kind() == reflect.Ptr && rv.IsNil()
}
// copySkill 复制技能实体
func (f *FightC) copySkill(action *action.SelectSkillAction) *info.SkillEntity {
if action == nil {
return nil
}
if action.SkillEntity == nil {
return nil
}
originalSkill, _ := deepcopy.Anything(action.SkillEntity) //备份技能
originalSkill.(*info.SkillEntity).Accuracy = action.SkillEntity.Accuracy //拷贝后命中丢失
return originalSkill.(*info.SkillEntity)
}
func (f *FightC) getSkillParticipants(skillAction *action.SelectSkillAction) (*input.Input, *input.Input) {
if skillAction == nil {
return nil, nil
}
return f.GetInputByAction(skillAction, false), f.GetInputByAction(skillAction, true)
}
// setEffectSkillContext 统一设置技能阶段 effect 上下文。
// 规则:
// 1) Our/Opp 由 ExecWithOpponent 负责写入carrier / 对位)。
// 2) Target 统一表示“本次动作被选中的目标位”,在攻防双方 hook 中保持一致。
func (f *FightC) setEffectSkillContext(effect input.Effect, skill *info.SkillEntity, target *input.Input) {
if effect == nil {
return
}
ctx := effect.Ctx()
ctx.SkillEntity = skill
if target != nil {
ctx.Target = target
}
}
func (f *FightC) setEffectTarget(effect input.Effect, target *input.Input) {
if effect == nil || target == nil {
return
}
effect.Ctx().Target = target
}
// plannedOpponentForCarrier 为旧 effect 提供“当前动作对应目标”上下文:
// 1) 若 carrier 是本回合出手方,返回其动作目标。
// 2) 若无匹配动作,返回 nil交给 Input 默认 OppTeam 回退。
func (f *FightC) plannedOpponentForCarrier(carrier *input.Input, acts ...*action.SelectSkillAction) *input.Input {
if carrier == nil {
return nil
}
for _, act := range acts {
if act == nil {
continue
}
attacker, defender := f.getSkillParticipants(act)
if attacker == carrier {
return defender
}
}
return nil
}
func (f *FightC) collectAttackValues(inputs []*input.Input) []model.AttackValue {
values := make([]model.AttackValue, 0, len(inputs))
for actorIndex, fighter := range inputs {
if fighter == nil || fighter.AttackValue == nil {
continue
}
attackValue := *fighter.AttackValue
attackValue.ActorIndex = uint32(actorIndex)
values = append(values, attackValue)
}
return values
}
func (f *FightC) buildNoteUseSkillOutboundInfo() info.NoteUseSkillOutboundInfo {
result := info.NoteUseSkillOutboundInfo{}
result.FirstAttackInfo = append(result.FirstAttackInfo, f.collectAttackValues(f.Our)...)
result.SecondAttackInfo = append(result.SecondAttackInfo, f.collectAttackValues(f.Opp)...)
return result
}
// enterturn 处理战斗回合逻辑
// 回合有先手方和后手方,同时有攻击方和被攻击方
func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) {
//双方首发精灵登场时,依挑战方机制:房主方先判定,挑战方后判定
//双方非首发精灵登场时,根据切换先后判定(看手速)
// 神罗、圣华登场时魂免“登场时xx”等效果
//阿枫的效果也在这里判断
if f.closefight {
return
}
f.Broadcast(func(ff *input.Input) {
ff.EffectCache = make([]input.Effect, 0) //先把上一回合数据清空,但是应该把本身延续类效果集成过来
ff.EffectLost = make([]input.Effect, 0)
opponent := f.plannedOpponentForCarrier(ff, firstAttack, secondAttack)
ff.ExecWithOpponent(opponent, func(effect input.Effect) bool { //回合开始前
f.setEffectTarget(effect, opponent)
effect.TurnStart(firstAttack, secondAttack)
return true
})
})
for _, skillAction := range []*action.SelectSkillAction{firstAttack, secondAttack} {
attackerInput, _ := f.getSkillParticipants(skillAction)
if attackerInput == nil {
continue
}
f.setActionAttackValue(skillAction)
attackerInput.Parseskill(skillAction)
}
f.Broadcast(func(fighter *input.Input) {
opponent := f.plannedOpponentForCarrier(fighter, firstAttack, secondAttack)
fighter.ExecWithOpponent(opponent, func(effect input.Effect) bool { //回合开始前
f.setEffectTarget(effect, opponent)
effect.ComparePre(firstAttack, secondAttack) //先结算技能的优先级
return true
})
fighter.ResetAttackValue()
})
f.First, f.Second = f.primaryOur(), f.primaryOpp()
switch {
case firstAttack != nil && secondAttack != nil:
f.First, _ = f.getSkillParticipants(firstAttack)
f.Second, _ = f.getSkillParticipants(secondAttack)
case firstAttack != nil:
f.First, f.Second = f.getSkillParticipants(firstAttack)
case secondAttack != nil:
f.First, f.Second = f.getSkillParticipants(secondAttack)
}
if f.First == nil {
f.First = f.primaryOur()
}
if f.Second == nil {
f.Second = f.primaryOpp()
}
if firstAttack != nil && secondAttack != nil {
switch {
case firstAttack.SkillEntity.XML.Priority < secondAttack.SkillEntity.XML.Priority:
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
case firstAttack.SkillEntity.XML.Priority == secondAttack.SkillEntity.XML.Priority:
if f.Second.GetProp(4).Cmp(f.First.GetProp(4)) > 0 {
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
if f.Second.GetProp(4).Cmp(f.First.GetProp(4)) == 0 {
if grand.Meet(1, 2) { //随机出手
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
}
}
}
if firstAttack == nil && secondAttack == nil {
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
var attacker, defender *input.Input
f.TrueFirst = f.First
//开始回合操作
for i := 0; i < 2; i++ {
var originalSkill *info.SkillEntity //原始技能
var currentSkill *info.SkillEntity //当前技能
var currentAction *action.SelectSkillAction
if i == 0 {
currentAction = firstAttack
attacker, defender = f.getSkillParticipants(firstAttack)
originalSkill = f.copySkill(firstAttack)
//先手阶段,先修复后手效果
f.Second.RecoverEffect()
} else {
currentAction = secondAttack
attacker, defender = f.getSkillParticipants(secondAttack)
originalSkill = f.copySkill(secondAttack)
//取消后手历史效果
f.Second.ReactvieEffect()
}
if attacker == nil {
attacker = f.First
}
if defender == nil {
defender = f.Second
}
currentSkill = originalSkill
defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //这个是能否使用技能
f.setEffectSkillContext(effect, currentSkill, defender)
return effect.ActionStartEx(firstAttack, secondAttack)
})
canUseSkill := attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //这个是能否使用技能
f.setEffectSkillContext(effect, currentSkill, defender)
return effect.ActionStart(firstAttack, secondAttack)
})
attackerPet := attacker.CurrentPet()
defenderPet := defender.CurrentPet()
canUse := canUseSkill && action.CanUse(currentSkill) && attacker != nil && attackerPet != nil && attackerPet.Info.Hp > 0
if !canUse {
attacker.RecoverEffect()
currentSkill = nil
if i == 0 { //先手方被控,这时候应该算做未出手状态
f.TrueFirst = defender
for _, effect := range defender.EffectCache {
effect.IsFirst(true)
}
}
//先手权不一定出手
} else {
f.setActionAttackValue(currentAction)
for _, effect := range attacker.EffectCache {
effect.IsFirst(true)
}
f.processSkillAttack(attacker, defender, currentSkill)
currentSkill = originalSkill //还原技能
_, skill, ok := utils.FindWithIndex(attackerPet.Info.SkillList, func(item model.SkillInfo) bool {
return item.ID == currentSkill.Info.ID
})
if ok {
usecount := 1
attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //技能使用后的我方效果
f.setEffectSkillContext(effect, currentSkill, defender)
effect.HookPP(&usecount)
return true
})
skill.Use(usecount)
}
}
if defenderPet != nil && defenderPet.Info.Hp > 0 {
//技能使用后
defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //技能使用后的我方效果
f.setEffectSkillContext(effect, currentSkill, defender)
effect.Skill_Use_ex()
return true
})
}
if attackerPet != nil && attackerPet.Info.Hp > 0 {
//技能使用后
attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //技能使用后的我方效果
f.setEffectSkillContext(effect, currentSkill, defender)
effect.Skill_Use()
return true
})
}
//技能使用后
defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //技能使用后的我方效果
f.setEffectSkillContext(effect, currentSkill, defender)
effect.Action_end_ex()
return true
})
//技能使用后
attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //技能使用后的我方效果
f.setEffectSkillContext(effect, currentSkill, defender)
effect.Action_end()
return true
})
if defenderPet != nil && attackerPet != nil && defenderPet.Info.Hp <= 0 && attackerPet.Info.Hp <= 0 { //先手方死亡,触发反同归于尽
attackerPet.Info.Hp = 1
}
if defenderPet != nil && defenderPet.Info.Hp <= 0 {
f.TURNOVER(defender)
break
}
if attackerPet != nil && attackerPet.Info.Hp <= 0 {
f.TURNOVER(attacker)
break
}
}
f.Broadcast(func(ff *input.Input) {
ff.GenSataus()
ff.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
t.TurnEnd() //返回本身结算,如果false,说明不能使用技能了
return true
})
ff.GenInfo()
ff.SnapshotTurnProp()
})
if f.TrueFirst != f.First {
f.First, f.Second = f.Second, f.First
}
if f.LegacyGroupProtocol {
f.sendLegacyRoundBroadcast(firstAttack, secondAttack)
}
attackValueResult := f.buildNoteUseSkillOutboundInfo()
//因为切完才能广播,所以必须和回合结束分开结算
f.Broadcast(func(fighter *input.Input) {
for _, switchAction := range f.Switch {
if fighter.Player.GetInfo().UserID != switchAction.Reason.UserId {
// println("切精灵", switchAction.Reason.UserId, switchAction.Reason.ID)
fighter.Player.SendPackCmd(2407, &switchAction.Reason)
}
}
})
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
if f.closefight && f.Info.Mode == info.BattleMode.PET_MELEE {
// f.Broadcast(func(fighter *input.Input) {
// if fighter.UserID != f.WinnerId {
// fighter.Player.SendPackCmd(2505, &attackValueResult)
// }
// })
return
}
f.Broadcast(func(fighter *input.Input) {
fighter.Player.SendPackCmd(2505, &attackValueResult)
fighter.CanChange = 0
})
if f.closefight {
return
}
}
func (f *FightC) TURNOVER(cur *input.Input) {
var _hasBackup bool
if cur == nil {
return
}
for _, pet := range cur.BenchPets() {
if pet != nil && pet.Info.Hp > 0 {
_hasBackup = true
break
}
}
f.sendLegacySpriteDie(cur, _hasBackup)
f.Broadcast(func(ff *input.Input) {
ff.Exec(func(t input.Effect) bool {
t.SwitchOut(cur)
return true
})
})
if f.IsWin(f.GetInputByPlayer(cur.Player, true)) { //然后检查是否战斗结束
f.FightOverInfo.WinnerId = f.GetInputByPlayer(cur.Player, true).UserID
f.FightOverInfo.Reason = model.BattleOverReason.DefaultEnd
f.WinnerId = f.FightOverInfo.WinnerId
f.Reason = model.BattleOverReason.DefaultEnd
f.closefight = true
// break
}
}