Files
bl/modules/blazing/model/pet.go
昔念 cccf26788e fix(socket): 玩家断开连接时增加保存锁,避免重复保存
在玩家断开连接时,使用 sync.Once 确保只保存一次玩家数据,
防止因并发或多次触发导致的数据异常。

feat(fight): 增加战斗资格判断与邀请取消功能

- 新增 Player.CanFight() 方法用于统一判断是否可以参与战斗
- 在多个战斗相关接口中加入 CanFight 检查
- 添加“取消战斗邀请”指令及处理逻辑(cmd: 2402)
- 修复部分错误码不准确的问题,提升提示一致性

refactor(login): 优化登录流程并增强健壮性

- 提前校验 session 合法性
- 增强获取玩家信息后的空指针检查
- 调整挖矿数据重置方式为 defer 执行
- 优化日志输出内容,便于调试追踪

docs(model): 更新部门、菜单等模型字段命名规范

将 orderNum 字段改为 ordernum,保持数据库列名风格一致,
同时更新了 base_sys_role 中 userId 为 userid。

perf(rate-limit): 提高登录接口的限流 Burst 容量

调整限流器配置,将请求 burst 容量从 2 提升至 5,
以应对短时间高频访问场景,改善用户体验。

chore(build): 忽略新增编译产物和临时文件

在 .gitignore 中添加 logic/logic2、login/login 等新生成文件路径,
避免误提交二进制文件到版本控制。
2025-10-31 00:53:22 +08:00

360 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
import (
"blazing/common/data/xmlres"
"blazing/cool"
"math"
"math/rand"
"time"
"github.com/gogf/gf/v2/util/gconv"
)
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 int `gorm:"not null;comment:'是否在背包中'" json:"in_bag"` //"0为放入仓库1为放入背包
CatchTime uint32 `gorm:"not null;unique;comment:'捕捉时间'" json:"catch_time"` //唯一键
// Owner uint32 `struc:"skip"` //仅作为存储
// FreedTime uint32 `struc:"skip"` //放生时间
//是否可交易这里应该定义在精灵ID里
//是否上架
Data string `gorm:"type:text;not null;comment:'精灵全部数据'" json:"data"`
}
type PetEX struct {
Pet
Data PetInfo `orm:"data" json:"data"`
}
// PetInfo 精灵信息结构(合并后的优化版本)
type PetInfo struct {
// 精灵编号(@UInt long → uint32
ID uint32 `fieldDesc:"精灵编号" `
// 名字默认为全0补齐到16字节固定长度 → [16]byte
Name string `struc:"[16]byte" `
// 个体值(@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:"最大生命" `
// * 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:"攻击" `
// // 防御(@UInt long → uint32
// Defence uint32 `fieldDesc:"防御" `
// // 特攻(@UInt long → uint32
// SpecialAttack uint32 `fieldDesc:"特攻" `
// // 特防(@UInt long → uint32
// SpecialDefence uint32 `fieldDesc:"特防" `
// // 速度(@UInt long → uint32
// Speed uint32 `fieldDesc:"速度" `
// 生命学习力(@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 `struc:"sizeof=SkillList"`
// 技能信息固定4条空则赋值0固定长度List → [4]SkillInfo零值即符合“赋值0”
SkillList []SkillInfo
// 捕捉时间(@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 → uint320=否1=是)
Shiny uint32 `fieldDesc:"是不是闪" `
// AbilityType uint32 `struc:"skip"` //特性
}
func (pet *PetInfo) Cure() {
pet.Hp = pet.MaxHp
for i := 0; i < len(pet.SkillList); 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)
}
}
}
// 传入bool则不升级
func (petinfo *PetInfo) Update(t ...bool) {
basic := xmlres.PetMAP[int(petinfo.ID)]
if len(t) == 0 {
// 检查是否可以进化
if basic.EvolvesTo != 0 && // 有明确的进化
int(petinfo.Level) >= basic.EvolvingLv && // 有明确的进化等级
basic.IsLarge == 0 { // 非最终形态
petinfo.ID = uint32(basic.EvolvesTo)
basic = xmlres.PetMAP[int(petinfo.ID)] //重新计算
}
}
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)
}
// PetEffectInfo 精灵特性信息结构
// <!-- 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 技能威力加成 -->
type PetEffectInfo struct {
ItemID uint32 `struc:"uint32" json:"item_id"` //如果是能量珠,就显示
Idx uint16 `struc:"skip" json:"new_se_idx"`
Status byte `struc:"byte" json:"status"` //特性为1,能量珠为2
LeftCount byte `struc:"byte" json:"left_count"` //剩余次数
EID uint16 `struc:"uint16" json:"effect_id"` //特效ID
ArgsLen uint32 `struc:"sizeof=Args"`
Args []int ` json:"Args"` //自定义参数装载
}
// SkillInfo 精灵技能信息结构SkillInfo
type SkillInfo struct {
ID uint32
PP uint32
}
// 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)
}
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)) // 最后四个技能
for i := 0; i < len(skills) && i < 4; i++ {
skillID := skills[i]
if info, ok := xmlres.SkillMap[int(skillID)]; ok {
p.SkillList = append(p.SkillList, SkillInfo{ID: skillID, PP: uint32(info.MaxPP)})
}
}
if len(p.SkillList) > 4 {
p.SkillList = p.SkillList[:4]
}
// ---- 属性计算 ----
p.CalculatePetPane()
p.Hp = p.MaxHp
p.Update(true)
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)
}