2025-07-26 02:06:08 +00:00
|
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-10-13 19:46:19 +08:00
|
|
|
|
"blazing/common/data/xmlres"
|
2025-07-26 02:06:08 +00:00
|
|
|
|
"blazing/cool"
|
2025-10-18 23:58:19 +08:00
|
|
|
|
"math"
|
2025-10-24 00:31:38 +08:00
|
|
|
|
"math/rand"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
2025-07-26 02:06:08 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const TableNamePet = "pet"
|
|
|
|
|
|
|
|
|
|
|
|
// Pet mapped from table <pet>
|
|
|
|
|
|
type Pet struct {
|
|
|
|
|
|
*cool.Model
|
2025-08-31 00:27:07 +08:00
|
|
|
|
PlayerID uint32 `gorm:"not null;index:idx_pet_by_player_id;comment:'所属玩家ID'" json:"player_id"`
|
2025-10-17 19:23:38 +00:00
|
|
|
|
InBag int `gorm:"not null;comment:'是否在背包中'" json:"in_bag"` //"0为放入仓库,1为放入背包
|
|
|
|
|
|
CatchTime uint32 `gorm:"not null;unique;comment:'捕捉时间'" json:"catch_time"` //唯一键
|
2025-08-31 06:53:42 +00:00
|
|
|
|
// Owner uint32 `struc:"skip"` //仅作为存储
|
|
|
|
|
|
// FreedTime uint32 `struc:"skip"` //放生时间
|
|
|
|
|
|
//是否可交易,这里应该定义在精灵ID里
|
|
|
|
|
|
//是否上架
|
|
|
|
|
|
Data string `gorm:"type:text;not null;comment:'精灵全部数据'" json:"data"`
|
2025-08-31 00:27:07 +08:00
|
|
|
|
}
|
2025-09-23 15:01:52 +00:00
|
|
|
|
type PetEX struct {
|
|
|
|
|
|
Pet
|
|
|
|
|
|
Data PetInfo `orm:"data" json:"data"`
|
|
|
|
|
|
}
|
2025-08-31 00:27:07 +08:00
|
|
|
|
|
2025-08-10 14:19:03 +08:00
|
|
|
|
// PetInfo 精灵信息结构(合并后的优化版本)
|
2025-07-26 02:06:08 +00:00
|
|
|
|
type PetInfo struct {
|
2025-08-31 06:53:42 +00:00
|
|
|
|
|
2025-08-24 17:33:19 +08:00
|
|
|
|
// 精灵编号(@UInt long → uint32)
|
|
|
|
|
|
ID uint32 `fieldDesc:"精灵编号" `
|
|
|
|
|
|
|
|
|
|
|
|
// 名字:默认为全0,补齐到16字节(固定长度 → [16]byte)
|
2025-08-31 00:27:07 +08:00
|
|
|
|
Name string `struc:"[16]byte" `
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 个体值(@UInt long → uint32)
|
|
|
|
|
|
Dv uint32 `fieldDesc:"个体值" `
|
|
|
|
|
|
|
|
|
|
|
|
// 性格(@UInt long → uint32)
|
|
|
|
|
|
Nature uint32 `fieldDesc:"性格" `
|
|
|
|
|
|
|
|
|
|
|
|
// 等级(@UInt long → uint32)
|
|
|
|
|
|
Level uint32 `fieldDesc:"等级" `
|
|
|
|
|
|
|
|
|
|
|
|
// 当前等级已获得经验(@UInt long → uint32)
|
|
|
|
|
|
Exp uint32 `fieldDesc:"当前等级已经获得的经验 2538" `
|
|
|
|
|
|
|
|
|
|
|
|
// 当前等级所需经验(@UInt long → uint32)
|
|
|
|
|
|
LvExp uint32 `fieldDesc:"当前等级所需的经验" `
|
|
|
|
|
|
|
|
|
|
|
|
// 升到下一级的经验(@UInt long → uint32)
|
|
|
|
|
|
NextLvExp uint32 `fieldDesc:"升到下一级的经验" `
|
|
|
|
|
|
|
|
|
|
|
|
// 当前生命(@UInt long → uint32)
|
|
|
|
|
|
Hp uint32 `fieldDesc:"当前生命" `
|
|
|
|
|
|
|
|
|
|
|
|
// 最大生命(@UInt long → uint32)
|
|
|
|
|
|
MaxHp uint32 `fieldDesc:"最大生命" `
|
2025-09-17 00:38:15 +08:00
|
|
|
|
// * battle_lv: atk(0), def(1), sp_atk(2), sp_def(3), spd(4), accuracy(5)
|
|
|
|
|
|
Prop [5]uint32 `fieldDesc:"属性" `
|
|
|
|
|
|
// // 攻击(@UInt long → uint32)
|
|
|
|
|
|
// Attack uint32 `fieldDesc:"攻击" `
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
2025-09-17 00:38:15 +08:00
|
|
|
|
// // 防御(@UInt long → uint32)
|
|
|
|
|
|
// Defence uint32 `fieldDesc:"防御" `
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
2025-09-17 00:38:15 +08:00
|
|
|
|
// // 特攻(@UInt long → uint32)
|
|
|
|
|
|
// SpecialAttack uint32 `fieldDesc:"特攻" `
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
2025-09-17 00:38:15 +08:00
|
|
|
|
// // 特防(@UInt long → uint32)
|
|
|
|
|
|
// SpecialDefence uint32 `fieldDesc:"特防" `
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
2025-09-17 00:38:15 +08:00
|
|
|
|
// // 速度(@UInt long → uint32)
|
|
|
|
|
|
// Speed uint32 `fieldDesc:"速度" `
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 生命学习力(@UInt long → uint32)
|
|
|
|
|
|
EvHp uint32 `fieldDesc:"生命学习力" `
|
|
|
|
|
|
|
|
|
|
|
|
// 攻击学习力(@UInt long → uint32)
|
|
|
|
|
|
EvAttack uint32 `fieldDesc:"攻击学习力" `
|
|
|
|
|
|
|
|
|
|
|
|
// 防御学习力(@UInt long → uint32)
|
|
|
|
|
|
EvDefence uint32 `fieldDesc:"防御学习力" `
|
|
|
|
|
|
|
|
|
|
|
|
// 特攻学习力(@UInt long → uint32)
|
|
|
|
|
|
EvSpecialAttack uint32 `fieldDesc:"特攻学习力" `
|
|
|
|
|
|
|
|
|
|
|
|
// 特防学习力(@UInt long → uint32,注意原Java拼写:evSpecialDefense)
|
|
|
|
|
|
EvSpecialDefense uint32 `fieldDesc:"特防学习力" `
|
|
|
|
|
|
|
|
|
|
|
|
// 速度学习力(@UInt long → uint32)
|
|
|
|
|
|
EvSpeed uint32 `fieldDesc:"速度学习力" `
|
|
|
|
|
|
SkillListLen uint32
|
|
|
|
|
|
// 技能信息:固定4条,空则赋值0(固定长度List → [4]SkillInfo,零值即符合“赋值0”)
|
2025-08-31 00:27:07 +08:00
|
|
|
|
SkillList [4]SkillInfo
|
2025-08-24 17:33:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 捕捉时间(@UInt long → 若为时间戳用uint32;若需时间类型可改为time.Time,需配合序列化处理)
|
|
|
|
|
|
CatchTime uint32 `fieldDesc:"捕捉时间" `
|
|
|
|
|
|
|
|
|
|
|
|
// 捕捉地图(@UInt long → uint32)
|
|
|
|
|
|
CatchMap uint32 `fieldDesc:"捕捉地图" `
|
|
|
|
|
|
|
|
|
|
|
|
// 未知默认0(@UInt long → uint32)
|
|
|
|
|
|
CatchRect uint32 `fieldDesc:"未知默认为0" `
|
|
|
|
|
|
|
|
|
|
|
|
// 捕获等级默认0(@UInt long → uint32)
|
|
|
|
|
|
CatchLevel uint32 `fieldDesc:"捕获等级 默认为0" `
|
|
|
|
|
|
EffectInfoLen uint16 `struc:"sizeof=EffectInfo"`
|
|
|
|
|
|
// 特性列表:长度用UShort存储(变长List → []PetEffectInfo + 长度前缀规则)
|
|
|
|
|
|
EffectInfo []PetEffectInfo `fieldDesc:"特性列表, 长度在头部以UShort存储" serialize:"lengthFirst,lengthType=uint16,type=structArray"`
|
|
|
|
|
|
|
|
|
|
|
|
// 皮肤ID默认0(@UInt long → uint32)
|
|
|
|
|
|
SkinID uint32 `fieldDesc:"皮肤id默认为0" `
|
|
|
|
|
|
|
|
|
|
|
|
// 是否闪光(@UInt long → uint32,0=否,1=是)
|
2025-09-01 01:03:46 +08:00
|
|
|
|
Shiny uint32 `fieldDesc:"是不是闪" `
|
|
|
|
|
|
// AbilityType uint32 `struc:"skip"` //特性
|
2025-07-26 02:06:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-13 19:46:19 +08:00
|
|
|
|
func (pet *PetInfo) Cure() {
|
|
|
|
|
|
pet.Hp = pet.MaxHp
|
|
|
|
|
|
for i := 0; i < 4; i++ {
|
|
|
|
|
|
maxPP, ok := xmlres.SkillMap[int(pet.SkillList[i].ID)]
|
|
|
|
|
|
// 恢复至最大PP值(从配置表获取)
|
|
|
|
|
|
if pet.SkillList[i].ID != 0 && ok {
|
|
|
|
|
|
pet.SkillList[i].PP = uint32(maxPP.MaxPP)
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-18 23:58:19 +08:00
|
|
|
|
func (petinfo *PetInfo) Update() {
|
|
|
|
|
|
basic := xmlres.PetMAP[int(petinfo.ID)]
|
|
|
|
|
|
// 检查是否可以进化
|
|
|
|
|
|
if basic.EvolvesTo != 0 && // 有明确的进化
|
|
|
|
|
|
int(petinfo.Level) >= basic.EvolvingLv && // 有明确的进化等级
|
|
|
|
|
|
basic.IsLarge == 0 { // 非最终形态
|
|
|
|
|
|
|
|
|
|
|
|
petinfo.ID = uint32(basic.EvolvesTo)
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
petinfo.LvExp = petinfo.NextLvExp
|
|
|
|
|
|
petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// calculateExperience 计算指定等级和种族值所需的经验值
|
|
|
|
|
|
// level: 当前等级
|
|
|
|
|
|
// baseValue: 种族值
|
|
|
|
|
|
func calculateExperience(level uint32, baseValue uint32) uint32 {
|
|
|
|
|
|
// 计算 A 部分:向上取整(3.75 * a * (a + 1))
|
|
|
|
|
|
partA := math.Ceil(3.75 * float64(level) * float64(level+1))
|
|
|
|
|
|
|
|
|
|
|
|
// 计算 B 部分:向上取整(b * log(1 + a / 100))
|
|
|
|
|
|
// 这里使用自然对数 math.Log,如果想换底数可以用换底公式
|
|
|
|
|
|
partB := math.Log(1.0 + float64(level)/100.0)
|
|
|
|
|
|
partB = float64(baseValue) * partB
|
|
|
|
|
|
partB = math.Ceil(partB)
|
|
|
|
|
|
|
|
|
|
|
|
// 总经验是两部分之和,并向上取整
|
|
|
|
|
|
totalExp := math.Ceil(partA + partB)
|
|
|
|
|
|
return uint32(totalExp)
|
|
|
|
|
|
}
|
2025-10-13 19:46:19 +08:00
|
|
|
|
|
2025-08-10 14:19:03 +08:00
|
|
|
|
// PetEffectInfo 精灵特性信息结构
|
2025-09-15 23:18:09 +08:00
|
|
|
|
// <!-- NewSeIdx: 精灵特效索引 (默认0: 无效) -->
|
|
|
|
|
|
// <!-- Type: 0 - 仅单人战斗; 1 - 仅组队战斗; 2 - both; (默认0: 仅单人) -->
|
|
|
|
|
|
// <!-- Eid: 精灵特效eid (默认0: 无效) -->
|
|
|
|
|
|
// <!-- Stat: 精灵特效Stat: 0: 无效(默认值), 1: 永久, 2: 有`有效次数'的特效 3: 爆发特效 4: 异能精灵特质-->
|
|
|
|
|
|
// <!-- Times: 精灵特效可使用次数: 当type==2时有效 (默认值:0) -->
|
|
|
|
|
|
// <!-- Args: 特效参数, 不超过8个 (注意: 每个参数不能超过 65535) -->
|
|
|
|
|
|
// <!-- AdditionType:特效加成类型 1 种族值加成 2 技能威力加成 -->
|
2025-07-26 02:06:08 +00:00
|
|
|
|
type PetEffectInfo struct {
|
2025-09-20 13:12:45 +08:00
|
|
|
|
ItemID uint32 `struc:"uint32" json:"item_id"` //如果是能量珠,就显示
|
|
|
|
|
|
Idx uint16 `struc:"skip" json:"new_se_idx"`
|
2025-09-15 23:18:09 +08:00
|
|
|
|
Status byte `struc:"byte" json:"status"` //特性为1,能量珠为2
|
|
|
|
|
|
LeftCount byte `struc:"byte" json:"left_count"` //剩余次数
|
|
|
|
|
|
EID uint16 `struc:"uint16" json:"effect_id"` //特效ID
|
2025-09-16 22:51:22 +08:00
|
|
|
|
ArgsLen uint32 `struc:"sizeof=Args"`
|
|
|
|
|
|
|
|
|
|
|
|
Args []int ` json:"Args"` //自定义参数装载
|
2025-09-15 23:18:09 +08:00
|
|
|
|
|
2025-07-26 02:06:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 23:22:10 +00:00
|
|
|
|
// SkillInfo 精灵技能信息结构(SkillInfo)
|
2025-08-15 08:17:43 +00:00
|
|
|
|
type SkillInfo struct {
|
2025-08-31 00:27:07 +08:00
|
|
|
|
ID uint32
|
2025-09-04 19:37:28 +08:00
|
|
|
|
PP uint32
|
2025-08-15 08:17:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-26 02:06:08 +00:00
|
|
|
|
// TableName Pet's table name
|
|
|
|
|
|
func (*Pet) TableName() string {
|
|
|
|
|
|
return TableNamePet
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GroupName Pet's table group
|
|
|
|
|
|
func (*Pet) GroupName() string {
|
|
|
|
|
|
return "default"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewPet create a new Pet
|
|
|
|
|
|
func NewPet() *Pet {
|
|
|
|
|
|
return &Pet{
|
|
|
|
|
|
Model: cool.NewModel(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// init 创建表
|
|
|
|
|
|
func init() {
|
|
|
|
|
|
_ = cool.CreateTable(&Pet{})
|
|
|
|
|
|
// fmt.Println(err)
|
|
|
|
|
|
}
|
2025-10-24 00:31:38 +08:00
|
|
|
|
func GenPetInfo(
|
|
|
|
|
|
id int,
|
|
|
|
|
|
dv, natureId, abilityTypeEnum, shinyid, level int,
|
|
|
|
|
|
) *PetInfo {
|
|
|
|
|
|
// 创建随机源
|
|
|
|
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化精灵
|
|
|
|
|
|
p := &PetInfo{
|
|
|
|
|
|
ID: uint32(id),
|
|
|
|
|
|
CatchTime: uint32(time.Now().Unix()),
|
|
|
|
|
|
Level: uint32(level),
|
|
|
|
|
|
EffectInfo: make([]PetEffectInfo, 0),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 处理闪光 ----
|
|
|
|
|
|
if shinyid != -1 {
|
|
|
|
|
|
p.Shiny = uint32(shinyid)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 性格 ----
|
|
|
|
|
|
if natureId == -1 {
|
|
|
|
|
|
p.Nature = uint32(rng.Intn(25))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
p.Nature = uint32(natureId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 个体值(DV)----
|
|
|
|
|
|
if dv == -1 {
|
|
|
|
|
|
p.Dv = uint32(CalculateIndividualValue(rng))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if dv < 0 {
|
|
|
|
|
|
dv = 0
|
|
|
|
|
|
} else if dv > 31 {
|
|
|
|
|
|
dv = 31
|
|
|
|
|
|
}
|
|
|
|
|
|
p.Dv = uint32(dv)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 特性 ----
|
|
|
|
|
|
switch {
|
|
|
|
|
|
case abilityTypeEnum == 0:
|
|
|
|
|
|
// 无特性
|
|
|
|
|
|
case abilityTypeEnum > 0:
|
|
|
|
|
|
// 指定特性
|
|
|
|
|
|
if v, ok := xmlres.PlayerEffectMAP[int(abilityTypeEnum)]; ok {
|
|
|
|
|
|
p.EffectInfo = append(p.EffectInfo, PetEffectInfo{
|
|
|
|
|
|
Idx: uint16(gconv.Int16(v.Idx)),
|
|
|
|
|
|
Status: 1,
|
|
|
|
|
|
EID: uint16(gconv.Int16(v.Eid)),
|
|
|
|
|
|
Args: v.ArgsS,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
case abilityTypeEnum == -1:
|
|
|
|
|
|
// 随机特性
|
|
|
|
|
|
randomIndex := rng.Intn(len(xmlres.PlayerEffectMAP))
|
|
|
|
|
|
|
|
|
|
|
|
var i int
|
|
|
|
|
|
for _, v := range xmlres.PlayerEffectMAP {
|
|
|
|
|
|
|
|
|
|
|
|
if i == randomIndex {
|
|
|
|
|
|
p.EffectInfo = append(p.EffectInfo, PetEffectInfo{
|
|
|
|
|
|
Idx: uint16(gconv.Int16(v.Idx)),
|
|
|
|
|
|
Status: 1,
|
|
|
|
|
|
EID: uint16(gconv.Int16(v.Eid)),
|
|
|
|
|
|
Args: v.ArgsS,
|
|
|
|
|
|
})
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
i++
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 技能学习 ----
|
|
|
|
|
|
skills := LastFourElements(p.GetLevelRangeCanLearningSkills(0, p.Level)) // 最后四个技能
|
|
|
|
|
|
p.SkillListLen = uint32(len(skills))
|
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < len(skills) && i < len(p.SkillList); i++ {
|
|
|
|
|
|
skillID := skills[i]
|
|
|
|
|
|
if info, ok := xmlres.SkillMap[int(skillID)]; ok {
|
|
|
|
|
|
p.SkillList[i].ID = skillID
|
|
|
|
|
|
p.SkillList[i].PP = uint32(info.MaxPP)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 属性计算 ----
|
|
|
|
|
|
p.CalculatePetPane()
|
|
|
|
|
|
p.Hp = p.MaxHp
|
|
|
|
|
|
|
|
|
|
|
|
p.Update()
|
|
|
|
|
|
|
|
|
|
|
|
return p
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func LastFourElements[T any](s []T) []T {
|
|
|
|
|
|
n := len(s)
|
|
|
|
|
|
if n <= 4 {
|
|
|
|
|
|
// 切片长度小于等于4时,返回整个切片
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
// 切片长度大于4时,返回最后4个元素(从n-4索引到末尾)
|
|
|
|
|
|
return s[n-4:]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 除数数组(放大100倍)
|
|
|
|
|
|
// 数组按递增顺序排列,用于判断个体值等级
|
|
|
|
|
|
var divisors = []int{
|
|
|
|
|
|
600, 1200, 1900, 2700, 3600, 4600, 5700, 6900, 8200, 9600,
|
|
|
|
|
|
11100, 12700, 14400, 16200, 18100, 20100, 22100, 24000,
|
|
|
|
|
|
25800, 27500, 29100, 30600, 32000, 33300, 34500, 35600,
|
|
|
|
|
|
36600, 37500, 38300, 39000, 39600,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CalculateIndividual 根据给定的a值计算个体值
|
|
|
|
|
|
// 返回值表示a大于等于多少个除数(范围:0-31)
|
|
|
|
|
|
func CalculateIndividual(a int) int {
|
|
|
|
|
|
individual := 0
|
|
|
|
|
|
for _, divisor := range divisors {
|
|
|
|
|
|
if a >= divisor {
|
|
|
|
|
|
individual++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
break // 数组是递增的,可提前跳出循环
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return individual
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CalculateIndividualValue 计算个体值(0-31)
|
|
|
|
|
|
// 接收外部随机数生成器,便于控制随机性和复用
|
|
|
|
|
|
func CalculateIndividualValue(random *rand.Rand) int {
|
|
|
|
|
|
// 生成0-40000的随机数,作为个体值计算的输入
|
|
|
|
|
|
a := random.Intn(40001)
|
|
|
|
|
|
return CalculateIndividual(a)
|
|
|
|
|
|
}
|