feat(pet): 新增宠物功能和相关数据结构

- 新增宠物配置和自然属性配置的 XML 解析
- 实现宠物信息生成和属性计算逻辑
- 添加宠物数据库模型和相关服务
- 更新登录和任务完成逻辑,支持宠物相关操作
This commit is contained in:
2025-08-31 00:27:07 +08:00
parent 75e428f62e
commit 3668f3c5b9
11 changed files with 307 additions and 28 deletions

View File

@@ -30,7 +30,9 @@ var (
//Monster MonsterRoot //野怪配置
MonsterMap map[int]TMapConfig
//Skill MovesTbl //技能配置
SkillMap map[int]Move
SkillMap map[int]Move
PetMAP map[int]PetMM //宠物配置
NatureRootMap map[int]NatureItem
)
func initfile() {
@@ -51,7 +53,17 @@ func initfile() {
return m.ID
})
pet := getXml[Monsters](path + "226.xml")
PetMAP = utils.ToMap[PetMM, int](pet.Monsters, func(m PetMM) int {
return m.ID
})
NatureRootMap1 := getXml[NatureRoot](path + "nature.xml")
NatureRootMap = utils.ToMap[NatureItem, int](NatureRootMap1.Items, func(m NatureItem) int {
return m.ID
})
}
func init() {

View File

@@ -0,0 +1,21 @@
package xmlres
import "github.com/ECUST-XX/xml"
// NatureItem 表示单个性格修正项
type NatureItem struct {
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
AttackCorrect float64 `xml:"m_attack,attr"` // 攻击修正
DefenseCorrect float64 `xml:"m_defence,attr"` // 防御修正
SaCorrect float64 `xml:"m_SA,attr"` // 特攻修正
SdCorrect float64 `xml:"m_SD,attr"` // 特防修正
SpeedCorrect float64 `xml:"m_speed,attr"` // 速度修正
Desc string `xml:"desc,attr"` // 描述
}
// NatureRoot 表示XML根节点
type NatureRoot struct {
XMLName xml.Name `xml:"root"`
Items []NatureItem `xml:"item"`
}

50
common/data/xmlres/pet.go Normal file
View File

@@ -0,0 +1,50 @@
package xmlres
import "github.com/ECUST-XX/xml"
// Move 表示怪物可学习的技能
type PetMoves struct {
ID int `xml:"ID,attr"`
LearningLv int `xml:"LearningLv,attr"`
}
// LearnableMoves 包含怪物可学习的技能列表
type LearnableMoves struct {
Moves []PetMoves `xml:"Move"`
}
// PetMM 表示一个怪物的信息
type PetMM struct {
ID int `xml:"ID,attr"`
DefName string `xml:"DefName,attr"`
Type int `xml:"Type,attr"`
GrowthType int `xml:"GrowthType,attr"`
HP int `xml:"HP,attr"`
Atk int `xml:"Atk,attr"`
Def int `xml:"Def,attr"`
SpAtk int `xml:"SpAtk,attr"`
SpDef int `xml:"SpDef,attr"`
Spd int `xml:"Spd,attr"`
YieldingExp int `xml:"YieldingExp,attr"`
CatchRate string `xml:"CatchRate,attr"`
YieldingEV string `xml:"YieldingEV,attr"`
EvolvesFrom int `xml:"EvolvesFrom,attr"`
EvolvesTo int `xml:"EvolvesTo,attr"`
EvolvingLv int `xml:"EvolvingLv,attr"`
FreeForbidden int `xml:"FreeForbidden,attr"`
FuseMaster int `xml:"FuseMaster,attr"`
FuseSub int `xml:"FuseSub,attr"`
Gender int `xml:"Gender,attr"`
PetClass int `xml:"PetClass,attr"`
FormParam float64 `xml:"FormParam,attr"`
CharacterAttrParam int `xml:"CharacterAttrParam,attr"`
GradeParam float64 `xml:"GradeParam,attr"`
AddSeParam int `xml:"AddSeParam,attr"`
LearnableMoves LearnableMoves `xml:"LearnableMoves"`
}
// Monsters 表示所有怪物的集合
type Monsters struct {
XMLName xml.Name `xml:"Monsters"`
Monsters []PetMM `xml:"Monster"`
}

View File

@@ -35,7 +35,8 @@ func (h *Controller) Login(data *login.InInfo, c *socket.Conn) (result *login.Ou
result.PlayerInfo = *t.Info
//result.TaskList = blservice.NewUserService(t.Info.UserID).GenTask()
result.PetList = blservice.NewUserService(t.Info.UserID).GetPetList()
result.PetList = blservice.NewUserService(t.Info.UserID).GetPetList(1)
tt := maps.NewOutInfo()
//copier.Copy(t.Info, tt)
t1 := handler.NewTomeeHeader(2001, t.Info.UserID)

View File

@@ -4,6 +4,7 @@ import (
"blazing/common/data/socket"
"blazing/common/socket/errorcode"
"blazing/logic/service/pet"
"blazing/modules/blazing/service"
)
// 获取精灵信息
@@ -12,7 +13,10 @@ func (h *Controller) GetPetInfo(
c *socket.Player) (result *pet.OutInfo,
err errorcode.ErrorCode) { //这个时候player应该是空的
return nil, 0
t := service.NewUserService(c.Info.UserID).GetPetInfo(data.CatchTime)
return &pet.OutInfo{
PetInfo: t,
}, 0
}
// 精灵背包仓库切换

View File

@@ -6,6 +6,7 @@ import (
"blazing/logic/service/task"
"blazing/modules/blazing/model"
"blazing/modules/blazing/service"
"math/rand"
"time"
)
@@ -51,6 +52,15 @@ func (h Controller) AddTaskBuf(data *task.AddTaskBufInboundInfo, c *socket.Playe
return &task.AddTaskBufOutboundInfo{}, 0
}
// 生成0-24的随机整数
func randInt0To24() int {
// 初始化随机种子(仅需初始化一次)
rand.Seed(time.Now().UnixNano())
// 生成0-24的随机数Intn(n)返回[0, n)的整数)
return rand.Intn(25)
}
/**
* 完成任务
*/
@@ -72,23 +82,28 @@ func (h Controller) Complete_Task(data *task.CompleteTaskInboundInfo, c *socket.
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 500001, ItemCount: 1})
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 500502, ItemCount: 1})
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 500503, ItemCount: 1})
// service.NewUserService(c.Info.UserID).ItemExec(func(tt []model.ItemE) []model.ItemE {
// return tt
// })
}
if data.TaskId == 86 { //新手注册任务
result.CaptureTime = uint32(time.Now().Unix())
result.PetTypeId = 1
r := model.GenPetInfo(1, 1, 1, 1, 1, 5)
result.CaptureTime = r.CatchTime
result.PetTypeId = r.ID
service.NewUserService(c.Info.UserID).PetAdd(r.CatchTime, 1, *r)
}
if data.TaskId == 87 { //新手注册任务
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 300001, ItemCount: 10})
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 300001, ItemCount: 5})
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 300011, ItemCount: 5})
}
if data.TaskId == 88 { //新手注册任务
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 1, ItemCount: 1000})
result.ItemList = append(result.ItemList, task.ItemInfo{ItemId: 1, ItemCount: 5000})
}
return result, 0

View File

@@ -7,12 +7,12 @@ import (
const TableNamePlayerBagItem = "player_bag_item"
// PlayerBagItem mapped from table <player_bag_item>
type PlayerBag struct {
type Item struct {
*cool.Model
PlayerID uint64 `gorm:"not null;index:idx_player_bag_item_by_player_id;comment:'所属玩家ID'" json:"player_id"`
Data string `gorm:"type:text;not null;comment:'全部数据'" json:"data"`
}
type PlayerBagItem struct {
type ItemE struct {
ID int32 `gorm:"not null;comment:'道具唯一编号'" json:"item_id"`
Count int32 `gorm:"not null;default:0;comment:'拥有数量 '" json:"count"`
Max int32 `gorm:"not null;default:0;comment:'最大数量 '" json:"max"`
@@ -20,23 +20,23 @@ type PlayerBagItem struct {
}
// TableName PlayerBagItem's table name
func (*PlayerBagItem) TableName() string {
func (*ItemE) TableName() string {
return TableNamePlayerBagItem
}
// GroupName PlayerBagItem's table group
func (*PlayerBagItem) GroupName() string {
func (*ItemE) GroupName() string {
return "default"
}
// NewPlayerBagItem create a new PlayerBagItem
func NewPlayerBag() *PlayerBag {
return &PlayerBag{
func NewPlayerBag() *Item {
return &Item{
Model: cool.NewModel(),
}
}
// init 创建表
func init() {
cool.CreateTable(&PlayerBag{})
cool.CreateTable(&Item{})
}

View File

@@ -1,7 +1,10 @@
package model
import (
"blazing/common/data/xmlres"
"blazing/cool"
"time"
)
const TableNamePet = "pet"
@@ -9,19 +12,126 @@ const TableNamePet = "pet"
// Pet mapped from table <pet>
type Pet struct {
*cool.Model
PlayerID uint32 `gorm:"not null;index:idx_pet_by_player_id;comment:'所属玩家ID'" json:"player_id"`
InBag bool `gorm:"not null;comment:'是否在背包中'" json:"in_bag"` //"0为放入仓库1为放入背包
Data string `gorm:"type:text;not null;comment:'精灵全部数据'" json:"data"`
PlayerID uint32 `gorm:"not null;index:idx_pet_by_player_id;comment:'所属玩家ID'" json:"player_id"`
InBag int `gorm:"not null;comment:'是否在背包中'" json:"in_bag"` //"0为放入仓库1为放入背包
CatchTime uint32 `gorm:"not null;comment:'捕捉时间'" json:"catch_time"`
Data string `gorm:"type:text;not null;comment:'精灵全部数据'" json:"data"`
}
// * @param petTypeId 精灵类型ID
// * @param individualValue 个体值
// * @param natureId 性格ID
// * @param abilityTypeEnum 特性类型
// * @param isShiny 是否为闪光
// * @param level 等级
// * @return 生成的精灵实体
func GenPetInfo(id, individual, natureId, abilityTypeEnum, shinyid, level uint32) *PetInfo {
p := &PetInfo{ID: id,
Shiny: shinyid, //闪光
Nature: natureId, //性格
Dv: individual,
AbilityType: abilityTypeEnum, //特性
CatchTime: uint32(time.Now().Unix()),
Level: level} //等级
petxml := xmlres.PetMAP[int(id)]
naxml := xmlres.NatureRootMap[int(natureId)]
tttt := make([]uint32, 0)
for _, v := range petxml.LearnableMoves.Moves {
if p.Level >= uint32(v.LearningLv) {
tttt = append(tttt, uint32(v.ID))
}
}
for i := 0; i < len(tttt); i++ {
p.SkillList[i].ID = tttt[i]
p.SkillList[i].Pp = uint32(xmlres.SkillMap[int(tttt[i])].MaxPP)
}
p.SkillListLen = uint32(len(tttt))
// 计算各项属性
hp := p.CalculatePetHPPanelSize(
uint32(petxml.HP),
p.Dv,
p.Level,
p.EvHp,
)
attack := p.CalculatePetPanelSize(
p.Attack,
p.Dv,
p.Level,
p.EvAttack,
naxml.AttackCorrect,
)
defense := p.CalculatePetPanelSize(
p.Defence,
p.Dv,
p.Level,
p.EvDefence,
naxml.DefenseCorrect,
)
specialAttack := p.CalculatePetPanelSize(
p.SpecialAttack,
p.Dv,
p.Level,
p.EvSpecialAttack,
naxml.SaCorrect,
)
specialDefense := p.CalculatePetPanelSize(
p.SpecialDefence,
p.Dv,
p.Level,
p.EvSpecialDefense,
naxml.SdCorrect,
)
speed := p.CalculatePetPanelSize(
p.Speed,
p.Dv,
p.Level,
p.EvSpeed,
naxml.SpeedCorrect,
)
// 设置计算结果
p.MaxHp = hp
p.Hp = hp
p.Attack = attack
p.Defence = defense
p.SpecialAttack = specialAttack
p.SpecialDefence = specialDefense
p.Speed = speed
return p
}
// 计算HP面板值无性格修正
func (c *PetInfo) CalculatePetHPPanelSize(base, iv, level, ev uint32) uint32 {
// 实现具体计算逻辑,示例公式:(基础值 + 个体值) * 等级 / 100 + 等级 + 10 + 努力值/4
return (base+iv)*level/100 + level + 10 + ev/4
}
// 计算其他属性面板值(带性格修正)
func (c *PetInfo) CalculatePetPanelSize(base, iv, level, ev uint32, natureCorrect float64) uint32 {
// 实现具体计算逻辑,示例公式:((基础值 + 个体值) * 等级 / 100 + 5 + 努力值/4) * 性格修正
baseValue := (base+iv)*level/100 + 5 + ev/4
return uint32(float64(baseValue) * natureCorrect)
}
// PetInfo 精灵信息结构(合并后的优化版本)
type PetInfo struct {
Owner uint32 `struc:"skip"` //仅作为存储
Owner uint32 `struc:"skip"` //仅作为存储
freedTime uint32 `struc:"skip"` //放生时间
// 精灵编号(@UInt long → uint32
ID uint32 `fieldDesc:"精灵编号" `
// 名字默认为全0补齐到16字节固定长度 → [16]byte
Name [16]byte `fieldDesc:"名字 默认为全0 但要补齐到16字节" serialize:"fixedLength=16,type=byteArray"`
Name string `struc:"[16]byte" `
// 个体值(@UInt long → uint32
Dv uint32 `fieldDesc:"个体值" `
@@ -81,7 +191,7 @@ type PetInfo struct {
EvSpeed uint32 `fieldDesc:"速度学习力" `
SkillListLen uint32
// 技能信息固定4条空则赋值0固定长度List → [4]SkillInfo零值即符合“赋值0”
SkillList [4]SkillInfo `fieldDesc:"32字节 技能信息 必须插入4条skillInfo若技能信息为空则要赋值成0" serialize:"fixedLength=4,type=structArray"`
SkillList [4]SkillInfo
// 捕捉时间(@UInt long → 若为时间戳用uint32若需时间类型可改为time.Time需配合序列化处理
CatchTime uint32 `fieldDesc:"捕捉时间" `
@@ -102,7 +212,8 @@ type PetInfo struct {
SkinID uint32 `fieldDesc:"皮肤id默认为0" `
// 是否闪光(@UInt long → uint320=否1=是)
Shiny uint32 `fieldDesc:"是不是闪" `
Shiny uint32 `fieldDesc:"是不是闪" `
AbilityType uint32 `struc:"skip"` //特性
}
// PetEffectInfo 精灵特性信息结构
@@ -119,8 +230,8 @@ type PetEffectInfo struct {
// SkillInfo 精灵技能信息结构SkillInfo
type SkillInfo struct {
ID uint32 `struc:"uint32"` // 技能id@UInt long
Pp uint32 `struc:"uint32"` // 剩余pp@UInt long
ID uint32
Pp uint32
}
// TableName Pet's table name

View File

@@ -0,0 +1,21 @@
package service
import (
"blazing/cool"
"blazing/modules/blazing/model"
"encoding/json"
)
func (s *UserService) ItemExec(t func([]model.ItemE) []model.ItemE) {
//todo待测试
var player model.Item
m1 := cool.DBM(s.reg.Model).Where("player_id", s.userid)
m1.Scan(&player)
var tt []model.ItemE
json.Unmarshal([]byte(player.Data), &tt)
t(tt)
tmep, _ := json.Marshal(tt)
player.Data = string(tmep)
m1.Save(player)
}

View File

@@ -6,10 +6,27 @@ import (
"encoding/json"
)
// 获取精灵信息
func (s *UserService) GetPetList() (ret []model.PetInfo) {
// 获取精灵信息 0是仓库,1是背包
func (s *UserService) GetPetList(flag int) (ret []model.PetInfo) {
ret = make([]model.PetInfo, 0)
m := cool.DBM(s.pet.Model).Where("player_id", s.userid).Where("in_bag", flag)
var tt []model.Pet
m.Scan(&tt)
m := cool.DBM(s.pet.Model).Where("player_id", s.userid)
for _, v := range tt {
var ret11 model.PetInfo
json.Unmarshal([]byte(v.Data), &ret11)
ret = append(ret, ret11)
}
return
}
func (s *UserService) GetPetInfo(cachetime uint32) (ret model.PetInfo) {
m := cool.DBM(s.pet.Model).Where("player_id", s.userid).Where("catch_time", cachetime)
var tt model.Pet
m.Scan(&tt)
json.Unmarshal([]byte(tt.Data), &ret)
@@ -17,3 +34,28 @@ func (s *UserService) GetPetList() (ret []model.PetInfo) {
return
}
func (s *UserService) PetExec(ctime uint32, t func(uint32, model.PetInfo) model.PetInfo) {
//todo待测试
var player model.Pet
m1 := cool.DBM(s.pet.Model).Where("player_id", s.userid).Where("catch_time", ctime)
m1.Scan(&player)
var tt model.PetInfo
json.Unmarshal([]byte(player.Data), &tt)
t(player.CatchTime, tt)
tmep, _ := json.Marshal(tt)
player.Data = string(tmep)
m1.Save(player)
}
func (s *UserService) PetAdd(ctime uint32, inbag int, y model.PetInfo) {
m1 := cool.DBM(s.pet.Model).Where("player_id", s.userid)
var player model.Pet
player.PlayerID = s.userid
player.CatchTime = ctime
player.InBag = inbag
tmp, _ := json.Marshal(y)
player.Data = string(tmp)
m1.Insert(player)
}

View File

@@ -11,6 +11,7 @@ type UserService struct {
task *cool.Service //任务
reg *cool.Service //注册
pet *cool.Service //精灵
item *cool.Service //物品
}
func NewUserService(id uint32) *UserService {
@@ -22,7 +23,8 @@ func NewUserService(id uint32) *UserService {
reg: &cool.Service{
Model: model.NewPlayer(),
},
pet: &cool.Service{Model: model.NewPet()},
pet: &cool.Service{Model: model.NewPet()},
item: &cool.Service{Model: model.NewPlayerBag()},
}
}