package model import ( "blazing/common/data" "blazing/common/data/xmlres" "blazing/common/utils" "blazing/cool" "encoding/json" "blazing/modules/config/model" "blazing/modules/config/service" "errors" "fmt" "math" "time" "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/grand" "github.com/jinzhu/copier" "github.com/samber/lo" ) const TableNamePet = "player_pet" // Pet mapped from table type Pet struct { Base PlayerID uint32 `gorm:"not null;index:idx_pet_by_player_id;comment:'所属玩家ID'" json:"player_id"` Free int `gorm:"not null;default:0;comment:'是否放生'" json:"free"` //"0为放入仓库,1为放生,2为上架 CatchTime uint32 `gorm:"not null;comment:'捕捉时间'" json:"catch_time"` //唯一键 SalePrice uint32 `gorm:"not null;default:0;comment:'出售价格'" json:"sale_price"` SaleCount uint32 `gorm:"not null;default:0;comment:'出售次数'" json:"sale_count"` // Owner uint32 `struc:"skip"` //仅作为存储 // FreedTime uint32 `struc:"skip"` //放生时间 //是否可交易,这里应该定义在精灵ID里 //是否上架 Data PetInfo `gorm:"type:jsonb;not null;comment:'精灵全部数据'" json:"data"` } // GetOffShelfFee 计算商品下架扣费规则 // 返回值:allowOffShelf(是否允许下架), feeRate(扣费比例), err(错误信息) func (tt *Pet) GetOffShelfFee() (bool, float64, error) { // 获取当前时间 now := time.Now() // 计算上架到现在的时长(小时) duration := now.Sub(tt.UpdateTime.Time) hours := duration.Hours() // 阶段1:0-24小时(展示期,可下架但阶梯扣费) if hours >= 0 && hours < 24 { switch { case hours < 6: // 0-6小时:0扣费,允许下架 return true, 0.0, nil case hours < 12: // 6-12小时:扣30%,允许下架 return true, 0.3, nil case hours < 18: // 12-18小时:扣60%,允许下架 return true, 0.6, nil case hours < 24: // 18-24小时:扣90%,允许下架 return true, 0.9, nil } } // 阶段2:24-30小时(禁止下架) if hours >= 24 && hours < 30 { return false, 0.0, errors.New("24-30小时交易期,禁止下架") } // 阶段3:30小时后(自由下架,0扣费) if hours >= 30 { return true, 0.0, nil } // 异常情况(上架时间在未来) return false, 0.0, errors.New("商品未到上架时间,无法操作下架") } // CalculateOffShelfAmount 根据扣费比例计算实际扣费金额 func (tt *Pet) CalculateOffShelfAmount(feeRate float64) float64 { return float64(tt.SalePrice) * feeRate } type Attr uint32 func (r Attr) sub() uint32 { if r > 0 { return uint32(r) - 1 } return 0 } // PetInfo 精灵信息结构(合并后的优化版本) type PetInfo struct { ID uint32 `fieldDesc:"精灵编号" ` // 名字:默认为全0,补齐到16字节(固定长度 → [16]byte) Name string `struc:"[16]byte" json:"Name,omitempty"` Gender int `struc:"uint16" fieldDesc:"性别" ` //0:无性别,1:雄性, 2:雌性 //generation Generation uint16 `fieldDesc:"世代" ` Dv uint32 `struc:"uint32" ` Nature uint32 `fieldDesc:"性格" ` Level uint32 `fieldDesc:"等级" ` Exp int64 `struc:"uint32"` LvExp int64 `struc:"uint32"` NextLvExp int64 `struc:"uint32"` Hp uint32 MaxHp uint32 `fieldDesc:"最大生命" ` // * battle_lv: atk(0), def(1), sp_atk(2), sp_def(3), spd(4), accuracy(5) Prop [5]uint32 `fieldDesc:"属性" ` // * ev:生命学习力,攻击学习力,防御学习力,特攻学习力,特防学习力,速度学习力 Ev [6]uint32 `fieldDesc:"属性" ` SkillListLen uint32 `struc:"sizeof=SkillList" json:"-"` // 技能信息:固定4条,空则赋值0(固定长度List → [4]SkillInfo,零值即符合“赋值0”) SkillList []SkillInfo CatchTime uint32 //显式忽略,不参与序列化 OldCatchTime uint32 `struc:"skip" fieldDesc:"旧捕捉时间" ` CatchMap uint32 `json:"CatchMap,omitempty"` CatchRect uint32 `json:"CatchRect,omitempty"` CatchLevel uint32 `fieldDesc:"捕获等级 默认为0" ` EffectInfoLen uint16 `struc:"sizeof=EffectInfo" json:"-"` // 特性列表:长度用UShort存储(变长List → []PetEffectInfo + 长度前缀规则) 第一个一定是特性 EffectInfo []PetEffectInfo SkinID uint32 `fieldDesc:"皮肤id默认为0" ` ShinyLen uint32 `struc:"sizeof=ShinyInfo"` ShinyInfo []data.GlowFilter `json:"ShinyInfo,omitempty"` //时间轮转,然后effect根据type同时只共存一个,特性是1 特质是1,柱子是两种,魂印是一个,然后异色字段,然后特训技能字段 ExtSKill []uint32 `struc:"skip"` //特训技能 ExtSkin []uint32 `struc:"skip"` //可用皮肤 } func (pet *PetInfo) ConfigBoss(bm model.PetBaseConfig) { var color data.GlowFilter err := json.Unmarshal([]byte(bm.Color), &color) if err == nil && color.Alpha != 0 { pet.ShinyInfo = append(pet.ShinyInfo, color) } if bm.Hp != 0 { pet.Hp = uint32(bm.Hp) pet.MaxHp = uint32(bm.Hp) } if len(bm.Prop) == 5 { for i := 0; i < 5; i++ { if bm.Prop[i] != 0 { pet.Prop[i] = bm.Prop[i] } } //monster.Prop = [5]uint32(bm.Prop) } if len(bm.SKill) != 0 { for i := 0; i < 4; i++ { if bm.SKill[i] != 0 { pet.SkillList[i].ID = bm.SKill[i] } } } } func (pet *PetInfo) Type() int { return xmlres.PetMAP[int(pet.ID)].Type } func (pet *PetInfo) ModelHP(tt int64) { if pet.Hp <= 0 { if tt > int64(pet.Hp) { pet.Hp = 0 } else { pet.Hp += uint32(tt) } } else { pet.Hp += uint32(tt) if pet.Hp > pet.MaxHp { pet.Hp = pet.MaxHp } } } func (pet *PetInfo) HealPP(value int) { for i := 0; i < len(pet.SkillList); i++ { if value == -1 { pet.SkillList[i].PP = uint32(xmlres.SkillMap[int(pet.SkillList[i].ID)].MaxPP) } else { pet.SkillList[i].PP += uint32(value) pet.SkillList[i].PP = utils.Min(pet.SkillList[i].PP, uint32(xmlres.SkillMap[int(pet.SkillList[i].ID)].MaxPP)) } } } // 定义常量,提升可维护性(避免魔法数字) const ( maxSingleEV uint32 = 255 // 单个EV最大值 maxTotalEV uint32 = 510 // 6个EV总和最大值 evFieldCount = 6 // EV字段数量(固定6个) ) // AddEV 优化后的EV值增加方法(符合Go命名规范:大写导出,动词开头) // 功能:为宠物6个EV值增加增量,保证单个≤255、总和≤510 // 参数:evadd - 6个EV字段的增量数组(长度必须为6) // 返回:error - 参数非法/逻辑异常时返回错误;bool - 是否触发了超额削减(方便业务监控) func (pet *PetInfo) AddEV(ev_add []int64) (bool, error) { // 1. 参数安全校验:避免数组越界panic if len(ev_add) != evFieldCount { return false, fmt.Errorf("evadd长度必须为%d,当前为%d", evFieldCount, len(ev_add)) } if len(pet.Ev) != evFieldCount { return false, errors.New("pet.Ev未初始化或长度不为6") } // 2. 第一步:直接添加并限制单项最大值(按索引顺序处理) var tempEV [evFieldCount]uint32 for i := 0; i < evFieldCount; i++ { // 直接累加增量 tempEV[i] = pet.Ev[i] + uint32(ev_add[i]) // 单项不超过255 if tempEV[i] > maxSingleEV { tempEV[i] = maxSingleEV } } // 3. 计算增量后的总和,检查是否超过510 totalTemp := lo.Sum(tempEV[:]) // 4. 若总和超额,按索引顺序(0→5)削减(优先削减前面的字段) hasCut := false if totalTemp > maxTotalEV { overTotal := totalTemp - maxTotalEV // 需要削减的总量 hasCut = true // 按索引顺序遍历削减(从第0个字段开始,依次处理) for i := 0; i < evFieldCount && overTotal > 0; i++ { // 可削减的最大值:最多削减到原始值(不触碰添加前的基础EV) cutAble := tempEV[i] - pet.Ev[i] if cutAble <= 0 { continue // 该字段无增量可削减,跳过 } // 实际削减量:取"可削减量"和"剩余需削减量"的较小值 cut := cutAble if cut > overTotal { cut = overTotal } // 执行削减 tempEV[i] -= cut overTotal -= cut } // 极端情况:即使削减所有增量后仍超额(如原始EV总和已超510),继续按顺序削减原始值 if overTotal > 0 { for i := 0; i < evFieldCount && overTotal > 0; i++ { // 此时可削减到0(根据业务需求调整,也可返回错误) cutAble := tempEV[i] if cutAble <= 0 { continue } cut := cutAble if cut > overTotal { cut = overTotal } tempEV[i] -= cut overTotal -= cut } } } // 5. 将处理后的结果赋值给原EV数组 copy(pet.Ev[:], tempEV[:]) return hasCut, nil } 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) } } } // 强制选色,这个是给熔炉用的 func (pet *PetInfo) FixShiny() bool { co := service.NewShinyService().RandShiny(pet.ID) if co != nil { pet.ShinyInfo = []data.GlowFilter{*co} return true } return false } // 比重融合 func (pet *PetInfo) RandomByWeightShiny() { co := service.NewShinyService().RandomByWeightShiny(pet.ID) if co != nil { pet.ShinyInfo = []data.GlowFilter{*co} } } func (pet *PetInfo) IsShiny() bool { return len(pet.ShinyInfo) > 0 } // 随机特性 func (pet *PetInfo) RnadEffect() { for _, v := range xmlres.PlayerEffectMAP { if gconv.Int(v.StarLevel) == 0 { ret := &PetEffectInfo{ Idx: uint16(gconv.Int16(v.Idx)), Status: 1, EID: uint16(gconv.Int16(v.Eid)), Args: v.ArgsS, } _, eff, ok := pet.GetEffect(1) if ok { if eff.Idx == ret.Idx { continue } copier.Copy(eff, ret) } else { pet.EffectInfo = append(pet.EffectInfo, *ret) } break } } } // 0: Boss特性 // 1: 特性 // 2: 能量珠 // 3: 爆发特效 // 4: 异能精灵特质 // 5: 特训 // 6: 魂印 // 7 :繁殖加成 // 8 :体力提升加成 const ( maxHPUpEffectIdx uint16 = 60000 maxHPUpEffectStatus byte = 8 maxHPUpEffectEID uint16 = 26 maxHPUpEffectCap = 20 trainingEffectStatus byte = 5 trainingAttrEffectIdx uint16 = 60001 trainingPowerEffectIdx uint16 = 60002 trainingAttrEffectEID uint16 = 247 trainingPowerEffectEID uint16 = 239 ) // 繁殖加成,体力提升加成 ,这里是防止和其他重复所以定义不同类别,但是实际上,能量珠那些事调用不同id的effect实现 // func (pet *PetInfo) GetEffect(ptype int) (int, *PetEffectInfo, bool) { return utils.FindWithIndex(pet.EffectInfo, func(item PetEffectInfo) bool { return int(item.Status) == ptype }) } func (pet *PetInfo) getEffectByStatusAndEID(status byte, eid uint16) (int, *PetEffectInfo, bool) { return utils.FindWithIndex(pet.EffectInfo, func(item PetEffectInfo) bool { return item.Status == status && item.EID == eid }) } func ensureEffectArgsLen(args []int, size int) []int { if len(args) >= size { return args } next := make([]int, size) copy(next, args) return next } func (pet *PetInfo) addTrainingEffectDelta(idx uint16, eid uint16, argsLen int, argIndex int, value int) bool { if pet == nil || value <= 0 || argIndex < 0 || argIndex >= argsLen { return false } if _, eff, ok := pet.getEffectByStatusAndEID(trainingEffectStatus, eid); ok { if eff.Idx == 0 { eff.Idx = idx } eff.Status = trainingEffectStatus eff.EID = eid eff.Args = ensureEffectArgsLen(eff.Args, argsLen) eff.Args[argIndex] += value return true } args := make([]int, argsLen) args[argIndex] = value pet.EffectInfo = append(pet.EffectInfo, PetEffectInfo{ Idx: idx, Status: trainingEffectStatus, EID: eid, Args: args, }) return true } func (pet *PetInfo) AddTrainingAttrBonus(attr int, value int) bool { return pet.addTrainingEffectDelta(trainingAttrEffectIdx, trainingAttrEffectEID, 6, attr, value) } func (pet *PetInfo) AddTrainingPowerBonus(value int) bool { return pet.addTrainingEffectDelta(trainingPowerEffectIdx, trainingPowerEffectEID, 2, 0, value) } func (pet *PetInfo) AddMaxHPUpEffect(itemID uint32, value int) bool { if pet == nil || value <= 0 { return false } if _, eff, ok := pet.GetEffect(int(maxHPUpEffectStatus)); ok { current := 0 if len(eff.Args) >= 2 && eff.Args[0] == 0 && eff.Args[1] > 0 { current = eff.Args[1] } if current >= maxHPUpEffectCap { return false } next := current + value if next > maxHPUpEffectCap { next = maxHPUpEffectCap } eff.ItemID = itemID eff.Idx = maxHPUpEffectIdx eff.Status = maxHPUpEffectStatus eff.EID = maxHPUpEffectEID eff.Args = []int{0, next} return next > current } if value > maxHPUpEffectCap { value = maxHPUpEffectCap } pet.EffectInfo = append(pet.EffectInfo, PetEffectInfo{ ItemID: itemID, Idx: maxHPUpEffectIdx, Status: maxHPUpEffectStatus, EID: maxHPUpEffectEID, Args: []int{0, value}, }) return true } func (pet *PetInfo) Downgrade(level uint32) { for pet.Level > uint32(level) { basic, ok := xmlres.PetMAP[int(pet.ID)] if ok { if basic.EvolvesFrom != 0 && xmlres.PetMAP[int(basic.EvolvesFrom)].EvolvFlag == 0 { pet.ID = uint32(basic.EvolvesFrom) } } pet.Level-- //进行降级操作 } pet.NextLvExp = 0 pet.Update(false) } // 执行进化逻辑 ,是否进化 func (petinfo *PetInfo) Update(isup bool) { // 最大进化次数限制(防止配置表闭环导致死循环) maxEvolveTimes := 1 evolveCount := 0 // 循环进化:直到不满足进化条件 或 达到最大进化次数 for { // 防止死循环,超出次数直接退出 if evolveCount >= maxEvolveTimes { break } // 获取当前宠物形态的配置 basic, ok := xmlres.PetMAP[int(petinfo.ID)] // 配置不存在,直接退出循环 if !ok { break } if !isup { petinfo.LvExp = calculatePreviousLevelExperience(petinfo.Level, basic.GetBasic()) petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic()) return } // 升级时保留上一等级需求,供经验结算使用。 petinfo.LvExp = petinfo.NextLvExp petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic()) // 检查是否满足进化条件 canEvolve := basic.EvolvesTo != 0 && // 有明确的进化目标 int(petinfo.Level) >= basic.EvolvingLv && // 等级达到进化要求 basic.IsLarge == 0 // 非最终形态 // 不满足进化条件,退出循环 if !canEvolve { break } // 执行进化:更新宠物ID为进化后形态 petinfo.ID = uint32(basic.EvolvesTo) evolveCount++ // 进化次数+1 } } func calculatePreviousLevelExperience(level uint32, baseValue uint32) int64 { if level <= 1 { return 0 } return calculateExperience(level-1, baseValue) } // calculateExperience 计算指定等级和种族值所需的经验值 // level: 当前等级 // baseValue: 种族值 func calculateExperience(level uint32, baseValue uint32) int64 { // 计算 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 int64(totalExp) } // PetEffectInfo 精灵特性信息结构 // // // // // // // type PetEffectInfo struct { ItemID uint32 `struc:"uint32" json:"item_id"` //如果是能量珠,就显示 Idx uint16 `struc:"skip" json:"new_se_idx"` //Type byte `struc:"skip" json:"type"` //pvp pve特性区分,通过具体effect实现 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" json:"-"` Args []int ` json:"Args"` //自定义参数装载 } // SkillInfo 精灵技能信息结构(SkillInfo) type SkillInfo struct { ID uint32 PP uint32 } func (s *SkillInfo) Use(count int) { s.PP -= uint32(count) } // 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{ Base: *NewBase(), } } // init 创建表 func init() { _ = cool.CreateTable(&Pet{}) // fmt.Println(err) } // GenPetInfo 生成一个新的精灵实例 // - 参数为 -1 时表示随机生成对应属性 // * @param petTypeId 精灵类型ID // * @param individualValue 个体值(0-31) // * @param natureId 性格ID(0-24) // * @param abilityTypeEnum 特性类型ID(0=无, >0=指定, -1=随机) // * @param shinyid 闪光ID(-1=随机) // * @param level 等级(1-100) // * @return 生成的精灵实体 func GenPetInfo( id int, dv, natureId, abilityTypeEnum, level int, shinyid []data.GlowFilter, gen int, ) *PetInfo { if id == 0 { return nil } // 创建随机源 //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 != nil { //todo 待实现异色字段 p.ShinyInfo = shinyid // r := service.NewShinyService().GetShiny(shinyid) // if r != nil { // p.ShinyInfo = append(p.ShinyInfo, *r) // } // p.Shiny = uint32(shinyid) } if gen == -1 { p.Gender = grand.N(1, 4) if cool.Config.ServerInfo.IsVip != 0 { p.Gender = grand.N(1, 2) } if p.Gender == 3 || p.Gender == 4 { p.Gender = 0 } } // ---- 性格 ---- if natureId == -1 { p.Nature = uint32(grand.Intn(25)) } else { p.Nature = uint32(natureId) } // ---- 个体值(DV)---- if dv == -1 { p.Dv = uint32(CalculateIndividualValue()) } 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: for _, v := range xmlres.PlayerEffectMAP { if gconv.Int(v.StarLevel) == 0 { p.EffectInfo = append(p.EffectInfo, PetEffectInfo{ Idx: uint16(gconv.Int16(v.Idx)), Status: 1, EID: uint16(gconv.Int16(v.Eid)), Args: v.ArgsS, }) break } } } // ---- 技能学习 ---- skills := utils.LastFourElements(p.GetLevelRangeCanLearningSkills(0, p.Level), 4) // 最后四个技能 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(0) p.Update(false) return p } // 除数数组(放大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() int { // 生成0-40000的随机数,作为个体值计算的输入 a := grand.Intn(40001) return CalculateIndividual(a) }