feat(fight): 优化Boss战奖励发放与经验通知逻辑

- 重构 Boss 怪物掉落物品发放代码,提高可读性与扩展性
- 注释掉宠物经验变化的通知指令(2509),暂不发送给客户端
- 修复战斗模式判断条件,从 Mode 改为 Status 判断 PVE 战斗
- 调整战斗超时逻辑,修改超时原因并增加调试日志输出
- 优化战斗结束回调执行顺序,确保广播前完成状态更新
- 重写 PetInfo.AddEV 方法,支持更安全
This commit is contained in:
2025-11-23 00:06:14 +08:00
parent 1dbd4169e9
commit 5c5e5c06ab
4 changed files with 95 additions and 27 deletions

View File

@@ -159,23 +159,21 @@ func (h Controller) OnPlayerFightNpcMonster(data *fight.FightNpcMonsterInboundIn
if foi.Reason == 0 && foi.WinnerId == c.Info.UserID { if foi.Reason == 0 && foi.WinnerId == c.Info.UserID {
if refpet.Item != 0 { if refpet.Item != 0 {
items := &info.S2C_GET_BOSS_MONSTER{}
c.SendPackCmd(8004, &info.S2C_GET_BOSS_MONSTER{ items.ItemList = c.ItemAdd(model.ItemInfo{
ItemId: refpet.Item,
ItemList: c.ItemAdd(model.ItemInfo{ ItemCnt: uint32(grand.Intn(2) + 1),
ItemId: refpet.Item,
ItemCnt: uint32(grand.Intn(2) + 1),
}),
}) })
c.SendPackCmd(8004, items)
} }
foi.Winpet.ADD_EV(gconv.Uint32s(strings.Split(xmlres.PetMAP[int(mo.ID)].YieldingEV, " "))) foi.Winpet.AddEV(gconv.Uint32s(strings.Split(xmlres.PetMAP[int(mo.ID)].YieldingEV, " ")))
exp := uint32(xmlres.PetMAP[int(mo.ID)].YieldingExp) * mo.Level / 7 exp := uint32(xmlres.PetMAP[int(mo.ID)].YieldingExp) * mo.Level / 7
c.Info.ExpPool += exp * 4 c.Info.ExpPool += exp * 4
c.AddPetExp(foi.Winpet, uint32(exp)*2) c.AddPetExp(foi.Winpet, uint32(exp)*2)
c.SendPackCmd(2509, &info.PET_WAR_EXP_NOTICE{ // c.SendPackCmd(2509, &info.PET_WAR_EXP_NOTICE{
EXP: exp * 2, // EXP: exp * 2,
}) // })
} }
return 0 return 0

View File

@@ -219,7 +219,7 @@ func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, err
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.Opp) f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.Opp)
var loadtime time.Duration = 120 * time.Second var loadtime time.Duration = 120 * time.Second
//说明是PVE //说明是PVE
if f.Info.Mode == info.BattleMode.FIGHT_WITH_NPC { if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
f.Opp.Finished = true //PVE 默认boss数据直接加载完成 f.Opp.Finished = true //PVE 默认boss数据直接加载完成
loadtime = 60 * time.Second loadtime = 60 * time.Second
@@ -237,9 +237,10 @@ func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, err
}) })
cool.Cron.AfterFunc(loadtime, func() { cool.Cron.AfterFunc(loadtime, func() {
fmt.Println(f.Our.UserID, "战斗超时结算")
if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成 if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成
f.closefight = true //阻止继续添加action f.closefight = true //阻止继续添加action
f.Reason = info.BattleOverReason.PlayerOVerTime f.Reason = info.BattleOverReason.PlayerOffline
switch { switch {
case !f.Opp.Finished: //邀请方没加载完成 先判断邀请方,如果都没加载完成,就算做房主胜利 case !f.Opp.Finished: //邀请方没加载完成 先判断邀请方,如果都没加载完成,就算做房主胜利
f.WinnerId = f.Our.Player.GetInfo().UserID f.WinnerId = f.Our.Player.GetInfo().UserID

View File

@@ -72,11 +72,14 @@ func (f *FightC) battleLoop() {
//大乱斗,给个延迟 //大乱斗,给个延迟
<-time.After(500) <-time.After(500)
} }
if f.callback != nil {
f.callback(&f.FightOverInfo) //先执行回调,再执行返回信息,在回调内修改战斗判断 })
if f.callback != nil {
} f.callback(&f.FightOverInfo) //先执行回调,再执行返回信息,在回调内修改战斗判断
}
f.Broadcast(func(ff *input.Input) {
ff.Player.SendPackCmd(2506, &f.FightOverInfo) ff.Player.SendPackCmd(2506, &f.FightOverInfo)
ff.Player.QuitFight() ff.Player.QuitFight()

View File

@@ -2,8 +2,9 @@ package model
import ( import (
"blazing/common/data/xmlres" "blazing/common/data/xmlres"
"blazing/common/utils"
"blazing/cool" "blazing/cool"
"errors"
"fmt"
"math" "math"
"math/rand" "math/rand"
"time" "time"
@@ -96,20 +97,85 @@ type PetInfo struct {
// AbilityType uint32 `struc:"skip"` //特性 // AbilityType uint32 `struc:"skip"` //特性
} }
func (pet *PetInfo) ADD_EV(evadd []uint32) { // 定义常量,提升可维护性(避免魔法数字)
var sum uint32 const (
for i := range pet.Ev { maxSingleEV uint32 = 255 // 单个EV最大值
sum += pet.Ev[i] maxTotalEV uint32 = 510 // 6个EV总和最大值
evFieldCount = 6 // EV字段数量固定6个
)
// AddEV 优化后的EV值增加方法符合Go命名规范大写导出动词开头
// 功能为宠物6个EV值增加增量保证单个≤255、总和≤510
// 参数evadd - 6个EV字段的增量数组长度必须为6
// 返回error - 参数非法/逻辑异常时返回错误bool - 是否触发了超额削减(方便业务监控)
func (pet *PetInfo) AddEV(evadd []uint32) (bool, error) {
// 1. 参数安全校验避免数组越界panic
if len(evadd) != evFieldCount {
return false, fmt.Errorf("evadd长度必须为%d当前为%d", evFieldCount, len(evadd))
}
if len(pet.Ev) != evFieldCount {
return false, errors.New("pet.Ev未初始化或长度不为6")
} }
cansum := 510 - sum // 2. 预计算当前EV总和 + 增量后的临时值(先不修改原数据,避免边改边算)
for i := 0; i < 6; i++ { var (
totalCurrent uint32 // 当前EV总和
pet.Ev[i] += evadd[i] tempEV [evFieldCount]uint32 // 增量后的临时EV值避免修改原数组导致计算错误
pet.Ev[i] = utils.Min(pet.Ev[i], 255) )
pet.Ev[i] = utils.Min(pet.Ev[i], cansum) // 一次性遍历:计算当前总和 + 初始化临时EV减少遍历次数
for i := 0; i < evFieldCount; i++ {
totalCurrent += pet.Ev[i]
// 先计算增量后的值同时限制单个不超过maxSingleEV
tempEV[i] = pet.Ev[i] + evadd[i]
if tempEV[i] > maxSingleEV {
tempEV[i] = maxSingleEV
}
} }
// 3. 计算增量后的临时总和检查是否超过maxTotalEV
totalTemp := uint32(0)
for _, v := range tempEV {
totalTemp += v
}
// 4. 若总和超额,执行削减逻辑(优先削减数值大的字段,保证公平性)
hasCut := false
if totalTemp > maxTotalEV {
overTotal := totalTemp - maxTotalEV // 需要削减的总量
hasCut = true
// 循环削减直到超额量为0优先削减数值大的字段避免小值被过度削减
for overTotal > 0 {
// 找到当前最大的EV索引
maxIdx := 0
for i := 1; i < evFieldCount; i++ {
if tempEV[i] > tempEV[maxIdx] {
maxIdx = i
}
}
// 计算该字段可削减的最大值至少留到原数值或削减到能覆盖overTotal
cutAble := tempEV[maxIdx] - pet.Ev[maxIdx] // 最多削减增量部分,不低于原值
if cutAble == 0 {
cutAble = tempEV[maxIdx] // 若增量为0可削减当前值根据业务调整也可panic
}
// 实际削减量:取可削减量和剩余超额量的较小值
cut := cutAble
if cut > overTotal {
cut = overTotal
}
// 执行削减
tempEV[maxIdx] -= cut
overTotal -= cut
}
}
// 5. 将处理后的临时值赋值给原EV批量赋值减少零散操作
copy(pet.Ev[:], tempEV[:])
return hasCut, nil
} }
func (pet *PetInfo) Cure() { func (pet *PetInfo) Cure() {
pet.Hp = pet.MaxHp pet.Hp = pet.MaxHp