diff --git a/common/cool/initdb.go b/common/cool/initdb.go index e4b20af67..7ce4307bd 100644 --- a/common/cool/initdb.go +++ b/common/cool/initdb.go @@ -3,6 +3,7 @@ package cool import ( _ "blazing/contrib/drivers/pgsql" "blazing/cool/cooldb" + "sync" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" @@ -10,6 +11,11 @@ import ( "gorm.io/gorm" ) +var ( + autoMigrateMu sync.Mutex + autoMigrateModels []IModel +) + // 初始化数据库连接供gorm使用 func InitDB(group string) (*gorm.DB, error) { // var ctx context.Context @@ -54,9 +60,33 @@ func getDBbyModel(model IModel) *gorm.DB { // 根据entity结构体创建表 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) - return db.AutoMigrate(model) + if err := db.AutoMigrate(model); err != nil { + return err + } } return nil } diff --git a/logic/controller/fight_boss野怪和地图怪.go b/logic/controller/fight_boss野怪和地图怪.go index 8e9a41b3a..1e61f4458 100644 --- a/logic/controller/fight_boss野怪和地图怪.go +++ b/logic/controller/fight_boss野怪和地图怪.go @@ -72,7 +72,7 @@ func startMapBossFight( ai *player.AI_player, fn func(model.FightOverInfo), ) (*fight.FightC, errorcode.ErrorCode) { - ourPets := p.GetPetInfo(100) + ourPets := p.GetPetInfo(p.CurrentMapPetLevelLimit()) oppPets := ai.GetPetInfo(0) if mapNode != nil && mapNode.IsGroupBoss != 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 { return nil, err } - if req.Number > 9 { - return nil, errorcode.ErrorCodes.ErrSystemError + if int(req.Number) >= len(p.Data) { + return nil, errorcode.ErrorCodes.ErrPokemonNotHere } 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.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) }) 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) { if refPet.ID == 0 { - return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists + return nil, nil, errorcode.ErrorCodes.ErrPokemonNotHere } monster := model.GenPetInfo( diff --git a/logic/controller/pet_ev.go b/logic/controller/pet_ev.go index 3faac5e83..c742b11a6 100644 --- a/logic/controller/pet_ev.go +++ b/logic/controller/pet_ev.go @@ -17,11 +17,16 @@ const ( // c: 当前玩家对象 // 返回: 分配结果和错误码 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 { return nil, errorcode.ErrorCodes.Err10401 } + currentPet := slot.PetInfoPtr() + if currentPet == nil { + return nil, errorcode.ErrorCodes.Err10401 + } + var targetTotal uint32 var currentTotal uint32 for i, evValue := range data.EVs { diff --git a/logic/controller/pet_ev_test.go b/logic/controller/pet_ev_test.go new file mode 100644 index 000000000..273b3cf29 --- /dev/null +++ b/logic/controller/pet_ev_test.go @@ -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) + } +} diff --git a/logic/controller/pet_info.go b/logic/controller/pet_info.go index 448788bf5..7726d9939 100644 --- a/logic/controller/pet_info.go +++ b/logic/controller/pet_info.go @@ -4,18 +4,20 @@ import ( "blazing/common/socket/errorcode" "blazing/logic/service/common" "blazing/logic/service/pet" - "blazing/logic/service/player" + playersvc "blazing/logic/service/player" "blazing/modules/player/model" ) // GetPetInfo 获取精灵信息 func (h Controller) GetPetInfo( data *GetPetInfoInboundInfo, - player *player.Player) (result *model.PetInfo, + player *playersvc.Player) (result *model.PetInfo, err errorcode.ErrorCode) { + levelLimit := player.CurrentMapPetLevelLimit() if slot, found := player.FindPetBagSlot(data.CatchTime); found { if petInfo := slot.PetInfoPtr(); petInfo != nil { - result = petInfo + petCopy := playersvc.ApplyPetLevelLimit(*petInfo, levelLimit) + result = &petCopy return result, 0 } } @@ -25,16 +27,18 @@ func (h Controller) GetPetInfo( return nil, errorcode.ErrorCodes.ErrPokemonNotExists } - result = &ret.Data + petData := ret.Data + petData = playersvc.ApplyPetLevelLimit(petData, levelLimit) + result = &petData return result, 0 } // GetUserBagPetInfo 获取主背包和并列备用精灵列表 func (h Controller) GetUserBagPetInfo( data *GetUserBagPetInfoInboundEmpty, - player *player.Player) (result *pet.GetUserBagPetInfoOutboundInfo, + player *playersvc.Player) (result *pet.GetUserBagPetInfoOutboundInfo, err errorcode.ErrorCode) { - return player.GetUserBagPetInfo(), 0 + return player.GetUserBagPetInfo(player.CurrentMapPetLevelLimit()), 0 } // GetPetListInboundEmpty 定义请求或响应数据结构。 @@ -45,7 +49,7 @@ type GetPetListInboundEmpty struct { // GetPetList 获取当前主背包列表 func (h Controller) GetPetList( data *GetPetListInboundEmpty, - player *player.Player) (result *pet.GetPetListOutboundInfo, + player *playersvc.Player) (result *pet.GetPetListOutboundInfo, err errorcode.ErrorCode) { return buildPetListOutboundInfo(player.Info.PetList), 0 } @@ -58,7 +62,7 @@ type GetPetListFreeInboundEmpty struct { // GetPetReleaseList 获取仓库可放生列表 func (h Controller) GetPetReleaseList( data *GetPetListFreeInboundEmpty, - player *player.Player) (result *pet.GetPetListOutboundInfo, + player *playersvc.Player) (result *pet.GetPetListOutboundInfo, err errorcode.ErrorCode) { return buildPetListOutboundInfo(player.WarehousePetList()), 0 @@ -67,7 +71,7 @@ func (h Controller) GetPetReleaseList( // PlayerShowPet 精灵展示 func (h Controller) PlayerShowPet( data *PetShowInboundInfo, - player *player.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) { + player *playersvc.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) { result = &pet.PetShowOutboundInfo{ UserID: data.Head.UserID, CatchTime: data.CatchTime, diff --git a/logic/service/player/base.go b/logic/service/player/base.go index 3bccf68f9..edf9bff07 100644 --- a/logic/service/player/base.go +++ b/logic/service/player/base.go @@ -32,17 +32,22 @@ func (p *baseplayer) GetInfo() *model.PlayerInfo { 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 { - var ret []model.PetInfo + ret := make([]model.PetInfo, 0, len(p.Info.PetList)) for _, pet := range p.Info.PetList { - if limitlevel > 0 { - pet.Level = utils.Min(pet.Level, limitlevel) - - } - - ret = append(ret, pet) + ret = append(ret, ApplyPetLevelLimit(pet, limitlevel)) } return ret } diff --git a/logic/service/player/pet_bag.go b/logic/service/player/pet_bag.go index 15920a038..cd1aca0dd 100644 --- a/logic/service/player/pet_bag.go +++ b/logic/service/player/pet_bag.go @@ -106,12 +106,20 @@ func validatePetBagOrder( 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 返回主背包和并列备用精灵列表。 -func (p *Player) GetUserBagPetInfo() *pet.GetUserBagPetInfoOutboundInfo { +func (p *Player) GetUserBagPetInfo(limitlevel uint32) *pet.GetUserBagPetInfoOutboundInfo { result := &pet.GetUserBagPetInfoOutboundInfo{ - PetList: p.Info.PetList, - BackupPetList: p.Info.BackupPetList, + PetList: buildLimitedPetList(p.Info.PetList, limitlevel), + BackupPetList: buildLimitedPetList(p.Info.BackupPetList, limitlevel), } return result diff --git a/logic/service/player/player.go b/logic/service/player/player.go index 628a43fdb..35139a582 100644 --- a/logic/service/player/player.go +++ b/logic/service/player/player.go @@ -228,6 +228,17 @@ func (p *Player) GetSpace() *space.Space { 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 检查玩家是否可以进行战斗 // 0无战斗,1PVP,2,BOOS,3PVE func (p *Player) CanFight() errorcode.ErrorCode { diff --git a/logic/service/space/space.go b/logic/service/space/space.go index 2b6cc2ca1..f424f8402 100644 --- a/logic/service/space/space.go +++ b/logic/service/space/space.go @@ -42,10 +42,11 @@ type Space struct { WeatherType []uint32 TimeBoss info.S2C_2022 - IsTime bool - DropItemIds []uint32 - PitS *csmap.CsMap[int, []model.MapPit] - MapNodeS *csmap.CsMap[uint32, *model.MapNode] + IsTime bool + IsLevelBreakMap bool + DropItemIds []uint32 + PitS *csmap.CsMap[int, []model.MapPit] + MapNodeS *csmap.CsMap[uint32, *model.MapNode] } func NewSpace() *Space { @@ -185,6 +186,9 @@ func (ret *Space) init() { if r.IsTimeSpace != 0 { ret.IsTime = true } + if r.IsLevelBreakMap != 0 { + ret.IsLevelBreakMap = true + } ret.MapBossSInfo = info.MapModelBroadcastInfo{} ret.MapBossSInfo.INFO = make([]info.MapModelBroadcastEntry, 0) diff --git a/login/internal/cmd/cmd.go b/login/internal/cmd/cmd.go index 77b1a8e7b..ab0a60390 100644 --- a/login/internal/cmd/cmd.go +++ b/login/internal/cmd/cmd.go @@ -1,94 +1,97 @@ -package cmd - -import ( - "blazing/common/rpc" - "blazing/cool" - "context" - "time" - - "github.com/yudeguang/ratelimit" - - i18n "blazing/modules/base/middleware" - - "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/net/ghttp" - "github.com/gogf/gf/v2/os/gcmd" - "github.com/gogf/gf/v2/os/gfile" - "github.com/xiaoqidun/qqwry" -) - -var ( - Main = gcmd.Command{ - Name: "main", - Usage: "main", - Brief: "start http server", - Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { - - r := parser.GetOpt("debug", false) - if r.Bool() { - g.DB().SetDebug(true) - // service.NewServerService().SetServerScreen(0, "sss") - cool.Config.ServerInfo.IsDebug = 1 - } - if cool.IsRedisMode { - go rpc.ListenFunc(ctx) - } - // // 从文件加载IP数据库 - if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil { - panic(err) - } - //go robot() - //go reg() - go startrobot() - s := g.Server() - s.Use(Limiter, ghttp.MiddlewareHandlerResponse) - s.EnableAdmin() - s.SetServerAgent(cool.Config.Name) - s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook) - // runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪 - // runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪 - // s.EnablePProf() - // 如果存在 data/cool-admin-vue/dist 目录,则设置为主目录 - if gfile.IsDir("public") { - s.SetServerRoot("public") - } - // i18n 信息 - 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() -} - -// var limiter = rate.NewLimiter(rate.Limit(150), 50) -var limiter *ratelimit.Rule = ratelimit.NewRule() - -// 简单规则案例 -func init() { - - //步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则 - 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" 获取一个速率限制器 - // - rate.Limit(2): 表示速率为 "每秒2个请求" - // - 2: 表示桶的容量 (Burst),允许瞬时处理2个请求 - ip := r.GetClientIp() - if !limiter.AllowVisitByIP4(ip) { - r.Response.WriteStatusExit(429) // Return 429 Too Many Requests - - r.ExitAll() - } - r.Middleware.Next() -} +package cmd + +import ( + "blazing/common/rpc" + "blazing/cool" + "context" + "time" + + "github.com/yudeguang/ratelimit" + + i18n "blazing/modules/base/middleware" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gfile" + "github.com/xiaoqidun/qqwry" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + + r := parser.GetOpt("debug", false) + if r.Bool() { + g.DB().SetDebug(true) + // service.NewServerService().SetServerScreen(0, "sss") + cool.Config.ServerInfo.IsDebug = 1 + } + if err = cool.RunAutoMigrate(); err != nil { + return err + } + if cool.IsRedisMode { + go rpc.ListenFunc(ctx) + } + // // 从文件加载IP数据库 + if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil { + panic(err) + } + //go robot() + //go reg() + go startrobot() + s := g.Server() + s.Use(Limiter, ghttp.MiddlewareHandlerResponse) + s.EnableAdmin() + s.SetServerAgent(cool.Config.Name) + s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook) + // runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪 + // runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪 + // s.EnablePProf() + // 如果存在 data/cool-admin-vue/dist 目录,则设置为主目录 + if gfile.IsDir("public") { + s.SetServerRoot("public") + } + // i18n 信息 + 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() +} + +// var limiter = rate.NewLimiter(rate.Limit(150), 50) +var limiter *ratelimit.Rule = ratelimit.NewRule() + +// 简单规则案例 +func init() { + + //步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则 + 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" 获取一个速率限制器 + // - rate.Limit(2): 表示速率为 "每秒2个请求" + // - 2: 表示桶的容量 (Burst),允许瞬时处理2个请求 + ip := r.GetClientIp() + if !limiter.AllowVisitByIP4(ip) { + r.Response.WriteStatusExit(429) // Return 429 Too Many Requests + + r.ExitAll() + } + r.Middleware.Next() +} diff --git a/modules/config/model/map.go b/modules/config/model/map.go index 73996aae8..c3d5a8695 100644 --- a/modules/config/model/map.go +++ b/modules/config/model/map.go @@ -18,6 +18,8 @@ type MapConfig struct { WeatherType []uint32 `gorm:"type:int[];comment:'天气类型( 0 晴天,1-雨天,2-雪天)'" json:"weather_type"` //是否超时空 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"`