1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
昔念
2026-04-15 00:07:36 +08:00
parent de755f8fd0
commit 6f51a2e349
11 changed files with 242 additions and 125 deletions

View File

@@ -3,6 +3,7 @@ package cool
import ( import (
_ "blazing/contrib/drivers/pgsql" _ "blazing/contrib/drivers/pgsql"
"blazing/cool/cooldb" "blazing/cool/cooldb"
"sync"
"github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
@@ -10,6 +11,11 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
var (
autoMigrateMu sync.Mutex
autoMigrateModels []IModel
)
// 初始化数据库连接供gorm使用 // 初始化数据库连接供gorm使用
func InitDB(group string) (*gorm.DB, error) { func InitDB(group string) (*gorm.DB, error) {
// var ctx context.Context // var ctx context.Context
@@ -54,9 +60,33 @@ func getDBbyModel(model IModel) *gorm.DB {
// 根据entity结构体创建表 // 根据entity结构体创建表
func CreateTable(model IModel) error { func CreateTable(model IModel) error {
if Config.AutoMigrate { autoMigrateMu.Lock()
autoMigrateModels = append(autoMigrateModels, model)
autoMigrateMu.Unlock()
return nil
}
// RunAutoMigrate 显式执行已注册模型的建表/迁移。
func RunAutoMigrate() error {
if !Config.AutoMigrate {
return nil
}
autoMigrateMu.Lock()
models := append([]IModel(nil), autoMigrateModels...)
autoMigrateMu.Unlock()
seen := make(map[string]struct{}, len(models))
for _, model := range models {
key := model.GroupName() + ":" + model.TableName()
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
db := getDBbyModel(model) db := getDBbyModel(model)
return db.AutoMigrate(model) if err := db.AutoMigrate(model); err != nil {
return err
}
} }
return nil return nil
} }

View File

@@ -72,7 +72,7 @@ func startMapBossFight(
ai *player.AI_player, ai *player.AI_player,
fn func(model.FightOverInfo), fn func(model.FightOverInfo),
) (*fight.FightC, errorcode.ErrorCode) { ) (*fight.FightC, errorcode.ErrorCode) {
ourPets := p.GetPetInfo(100) ourPets := p.GetPetInfo(p.CurrentMapPetLevelLimit())
oppPets := ai.GetPetInfo(0) oppPets := ai.GetPetInfo(0)
if mapNode != nil && mapNode.IsGroupBoss != 0 { if mapNode != nil && mapNode.IsGroupBoss != 0 {
if len(ourPets) > 0 && len(oppPets) > 0 { if len(ourPets) > 0 && len(oppPets) > 0 {
@@ -98,8 +98,8 @@ func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *pl
if err = p.CanFight(); err != 0 { if err = p.CanFight(); err != 0 {
return nil, err return nil, err
} }
if req.Number > 9 { if int(req.Number) >= len(p.Data) {
return nil, errorcode.ErrorCodes.ErrSystemError return nil, errorcode.ErrorCodes.ErrPokemonNotHere
} }
refPet := p.Data[req.Number] refPet := p.Data[req.Number]
@@ -114,7 +114,7 @@ func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *pl
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
_, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) { _, err = fight.NewFight(p, ai, p.GetPetInfo(p.CurrentMapPetLevelLimit()), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
handleNpcFightRewards(p, foi, monster) handleNpcFightRewards(p, foi, monster)
}) })
if err != 0 { if err != 0 {
@@ -236,7 +236,7 @@ func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig c
func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) { func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) {
if refPet.ID == 0 { if refPet.ID == 0 {
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists return nil, nil, errorcode.ErrorCodes.ErrPokemonNotHere
} }
monster := model.GenPetInfo( monster := model.GenPetInfo(

View File

@@ -17,11 +17,16 @@ const (
// c: 当前玩家对象 // c: 当前玩家对象
// 返回: 分配结果和错误码 // 返回: 分配结果和错误码
func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
_, currentPet, found := c.FindPet(data.CacthTime) slot, found := c.FindPetBagSlot(data.CacthTime)
if !found { if !found {
return nil, errorcode.ErrorCodes.Err10401 return nil, errorcode.ErrorCodes.Err10401
} }
currentPet := slot.PetInfoPtr()
if currentPet == nil {
return nil, errorcode.ErrorCodes.Err10401
}
var targetTotal uint32 var targetTotal uint32
var currentTotal uint32 var currentTotal uint32
for i, evValue := range data.EVs { for i, evValue := range data.EVs {

View File

@@ -0,0 +1,45 @@
package controller
import (
"testing"
"blazing/logic/service/player"
playermodel "blazing/modules/player/model"
)
func TestPetEVDiy_AppliesToBackupPet(t *testing.T) {
p := player.NewPlayer(nil)
p.Info = &playermodel.PlayerInfo{
EVPool: 20,
PetList: []playermodel.PetInfo{
{CatchTime: 1},
},
BackupPetList: []playermodel.PetInfo{
{
CatchTime: 2,
Level: 100,
Ev: [6]uint32{0, 4, 0, 0, 0, 0},
},
},
}
data := &PetEV{
CacthTime: 2,
EVs: [6]uint32{0, 8, 4, 0, 0, 0},
}
_, err := (Controller{}).PetEVDiy(data, p)
if err != 0 {
t.Fatalf("PetEVDiy returned error: %v", err)
}
got := p.Info.BackupPetList[0].Ev
want := [6]uint32{0, 8, 4, 0, 0, 0}
if got != want {
t.Fatalf("backup pet EV mismatch, got %v want %v", got, want)
}
if gotPool, wantPool := p.Info.EVPool, int64(12); gotPool != wantPool {
t.Fatalf("EVPool mismatch, got %d want %d", gotPool, wantPool)
}
}

View File

@@ -4,18 +4,20 @@ import (
"blazing/common/socket/errorcode" "blazing/common/socket/errorcode"
"blazing/logic/service/common" "blazing/logic/service/common"
"blazing/logic/service/pet" "blazing/logic/service/pet"
"blazing/logic/service/player" playersvc "blazing/logic/service/player"
"blazing/modules/player/model" "blazing/modules/player/model"
) )
// GetPetInfo 获取精灵信息 // GetPetInfo 获取精灵信息
func (h Controller) GetPetInfo( func (h Controller) GetPetInfo(
data *GetPetInfoInboundInfo, data *GetPetInfoInboundInfo,
player *player.Player) (result *model.PetInfo, player *playersvc.Player) (result *model.PetInfo,
err errorcode.ErrorCode) { err errorcode.ErrorCode) {
levelLimit := player.CurrentMapPetLevelLimit()
if slot, found := player.FindPetBagSlot(data.CatchTime); found { if slot, found := player.FindPetBagSlot(data.CatchTime); found {
if petInfo := slot.PetInfoPtr(); petInfo != nil { if petInfo := slot.PetInfoPtr(); petInfo != nil {
result = petInfo petCopy := playersvc.ApplyPetLevelLimit(*petInfo, levelLimit)
result = &petCopy
return result, 0 return result, 0
} }
} }
@@ -25,16 +27,18 @@ func (h Controller) GetPetInfo(
return nil, errorcode.ErrorCodes.ErrPokemonNotExists return nil, errorcode.ErrorCodes.ErrPokemonNotExists
} }
result = &ret.Data petData := ret.Data
petData = playersvc.ApplyPetLevelLimit(petData, levelLimit)
result = &petData
return result, 0 return result, 0
} }
// GetUserBagPetInfo 获取主背包和并列备用精灵列表 // GetUserBagPetInfo 获取主背包和并列备用精灵列表
func (h Controller) GetUserBagPetInfo( func (h Controller) GetUserBagPetInfo(
data *GetUserBagPetInfoInboundEmpty, data *GetUserBagPetInfoInboundEmpty,
player *player.Player) (result *pet.GetUserBagPetInfoOutboundInfo, player *playersvc.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
err errorcode.ErrorCode) { err errorcode.ErrorCode) {
return player.GetUserBagPetInfo(), 0 return player.GetUserBagPetInfo(player.CurrentMapPetLevelLimit()), 0
} }
// GetPetListInboundEmpty 定义请求或响应数据结构。 // GetPetListInboundEmpty 定义请求或响应数据结构。
@@ -45,7 +49,7 @@ type GetPetListInboundEmpty struct {
// GetPetList 获取当前主背包列表 // GetPetList 获取当前主背包列表
func (h Controller) GetPetList( func (h Controller) GetPetList(
data *GetPetListInboundEmpty, data *GetPetListInboundEmpty,
player *player.Player) (result *pet.GetPetListOutboundInfo, player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
err errorcode.ErrorCode) { err errorcode.ErrorCode) {
return buildPetListOutboundInfo(player.Info.PetList), 0 return buildPetListOutboundInfo(player.Info.PetList), 0
} }
@@ -58,7 +62,7 @@ type GetPetListFreeInboundEmpty struct {
// GetPetReleaseList 获取仓库可放生列表 // GetPetReleaseList 获取仓库可放生列表
func (h Controller) GetPetReleaseList( func (h Controller) GetPetReleaseList(
data *GetPetListFreeInboundEmpty, data *GetPetListFreeInboundEmpty,
player *player.Player) (result *pet.GetPetListOutboundInfo, player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
err errorcode.ErrorCode) { err errorcode.ErrorCode) {
return buildPetListOutboundInfo(player.WarehousePetList()), 0 return buildPetListOutboundInfo(player.WarehousePetList()), 0
@@ -67,7 +71,7 @@ func (h Controller) GetPetReleaseList(
// PlayerShowPet 精灵展示 // PlayerShowPet 精灵展示
func (h Controller) PlayerShowPet( func (h Controller) PlayerShowPet(
data *PetShowInboundInfo, data *PetShowInboundInfo,
player *player.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) { player *playersvc.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
result = &pet.PetShowOutboundInfo{ result = &pet.PetShowOutboundInfo{
UserID: data.Head.UserID, UserID: data.Head.UserID,
CatchTime: data.CatchTime, CatchTime: data.CatchTime,

View File

@@ -32,17 +32,22 @@ func (p *baseplayer) GetInfo() *model.PlayerInfo {
return p.Info return p.Info
} }
func ApplyPetLevelLimit(pet model.PetInfo, limitlevel uint32) model.PetInfo {
originalHP := pet.Hp
if limitlevel > 0 {
pet.Level = utils.Min(pet.Level, limitlevel)
}
pet.CalculatePetPane(limitlevel)
pet.Hp = utils.Min(originalHP, pet.MaxHp)
return pet
}
func (p *baseplayer) GetPetInfo(limitlevel uint32) []model.PetInfo { func (p *baseplayer) GetPetInfo(limitlevel uint32) []model.PetInfo {
var ret []model.PetInfo ret := make([]model.PetInfo, 0, len(p.Info.PetList))
for _, pet := range p.Info.PetList { for _, pet := range p.Info.PetList {
if limitlevel > 0 { ret = append(ret, ApplyPetLevelLimit(pet, limitlevel))
pet.Level = utils.Min(pet.Level, limitlevel)
}
ret = append(ret, pet)
} }
return ret return ret
} }

View File

@@ -106,12 +106,20 @@ func validatePetBagOrder(
return true return true
} }
func buildLimitedPetList(petList []model.PetInfo, limitlevel uint32) []model.PetInfo {
result := make([]model.PetInfo, 0, len(petList))
for _, petInfo := range petList {
result = append(result, ApplyPetLevelLimit(petInfo, limitlevel))
}
return result
}
// GetUserBagPetInfo 返回主背包和并列备用精灵列表。 // GetUserBagPetInfo 返回主背包和并列备用精灵列表。
func (p *Player) GetUserBagPetInfo() *pet.GetUserBagPetInfoOutboundInfo { func (p *Player) GetUserBagPetInfo(limitlevel uint32) *pet.GetUserBagPetInfoOutboundInfo {
result := &pet.GetUserBagPetInfoOutboundInfo{ result := &pet.GetUserBagPetInfoOutboundInfo{
PetList: p.Info.PetList, PetList: buildLimitedPetList(p.Info.PetList, limitlevel),
BackupPetList: p.Info.BackupPetList, BackupPetList: buildLimitedPetList(p.Info.BackupPetList, limitlevel),
} }
return result return result

View File

@@ -228,6 +228,17 @@ func (p *Player) GetSpace() *space.Space {
return space.GetSpace(p.Info.MapID) return space.GetSpace(p.Info.MapID)
} }
func (p *Player) CurrentMapPetLevelLimit() uint32 {
if p == nil {
return 100
}
currentSpace := p.GetSpace()
if currentSpace != nil && currentSpace.IsLevelBreakMap {
return 0
}
return 100
}
// CanFight 检查玩家是否可以进行战斗 // CanFight 检查玩家是否可以进行战斗
// 0无战斗1PVP2,BOOS,3PVE // 0无战斗1PVP2,BOOS,3PVE
func (p *Player) CanFight() errorcode.ErrorCode { func (p *Player) CanFight() errorcode.ErrorCode {

View File

@@ -42,10 +42,11 @@ type Space struct {
WeatherType []uint32 WeatherType []uint32
TimeBoss info.S2C_2022 TimeBoss info.S2C_2022
IsTime bool IsTime bool
DropItemIds []uint32 IsLevelBreakMap bool
PitS *csmap.CsMap[int, []model.MapPit] DropItemIds []uint32
MapNodeS *csmap.CsMap[uint32, *model.MapNode] PitS *csmap.CsMap[int, []model.MapPit]
MapNodeS *csmap.CsMap[uint32, *model.MapNode]
} }
func NewSpace() *Space { func NewSpace() *Space {
@@ -185,6 +186,9 @@ func (ret *Space) init() {
if r.IsTimeSpace != 0 { if r.IsTimeSpace != 0 {
ret.IsTime = true ret.IsTime = true
} }
if r.IsLevelBreakMap != 0 {
ret.IsLevelBreakMap = true
}
ret.MapBossSInfo = info.MapModelBroadcastInfo{} ret.MapBossSInfo = info.MapModelBroadcastInfo{}
ret.MapBossSInfo.INFO = make([]info.MapModelBroadcastEntry, 0) ret.MapBossSInfo.INFO = make([]info.MapModelBroadcastEntry, 0)

View File

@@ -1,94 +1,97 @@
package cmd package cmd
import ( import (
"blazing/common/rpc" "blazing/common/rpc"
"blazing/cool" "blazing/cool"
"context" "context"
"time" "time"
"github.com/yudeguang/ratelimit" "github.com/yudeguang/ratelimit"
i18n "blazing/modules/base/middleware" i18n "blazing/modules/base/middleware"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd" "github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gfile"
"github.com/xiaoqidun/qqwry" "github.com/xiaoqidun/qqwry"
) )
var ( var (
Main = gcmd.Command{ Main = gcmd.Command{
Name: "main", Name: "main",
Usage: "main", Usage: "main",
Brief: "start http server", Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
r := parser.GetOpt("debug", false) r := parser.GetOpt("debug", false)
if r.Bool() { if r.Bool() {
g.DB().SetDebug(true) g.DB().SetDebug(true)
// service.NewServerService().SetServerScreen(0, "sss") // service.NewServerService().SetServerScreen(0, "sss")
cool.Config.ServerInfo.IsDebug = 1 cool.Config.ServerInfo.IsDebug = 1
} }
if cool.IsRedisMode { if err = cool.RunAutoMigrate(); err != nil {
go rpc.ListenFunc(ctx) return err
} }
// // 从文件加载IP数据库 if cool.IsRedisMode {
if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil { go rpc.ListenFunc(ctx)
panic(err) }
} // // 从文件加载IP数据库
//go robot() if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil {
//go reg() panic(err)
go startrobot() }
s := g.Server() //go robot()
s.Use(Limiter, ghttp.MiddlewareHandlerResponse) //go reg()
s.EnableAdmin() go startrobot()
s.SetServerAgent(cool.Config.Name) s := g.Server()
s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook) s.Use(Limiter, ghttp.MiddlewareHandlerResponse)
// runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪 s.EnableAdmin()
// runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪 s.SetServerAgent(cool.Config.Name)
// s.EnablePProf() s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook)
// 如果存在 data/cool-admin-vue/dist 目录,则设置为主目录 // runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪
if gfile.IsDir("public") { // runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪
s.SetServerRoot("public") // s.EnablePProf()
} // 如果存在 data/cool-admin-vue/dist 目录,则设置为主目录
// i18n 信息 if gfile.IsDir("public") {
s.BindHandler("/i18n", i18n.I18nInfo) s.SetServerRoot("public")
// g.Server().BindMiddleware("/*", MiddlewareCORS) }
s.Run() // i18n 信息
return nil s.BindHandler("/i18n", i18n.I18nInfo)
}, // g.Server().BindMiddleware("/*", MiddlewareCORS)
} s.Run()
) return nil
},
func beforeServeHook(r *ghttp.Request) { }
//glog.Debugf(r.GetCtx(), "beforeServeHook [is file:%v] URI:%s", r.IsFileRequest(), r.RequestURI) )
r.Response.CORSDefault()
} func beforeServeHook(r *ghttp.Request) {
//glog.Debugf(r.GetCtx(), "beforeServeHook [is file:%v] URI:%s", r.IsFileRequest(), r.RequestURI)
// var limiter = rate.NewLimiter(rate.Limit(150), 50) r.Response.CORSDefault()
var limiter *ratelimit.Rule = ratelimit.NewRule() }
// 简单规则案例 // var limiter = rate.NewLimiter(rate.Limit(150), 50)
func init() { var limiter *ratelimit.Rule = ratelimit.NewRule()
//步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则 // 简单规则案例
limiter.AddRule(time.Second*1, 20) func init() {
//步骤三:调用函数判断某用户是否允许访问 allow:= r.AllowVisit(user)
//步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则
} limiter.AddRule(time.Second*1, 20)
//步骤三:调用函数判断某用户是否允许访问 allow:= r.AllowVisit(user)
// Limiter is a middleware that implements rate limiting for all HTTP requests.
// It returns HTTP 429 (Too Many Requests) when the rate limit is exceeded. }
func Limiter(r *ghttp.Request) {
// 3. 为任意键 "some-key" 获取一个速率限制器 // Limiter is a middleware that implements rate limiting for all HTTP requests.
// - rate.Limit(2): 表示速率为 "每秒2个请求" // It returns HTTP 429 (Too Many Requests) when the rate limit is exceeded.
// - 2: 表示桶的容量 (Burst)允许瞬时处理2个请求 func Limiter(r *ghttp.Request) {
ip := r.GetClientIp() // 3. 为任意键 "some-key" 获取一个速率限制器
if !limiter.AllowVisitByIP4(ip) { // - rate.Limit(2): 表示速率为 "每秒2个请求"
r.Response.WriteStatusExit(429) // Return 429 Too Many Requests // - 2: 表示桶的容量 (Burst)允许瞬时处理2个请求
ip := r.GetClientIp()
r.ExitAll() if !limiter.AllowVisitByIP4(ip) {
} r.Response.WriteStatusExit(429) // Return 429 Too Many Requests
r.Middleware.Next()
} r.ExitAll()
}
r.Middleware.Next()
}

View File

@@ -18,6 +18,8 @@ type MapConfig struct {
WeatherType []uint32 `gorm:"type:int[];comment:'天气类型( 0 晴天,1-雨天2-雪天)'" json:"weather_type"` WeatherType []uint32 `gorm:"type:int[];comment:'天气类型( 0 晴天,1-雨天2-雪天)'" json:"weather_type"`
//是否超时空 //是否超时空
IsTimeSpace int `gorm:"type:int;default:0;comment:'是否超时空'" json:"is_time_space"` IsTimeSpace int `gorm:"type:int;default:0;comment:'是否超时空'" json:"is_time_space"`
// 是否等级突破地图
IsLevelBreakMap int `gorm:"type:int;default:0;comment:'是否等级突破地图'" json:"is_level_break_map"`
// 掉落物配置 // 掉落物配置
DropItemIds []uint32 `gorm:"type:int[];comment:'掉落物IDs" json:"drop_item_ids"` DropItemIds []uint32 `gorm:"type:int[];comment:'掉落物IDs" json:"drop_item_ids"`