feat(item): 添加 NatureProbs 字段并优化宠物道具使用逻辑

- 在 `Item` 结构体中新增 `NatureProbs` 字段,用于支持性格概率配置
- 重构 `ItemUsePet` 控制器方法,引入处理器注册机制统一管理道具效果
- 修复神经元相关道具的特殊处理逻辑,增强代码可维护性
- 调整 `S2C_USE_PET_ITEM_OUT_OF_FIGHT` 响应结构体,增加技能列表长度字段
- 修改 `ResetNature` 方法命名及判断条件,提升语义清晰度与健壮性
- 新增 `PetInfo_One_Unscoped` 查询方法以支持软删除数据访问
- 实
This commit is contained in:
2025-12-07 01:43:12 +08:00
parent 35c89215f7
commit 004eec219c
6 changed files with 338 additions and 43 deletions

View File

@@ -47,10 +47,11 @@ type Item struct {
YuanshenDegrade int `xml:"YuanshenDegrade,attr,omitempty"` // 融合精灵还原标识(融合精灵还原药剂) YuanshenDegrade int `xml:"YuanshenDegrade,attr,omitempty"` // 融合精灵还原标识(融合精灵还原药剂)
EvRemove int `xml:"EvRemove,attr,omitempty"` // 学习力遗忘类型(各类学习力遗忘剂) EvRemove int `xml:"EvRemove,attr,omitempty"` // 学习力遗忘类型(各类学习力遗忘剂)
//bShowPetBag int `xml:"bShowPetBag,attr,omitempty"` // 宠物背包显示标识(副融合精灵保留药剂等) //bShowPetBag int `xml:"bShowPetBag,attr,omitempty"` // 宠物背包显示标识(副融合精灵保留药剂等)
Nature *string `xml:"Nature,attr"` // 指向int的指针无值时为nil Nature *string `xml:"Nature,attr"` // 指向int的指针无值时为nil
NatureSet *string `xml:"NatureSet,attr"` // 指向string的指针无值时为nil NatureSet *string `xml:"NatureSet,attr"` // 指向string的指针无值时为nil
Pet *Pet `xml:"pet,omitempty"` // 精灵属性子节点 NatureProbs *string `xml:"NatureProbs,attr"` // 指向string的指针无值时为nil
TeamPK *TeamPK `xml:"teamPK,omitempty"` // 要塞保卫战子节点 Pet *Pet `xml:"pet,omitempty"` // 精灵属性子节点
TeamPK *TeamPK `xml:"teamPK,omitempty"` // 要塞保卫战子节点
} }
// Pet 精灵属性子节点,对应<pet>标签 // Pet 精灵属性子节点,对应<pet>标签

View File

@@ -1,16 +1,12 @@
package controller package controller
import ( import (
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode" "blazing/common/socket/errorcode"
"blazing/logic/service/fight" "blazing/logic/service/fight"
"blazing/logic/service/item" "blazing/logic/service/item"
"blazing/logic/service/player" "blazing/logic/service/player"
"blazing/modules/blazing/model" "blazing/modules/blazing/model"
"strings"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
) )
@@ -35,40 +31,33 @@ func (h Controller) ItemUsePet(data *item.C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *play
if !ok { if !ok {
return nil, errorcode.ErrorCodes.Err10401 return nil, errorcode.ErrorCodes.Err10401
} }
// 绑定变量到switch显式匹配true
switch itemID := data.ItemID; true {
//这是学习力遗忘
case itemID >= 300037 && itemID <= 300041:
onpet.Ev[itemID-300037+1] = 0
// 体力遗忘
case itemID == 300042:
onpet.Ev[0] = 0
// 全能遗忘
case itemID == 300650:
onpet.Ev = [6]uint32{}
//性格随机
case itemID == 300025:
onpet.Nature = (onpet.Nature + uint32(grand.Intn(25))) % 25
//性格转换
case itemID >= 300081 && itemID <= 300086:
onpet.Nature = gconv.Uint32(*xmlres.ItemsMAP[int(itemID)].Nature)
//性格转换
case (itemID >= 300602 && itemID <= 300605) || itemID == 300119 || itemID == 300120:
rr := strings.Split(*xmlres.ItemsMAP[int(itemID)].NatureSet, " ")
onpet.Nature = gconv.Uint32(rr[grand.Intn(len(rr))-1])
default:
// 无效ID处理
return nil, errorcode.ErrorCodes.ErrSystemError
}
if c.Service.Item.CheakItem(data.ItemID) == 0 { if c.Service.Item.CheakItem(data.ItemID) == 0 {
return nil, errorcode.ErrorCodes.ErrSystemError return nil, errorcode.ErrorCodes.ErrSystemError
} }
hd := item.PetItemRegistry.GetHandler(data.ItemID)
if hd == nil {
return nil, errorcode.ErrorCodes.ErrSystemError
}
if data.ItemID == 300025 {
//神经元需要特殊处理
if onpet.OldCatchTime == 0 {
return nil, errorcode.ErrorCodes.ErrSystemError
}
oldpetc := onpet.CatchTime
oldpet := c.Service.Pet.PetInfo_One_Unscoped(onpet.OldCatchTime)
copier.Copy(onpet, oldpet.Data)
onpet.CatchTime = oldpetc
} else {
r := hd(data.ItemID, onpet)
if !r {
return nil, errorcode.ErrorCodes.ErrSystemError
}
}
c.Service.Item.SubItem(data.ItemID, 1) c.Service.Item.SubItem(data.ItemID, 1)
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{} result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
onpet.CalculatePetPane() onpet.CalculatePetPane()
@@ -77,8 +66,8 @@ func (h Controller) ItemUsePet(data *item.C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *play
return result, 0 return result, 0
} }
func (h Controller) RESET_NATURE(data *item.C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { func (h Controller) ResetNature(data *item.C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.Service.Item.CheakItem(data.ItemId) == 0 { if c.Service.Item.CheakItem(data.ItemId) <= 0 {
return nil, errorcode.ErrorCodes.ErrSystemError return nil, errorcode.ErrorCodes.ErrSystemError
} }
_, onpet, ok := c.FindPet(data.CatchTime) _, onpet, ok := c.FindPet(data.CatchTime)

View File

@@ -26,8 +26,9 @@ type S2C_USE_PET_ITEM_OUT_OF_FIGHT struct {
// * ev:生命学习力,攻击学习力,防御学习力,特攻学习力,特防学习力,速度学习力 // * ev:生命学习力,攻击学习力,防御学习力,特攻学习力,特防学习力,速度学习力
Ev [6]uint32 `fieldDesc:"属性" ` Ev [6]uint32 `fieldDesc:"属性" `
// * battle_lv: atk(0), def(1), sp_atk(2), sp_def(3), spd(4), accuracy(5) // * battle_lv: atk(0), def(1), sp_atk(2), sp_def(3), spd(4), accuracy(5)
Prop [5]uint32 `fieldDesc:"属性" ` Prop [5]uint32 `fieldDesc:"属性" `
SkillList []model.SkillInfo `json:"skilllist"` // 技能数组 SkillListLen uint32 `struc:"sizeof=SkillList"`
SkillList []model.SkillInfo `json:"skilllist"` // 技能数组
} }
type C2S_PET_RESET_NATURE struct { type C2S_PET_RESET_NATURE struct {
Head common.TomeeHeader `cmd:"2343" struc:"skip"` Head common.TomeeHeader `cmd:"2343" struc:"skip"`

View File

@@ -0,0 +1,258 @@
package item
import (
"blazing/common/data/xmlres"
"blazing/modules/blazing/model"
"errors"
"strconv"
"strings"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
"github.com/jinzhu/copier"
)
type PetItemHandler func(itemid uint32, ctx *model.PetInfo) bool
// RangeHandler 范围ID处理器用于ID区间匹配
type RangeHandler struct {
Start uint32
End uint32
Handler PetItemHandler
}
// SetHandler 集合ID处理器用于离散ID匹配
type SetHandler struct {
IDs []uint32
Handler PetItemHandler
}
// PetItemHandlerRegistry 道具处理器注册器
type PetItemHandlerRegistry struct {
exactHandlers map[uint32]PetItemHandler // 精确ID映射
rangeHandlers []RangeHandler // 范围ID映射
setHandlers []SetHandler // 集合ID映射
}
// 全局注册器实例
var PetItemRegistry = &PetItemHandlerRegistry{
exactHandlers: make(map[uint32]PetItemHandler),
}
// -------------------------- 4. 注册器方法 --------------------------
// RegisterExact 注册精确ID的处理函数
func (r *PetItemHandlerRegistry) RegisterExact(itemID uint32, handler PetItemHandler) {
r.exactHandlers[itemID] = handler
}
// RegisterRange 注册ID范围的处理函数
func (r *PetItemHandlerRegistry) RegisterRange(start, end uint32, handler PetItemHandler) {
r.rangeHandlers = append(r.rangeHandlers, RangeHandler{
Start: start,
End: end,
Handler: handler,
})
}
// RegisterSet 注册ID集合的处理函数
func (r *PetItemHandlerRegistry) RegisterSet(ids []uint32, handler PetItemHandler) {
r.setHandlers = append(r.setHandlers, SetHandler{
IDs: ids,
Handler: handler,
})
}
// GetHandler 根据ItemID获取对应的处理函数
func (r *PetItemHandlerRegistry) GetHandler(itemID uint32) PetItemHandler {
// 1. 优先匹配精确ID
if handler, ok := r.exactHandlers[itemID]; ok {
return handler
}
// 2. 匹配范围ID
for _, rh := range r.rangeHandlers {
if itemID >= rh.Start && itemID <= rh.End {
return rh.Handler
}
}
// 3. 匹配集合ID
for _, sh := range r.setHandlers {
for _, id := range sh.IDs {
if itemID == id {
return sh.Handler
}
}
}
// 4. 无匹配的处理器
return nil
}
func nvfunc(itemid uint32, onpet *model.PetInfo) bool {
// 1. 取出配置(保留你原有的取值方式)
itemConf := xmlres.ItemsMAP[int(itemid)]
// 2. 分割NatureSet保留你原有的写法
rr := strings.Split(*itemConf.NatureSet, " ")
// 3. 分割NatureProbs新增概率解析
var probList []string
if itemConf.NatureProbs != nil {
probList = strings.Split(*itemConf.NatureProbs, " ")
}
// 4. 按概率选值(核心改造:替换原有随机逻辑)
selectedVal, err := randomByProbs(rr, probList)
if err != nil {
return false
}
// 5. 赋值(保留你原有的类型转换)
onpet.Nature = gconv.Uint32(selectedVal)
return true
}
// -------------------------- 6. 初始化注册器(注册所有处理器) --------------------------
func init() {
PetItemRegistry.RegisterRange(300037, 300041, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Ev[itemid-300037+1] = 0
return true
})
PetItemRegistry.RegisterExact(300042, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Ev[0] = 0
return true
})
PetItemRegistry.RegisterExact(300650, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Ev = [6]uint32{}
return true
})
PetItemRegistry.RegisterExact(300025, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Nature = (onpet.Nature + uint32(grand.Intn(25))) % 25
return true
})
PetItemRegistry.RegisterRange(300081, 300086, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Nature = gconv.Uint32(*xmlres.ItemsMAP[int(itemid)].Nature)
return true
})
PetItemRegistry.RegisterRange(300602, 300605, nvfunc)
//特判转换
PetItemRegistry.RegisterSet([]uint32{300119, 300120}, nvfunc)
PetItemRegistry.RegisterExact(300790, func(itemid uint32, onpet *model.PetInfo) bool {
r := grand.Intn(2)
if r == 0 {
onpet.Dv--
} else {
onpet.Dv++
}
return true
})
//基因重组
PetItemRegistry.RegisterExact(300024, func(itemid uint32, onpet *model.PetInfo) bool {
oldcat := onpet.CatchTime
ab := 0
for _, v := range onpet.EffectInfo {
if v.Type == 1 {
ab = int(v.Idx)
}
}
r := model.GenPetInfo(int(onpet.ID), -1, -1, ab, 0, 1)
copier.Copy(onpet, r)
onpet.CatchTime = oldcat
return true
})
PetItemRegistry.RegisterExact(300668, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Dv = uint32(grand.Intn(32))
return true
})
PetItemRegistry.RegisterExact(300791, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Dv = 31
return true
})
PetItemRegistry.RegisterExact(300792, func(itemid uint32, onpet *model.PetInfo) bool {
onpet.Dv = uint32(grand.Intn(31-int(onpet.Dv)) + 1)
return true
})
PetItemRegistry.RegisterExact(300053, func(itemid uint32, onpet *model.PetInfo) bool {
if !onpet.HaveAN() {
onpet.RnadAN()
return true
}
return false
})
PetItemRegistry.RegisterExact(300054, func(itemid uint32, onpet *model.PetInfo) bool {
if onpet.HaveAN() {
onpet.RnadAN()
return true
}
return false
})
}
// 替换你原有性格转换的代码段,新增概率解析+权重随机逻辑
// 注保留你原有的变量名如rr、函数调用grand.Intn仅扩展概率逻辑
// 先定义一个辅助函数:按概率从列表中选值(贴合你的代码风格)
func randomByProbs(natureSet []string, probs []string) (string, error) {
// 1. 若概率配置为空/长度不匹配,降级为等概率随机(兼容原有逻辑)
if len(probs) == 0 || len(natureSet) != len(probs) {
if len(natureSet) == 0 {
return "", errors.New("natureSet is empty")
}
// 修复你原有代码的索引越界问题grand.Intn(len(rr))-1 → 改为grand.Intn(len(rr))
// 因为grand.Intn(n)返回0~n-1直接作为索引即可无需-1
return natureSet[grand.Intn(len(natureSet))], nil
}
// 2. 解析概率为int数组校验非负
probInts := make([]int, len(probs))
totalProb := 0
for i, p := range probs {
val, err := strconv.Atoi(p)
if err != nil || val < 0 {
return "", errors.New("invalid prob value: " + p)
}
probInts[i] = val
totalProb += val
}
// 3. 总概率为0降级为等概率
if totalProb == 0 {
return natureSet[grand.Intn(len(natureSet))], nil
}
// 4. 计算前缀和(权重随机核心)
prefixSum := make([]int, len(probInts))
prefixSum[0] = probInts[0]
for i := 1; i < len(probInts); i++ {
prefixSum[i] = prefixSum[i-1] + probInts[i]
}
// 5. 生成随机数匹配前缀和
randVal := grand.Intn(totalProb)
for i, sum := range prefixSum {
if randVal < sum {
return natureSet[i], nil
}
}
// 理论上不会走到这里
return natureSet[0], nil
}

View File

@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/gconv"
"github.com/jinzhu/copier"
"github.com/samber/lo" "github.com/samber/lo"
) )
@@ -89,7 +90,7 @@ type PetInfo struct {
// 捕获等级默认0@UInt long → uint32 // 捕获等级默认0@UInt long → uint32
CatchLevel uint32 `fieldDesc:"捕获等级 默认为0" ` CatchLevel uint32 `fieldDesc:"捕获等级 默认为0" `
EffectInfoLen uint16 `struc:"sizeof=EffectInfo"` EffectInfoLen uint16 `struc:"sizeof=EffectInfo"`
// 特性列表长度用UShort存储变长List → []PetEffectInfo + 长度前缀规则) // 特性列表长度用UShort存储变长List → []PetEffectInfo + 长度前缀规则) 第一个一定是特性
EffectInfo []PetEffectInfo `fieldDesc:"特性列表, 长度在头部以UShort存储" serialize:"lengthFirst,lengthType=uint16,type=structArray"` EffectInfo []PetEffectInfo `fieldDesc:"特性列表, 长度在头部以UShort存储" serialize:"lengthFirst,lengthType=uint16,type=structArray"`
// 皮肤ID默认0@UInt long → uint32 // 皮肤ID默认0@UInt long → uint32
@@ -196,6 +197,42 @@ func (pet *PetInfo) Cure() {
} }
} }
// 随机特性
func (pet *PetInfo) RnadAN() {
// 随机特性
randomIndex := rand.Intn(len(xmlres.PlayerEffectMAP))
var i int
for _, v := range xmlres.PlayerEffectMAP {
if i == randomIndex {
ret := &PetEffectInfo{
Idx: uint16(gconv.Int16(v.Idx)),
Status: 1,
EID: uint16(gconv.Int16(v.Eid)),
Args: v.ArgsS,
}
_, eff1, ok := utils.FindWithIndex(pet.EffectInfo, func(item PetEffectInfo) bool {
return uint16(item.Type) == 1
})
if ok {
copier.Copy(eff1, ret)
}
pet.EffectInfo = append(pet.EffectInfo, *ret)
break
}
i++
}
}
func (pet *PetInfo) HaveAN() bool {
_, _, ok := utils.FindWithIndex(pet.EffectInfo, func(item PetEffectInfo) bool {
return uint16(item.Type) == 1
})
return ok
}
func (pet *PetInfo) Downgrade(level uint32) { func (pet *PetInfo) Downgrade(level uint32) {
for pet.Level > uint32(level) { for pet.Level > uint32(level) {

View File

@@ -50,6 +50,15 @@ func (s *PetService) PetInfo_One(cachetime uint32) model.PetEX {
tt.Data.CatchTime = tt.CatchTime tt.Data.CatchTime = tt.CatchTime
return tt return tt
} }
func (s *PetService) PetInfo_One_Unscoped(cachetime uint32) model.PetEX {
m := cool.DBM(s.Model).Where("player_id", s.userid).Where("catch_time", cachetime).Unscoped()
var tt model.PetEX
m.Scan(&tt)
tt.Data.CatchTime = tt.CatchTime
return tt
}
func (s *PetService) Pet_del(cachetime uint32) { func (s *PetService) Pet_del(cachetime uint32) {
cool.DBM(s.Model).Where("player_id", s.userid).Where("catch_time", cachetime).Delete() cool.DBM(s.Model).Where("player_id", s.userid).Where("catch_time", cachetime).Delete()