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

387 lines
11 KiB
Go
Raw Normal View History

package fight
import (
"blazing/common/utils"
2025-11-15 22:17:43 +00:00
2025-09-28 08:13:42 +00:00
"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 (*FightC) processSkillAttack(attacker, defender *input.Input, skill *info.SkillEntity) {
skill.AttackTimeC(attacker.GetProp(5, true)) //计算命中
defender.Exec(func(effect input.Effect) bool { //计算闪避,然后修改对方命中),同时相当于计算属性无效这种
effect.Ctx().SkillEntity = skill
effect.SkillHit_ex()
return true
})
attacker.Exec(func(effect input.Effect) bool {
//计算变威力
effect.Ctx().SkillEntity = skill
effect.SkillHit() //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
var originalProps [2][6]int8
var originalPetInfo [2]model.PetInfo
originalProps[0], originalProps[1] = attacker.Prop, defender.Prop //先复制能力提升
originalPetInfo[0], originalPetInfo[1] = attacker.CurrentPet.Info, defender.CurrentPet.Info
attacker.Exec(func(effect input.Effect) bool {
2025-11-11 05:54:24 +00:00
//计算变威力
effect.Ctx().SkillEntity = skill
effect.CalculatePre() //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
//技能命中+效果失效 这里就是修改效果命中为false
//技能miss+效果生效 这里属于强制改命中效果,但是正常来说,技能miss掉后效果也应该失效
//技能失效+效果失效
attacker.AttackTime = skill.AttackTime
attacker.SkillID = uint32(skill.ID) //获取技能ID
if skill.AttackTime != 0 { //如果命中
attacker.CalculateCrit(defender, skill) //暴击计算
attacker.IsCritical = skill.Crit
attacker.SumDamage = attacker.CalculatePower(defender, skill)
}
attacker.Prop, defender.Prop = originalProps[0], originalProps[1]
attacker.CurrentPet.Info, defender.CurrentPet.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
}
//暴击翻倍
attacker.SumDamage = attacker.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.Exec(func(effect input.Effect) bool {
effect.Ctx().SkillEntity = skill
effect.OnSkill() //调用伤害计算
return true
})
2025-09-29 02:40:35 +08:00
defender.Damage(attacker, &info.DamageZone{
2025-11-21 05:47:51 +00:00
Damage: attacker.SumDamage,
})
}
// 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).Rand = f.rand //拷贝后随机数丢失
return originalSkill.(*info.SkillEntity)
}
// enterturn 处理战斗回合逻辑
// 回合有先手方和后手方,同时有攻击方和被攻击方
func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) {
//双方首发精灵登场时,依挑战方机制:房主方先判定,挑战方后判定
//双方非首发精灵登场时,根据切换先后判定(看手速)
// 神罗、圣华登场时魂免“登场时xx”等效果
//阿枫的效果也在这里判断
if f.closefight {
return
}
2025-11-16 20:30:17 +00:00
f.Broadcast(func(ff *input.Input) {
ff.EffectCache = make([]input.Effect, 0) //先把上一回合数据清空,但是应该把本身延续类效果集成过来
ff.EffectLost = make([]input.Effect, 0)
ff.Exec(func(effect input.Effect) bool { //回合开始前
effect.TurnStart(firstAttack, secondAttack)
2025-11-16 20:30:17 +00:00
return true
})
})
2025-09-30 18:32:15 +08:00
if firstAttack != nil { //如果首技能是空的,说明都空过了
if firstAttack.GetPlayerID() == f.ownerID {
//是否miss都应该施加解析effect
f.Our.Parseskill(firstAttack) //解析到临时数据
f.Opp.Parseskill(secondAttack) //解析到临时数据
} else {
f.Opp.Parseskill(firstAttack)
f.Our.Parseskill(secondAttack)
}
}
f.Broadcast(func(fighter *input.Input) {
fighter.Exec(func(effect input.Effect) bool { //回合开始前
effect.ComparePre(firstAttack, secondAttack) //先结算技能的优先级
2025-11-16 20:30:17 +00:00
return true
})
fighter.ResetAttackValue()
})
2025-11-16 20:30:17 +00:00
f.First, f.Second = f.Our, f.Opp
if firstAttack != nil {
if firstAttack.GetPlayerID() != f.ownerID {
f.First, f.Second = f.Opp, f.Our // 攻击方为对方时,主攻击方是对方
}
2025-09-30 18:32:15 +08:00
}
if firstAttack != nil && secondAttack != nil {
switch {
case firstAttack.SkillEntity.Priority < secondAttack.SkillEntity.Priority:
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
case firstAttack.SkillEntity.Priority == secondAttack.SkillEntity.Priority:
if f.Second.GetProp(4, false) > f.First.GetProp(4, false) {
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
if f.Second.GetProp(4, false) == f.First.GetProp(4, false) {
if grand.Meet(1, 2) { //随机出手
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
}
2025-09-30 18:32:15 +08:00
}
}
if firstAttack == nil && secondAttack == nil {
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
2025-09-30 10:40:36 +00:00
var attacker, defender *input.Input
//开始回合操作
for i := 0; i < 2; i++ {
var originalSkill *info.SkillEntity //原始技能
var currentSkill *info.SkillEntity //当前技能
if i == 0 {
2025-09-30 10:40:36 +00:00
attacker, defender = f.First, f.Second
originalSkill = f.copySkill(firstAttack)
//先手阶段,先修复后手效果
f.Second.RecoverEffect()
2025-09-30 10:40:36 +00:00
} else {
attacker, defender = f.Second, f.First
originalSkill = f.copySkill(secondAttack)
f.Second.ReactvieEffect()
}
currentSkill = originalSkill
defender.Exec(func(effect input.Effect) bool { //这个是能否使用技能
effect.Ctx().SkillEntity = currentSkill
return effect.ActionStartEx(firstAttack, secondAttack)
})
canUseSkill := attacker.Exec(func(effect input.Effect) bool { //这个是能否使用技能
effect.Ctx().SkillEntity = currentSkill
return effect.ActionStart(firstAttack, secondAttack)
})
canUse := canUseSkill && action.CanUse(currentSkill) && attacker.CurrentPet.Info.Hp > 0
if i == 0 { //先手方被控,这时候应该算做未出手状态
if canUse {
f.TrueFirst = attacker
} else {
f.TrueFirst = defender
}
}
if !canUse {
attacker.RecoverEffect()
currentSkill = nil
} else {
f.processSkillAttack(attacker, defender, currentSkill)
currentSkill = originalSkill //还原技能
_, skill, ok := utils.FindWithIndex(attacker.CurrentPet.Info.SkillList, func(item model.SkillInfo) bool {
return item.ID == currentSkill.Info.ID
})
if ok {
skill.PP--
}
}
if defender.CurrentPet.Info.Hp > 0 {
//技能使用后
defender.Exec(func(effect input.Effect) bool {
effect.Ctx().SkillEntity = currentSkill
effect.Skill_Use_ex()
return true
})
}
2025-09-29 02:40:35 +08:00
//技能使用后
attacker.Exec(func(effect input.Effect) bool { //技能使用后的我方效果
effect.Ctx().SkillEntity = currentSkill
effect.SkillUseed()
return true
})
defender.Exec(func(effect input.Effect) bool {
effect.Ctx().SkillEntity = currentSkill
effect.ActionEndEx()
return true
})
if !attacker.CurrentPet.NotAlive {
//技能使用后
attacker.Exec(func(effect input.Effect) bool { //技能使用后的我方效果
effect.Ctx().SkillEntity = currentSkill
effect.Action_end()
return true
})
}
// fmt.Println(i,
// "技能名称:", attacker.CurrentPet.Info.Name,
// "玩家技能伤害:", attacker.SumDamage,
// "自身剩余血量:", attacker.CurrentPet.Info.Hp,
// "对手剩余血量:", defender.CurrentPet.Info.Hp,
// )
if attacker.CurrentPet.Info.Hp <= 0 {
attacker.CurrentPet.NotAlive = true
f.Broadcast(func(ff *input.Input) {
ff.Exec(func(t input.Effect) bool {
t.SwitchOut(attacker)
return true
})
})
if defender.CurrentPet.Info.Hp == 0 { //先手方死亡,触发反同归于尽
defender.CurrentPet.Info.Hp = 1
}
if f.IsWin(defender) { //然后检查是否战斗结束
f.FightOverInfo.WinnerId = defender.UserID
f.closefight = true
// break
}
//attacker.CanAction = true
```text refactor(fight): 重构战斗准备逻辑并优化战斗启动流程 将 ReadyFight 方法拆分为多个职责清晰的子方法: - buildFightStartInfo: 构建战斗初始信息 - checkBothPlayersReady: 检查PVP双方是否就绪 - handleNPCFightSpecial: 处理NPC战斗特殊逻辑(如可捕捉标记) - startBattle: 统一启动战斗流程 同时修复部分逻辑顺序问题,增强代码可读性和扩展性。 feat(fight): 新增精灵王挑战协议支持 增加 StartPetWarInboundInfo 结构体用于接收精灵王挑战请求, 为后续实现相关功能提供基础。 fix(effect): 修正多个技能效果数值引用错误 - effect_37: 技能威力计算使用正确参数索引 - effect_50: 固定减伤比例调整为除以2 - effect_65: 正确比较技能分类类型 - effect_68: 致死保护改为锁定剩余1点生命值 - effect_77: 回复目标由敌方改为己方 - effect_93: 固定伤害值直接取参数 refactor(effect): 移除冗余效果类文件 删除 effect_133.go 和 effect_90.go 文件,其功能已被统一条件伤害和倍率系统取代; 移除 effect_74.go、effect_75.go 中重复的状态随机施加逻辑。 refactor(effect): 更新能力操作枚举命名一致性 重命名 AbilityOpType 枚举项名称,去除前缀,提升语义清晰度: - AbilityOpStealStrengthen → StealStrengthen - AbilityOpReverse → Reverse - AbilityOpBounceWeaken → BounceWeaken chore(fight): 完善 BattlePetEntity 属性初始化逻辑 在创建 BattlePetEntity 时即设置 PType,避免后续多次查询 PetMAP; 移除 Type() 方法中的冗余配置查找逻辑。 fix(skill): 确保必中技能不参与命中率计算 在 AttackTimeC 方法中添加 return 防止必中技能继续执行命中率公式计算。 refactor(fight): 调整战斗回合结束逻辑 进入新回合时允许玩家更换精灵,并提前跳出循环防止多余处理。 style(effect): 更正拼写及变量命名风格 修改 BaseSataus.Switch 方法签名中的参数命名; 更正 Effect58 中 can 字段首字母大写;
2025-11-14 23:09:16 +08:00
break
}
if defender.CurrentPet.Info.Hp == 0 {
if attacker.CurrentPet.Info.Hp == 0 { //先手方死亡,触发反同归于尽
attacker.CurrentPet.Info.Hp = 1
}
//defender.CanAction = true //被打死就可以切精灵了
// AI自动技能
defender.CurrentPet.NotAlive = true
f.Broadcast(func(ff *input.Input) {
ff.Exec(func(t input.Effect) bool {
t.SwitchOut(defender)
return true
})
})
if f.IsWin(attacker) { //然后检查是否战斗结束
var WinnerId uint32
if i == 0 {
WinnerId = f.First.Player.GetInfo().UserID
} else {
WinnerId = f.Second.Player.GetInfo().UserID
}
f.FightOverInfo.WinnerId = WinnerId
f.closefight = true
2025-09-07 05:58:47 +08:00
}
//break
}
}
2025-11-11 05:54:24 +00:00
f.Broadcast(func(ff *input.Input) {
ff.GenSataus()
ff.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
t.TurnEnd() //返回本身结算,如果false,说明不能使用技能了
2025-11-11 05:54:24 +00:00
return true
2025-09-29 02:40:35 +08:00
})
2025-11-11 05:54:24 +00:00
ff.GenInfo()
})
if f.TrueFirst != f.First {
f.First, f.Second = f.Second, f.First
}
attackValueResult := info.AttackValueS{
FAttack: *f.First.AttackValue,
SAttack: *f.Second.AttackValue,
}
//因为切完才能广播,所以必须和回合结束分开结算
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[uint32]*action.ActiveSwitchAction)
if f.closefight && f.Info.Mode == info.BattleMode.PET_MELEE {
return
}
f.Broadcast(func(fighter *input.Input) {
fighter.Player.SendPackCmd(2505, &attackValueResult)
fighter.CanChange = 0
})
if f.closefight {
return
}
//println("回合结束")
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
if f.Opp.CurrentPet.Info.Hp <= 0 {
f.Opp.GetAction()
//panic("AI自动技能")
}
}
}