feat(item): 出售物品后减少背包中对应物品数量

在处理物品出售逻辑时,增加调用 SubItem 方法以正确扣减玩家背包中的物品数量。

feat(map): 玩家离开地图时重置 Canmon 标志位

通过 atomic.StoreUint32 将玩家的 Canmon 状态设置为 0,确保线程安全。

fix(pet): 调整宠物经验增加逻辑并修复技能学习问题

重构 AddPetExp 方法逻辑,优化升级流程、技能学习机制,并修正经验显示
This commit is contained in:
2025-12-13 21:47:07 +08:00
parent 2ab7f59667
commit fe89620efb
11 changed files with 96 additions and 45 deletions

View File

@@ -0,0 +1,30 @@
package controller
import (
"blazing/common/socket/errorcode"
"blazing/common/utils"
"blazing/logic/service/fight"
"blazing/logic/service/player"
"sync/atomic"
)
func (h Controller) FRESH_CHOICE_FIGHT_LEVEL(data *fight.C2S_FRESH_CHOICE_FIGHT_LEVEL, c *player.Player) (result *fight.S2C_FreshChoiceLevelRequestInfo, err errorcode.ErrorCode) {
result = &fight.S2C_FreshChoiceLevelRequestInfo{}
switch data.Head.CMD {
case 2428: //试炼之塔
c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1)
result.CurFightLevel = uint(c.Info.CurrentFreshStage)
case 2414: //勇者之塔
c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1)
result.CurFightLevel = uint(c.Info.CurrentStage)
}
result.BossId = []uint32{10}
atomic.StoreUint32(&c.Canmon, 0)
return result, 0
}
func (h Controller) FRESH_LEAVE_FIGHT_LEVEL(data *fight.FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
return result, 0
}

View File

@@ -17,5 +17,6 @@ func (h Controller) ITEM_SALE(data *item.C2S_ITEM_SALE, c *player.Player) (resul
c.Info.Coins += uint32(int64(data.Amount) * int64(xmlres.ItemsMAP[int(data.ItemId)].SellPrice))
}
c.Service.Item.SubItem(data.ItemId, data.Amount)
return result, 0
}

View File

@@ -37,7 +37,7 @@ func (h Controller) MapHot(data *maphot.InInfo, c *player.Player) (result *mapho
return
}
func (h *Controller) MapLeave(data *space.LeaveMapInboundInfo, c *player.Player) (result *info.LeaveMapOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
atomic.StoreUint32(&c.Canmon, 0)
//data.Broadcast(c.Info.MapID, info.LeaveMapOutboundInfo{UserID: c.Info.UserID}) //同步广播
result = &info.LeaveMapOutboundInfo{
UserID: c.Info.UserID,

View File

@@ -229,7 +229,7 @@ func (h Controller) SetPetExp(data *pet.PetSetExpInboundInfo, c *player.Player)
_, onpet, ok := c.FindPet(data.CatchTime)
if ok && onpet.Level < 100 {
defer c.AddPetExp(onpet, data.Exp)
c.AddPetExp(onpet, data.Exp)
return &pet.PetSetExpOutboundInfo{
Exp: c.Info.ExpPool,
}, 0

View File

@@ -124,3 +124,23 @@ type ChatInfo struct {
MessageLen uint32 `struc:"sizeof=Message"`
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"` // 消息内容包含utf-8空字符('\x00')作为结束符
}
type C2S_FRESH_CHOICE_FIGHT_LEVEL struct {
Head common.TomeeHeader `cmd:"2428|2414" struc:"skip"`
// Level 挑战层数选择标识
// 0: 继续挑战(从数据库存储的当前通关层数开始)
// 1: 从1层开始
// 2: 从11层开始
// 3: 从21层开始
// 4: 从31层开始
Level uint `json:"level"` // 若使用JSON序列化添加tag若用protobuf可替换为对应的tag
}
type S2C_FreshChoiceLevelRequestInfo struct {
// CurFightLevel 当前战斗层数(对应前端 curFightLevel 字段)
CurFightLevel uint `json:"curFightLevel"` // JSON序列化tag保证字段名与前端一致
BossIdLen uint32 `struc:"sizeof=BossId"`
// BossId 当前层数的精灵ID数组对应前端 bossId 字段C# List<uint> 对应Go []uint切片
BossId []uint32 `json:"bossId"`
}
type FRESH_LEAVE_FIGHT_LEVEL struct {
Head common.TomeeHeader `cmd:"2430|2416" struc:"skip"`
}

View File

@@ -164,7 +164,7 @@ func init() {
onpet.Downgrade(1)
onpet.NextLvExp = 0
onpet.Update_EXP()
onpet.Update(false)
onpet.Ev = [6]uint32{}
onpet.Dv = uint32(grand.Intn(32))
onpet.Nature = (onpet.Nature + uint32(grand.Intn(25))) % 25

View File

@@ -18,23 +18,22 @@ func (p *Player) AddPetExp(petinfo *model.PetInfo, addExp uint32) {
addExp = utils.Min(addExp, p.Info.ExpPool)
originalLevel := petinfo.Level
Exp := petinfo.Exp + addExp
petinfo.Update_EXP()
petinfo.Update()
p.Info.ExpPool -= addExp //减去已使用的经验
gainexp := Exp //已获得的经验
for Exp >= petinfo.NextLvExp {
petinfo.Level++
petinfo.Update_EXP()
petinfo.Update()
petinfo.Update(true)
Exp -= petinfo.LvExp
if originalLevel < 100 && petinfo.Level == 100 { //升到100了
p.Info.ExpPool += Exp //减去已使用的经验
gainexp -= Exp
Exp = 0
break //停止升级
}
}
petinfo.Exp = Exp
// 重新计算面板
if originalLevel != petinfo.Level {
@@ -42,27 +41,26 @@ func (p *Player) AddPetExp(petinfo *model.PetInfo, addExp uint32) {
petinfo.Cure()
p.Info.PetMaxLevel = utils.Max(petinfo.Level, p.Info.PetMaxLevel)
}
// 处理技能学习
canLearnSkillList := utils.LastFourElements(petinfo.GetLevelRangeCanLearningSkills(originalLevel, petinfo.Level), 4) //获取最后四个技能,如果不足,那就取全部技能
// 处理技能学习
canLearnSkillList := utils.LastFourElements(petinfo.GetLevelRangeCanLearningSkills(originalLevel, petinfo.Level), 4) //获取最后四个技能,如果不足,那就取全部技能
for i := 0; i < 4; i++ {
for i := 0; i < 4; i++ {
if len(canLearnSkillList) != 0 {
skid := canLearnSkillList[len(canLearnSkillList)-1]
petinfo.SkillList = append(petinfo.SkillList, model.SkillInfo{
if len(canLearnSkillList) != 0 {
skid := canLearnSkillList[len(canLearnSkillList)-1]
petinfo.SkillList = append(petinfo.SkillList, model.SkillInfo{
ID: skid,
PP: uint32(xmlres.SkillMap[int(skid)].MaxPP),
})
ID: skid,
PP: uint32(xmlres.SkillMap[int(skid)].MaxPP),
})
canLearnSkillList = canLearnSkillList[:len(canLearnSkillList)-1]
}
canLearnSkillList = canLearnSkillList[:len(canLearnSkillList)-1]
}
}
if len(petinfo.SkillList) > 4 {
petinfo.SkillList = petinfo.SkillList[:4] //归正到4
if len(petinfo.SkillList) > 4 {
petinfo.SkillList = petinfo.SkillList[:4] //归正到4
}
}
t1 := common.NewTomeeHeader(2508, p.Info.UserID)
@@ -71,6 +69,7 @@ func (p *Player) AddPetExp(petinfo *model.PetInfo, addExp uint32) {
var petinfwo info.UpdatePropInfo
copier.Copy(&petinfwo, petinfo)
petinfwo.Exp = gainexp
rrr.Data = append(rrr.Data, petinfwo)
p.SendPack(t1.Pack(rrr)) //准备包由各自发,因为协议不一样
// 发送经验更新消息

View File

@@ -270,8 +270,8 @@ func (pet *PetInfo) Downgrade(level uint32) {
}
// 执行进化逻辑
func (petinfo *PetInfo) Update() {
// 执行进化逻辑 ,是否进化
func (petinfo *PetInfo) Update(isup bool) {
// 最大进化次数限制(防止配置表闭环导致死循环)
maxEvolveTimes := 10
@@ -283,14 +283,18 @@ func (petinfo *PetInfo) Update() {
if evolveCount >= maxEvolveTimes {
break
}
// 进化完成后,统一更新经验(原逻辑保留)
petinfo.LvExp = petinfo.NextLvExp
// 获取当前宠物形态的配置
basic, ok := xmlres.PetMAP[int(petinfo.ID)]
// 配置不存在,直接退出循环
if !ok {
break
}
petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic())
if !isup {
return
}
// 检查是否满足进化条件
canEvolve := basic.EvolvesTo != 0 && // 有明确的进化目标
int(petinfo.Level) >= basic.EvolvingLv && // 等级达到进化要求
@@ -308,19 +312,6 @@ func (petinfo *PetInfo) Update() {
}
// 传入bool则不升级
// Update 改造为循环进化:直到宠物无法再进化为止,再更新经验
// t ...bool原参数逻辑len(t)==0时触发进化检查否则仅更新经验
func (petinfo *PetInfo) Update_EXP() {
// 进化完成后,统一更新经验(原逻辑保留)
petinfo.LvExp = petinfo.NextLvExp
// 获取最终形态的宠物配置,计算下一等级经验
basic := xmlres.PetMAP[int(petinfo.ID)]
petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic())
}
// calculateExperience 计算指定等级和种族值所需的经验值
// level: 当前等级
// baseValue: 种族值
@@ -485,7 +476,7 @@ func GenPetInfo(
// ---- 属性计算 ----
p.CalculatePetPane(true)
p.Update_EXP()
p.Update(false)
return p
}

View File

@@ -2,7 +2,6 @@ package model
import (
"blazing/common/data/xmlres"
"blazing/common/utils"
)
// 实现获取等级范围内可学习的技能
@@ -35,7 +34,16 @@ func (c *PetInfo) calculatePetPanelSize(base, ev uint32, natureCorrect float64)
// 计算生成面板,只允许第一次生成超过100比如boss,不允许额外超过
func (p *PetInfo) CalculatePetPane(frist bool) {
if !frist {
p.Level = utils.Min(p.Level, 100)
if p.Level > 100 {
oldlveel := p.Level
p.Level = 100
defer func() {
p.Level = oldlveel
}()
}
}
naxml := xmlres.NatureRootMap[int(p.Nature)]
petxml := xmlres.PetMAP[int(p.ID)]

View File

@@ -158,7 +158,7 @@ type PlayerInfo struct {
AllPetNumber uint32 `struc:"uint32" json:"all_pet_number"` // 精灵数量
MonKingWin uint32 `struc:"uint32" json:"mon_king_win"` // 精灵王胜场
MessWin uint32 `struc:"skip" json:"mess_win"` // 大乱斗胜场
CurrentStage uint32 `struc:"uint32" json:"current_stage"` // 勇者之塔层数
CurrentStage uint32 `struc:"uint32" default:"1" json:"current_stage"` // 勇者之塔层数
MaxStage uint32 `struc:"uint32" json:"max_stage"` // 试炼之塔最高层
CurrentFreshStage uint32 `struc:"uint32" json:"current_fresh_stage"` // 当前试炼层数
MaxFreshStage uint32 `struc:"uint32" json:"max_fresh_stage"` // 最高试炼层

View File

@@ -151,6 +151,8 @@ func NewInfoService(id uint32) *InfoService {
Service: &cool.Service{Model: model.NewPlayer(), UniqueKey: map[string]string{
"player_id": "角色名称不能重复",
}, PageQueryOp: &cool.QueryOp{
FieldEQ: []string{"player_id"},
}},
},
}