diff --git a/common/socket/ServerEvent.go b/common/socket/ServerEvent.go index cd70138c0..cfe1f3d0a 100644 --- a/common/socket/ServerEvent.go +++ b/common/socket/ServerEvent.go @@ -1,295 +1,269 @@ -package socket - -import ( - "context" - "encoding/binary" - "errors" - "io" - "log" - "os" - "sync/atomic" - "time" - - "blazing/cool" - "blazing/logic/service/common" - "blazing/logic/service/player" - "blazing/modules/config/service" - - "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/gtime" - "github.com/panjf2000/gnet/v2" - "github.com/valyala/bytebufferpool" -) - -func (s *Server) Boot(serverid, port uint32) error { - // go s.bootws() - s.serverid = serverid - s.port = port - - err := gnet.Run(s, s.network+"://"+s.addr, - gnet.WithMulticore(true), - gnet.WithTicker(true), - - // 其他调优配置↓ - gnet.WithTCPNoDelay(gnet.TCPNoDelay), // 禁用Nagle算法(降低延迟,适合小数据包场景) - //gnet.WithReusePort(true), // 开启SO_REUSEPORT(多核下提升并发) - //gnet.WithReadBufferCap(1024*64), // 读缓冲区64KB(根据业务调整,默认太小) - // gnet.WithWriteBufferCap(1024*64), // 写缓冲区64KB - - //gnet.WithLockOSThread(true), // 绑定goroutine到OS线程(减少上下文切换) - ) - if err != nil { - panic(err) - } - - return nil -} - -func (s *Server) Stop() error { - _ = s.eng.Stop(context.Background()) - s.workerPool.Release() - - return nil -} - -func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) { - defer func() { - if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值 - // 1. 打印错误信息 - if t, ok := c.Context().(*player.ClientData); ok { - if t.Player != nil { - if t.Player.Info != nil { - cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err) - t.Player.Service.Info.Save(*t.Player.Info) - } - - } - - } else { - cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, err) - - } - - } - }() - // 识别 RST 导致的连接中断(错误信息含 "connection reset") - // if err != nil && (strings.Contains(err.Error(), "connection reset") || strings.Contains(err.Error(), "reset by peer")) { - // remoteIP := c.RemoteAddr().(*net.TCPAddr).IP.String() - - // log.Printf("RST 攻击检测: 来源 %s, 累计攻击次数 %d", remoteIP) - - // // 防护逻辑:临时封禁异常 IP(可扩展为 IP 黑名单) - // // go s.tempBlockIP(remoteIP, 5*time.Minute) - // } - //fmt.Println(err, c.RemoteAddr().String(), "断开连接") - atomic.AddInt64(&cool.Connected, -1) - - //logging.Infof("conn[%v] disconnected", c.RemoteAddr().String()) - v, _ := c.Context().(*player.ClientData) - - v.LF.Close() - // v.LF.Close() - //close(v.MsgChan) - if v.Player != nil { - v.Player.Save() //保存玩家数据 - - } - - //} - //关闭连接 - return -} -func (s *Server) OnTick() (delay time.Duration, action gnet.Action) { - g.Log().Async().Info(context.Background(), gtime.Now().ISO8601(), "服务器ID", cool.Config.ServerInfo.OnlineID, "链接数", atomic.LoadInt64(&cool.Connected)) - if s.quit && atomic.LoadInt64(&cool.Connected) == 0 { - //执行正常退出逻辑 - os.Exit(0) - } - return 30 * time.Second, gnet.None -} -func (s *Server) OnBoot(eng gnet.Engine) gnet.Action { - s.eng = eng - - service.NewServerService().SetServerID(s.serverid, s.port) //设置当前服务器端口 - return gnet.None -} - -func (s *Server) OnOpen(conn gnet.Conn) (out []byte, action gnet.Action) { - if s.network != "tcp" { - return nil, gnet.Close - } - - if conn.Context() == nil { - conn.SetContext(player.NewClientData(conn)) //注入data - } - - atomic.AddInt64(&cool.Connected, 1) - - return nil, gnet.None -} - -func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) { - defer func() { - if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值 - // 1. 打印错误信息 - if t, ok := c.Context().(*player.ClientData); ok { - if t.Player != nil { - if t.Player.Info != nil { - cool.Logger.Error(context.TODO(), "OnTraffic 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err) - t.Player.Service.Info.Save(*t.Player.Info) - - } - - } - - } - - } - }() - - ws := c.Context().(*player.ClientData).Wsmsg - if ws.Tcp { //升级失败时候防止缓冲区溢出 - return s.handleTCP(c) - - } - - tt, len1 := ws.ReadBufferBytes(c) - if tt == gnet.Close { - - return gnet.Close - } - - ok, action := ws.Upgrade(c) - if action != gnet.None { //连接断开 - return action - } - if !ok { //升级失败,说明是tcp连接 - ws.Tcp = true - - return s.handleTCP(c) - - } - // fmt.Println(ws.Buf.Bytes()) - c.Discard(len1) - - messages, err := ws.Decode(c) - if err != nil { - return gnet.Close - } - if messages == nil { - return - } - - for _, msg := range messages { - - s.onevent(c, msg.Payload) - //t.OnEvent(msg.Payload) - } - - return gnet.None -} - -const maxBodyLen = 10 * 1024 // 业务最大包体长度,按需调整 -func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) { - - conn.Context().(*player.ClientData).IsCrossDomain.Do(func() { //跨域检测 - handle(conn) - }) - - // handle(c) - // 先读取4字节的包长度 - lenBuf, err := conn.Peek(4) - - if err != nil { - if errors.Is(err, io.ErrShortBuffer) { - return - } - return gnet.Close - } - - bodyLen := binary.BigEndian.Uint32(lenBuf) - - if bodyLen > maxBodyLen { - return gnet.Close - } - - if conn.InboundBuffered() < int(bodyLen) { - return - } - // 提取包体 - body, err := conn.Next(int(bodyLen)) - if err != nil { - if errors.Is(err, io.ErrShortBuffer) { - return - } - return gnet.Close - } - - s.onevent(conn, body) - - if conn.InboundBuffered() > 0 { - if err := conn.Wake(nil); err != nil { // wake up the connection manually to avoid missing the leftover data - - return gnet.Close - } - } - return action - -} - -// CROSS_DOMAIN 定义跨域策略文件内容 -const CROSS_DOMAIN = "\x00" - -// TEXT 定义跨域请求的文本格式 -const TEXT = "\x00" - -func handle(c gnet.Conn) { - - // 读取数据并检查是否为跨域请求 - data, err := c.Peek(len(TEXT)) - if err != nil { - log.Printf("Error reading cross-domain request: %v", err) - return - } - - if string(data) == TEXT { //判断是否是跨域请求 - //log.Printf("Received cross-domain request from %s", c.RemoteAddr()) - // 处理跨域请求 - c.Write([]byte(CROSS_DOMAIN)) - c.Discard(len(TEXT)) - - return - } - - //return -} - -func (s *Server) onevent(c gnet.Conn, v []byte) { - if t, ok := c.Context().(*player.ClientData); ok { - var header common.TomeeHeader - // 解析Len(0-3字节) - header.Len = binary.BigEndian.Uint32(v[0:4]) - // 解析Version(第4字节) - //header.Version = v[4] - // 解析CMD(5-8字节) - header.CMD = binary.BigEndian.Uint32(v[5:9]) - // 解析UserID(9-12字节) - header.UserID = binary.BigEndian.Uint32(v[9:13]) - // 解析Result(13-16字节) - //header.Result = binary.BigEndian.Uint32(v[13:17]) - // 解析数据部分(17字节之后) - // 数据部分:直接引用切片,避免 make - if len(v) > 17 { - header.Data = bytebufferpool.Get() - header.Data.Write(v[17:]) - //copy(header.Data, v[17:]) // 核心修改:拷贝数据 - } - //t.OnEvent(header) - // t.LF.Push(header) - s.workerPool.Submit(func() { - t.LF.Producer().Write(header) - // t.LF.Producer().Write(header) - }) - - } -} +package socket + +import ( + "context" + "encoding/binary" + "errors" + "io" + "log" + "os" + "sync/atomic" + "time" + + "blazing/cool" + "blazing/logic/service/player" + "blazing/modules/config/service" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/panjf2000/gnet/v2" +) + +func (s *Server) Boot(serverid, port uint32) error { + // go s.bootws() + s.serverid = serverid + s.port = port + + err := gnet.Run(s, s.network+"://"+s.addr, + gnet.WithMulticore(true), + gnet.WithTicker(true), + + // 其他调优配置↓ + gnet.WithTCPNoDelay(gnet.TCPNoDelay), // 禁用Nagle算法(降低延迟,适合小数据包场景) + //gnet.WithReusePort(true), // 开启SO_REUSEPORT(多核下提升并发) + //gnet.WithReadBufferCap(1024*64), // 读缓冲区64KB(根据业务调整,默认太小) + // gnet.WithWriteBufferCap(1024*64), // 写缓冲区64KB + + //gnet.WithLockOSThread(true), // 绑定goroutine到OS线程(减少上下文切换) + ) + if err != nil { + panic(err) + } + + return nil +} + +func (s *Server) Stop() error { + _ = s.eng.Stop(context.Background()) + s.workerPool.Release() + + return nil +} + +func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) { + defer func() { + if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值 + // 1. 打印错误信息 + if t, ok := c.Context().(*player.ClientData); ok { + if t.Player != nil { + if t.Player.Info != nil { + cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err) + t.Player.Service.Info.Save(*t.Player.Info) + } + + } + + } else { + cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, err) + + } + + } + }() + // 识别 RST 导致的连接中断(错误信息含 "connection reset") + // if err != nil && (strings.Contains(err.Error(), "connection reset") || strings.Contains(err.Error(), "reset by peer")) { + // remoteIP := c.RemoteAddr().(*net.TCPAddr).IP.String() + + // log.Printf("RST 攻击检测: 来源 %s, 累计攻击次数 %d", remoteIP) + + // // 防护逻辑:临时封禁异常 IP(可扩展为 IP 黑名单) + // // go s.tempBlockIP(remoteIP, 5*time.Minute) + // } + //fmt.Println(err, c.RemoteAddr().String(), "断开连接") + atomic.AddInt64(&cool.Connected, -1) + + //logging.Infof("conn[%v] disconnected", c.RemoteAddr().String()) + v, _ := c.Context().(*player.ClientData) + + v.LF.Close() + // v.LF.Close() + //close(v.MsgChan) + if v.Player != nil { + v.Player.Save() //保存玩家数据 + + } + + //} + //关闭连接 + return +} +func (s *Server) OnTick() (delay time.Duration, action gnet.Action) { + g.Log().Async().Info(context.Background(), gtime.Now().ISO8601(), "服务器ID", cool.Config.ServerInfo.OnlineID, "链接数", atomic.LoadInt64(&cool.Connected)) + if s.quit && atomic.LoadInt64(&cool.Connected) == 0 { + //执行正常退出逻辑 + os.Exit(0) + } + return 30 * time.Second, gnet.None +} +func (s *Server) OnBoot(eng gnet.Engine) gnet.Action { + s.eng = eng + + service.NewServerService().SetServerID(s.serverid, s.port) //设置当前服务器端口 + return gnet.None +} + +func (s *Server) OnOpen(conn gnet.Conn) (out []byte, action gnet.Action) { + if s.network != "tcp" { + return nil, gnet.Close + } + + if conn.Context() == nil { + conn.SetContext(player.NewClientData(conn)) //注入data + } + + atomic.AddInt64(&cool.Connected, 1) + + return nil, gnet.None +} + +func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) { + defer func() { + if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值 + // 1. 打印错误信息 + if t, ok := c.Context().(*player.ClientData); ok { + if t.Player != nil { + if t.Player.Info != nil { + cool.Logger.Error(context.TODO(), "OnTraffic 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err) + t.Player.Service.Info.Save(*t.Player.Info) + + } + + } + + } + + } + }() + + ws := c.Context().(*player.ClientData).Wsmsg + if ws.Tcp { //升级失败时候防止缓冲区溢出 + return s.handleTCP(c) + + } + + tt, len1 := ws.ReadBufferBytes(c) + if tt == gnet.Close { + + return gnet.Close + } + + ok, action := ws.Upgrade(c) + if action != gnet.None { //连接断开 + return action + } + if !ok { //升级失败,说明是tcp连接 + ws.Tcp = true + + return s.handleTCP(c) + + } + // fmt.Println(ws.Buf.Bytes()) + c.Discard(len1) + + messages, err := ws.Decode(c) + if err != nil { + return gnet.Close + } + if messages == nil { + return + } + + for _, msg := range messages { + + s.onevent(c, msg.Payload) + //t.OnEvent(msg.Payload) + } + + return gnet.None +} + +const maxBodyLen = 10 * 1024 // 业务最大包体长度,按需调整 +func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) { + + conn.Context().(*player.ClientData).IsCrossDomain.Do(func() { //跨域检测 + handle(conn) + }) + + // handle(c) + // 先读取4字节的包长度 + lenBuf, err := conn.Peek(4) + + if err != nil { + if errors.Is(err, io.ErrShortBuffer) { + return + } + return gnet.Close + } + + bodyLen := binary.BigEndian.Uint32(lenBuf) + + if bodyLen > maxBodyLen { + return gnet.Close + } + + if conn.InboundBuffered() < int(bodyLen) { + return + } + // 提取包体 + body, err := conn.Next(int(bodyLen)) + if err != nil { + if errors.Is(err, io.ErrShortBuffer) { + return + } + return gnet.Close + } + + s.onevent(conn, body) + + if conn.InboundBuffered() > 0 { + if err := conn.Wake(nil); err != nil { // wake up the connection manually to avoid missing the leftover data + + return gnet.Close + } + } + return action + +} + +// CROSS_DOMAIN 定义跨域策略文件内容 +const CROSS_DOMAIN = "\x00" + +// TEXT 定义跨域请求的文本格式 +const TEXT = "\x00" + +func handle(c gnet.Conn) { + + // 读取数据并检查是否为跨域请求 + data, err := c.Peek(len(TEXT)) + if err != nil { + log.Printf("Error reading cross-domain request: %v", err) + return + } + + if string(data) == TEXT { //判断是否是跨域请求 + //log.Printf("Received cross-domain request from %s", c.RemoteAddr()) + // 处理跨域请求 + c.Write([]byte(CROSS_DOMAIN)) + c.Discard(len(TEXT)) + + return + } + + //return +} + +func (s *Server) onevent(c gnet.Conn, v []byte) { + if t, ok := c.Context().(*player.ClientData); ok { + t.PushEvent(v, s.workerPool.Submit) + } +} diff --git a/common/socket/ServerOption.go b/common/socket/ServerOption.go index c2d243289..e0c1668e3 100644 --- a/common/socket/ServerOption.go +++ b/common/socket/ServerOption.go @@ -38,7 +38,7 @@ func NewServer(options ...Option) *Server { // handler: handler.NewTomeeHandler(), //请求返回 codec: codec.NewTomeeSocketCodec(), //默认解码器 len+pack workerPool: goroutine.Default(), - bufferSize: 40960, //默认缓冲区大小 + bufferSize: 40960, //默认缓冲区大小 multicore: true, //batchRead: 8, //discorse: true, diff --git a/logic/controller/buy_seerdou_item.go b/logic/controller/buy_seerdou_item.go new file mode 100644 index 000000000..ea32d522b --- /dev/null +++ b/logic/controller/buy_seerdou_item.go @@ -0,0 +1,30 @@ +package controller + +import ( + "blazing/common/data/xmlres" + "blazing/common/socket/errorcode" + "blazing/logic/service/player" +) + +func buySeerdouBackpackItem(player *player.Player, itemID int64, count int64) (bought bool, err errorcode.ErrorCode) { + if itemID <= 0 || count <= 0 { + return false, errorcode.ErrorCodes.ErrSystemError + } + + itemInfo, exists := xmlres.ItemsMAP[int(itemID)] + if !exists { + return false, 0 + } + + totalCost := int64(itemInfo.Price) * count + if totalCost > 0 && !player.GetCoins(totalCost) { + return false, errorcode.ErrorCodes.ErrSunDouInsufficient10016 + } + + if !player.ItemAdd(itemID, count) { + return false, 0 + } + + player.Info.Coins -= totalCost + return true, 0 +} diff --git a/logic/controller/fight_boss野怪和地图怪.go b/logic/controller/fight_boss野怪和地图怪.go index 4eae9927b..e29ac6b0a 100644 --- a/logic/controller/fight_boss野怪和地图怪.go +++ b/logic/controller/fight_boss野怪和地图怪.go @@ -7,9 +7,8 @@ import ( "strings" "blazing/logic/service/fight" - "blazing/logic/service/fight/info" + fightinfo "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" - "blazing/logic/service/player" configmodel "blazing/modules/config/model" "blazing/modules/config/service" @@ -20,217 +19,259 @@ import ( ) // PlayerFightBoss 挑战地图boss -// data: 包含挑战Boss信息的输入数据 -// player: 当前玩家对象 -// 返回: 战斗结果和错误码 -func (Controller) PlayerFightBoss(data1 *fight.ChallengeBossInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { - r := p.CanFight() - if p.CanFight() != 0 { - return nil, r - } - var monster *model.PetInfo - monsterInfo := &model.PlayerInfo{} - - mdata := service.NewMapNodeService().GetDataNode(p.Info.MapID, data1.BossId) - if mdata == nil { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - var bosinfo []configmodel.BossConfig - switch len(mdata.BossIds) { - case 0: - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - case 1: - bosinfo = service.NewBossService().Get(mdata.BossIds[0]) - default: - bosinfo = service.NewBossService().Get(mdata.BossIds[grand.Intn(len(mdata.BossIds))]) +func (Controller) PlayerFightBoss(req *fight.ChallengeBossInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { + if err = p.CanFight(); err != 0 { + return nil, err } - if len(bosinfo) == 0 { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists + mapNode := service.NewMapNodeService().GetDataNode(p.Info.MapID, req.BossId) + bossConfigs, err := loadMapBossConfigs(mapNode) + if err != 0 { + return nil, err } - dv := 24 - ger := 0 - if bosinfo[0].IsCapture == 1 { - dv = -1 - ger = -1 + + monsterInfo, leadMonsterID, err := buildBossMonsterInfo(mapNode.NodeName, bossConfigs) + if err != 0 { + return nil, err } - for i, bm := range bosinfo { - monster = model.GenPetInfo( - gconv.Int(bm.MonID), dv, //24个体 - -1, - 0, //野怪没特性 - - int(bm.Lv), nil, ger) - monster.CatchTime = uint32(i) - monster.ConfigBoss(bm.PetBaseConfig) - effects := service.NewEffectService().Args(bm.Effect) - - for _, v := range effects { - monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{ - Idx: uint16(v.SeIdx), - EID: gconv.Uint16(v.Eid), - Args: gconv.Ints(v.Args), - }) - } - monsterInfo.PetList = append(monsterInfo.PetList, *monster) - } - if bosinfo[0].IsCapture == 1 { - monsterInfo.PetList[0].ShinyInfo = make([]data.GlowFilter, 0) - if grand.Meet(1, 500) { - monsterInfo.PetList[0].RandomByWeightShiny() - } - - } - monsterInfo.Nick = mdata.NodeName //xmlres.PetMAP[int(monster.ID)].DefName - - if len(monsterInfo.PetList) == 0 { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - p.Fightinfo.Status = info.BattleMode.FIGHT_WITH_NPC - p.Fightinfo.Mode = info.BattleMode.MULTI_MODE + p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC + p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE ai := player.NewAI_player(monsterInfo) - ai.CanCapture = bosinfo[0].IsCapture - if bosinfo[0].IsCapture != 0 { - ai.CanCapture = xmlres.PetMAP[int(monster.ID)].CatchRate - if xmlres.PetMAP[int(monster.ID)].CatchRate == 0 { - ai.CanCapture = 0 - } - } - + ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID) ai.Prop[0] = 2 - var fighc *fight.FightC - fighc, _ = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) { - if mdata.WinBonusID != 0 { - if len(bosinfo[0].Rule) == 0 { - if foi.Reason == 0 && foi.WinnerId == p.Info.UserID { - p.SptCompletedTask(mdata.WinBonusID, 1) - - } - } else { - //说明是带规则的 - iswin := true - for _, v := range service.NewFightRuleService().GetByRuleIdxs(bosinfo[0].Rule) { - r := input.GetRule(int64(v.RuleIdx)) - if r != nil { - r.SetArgs(v.Args...) - - if !(r.Exec(fighc, &foi)) { - iswin = false - break - } - } - - } - if iswin { - p.SptCompletedTask(mdata.WinBonusID, 1) - } - } + var fightC *fight.FightC + fightC, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) { + if mapNode.WinBonusID == 0 { + return + } + if shouldGrantBossWinBonus(fightC, p.Info.UserID, bossConfigs[0], foi) { + p.SptCompletedTask(mapNode.WinBonusID, 1) } - - //p.Done.Exec(model.MilestoneMode.BOSS, []uint32{p.Info.MapID, data.BossId, uint32(foi.Reason)}, nil) - }) + if err != 0 { + return nil, err + } return nil, -1 } // OnPlayerFightNpcMonster 战斗野怪 -// data: 包含战斗野怪信息的输入数据 -// player: 当前玩家对象 -// 返回: 战斗结果和错误码 -func (Controller) OnPlayerFightNpcMonster(data1 *fight.FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { - r := p.CanFight() - if p.CanFight() != 0 { - return nil, r +func (Controller) OnPlayerFightNpcMonster(req *fight.FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { + if err = p.CanFight(); err != 0 { + return nil, err } - if data1.Number > 9 { + if req.Number > 9 { return nil, errorcode.ErrorCodes.ErrSystemError } - refPet := p.Data[data1.Number] - if refPet.ID == 0 { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists + refPet := p.Data[req.Number] + monster, monsterInfo, err := buildNpcMonsterInfo(refPet, p.Info.MapID) + if err != 0 { + return nil, err } - monster := model.GenPetInfo( - int(refPet.GetID()), -1, - -1, - 0, //野怪没特性 - - int(refPet.GetLevel()), - refPet.ShinyInfo, -1) - monster.CatchMap = p.Info.MapID //设置当前地图 - if refPet.Ext != 0 { - if grand.Meet(1, 500) { - monster.RandomByWeightShiny() - } - } - - monsterInfo := &model.PlayerInfo{} - monsterInfo.Nick = xmlres.PetMAP[int(monster.ID)].DefName - monsterInfo.PetList = append(monsterInfo.PetList, *monster) ai := player.NewAI_player(monsterInfo) + ai.CanCapture = refPet.IsCapture - ai.CanCapture = refPet.IsCapture //handleNPCFightSpecial(monster.ID) - - p.Fightinfo.Status = info.BattleMode.FIGHT_WITH_NPC //打野怪 - p.Fightinfo.Mode = info.BattleMode.MULTI_MODE //多人模式 - - fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) { - //p.Done.Exec(model.MilestoneMode.Moster, []uint32{p.Info.MapID, monsterInfo.PetList[0].ID, uint32(foi.Reason)}, nil) - if foi.Reason == 0 && foi.WinnerId == p.Info.UserID && p.CanGet() { - - exp := uint32(xmlres.PetMAP[int(monster.ID)].YieldingExp) * monster.Level / 7 - addlevel, poolevel := p.CanGetExp() - addexp := gconv.Float32(addlevel * gconv.Float32(exp)) - - poolexp := gconv.Float32(poolevel) * gconv.Float32((exp)) - items := &info.S2C_GET_BOSS_MONSTER{} - - p.ItemAdd(3, int64(poolexp+addexp)) - items.ADDitem(3, uint32(poolexp)) - - p.AddPetExp(foi.Winpet, int64(addexp)) - pettype := int64(xmlres.PetMAP[int(refPet.GetID())].Type) - - if p.CanGetItem() { - - item := p.GetSpace().GetDrop() - if item != 0 { - count := int64(grand.N(1, 2)) - ok := p.ItemAdd(item, count) - if ok { - items.ADDitem(uint32(item), uint32(count)) - } - } - - } - if monster.IsShiny() && p.CanGetXUAN() && pettype < 16 { - xuan := 400686 + pettype - count := uint32(grand.N(1, 2)) - ok := p.ItemAdd(xuan, int64(count)) - if ok { - items.ADDitem(uint32(xuan), count) - } - - } - p.SendPackCmd(8004, items) - - evs := gconv.Int64s(strings.Split(xmlres.PetMAP[int(monster.ID)].YieldingEV, " ")) - - foi.Winpet.AddEV(evs) - //取消累计学习力掉落 - // if leve == 8 { - // items.EV = lo.Sum(evs) - 1 - // p.Info.EVPool += lo.Sum(evs) //给予累计学习力 - // } - - } + 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) { + handleNpcFightRewards(p, foi, monster) }) + if err != 0 { + return nil, err + } return nil, -1 } + +func loadMapBossConfigs(mapNode *configmodel.MapNode) ([]configmodel.BossConfig, errorcode.ErrorCode) { + if mapNode == nil || len(mapNode.BossIds) == 0 { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + + bossID := mapNode.BossIds[0] + if len(mapNode.BossIds) > 1 { + bossID = mapNode.BossIds[grand.Intn(len(mapNode.BossIds))] + } + + bossConfigs := service.NewBossService().Get(bossID) + if len(bossConfigs) == 0 { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + + return bossConfigs, 0 +} + +func buildBossMonsterInfo(nodeName string, bossConfigs []configmodel.BossConfig) (*model.PlayerInfo, uint32, errorcode.ErrorCode) { + monsterInfo := &model.PlayerInfo{Nick: nodeName} + var leadMonsterID uint32 + + for i, bossConfig := range bossConfigs { + dv, generation := bossFightPetArgs(bossConfig.IsCapture) + monster := model.GenPetInfo( + gconv.Int(bossConfig.MonID), + dv, + -1, + 0, + int(bossConfig.Lv), + nil, + generation, + ) + if monster == nil { + return nil, 0, errorcode.ErrorCodes.ErrPokemonNotExists + } + monster.CatchTime = uint32(i) + monster.ConfigBoss(bossConfig.PetBaseConfig) + appendPetEffects(monster, bossConfig.Effect) + + if i == 0 { + leadMonsterID = monster.ID + } + monsterInfo.PetList = append(monsterInfo.PetList, *monster) + } + + if len(monsterInfo.PetList) == 0 { + return nil, 0, errorcode.ErrorCodes.ErrPokemonNotExists + } + + if bossConfigs[0].IsCapture == 1 { + monsterInfo.PetList[0].ShinyInfo = make([]data.GlowFilter, 0) + if grand.Meet(1, 500) { + monsterInfo.PetList[0].RandomByWeightShiny() + } + } + + return monsterInfo, leadMonsterID, 0 +} + +func bossFightPetArgs(canCapture int) (dv int, generation int) { + if canCapture == 1 { + return -1, -1 + } + return 24, 0 +} + +func appendPetEffects(monster *model.PetInfo, effectIDs []uint32) { + effects := service.NewEffectService().Args(effectIDs) + for _, effect := range effects { + monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{ + Idx: uint16(effect.SeIdx), + EID: gconv.Uint16(effect.Eid), + Args: gconv.Ints(effect.Args), + }) + } +} + +func resolveBossCaptureRate(canCapture int, petID uint32) int { + if canCapture == 0 { + return 0 + } + + petCfg, ok := xmlres.PetMAP[int(petID)] + if !ok || petCfg.CatchRate == 0 { + return 0 + } + + return petCfg.CatchRate +} + +func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig configmodel.BossConfig, foi model.FightOverInfo) bool { + if len(bossConfig.Rule) == 0 { + return foi.Reason == 0 && foi.WinnerId == playerID + } + + for _, ruleConfig := range service.NewFightRuleService().GetByRuleIdxs(bossConfig.Rule) { + rule := input.GetRule(int64(ruleConfig.RuleIdx)) + if rule == nil { + continue + } + rule.SetArgs(ruleConfig.Args...) + if !rule.Exec(fightC, &foi) { + return false + } + } + + return true +} + +func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) { + if refPet.ID == 0 { + return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + + monster := model.GenPetInfo( + refPet.GetID(), + -1, + -1, + 0, + refPet.GetLevel(), + refPet.ShinyInfo, + -1, + ) + if monster == nil { + return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + monster.CatchMap = mapID + if refPet.Ext != 0 && grand.Meet(1, 500) { + monster.RandomByWeightShiny() + } + + petCfg, ok := xmlres.PetMAP[int(monster.ID)] + if !ok { + return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + + monsterInfo := &model.PlayerInfo{ + Nick: petCfg.DefName, + PetList: []model.PetInfo{*monster}, + } + return monster, monsterInfo, 0 +} + +func handleNpcFightRewards(p *player.Player, foi model.FightOverInfo, monster *model.PetInfo) { + if foi.Reason != 0 || foi.WinnerId != p.Info.UserID || !p.CanGet() { + return + } + + petCfg, ok := xmlres.PetMAP[int(monster.ID)] + if !ok { + return + } + + exp := uint32(petCfg.YieldingExp) * monster.Level / 7 + addlevel, poolevel := p.CanGetExp() + addexp := gconv.Float32(addlevel * gconv.Float32(exp)) + poolexp := gconv.Float32(poolevel) * gconv.Float32(exp) + rewards := &fightinfo.S2C_GET_BOSS_MONSTER{} + + p.ItemAdd(3, int64(poolexp+addexp)) + rewards.ADDitem(3, uint32(poolexp)) + p.AddPetExp(foi.Winpet, int64(addexp)) + + if p.CanGetItem() { + itemID := p.GetSpace().GetDrop() + if itemID != 0 { + count := uint32(grand.N(1, 2)) + if p.ItemAdd(itemID, int64(count)) { + rewards.ADDitem(uint32(itemID), count) + } + } + } + + petType := int64(petCfg.Type) + if monster.IsShiny() && p.CanGetXUAN() && petType < 16 { + xuanID := uint32(400686 + petType) + count := uint32(grand.N(1, 2)) + if p.ItemAdd(int64(xuanID), int64(count)) { + rewards.ADDitem(xuanID, count) + } + } + + p.SendPackCmd(8004, rewards) + foi.Winpet.AddEV(gconv.Int64s(strings.Fields(petCfg.YieldingEV))) +} diff --git a/logic/controller/fight_塔.go b/logic/controller/fight_塔.go index 86cbaa6d3..b821dc254 100644 --- a/logic/controller/fight_塔.go +++ b/logic/controller/fight_塔.go @@ -17,201 +17,239 @@ import ( "github.com/jinzhu/copier" ) +const ( + towerCmdChoiceTrial uint32 = 2428 + towerCmdChoiceBrave uint32 = 2414 + towerCmdFightTrial uint32 = 2429 + towerCmdFightBrave uint32 = 2415 + towerCmdFightDark uint32 = 2425 + + towerTaskDark int = 110 + towerTaskBrave int = 500 + towerTaskTrial int = 600 +) + +type towerChoiceState struct { + currentLevel *uint32 + maxLevel *uint32 + service *service.TowerService +} + // 暗黑门进入boss func (h Controller) FreshOpen(data *fight.C2S_OPEN_DARKPORTAL, c *player.Player) (result *fight.S2C_OPEN_DARKPORTAL, err errorcode.ErrorCode) { - result = &fight.S2C_OPEN_DARKPORTAL{} - // c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1) - // c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1) - boss := service.NewTower110Service().Boss(uint32(data.Level)) - if len(boss) == 0 { + + towerBosses := service.NewTower110Service().Boss(uint32(data.Level)) + bossConfig, ok := firstTowerBossConfig(towerBosses) + if !ok { return nil, errorcode.ErrorCodes.ErrPokemonNotExists } - if len(boss[0].BossIds) == 0 { + + bosses := service.NewBossService().Get(bossConfig.BossIds[0]) + if len(bosses) == 0 { return nil, errorcode.ErrorCodes.ErrPokemonNotExists } - result = &fight.S2C_OPEN_DARKPORTAL{} - r := service.NewBossService().Get(boss[0].BossIds[0]) - result.CurBossID = uint32(r[0].MonID) + result.CurBossID = uint32(bosses[0].MonID) c.CurDark = uint32(data.Level) defer c.GetSpace().LeaveMap(c) return result, 0 } // FreshChoiceFightLevel 处理玩家选择挑战模式(试炼之塔或勇者之塔) -// 根据不同的CMD值设置玩家的挑战状态和地图信息,并返回当前挑战层级信息 -// 参数: -// -// data: 客户端发送的挑战层级选择请求数据,包含CMD和挑战层级 -// c: 玩家对象,包含玩家的详细信息 -// -// 返回值: -// -// result: 服务器返回给客户端的挑战层级信息,包含当前战斗层级和Boss ID -// err: 错误码,表示处理过程中是否出现错误 func (h Controller) FreshChoiceFightLevel(data *fight.C2S_FRESH_CHOICE_FIGHT_LEVEL, c *player.Player) (result *fight.S2C_FreshChoiceLevelRequestInfo, err errorcode.ErrorCode) { - result = &fight.S2C_FreshChoiceLevelRequestInfo{} c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1) c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1) + choiceState, ok := towerChoiceRuntime(c, data.Head.CMD) + if !ok { + return nil, errorcode.ErrorCodes.ErrSystemError + } + if data.Level > 0 { - switch data.Head.CMD { - case 2428: //试炼之塔 - if data.Level > uint(c.Info.MaxFreshStage) && data.Level != 1 { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - - c.Info.CurrentFreshStage = uint32(data.Level) - case 2414: //勇者之塔 - if data.Level > uint(c.Info.MaxStage) && data.Level != 1 { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - c.Info.CurrentStage = uint32(data.Level) - + if !canSelectTowerLevel(data.Level, *choiceState.maxLevel) { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists } - + *choiceState.currentLevel = uint32(data.Level) } - var boss []configmodel.BaseTowerConfig - switch data.Head.CMD { - case 2428: //试炼之塔 - result.CurFightLevel = uint32(c.Info.CurrentFreshStage) - boss = service.NewTower600Service().Boss(c.Info.CurrentFreshStage) + result.CurFightLevel = *choiceState.currentLevel + appendTowerBossPreview(&result.BossId, choiceState.service.Boss(*choiceState.currentLevel)) - case 2414: //勇者之塔 - - result.CurFightLevel = uint32(c.Info.CurrentStage) - boss = service.NewTower500Service().Boss(c.Info.CurrentStage) - //next := service.NewTower600Service().Boss(c.Info.CurrentFreshStage + 1) - - } - if len(boss) != 0 && len(boss[0].BossIds) != 0 { - //单节点,取获取到的一个,然后因为不是剧情,所以只有一层 - r := service.NewBossService().Get(boss[0].BossIds[0]) - for _, v := range r { - result.BossId = append(result.BossId, uint32(v.MonID)) - } - - } - // 重置玩家的Canmon标志位为0,表示可以刷怪 atomic.StoreUint32(&c.Canmon, 0) - // 在函数结束时将玩家传送到对应地图 defer c.GetSpace().LeaveMap(c) return result, 0 } -func (h Controller) FreshLeaveFightLevel(data *fight.FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { +func (h Controller) FreshLeaveFightLevel(data *fight.FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { + _ = data defer c.GetSpace().EnterMap(c) out := info.NewOutInfo() copier.CopyWithOption(out, c.GetInfo(), copier.Option{DeepCopy: true}) - //c.SendPackCmd(2001, out) return result, 0 } func (h Controller) PetTawor(data *fight.StartTwarInboundInfo, c *player.Player) (result *fight.S2C_ChoiceLevelRequestInfo, err errorcode.ErrorCode) { - r := c.CanFight() - if c.CanFight() != 0 { - return nil, r + if err = c.CanFight(); err != 0 { + return nil, err } + + bossList, currentLevel, taskID, ok := towerFightBosses(c, data.Head.CMD) + if !ok { + return nil, errorcode.ErrorCodes.ErrSystemError + } + + currentBoss, ok := firstTowerBossConfig(bossList) + if !ok { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + + result = &fight.S2C_ChoiceLevelRequestInfo{CurFightLevel: currentLevel} + appendTowerNextBossPreview(&result.BossID, bossList) + + monsterInfo, ok := buildTowerMonsterInfo(currentBoss) + if !ok { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + c.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE c.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC - monsterInfo := &model.PlayerInfo{} - var boss []configmodel.BaseTowerConfig - - result = &fight.S2C_ChoiceLevelRequestInfo{} - switch data.Head.CMD { - case 2429: //试炼之塔 - boss = service.NewTower600Service().Boss(c.Info.CurrentFreshStage, c.Info.CurrentFreshStage+1) - - result.CurFightLevel = uint32(c.Info.CurrentFreshStage) - case 2415: //勇者之塔 - boss = service.NewTower500Service().Boss(c.Info.CurrentStage, c.Info.CurrentStage+1) - - result.CurFightLevel = uint32(c.Info.CurrentStage) - case 2425: - boss = service.NewTower110Service().Boss(c.CurDark) - - } - if len(boss) > 1 { - r := service.NewBossService().Get(boss[1].BossIds[0]) - for _, v := range r { - result.BossID = append(result.BossID, uint32(v.MonID)) - } - - } - - bosss := service.NewBossService().Get(boss[0].BossIds[0]) - monsterInfo.Nick = boss[0].Name - for i, r := range bosss { - - monster := model.GenPetInfo(int(r.MonID), 24, int(r.Nature), 0, int(r.Lv), nil, 0) - if r.Hp != 0 { - monster.Hp = uint32(r.Hp) - monster.MaxHp = uint32(r.Hp) - - } - - for i, v := range r.Prop { - if v != 0 { - monster.Prop[i] = v - } - - } - - if len(r.SKill) != 0 { - for i := 0; i < len(monster.SkillList); i++ { - if r.SKill[i] != 0 { - monster.SkillList[i].ID = r.SKill[i] - } - - } - - } - - effects := service.NewEffectService().Args(r.Effect) - - for _, v := range effects { - monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{ - Idx: uint16(v.SeIdx), - EID: gconv.Uint16(v.Eid), - Args: gconv.Ints(v.Args), - }) - } - monster.CatchTime = uint32(i) - monsterInfo.PetList = append(monsterInfo.PetList, *monster) - - } ai := player.NewAI_player(monsterInfo) - _, err = fight.NewFight(c, ai, c.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) { - - if foi.Reason == 0 && foi.WinnerId == c.Info.UserID { //我放获胜 - switch data.Head.CMD { - case 2429: //试炼之塔 - c.TawerCompletedTask(600, int(c.Info.CurrentFreshStage)) - c.Info.CurrentFreshStage++ - if c.Info.CurrentFreshStage >= c.Info.MaxFreshStage { - c.Info.MaxFreshStage = c.Info.CurrentFreshStage - } - - case 2415: //勇者之塔 - c.TawerCompletedTask(500, int(c.Info.CurrentStage)) - c.Info.CurrentStage++ - if c.Info.CurrentStage >= c.Info.MaxStage { - c.Info.MaxStage = c.Info.CurrentStage - } - case 2425: - c.TawerCompletedTask(110, int(c.CurDark)) - } - + if foi.Reason != 0 || foi.WinnerId != c.Info.UserID { + return } - - }) ///开始对战,房主方以及被邀请方 + handleTowerFightWin(c, data.Head.CMD, taskID, currentLevel) + }) return } + +func towerChoiceRuntime(c *player.Player, cmd uint32) (towerChoiceState, bool) { + switch cmd { + case towerCmdChoiceTrial: + return towerChoiceState{ + currentLevel: &c.Info.CurrentFreshStage, + maxLevel: &c.Info.MaxFreshStage, + service: service.NewTower600Service(), + }, true + case towerCmdChoiceBrave: + return towerChoiceState{ + currentLevel: &c.Info.CurrentStage, + maxLevel: &c.Info.MaxStage, + service: service.NewTower500Service(), + }, true + default: + return towerChoiceState{}, false + } +} + +func towerFightBosses(c *player.Player, cmd uint32) ([]configmodel.BaseTowerConfig, uint32, int, bool) { + switch cmd { + case towerCmdFightTrial: + return service.NewTower600Service().Boss(c.Info.CurrentFreshStage, c.Info.CurrentFreshStage+1), c.Info.CurrentFreshStage, towerTaskTrial, true + case towerCmdFightBrave: + return service.NewTower500Service().Boss(c.Info.CurrentStage, c.Info.CurrentStage+1), c.Info.CurrentStage, towerTaskBrave, true + case towerCmdFightDark: + return service.NewTower110Service().Boss(c.CurDark), c.CurDark, towerTaskDark, true + default: + return nil, 0, 0, false + } +} + +func canSelectTowerLevel(targetLevel uint, maxLevel uint32) bool { + return targetLevel == 1 || targetLevel <= uint(maxLevel) +} + +func firstTowerBossConfig(bossList []configmodel.BaseTowerConfig) (configmodel.BaseTowerConfig, bool) { + if len(bossList) == 0 || len(bossList[0].BossIds) == 0 { + return configmodel.BaseTowerConfig{}, false + } + return bossList[0], true +} + +func appendTowerBossPreview(dst *[]uint32, bossList []configmodel.BaseTowerConfig) { + bossConfig, ok := firstTowerBossConfig(bossList) + if !ok { + return + } + + bosses := service.NewBossService().Get(bossConfig.BossIds[0]) + for _, boss := range bosses { + *dst = append(*dst, uint32(boss.MonID)) + } +} + +func appendTowerNextBossPreview(dst *[]uint32, bossList []configmodel.BaseTowerConfig) { + if len(bossList) < 2 || len(bossList[1].BossIds) == 0 { + return + } + + bosses := service.NewBossService().Get(bossList[1].BossIds[0]) + for _, boss := range bosses { + *dst = append(*dst, uint32(boss.MonID)) + } +} + +func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.PlayerInfo, bool) { + bosses := service.NewBossService().Get(towerBoss.BossIds[0]) + if len(bosses) == 0 { + return nil, false + } + + monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name} + for i, boss := range bosses { + monster := model.GenPetInfo(int(boss.MonID), 24, int(boss.Nature), 0, int(boss.Lv), nil, 0) + if boss.Hp != 0 { + monster.Hp = uint32(boss.Hp) + monster.MaxHp = uint32(boss.Hp) + } + + for statIdx, prop := range boss.Prop { + if prop != 0 { + monster.Prop[statIdx] = prop + } + } + + for skillIdx := 0; skillIdx < len(monster.SkillList) && skillIdx < len(boss.SKill); skillIdx++ { + if boss.SKill[skillIdx] != 0 { + monster.SkillList[skillIdx].ID = boss.SKill[skillIdx] + } + } + + effects := service.NewEffectService().Args(boss.Effect) + for _, effect := range effects { + monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{ + Idx: uint16(effect.SeIdx), + EID: gconv.Uint16(effect.Eid), + Args: gconv.Ints(effect.Args), + }) + } + + monster.CatchTime = uint32(i) + monsterInfo.PetList = append(monsterInfo.PetList, *monster) + } + + return monsterInfo, true +} + +func handleTowerFightWin(c *player.Player, cmd uint32, taskID int, currentLevel uint32) { + c.TawerCompletedTask(taskID, int(currentLevel)) + + switch cmd { + case towerCmdFightTrial: + c.Info.CurrentFreshStage++ + if c.Info.CurrentFreshStage >= c.Info.MaxFreshStage { + c.Info.MaxFreshStage = c.Info.CurrentFreshStage + } + case towerCmdFightBrave: + c.Info.CurrentStage++ + if c.Info.CurrentStage >= c.Info.MaxStage { + c.Info.MaxStage = c.Info.CurrentStage + } + } +} diff --git a/logic/controller/item_buy.go b/logic/controller/item_buy.go index 020781d4a..a5e5722dc 100644 --- a/logic/controller/item_buy.go +++ b/logic/controller/item_buy.go @@ -1,7 +1,6 @@ package controller import ( - "blazing/common/data/xmlres" "blazing/common/socket/errorcode" "blazing/modules/config/service" @@ -12,73 +11,34 @@ import ( // 防止封包通过领取来获取道具 // BuyItem 购买单个道具 -// data: 包含购买道具信息的输入数据 -// player: 当前玩家对象 -// 返回: 购买结果和错误码 func (h Controller) BuyItem(data *item.BuyInboundInfo, player *player.Player) (result *item.BuyOutboundInfo, err errorcode.ErrorCode) { - itemInfo, exists := xmlres.ItemsMAP[int(data.ItemId)] - if !exists { - return &item.BuyOutboundInfo{Coins: player.Info.Coins}, 0 + result = &item.BuyOutboundInfo{Coins: player.Info.Coins} + + bought, err := buySeerdouBackpackItem(player, data.ItemId, data.Count) + if err != 0 { + return result, err + } + if !bought { + result.Coins = player.Info.Coins + return result, 0 } - // 免费道具直接添加 - if itemInfo.Price == 0 { - if player.ItemAdd(data.ItemId, data.Count) { - return &item.BuyOutboundInfo{ - ItemId: data.ItemId, - Level: 1, - Count: data.Count, - Coins: player.Info.Coins, - }, 0 - } - return &item.BuyOutboundInfo{Coins: player.Info.Coins}, 0 - } - - // 需要付费的道具 - totalCost := int64(data.Count) * int64(itemInfo.Price) - if !player.GetCoins(totalCost) { - return &item.BuyOutboundInfo{Coins: player.Info.Coins}, errorcode.ErrorCodes.ErrSunDouInsufficient10016 - } - - if player.ItemAdd(data.ItemId, data.Count) { - player.Info.Coins -= totalCost - return &item.BuyOutboundInfo{ - ItemId: data.ItemId, - Level: 1, - Count: data.Count, - Coins: player.Info.Coins, - }, 0 - } - - // 购买失败,返还赛尔豆 - player.Info.Coins += totalCost - return &item.BuyOutboundInfo{Coins: player.Info.Coins}, 0 + result.ItemId = data.ItemId + result.Level = 1 + result.Count = data.Count + result.Coins = player.Info.Coins + return result, 0 } // BuyMultipleItems 批量购买道具 -// data: 包含批量购买道具信息的输入数据 -// player: 当前玩家对象 -// 返回: 批量购买结果和错误码 func (h Controller) BuyMultipleItems(data *item.BuyMultiInboundInfo, player *player.Player) (result *item.BuyMultiOutboundInfo, err errorcode.ErrorCode) { for _, itemID := range data.ItemIds { - itemInfo, exists := xmlres.ItemsMAP[int(itemID)] - if !exists { - continue - } - - // 免费道具直接添加 - if itemInfo.Price == 0 { - player.ItemAdd(int64(itemID), 1) - continue - } - - // 需要付费的道具 - if !player.GetCoins(int64(itemInfo.Price)) { + bought, buyErr := buySeerdouBackpackItem(player, int64(itemID), 1) + if buyErr == errorcode.ErrorCodes.ErrSunDouInsufficient10016 { break } - - if player.ItemAdd(int64(itemID), 1) { - player.Info.Coins -= int64(itemInfo.Price) + if buyErr != 0 || !bought { + continue } } @@ -88,11 +48,7 @@ func (h Controller) BuyMultipleItems(data *item.BuyMultiInboundInfo, player *pla } // BuyGoldItem 使用金豆购买商品 -// data: 包含金豆购买商品信息的输入数据 -// player: 当前玩家对象 -// 返回: 金豆购买结果和错误码 func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player.Player) (result *item.S2C_GoldBuyProductInfo, err errorcode.ErrorCode) { - //product, exists := xmlres.GoldProductMap[int(data.ProductID)] pro := service.NewShopService().Get(data.ProductID) if pro == nil { return nil, errorcode.ErrorCodes.ErrTooManyProducts @@ -105,15 +61,6 @@ func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player. } } - // if pro.QuotaType != 0 { - // if data.Count > int64(pro.QuotaLimit) { - // return nil, errorcode.ErrorCodes.ErrExceedStock - // } - // if player.Service.Talk.Cheak(0, int(data.ProductID)) { - // return nil, errorcode.ErrorCodes.ErrExceedStock - // } - // } - var usegold uint64 switch data.Type { @@ -136,12 +83,10 @@ func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player. return nil, errorcode.ErrorCodes.ErrSystemError } usegold = uint64(data.Count) * uint64(pro.JindouPrice*100) - } switch data.Type { case 0: - if player.ItemAdd(pro.ProductID, data.Count) { player.Info.Coins -= int64(usegold) } @@ -157,9 +102,6 @@ func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player. if player.ItemAdd(pro.ProductID, data.Count) { player.User.UpdateGold(player.Info.UserID, -int64(usegold)) } - - //购买成功,执行记录 - } player.SendPackCmd(1105, item.GoldOnlineRemainOutboundInfo{ diff --git a/logic/controller/item_use.go b/logic/controller/item_use.go index 1db177769..ddd443d4f 100644 --- a/logic/controller/item_use.go +++ b/logic/controller/item_use.go @@ -22,16 +22,8 @@ const ( // c: 当前玩家对象 // 返回: 道具列表和错误码 func (h Controller) GetUserItemList(data *item.ItemListInboundInfo, c *player.Player) (result *item.ItemListOutboundInfo, err errorcode.ErrorCode) { - result = &item.ItemListOutboundInfo{} - - items := c.Service.Item.Get(data.Param1, data.Param2) - result.ItemList = make([]model.SingleItemInfo, len(items)) - for i, itemData := range items { - result.ItemList[i] = model.SingleItemInfo{ - ItemId: itemData.ItemId, - ItemCnt: uint32(itemData.ItemCnt), - LeftTime: ItemDefaultLeftTime, - } + result = &item.ItemListOutboundInfo{ + ItemList: c.Service.Item.GetUserItemList(data.Param1, data.Param2, ItemDefaultLeftTime), } return result, 0 } @@ -48,7 +40,7 @@ func (h Controller) UsePetItemOutOfFight(data *item.C2S_USE_PET_ITEM_OUT_OF_FIGH itemID := uint32(data.ItemID) if c.Service.Item.CheakItem(itemID) == 0 { - return nil, errorcode.ErrorCodes.ErrSystemError + return nil, errorcode.ErrorCodes.ErrInsufficientItems } itemCfg, ok := xmlres.ItemsMAP[int(itemID)] @@ -195,7 +187,7 @@ func (h Controller) ResetNature(data *item.C2S_PET_RESET_NATURE, c *player.Playe } if c.Service.Item.CheakItem(data.ItemId) <= 0 { - return nil, errorcode.ErrorCodes.ErrSystemError + return nil, errorcode.ErrorCodes.ErrInsufficientItems } currentHP := currentPet.Hp @@ -218,7 +210,7 @@ func (h Controller) UseSpeedupItem(data *item.C2S_USE_SPEEDUP_ITEM, c *player.Pl // 1. 校验道具是否存在且数量充足 itemCount := c.Service.Item.CheakItem(data.ItemID) if itemCount <= 0 { - return nil, errorcode.ErrorCodes.ErrSystemError // 道具不足复用系统错误码(可根据需求改为专属错误码) + return nil, errorcode.ErrorCodes.ErrInsufficientItems } result = &item.S2C_USE_SPEEDUP_ITEM{} @@ -275,7 +267,7 @@ func (h Controller) UseEnergyXishou(data *item.C2S_USE_ENERGY_XISHOU, c *player. // 1. 校验道具是否存在且数量充足 itemCount := c.Service.Item.CheakItem(data.ItemID) if itemCount <= 0 { - return nil, errorcode.ErrorCodes.ErrSystemError + return nil, errorcode.ErrorCodes.ErrInsufficientItems } if c.Info.EnergyTime != 0 { return nil, errorcode.ErrorCodes.ErrItemInUse @@ -311,7 +303,7 @@ func (h Controller) UseAutoFightItem(data *item.C2S_USE_AUTO_FIGHT_ITEM, c *play itemCount := c.Service.Item.CheakItem(data.ItemID) if itemCount <= 0 { - return nil, errorcode.ErrorCodes.ErrSystemError + return nil, errorcode.ErrorCodes.ErrInsufficientItems } if c.Info.AutoFightTime != 0 { return nil, errorcode.ErrorCodes.ErrItemInUse diff --git a/logic/controller/login_getserver.go b/logic/controller/login_getserver.go deleted file mode 100644 index 098c44fce..000000000 --- a/logic/controller/login_getserver.go +++ /dev/null @@ -1,54 +0,0 @@ -package controller - -// var sg singleflight.Group - -// var ServerList []rpc.ServerInfo - -// func fetchData() (any, error) { -// ServerList = rpc.GetServerInfoList() //todo 待修改增加缓存 -// return ServerList, nil -// } - -// GetServerOnline 处理命令: 105 -// func (h Controller) GetServerOnline(data *user.SidInfo, c gnet.Conn) (result *rpc.CommendSvrInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 -// result = rpc.NewInInfo() -// // tt, _ := cool.CacheManager.Keys(context.Background()) -// //g.Dump(tt) -// t1 := hex.EncodeToString(data.Sid) -// userid, ok := playerservice.User.Load(t1) -// if !ok || userid != data.Head.UserID { - -// defer c.Close() -// return -// } - -// result.IsVip = 1 -// result.ServerList = rpc.GetServerInfoList(service.NewBaseSysUserService().GetPerson(data.Head.UserID).Debug) -// ser := playerservice.NewUserService(data.Head.UserID) -// f, b := ser.Friend.Get() -// for _, v := range f { -// result.FriendInfo = append(result.FriendInfo, rpc.FriendInfo{Userid: v, TimePoke: 1}) -// } -// result.BlackInfo = b -// defer func() { - -// // share.ShareManager.DeleteSession(t1) - -// kickErr := ser.Info.Kick(data.Head.UserID) -// if kickErr != nil { -// fmt.Println("踢人失败", kickErr) - -// } -// logininfo := ser.Info.SetLogin() -// if logininfo != nil { -// cool.CacheManager.Set(context.TODO(), fmt.Sprintf("player:%d", data.Head.UserID), logininfo, 0) -// cool.CacheManager.Set(context.Background(), fmt.Sprintf("session:%d", data.Head.UserID), t1, 0) - -// } - -// }() - -// return - -// //return //TODO 这里待实现改成接口调用Ret方法 -// } diff --git a/logic/controller/nono.go b/logic/controller/nono.go index 5ca209ef4..9851a8276 100644 --- a/logic/controller/nono.go +++ b/logic/controller/nono.go @@ -4,20 +4,25 @@ import ( "blazing/common/socket/errorcode" "blazing/logic/service/nono" "blazing/logic/service/player" +) - "github.com/jinzhu/copier" +const ( + nonoFuncValue = 255 + nonoDefaultNum = 1 + nonoDefaultPower = 0 + nonoDefaultLevel = 12 + nonoPetCureCost int64 = 50 ) func (h Controller) NonoFollowOrHome(data *nono.NonoFollowOrHomeInInfo, c *player.Player) (result *nono.NonoFollowOutInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 c.Info.NONO.Flag = data.Flag result = &nono.NonoFollowOutInfo{ - UserID: data.Head.UserID, SuperStage: data.Flag, Flag: data.Flag, Nick: "", Color: c.Info.NONO.NonoColor, - Power: 0, + Power: nonoDefaultPower, } return @@ -25,22 +30,26 @@ func (h Controller) NonoFollowOrHome(data *nono.NonoFollowOrHomeInInfo, c *playe // GetNonoInfo 获取nono信息 func (h *Controller) GetNonoInfo(data *nono.NonoInboundInfo, c *player.Player) (result *nono.NonoOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 + _ = data - result = &nono.NonoOutboundInfo{} - copier.Copy(result, &c.Info.NONO) - result.UserID = c.Info.UserID - - for i := 0; i < 20; i++ { - result.Func[i] = 255 + result = &nono.NonoOutboundInfo{ + UserID: c.Info.UserID, + Nick: c.Info.NONO.Nick, + SuperNono: nonoDefaultNum, + Color: c.Info.NONO.NonoColor, + Num: nonoDefaultNum, + SuperLevel: nonoDefaultLevel, + SuperStage: c.Info.NONO.Flag, + Power: nonoDefaultPower, + SuperEnergy: nonoDefaultPower, + } + for i := range result.Func { + result.Func[i] = nonoFuncValue } - result.Num = 1 - result.SuperNono = 1 - result.SuperLevel = 12 return } func (h *Controller) SwitchFlying(data *nono.SwitchFlyingInboundInfo, c *player.Player) (result *nono.SwitchFlyingOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 - result = &nono.SwitchFlyingOutboundInfo{ UserId: data.Head.UserID, Type: data.Type, @@ -51,16 +60,16 @@ func (h *Controller) SwitchFlying(data *nono.SwitchFlyingInboundInfo, c *player. } func (h *Controller) PlayerPetCure(data *nono.PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的 + _ = data if c.GetSpace().Owner.UserID == c.Info.UserID { return result, errorcode.ErrorCodes.ErrChampionCannotHeal } - if !c.GetCoins(50) { + if !c.GetCoins(nonoPetCureCost) { return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016 } - for i := 0; i < len(c.Info.PetList); i++ { + for i := range c.Info.PetList { c.Info.PetList[i].Cure() - } - c.Info.Coins -= 50 + c.Info.Coins -= nonoPetCureCost return } diff --git a/logic/controller/pet_elo.go b/logic/controller/pet_elo.go index ef61343ec..7fdc7668b 100644 --- a/logic/controller/pet_elo.go +++ b/logic/controller/pet_elo.go @@ -18,34 +18,34 @@ func (h Controller) PetELV(data *C2S_PET_EVOLVTION, c *player.Player) (result *f return nil, errorcode.ErrorCodes.Err10401 } - flag := xmlres.PetMAP[int(currentPet.ID)].EvolvFlag - - if flag == 0 { + petCfg, ok := xmlres.PetMAP[int(currentPet.ID)] + if !ok || petCfg.EvolvFlag == 0 { return nil, errorcode.ErrorCodes.ErrPokemonNotEvolveReady } - if xmlres.PetMAP[int(currentPet.ID)].EvolvingLv > int(currentPet.Level) { + if petCfg.EvolvingLv > int(currentPet.Level) { return nil, errorcode.ErrorCodes.ErrPokemonNotEvolveReady } - evinfo := xmlres.EVOLVMAP[flag].Branches[data.Index-1] - if c.Service.Item.CheakItem(uint32(evinfo.EvolvItem)) < int64(evinfo.EvolvItemCount) { + evolveCfg, ok := xmlres.EVOLVMAP[petCfg.EvolvFlag] + if !ok || data.Index == 0 || int(data.Index) > len(evolveCfg.Branches) { + return nil, errorcode.ErrorCodes.ErrPokemonNotEvolveReady + } + + branch := evolveCfg.Branches[data.Index-1] + if branch.EvolvItem != 0 && c.Service.Item.CheakItem(uint32(branch.EvolvItem)) < int64(branch.EvolvItemCount) { return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti } - if evinfo.EvolvItem != 0 { - c.Service.Item.UPDATE(uint32(evinfo.EvolvItem), -evinfo.EvolvItemCount) + if branch.EvolvItem != 0 { + c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount) } - currentPet.ID = uint32(xmlres.EVOLVMAP[flag].Branches[data.Index-1].MonTo) + currentPet.ID = uint32(branch.MonTo) currentPet.Update(true) currentPet.CalculatePetPane(100) - currentPet.Update(true) updateOutbound := &info.PetUpdateOutboundInfo{} - var petUpdateInfo info.UpdatePropInfo - copier.Copy(&petUpdateInfo, currentPet) - updateOutbound.Data = append(updateOutbound.Data, petUpdateInfo) c.SendPackCmd(2508, updateOutbound) //准备包由各自发,因为协议不一样 return nil, -1 diff --git a/logic/controller/pet_ev.go b/logic/controller/pet_ev.go index 4e01ce420..eabf6cdd3 100644 --- a/logic/controller/pet_ev.go +++ b/logic/controller/pet_ev.go @@ -5,8 +5,11 @@ import ( "blazing/logic/service/common" "blazing/logic/service/fight" "blazing/logic/service/player" +) - "github.com/samber/lo" +const ( + petEVMaxTotal uint32 = 510 + petEVMaxSingle uint32 = 255 ) // PetEVDiy 自定义分配宠物努力值(EV) @@ -18,30 +21,39 @@ func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullO if !found { return nil, errorcode.ErrorCodes.Err10401 } - // 分配超过510的数据 - if lo.Sum(data.EVs[:]) > 510 { - return nil, errorcode.ErrorCodes.Err10401 - } + var targetTotal uint32 + var currentTotal uint32 for i, evValue := range data.EVs { - // 分配超过255的数据 - if evValue > 255 { + currentEV := currentPet.Ev[i] + + // 单项分配不能超过上限。 + if evValue > petEVMaxSingle { return nil, errorcode.ErrorCodes.Err10401 } - // 分配比之前点数少的 - if evValue < currentPet.Ev[i] { + + targetTotal += evValue + if targetTotal > petEVMaxTotal { return nil, errorcode.ErrorCodes.Err10401 } - } - if lo.Sum(data.EVs[:]) < lo.Sum(currentPet.Ev[:]) { - return nil, errorcode.ErrorCodes.Err10401 + + // 不允许回退已分配的努力值。 + if evValue < currentEV { + return nil, errorcode.ErrorCodes.Err10401 + } + currentTotal += currentEV } - usedEV := lo.Sum(data.EVs[:]) - lo.Sum(currentPet.Ev[:]) - // 加的比池子还多 + usedEV := targetTotal - currentTotal + if usedEV == 0 { + return result, 0 + } + + // 新增分配不能超过玩家累计学习力池。 if int64(usedEV) > c.Info.EVPool { return nil, errorcode.ErrorCodes.Err10401 } + currentPet.Ev = data.EVs currentPet.CalculatePetPane(100) c.Info.EVPool -= int64(usedEV) diff --git a/logic/controller/pet_fusion.go b/logic/controller/pet_fusion.go index e36809f88..7daa47bc5 100644 --- a/logic/controller/pet_fusion.go +++ b/logic/controller/pet_fusion.go @@ -10,133 +10,159 @@ import ( "github.com/alpacahq/alpacadecimal" "github.com/gogf/gf/v2/util/grand" - "github.com/samber/lo" +) + +const ( + petFusionCost = 1000 + petFusionKeepAuxItemID = 300043 + petFusionFailureItemID = 300044 + petFusionSoulID = 1000017 ) func (h Controller) PetFusion(data *pet.C2S_PetFusion, c *player.Player) (result *pet.PetFusionInfo, err errorcode.ErrorCode) { - if !c.GetCoins(1000) { - return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016 + result = &pet.PetFusionInfo{ + SoulID: petFusionSoulID, } - c.Info.Coins -= 1000 - // g.Dump(c.Info.PetList) - //防止同一只 if data.Mcatchtime == data.Auxcatchtime { return result, errorcode.ErrorCodes.ErrPokemonNotFusionReady } - _, Mcatchpetinfo, ok := c.FindPet(data.Mcatchtime) + _, masterPet, ok := c.FindPet(data.Mcatchtime) if !ok { return result, errorcode.ErrorCodes.ErrPokemonNotFusionReady2 } - - if xmlres.PetMAP[int(Mcatchpetinfo.ID)].FuseMaster == 0 { + masterCfg, ok := xmlres.PetMAP[int(masterPet.ID)] + if !ok || masterCfg.FuseMaster == 0 { return result, errorcode.ErrorCodes.ErrPokemonNotFusionReady3 } - _, Auxpetinfo, ok := c.FindPet(data.Auxcatchtime) + + _, auxPet, ok := c.FindPet(data.Auxcatchtime) if !ok { return result, errorcode.ErrorCodes.ErrPokemonNotFusionReady2 } - - if xmlres.PetMAP[int(Auxpetinfo.ID)].FuseSub == 0 { + auxCfg, ok := xmlres.PetMAP[int(auxPet.ID)] + if !ok || auxCfg.FuseSub == 0 { return result, errorcode.ErrorCodes.ErrPokemonNotFusionReady3 } - //println(len(c.Info.PetList), data.Mcatchtime, data.Auxcatchtime, Mcatchpetinfo.CatchTime, Auxpetinfo.CatchTime) - ///性格生成 - var natureId int32 = -1 - if Auxpetinfo.Nature == Mcatchpetinfo.Nature { - natureId = int32(Auxpetinfo.Nature) + + materialCounts := collectItemCounts(data.Item1[:]) + if !hasEnoughItems(c, materialCounts) { + return result, errorcode.ErrorCodes.ErrInsufficientItems } - for _, v := range c.Service.Item.CheakItemM(data.Item1[:]...) { - - if v.ItemCnt <= 0 { - return result, errorcode.ErrorCodes.ErrInsufficientItems - } - } - - resid := int(service.NewPetFusionService().Data(Mcatchpetinfo.ID, Auxpetinfo.ID, Mcatchpetinfo.Level+Auxpetinfo.Level)) - effect := int(service.NewPetFusionMaterialService().Data(data.Item1)) - + materialService := service.NewPetFusionMaterialService() + effect := int(materialService.Data(data.Item1)) if effect == 0 { return result, errorcode.ErrorCodes.ErrSpiritOrbNotExists } - //c.Service.Item.UPDATEM(data.Item1[:], -1) - // utils.CountSliceElements(data.Item1[:]) - - for _, v := range data.Item1 { - err := c.Service.Item.UPDATE(v, -1) - if err != nil { - return result, errorcode.ErrorCodes.ErrInsufficientItems - } + fusionService := service.NewPetFusionService() + resultPetID := int(fusionService.Data(masterPet.ID, auxPet.ID, masterPet.Level+auxPet.Level)) + if !c.GetCoins(petFusionCost) { + return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016 } - result = &pet.PetFusionInfo{ - SoulID: 1000017, + consumeItems(c, materialCounts) + c.Info.Coins -= petFusionCost - CostItemFlag: 0, - } - if resid == 0 { - - _, ok := lo.Find(data.GoldItem1[:], func(item uint32) bool { - return item == 300044 - }) - if c.Service.Item.CheakItem(300044) > 0 && ok { - c.Service.Item.UPDATE(300044, -1) + if resultPetID == 0 { + if useOptionalItem(c, data.GoldItem1[:], petFusionFailureItemID) { result.CostItemFlag = 1 - + } else if auxPet.Level > 5 { + auxPet.Downgrade(auxPet.Level - 5) } else { - if Auxpetinfo.Level > 5 { - // Auxpetinfo.Level = Auxpetinfo.Level - 5 - Auxpetinfo.Downgrade(Auxpetinfo.Level - 5) - - } else { - Auxpetinfo.Downgrade(1) - - } - + auxPet.Downgrade(1) } - return &pet.PetFusionInfo{}, 0 } - dv1 := alpacadecimal.NewFromInt(2).Div(alpacadecimal.NewFromInt(3)).Mul(alpacadecimal.NewFromInt(int64(Mcatchpetinfo.Dv))) - dv2 := alpacadecimal.NewFromInt(1).Div(alpacadecimal.NewFromInt(3)).Mul(alpacadecimal.NewFromInt(int64(Auxpetinfo.Dv))) + natureID := int32(-1) + if auxPet.Nature == masterPet.Nature { + natureID = int32(auxPet.Nature) + } + + dv1 := alpacadecimal.NewFromInt(2).Div(alpacadecimal.NewFromInt(3)).Mul(alpacadecimal.NewFromInt(int64(masterPet.Dv))) + dv2 := alpacadecimal.NewFromInt(1).Div(alpacadecimal.NewFromInt(3)).Mul(alpacadecimal.NewFromInt(int64(auxPet.Dv))) dv := dv1.Add(dv2).Add(alpacadecimal.NewFromInt(1)).IntPart() - r := model.GenPetInfo(resid, int(dv), int(natureId), effect, 1, nil, -1) - r.OldCatchTime = Mcatchpetinfo.CatchTime + newPet := model.GenPetInfo(resultPetID, int(dv), int(natureID), effect, 1, nil, -1) + newPet.OldCatchTime = masterPet.CatchTime - shinycont := 1 - if Mcatchpetinfo.IsShiny() || Auxpetinfo.IsShiny() { - shinycont = 50 + shinyChance := 1 + if masterPet.IsShiny() || auxPet.IsShiny() { + shinyChance = 50 } - if Mcatchpetinfo.IsShiny() && Auxpetinfo.IsShiny() { - shinycont = 100 + if masterPet.IsShiny() && auxPet.IsShiny() { + shinyChance = 100 + } + if grand.Meet(shinyChance, 100) { + newPet.RandomByWeightShiny() } - if grand.Meet(shinycont, 100) { - r.RandomByWeightShiny() - } - c.Service.Pet.PetAdd(r, 0) - println(c.Info.UserID, "进行融合", len(c.Info.PetList), Mcatchpetinfo.ID, Auxpetinfo.ID, r.ID) + c.Service.Pet.PetAdd(newPet, 0) + println(c.Info.UserID, "进行融合", len(c.Info.PetList), masterPet.ID, auxPet.ID, newPet.ID) c.PetDel(data.Mcatchtime) - _, ok2 := lo.Find(data.GoldItem1[:], func(item uint32) bool { - return item == 300043 - }) - - if c.Service.Item.CheakItem(300043) > 0 && ok2 { - c.Service.Item.UPDATE(300043, -1) + if useOptionalItem(c, data.GoldItem1[:], petFusionKeepAuxItemID) { result.CostItemFlag = 1 } else { - c.PetDel(data.Auxcatchtime) - } - result.ObtainTime = r.CatchTime - result.StarterCpTm = r.ID - //todo材料扣除 + + result.ObtainTime = newPet.CatchTime + result.StarterCpTm = newPet.ID return result, 0 } + +func collectItemCounts(itemIDs []uint32) map[uint32]int { + counts := make(map[uint32]int, len(itemIDs)) + for _, itemID := range itemIDs { + counts[itemID]++ + } + return counts +} + +func hasEnoughItems(c *player.Player, itemCounts map[uint32]int) bool { + if len(itemCounts) == 0 { + return true + } + + itemIDs := make([]uint32, 0, len(itemCounts)) + for itemID := range itemCounts { + itemIDs = append(itemIDs, itemID) + } + + hadItems := c.Service.Item.CheakItemM(itemIDs...) + ownedCounts := make(map[uint32]int64, len(hadItems)) + for _, item := range hadItems { + ownedCounts[item.ItemId] = item.ItemCnt + } + + for itemID, need := range itemCounts { + if ownedCounts[itemID] < int64(need) { + return false + } + } + + return true +} + +func consumeItems(c *player.Player, itemCounts map[uint32]int) { + for itemID, count := range itemCounts { + _ = c.Service.Item.UPDATE(itemID, -count) + } +} + +func useOptionalItem(c *player.Player, itemIDs []uint32, target uint32) bool { + if c.Service.Item.CheakItem(target) <= 0 { + return false + } + for _, itemID := range itemIDs { + if itemID == target { + _ = c.Service.Item.UPDATE(target, -1) + return true + } + } + return false +} diff --git a/logic/controller/pet_繁殖.go b/logic/controller/pet_繁殖.go index 6006b03a9..48f624af5 100644 --- a/logic/controller/pet_繁殖.go +++ b/logic/controller/pet_繁殖.go @@ -10,7 +10,6 @@ import ( "time" "github.com/gogf/gf/v2/util/grand" - "github.com/samber/lo" "github.com/yudeguang/ratelimit" ) @@ -35,40 +34,27 @@ func (ctl Controller) GetBreedInfo( // 前端到后端 func (ctl Controller) GetBreedPet( data *pet.C2S_GET_BREED_PET, playerObj *player.Player) (result *pet.S2C_GET_BREED_PET, err errorcode.ErrorCode) { //这个时候player应该是空的 - _, fPet, found := playerObj.FindPet(data.MaleCatchTime) - if !found { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - if fPet.Gender != 1 { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - if fPet.Generation > 9 { + _, malePet, found := playerObj.FindPet(data.MaleCatchTime) + if !found || !canBreedPet(malePet, 1) { return nil, errorcode.ErrorCodes.ErrPokemonNotExists } + result = &pet.S2C_GET_BREED_PET{} + compatibleFemaleIDs := buildBreedPetIDSet(service.NewEggService().GetData(malePet.ID)) + if len(compatibleFemaleIDs) == 0 { + return result, 0 + } - MPETS := service.NewEggService().GetData(fPet.ID) - - // 这里只是示例,实际应该根据雄性精灵的catchTime查找可繁殖的雌性精灵 - for _, v := range playerObj.Info.PetList { - if v.Level < 50 { + result.FemaleList = make([]uint32, 0, len(playerObj.Info.PetList)) + for i := range playerObj.Info.PetList { + femalePet := &playerObj.Info.PetList[i] + if !canBreedPet(femalePet, 2) { continue } - //不是雌性 - if v.Gender != 2 { + if _, ok := compatibleFemaleIDs[femalePet.ID]; !ok { continue } - if v.Generation > 9 { - continue - } - _, ok := lo.Find(MPETS, func(item int32) bool { - return item == int32(v.ID) - }) - if !ok { - continue - } - // 如果是雌性精灵,且可以繁殖,则添加到列表 - result.FemaleList = append(result.FemaleList, v.CatchTime) + result.FemaleList = append(result.FemaleList, femalePet.CatchTime) } return result, 0 @@ -78,34 +64,38 @@ func (ctl Controller) GetBreedPet( // 前端到后端 func (ctl Controller) StartBreed( data *pet.C2S_START_BREED, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 - - if !player.GetCoins(1000) { - return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016 - } - player.Info.Coins -= 1000 - _, MalePet, found := player.FindPet(data.Male) - if !found { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - _, Female, found := player.FindPet(data.Female) - if !found { - return nil, errorcode.ErrorCodes.ErrPokemonNotExists - } - // TODO: 实现开始繁殖的具体逻辑 - result = &fight.NullOutboundInfo{} - r := player.Service.Egg.StartBreed(MalePet, Female) - if !r { + if data.Male == data.Female { return nil, errorcode.ErrorCodes.ErrCannotPerformAction } - MalePet.Generation++ - Female.Generation++ + _, malePet, found := player.FindPet(data.Male) + if !found || !canBreedPet(malePet, 1) { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + _, femalePet, found := player.FindPet(data.Female) + if !found || !canBreedPet(femalePet, 2) { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + if !canBreedPair(malePet.ID, femalePet.ID) { + return nil, errorcode.ErrorCodes.ErrCannotPerformAction + } + if !player.GetCoins(breedCost) { + return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016 + } - r1 := grand.Meet(1, 2) - if r1 { - MalePet.Gender = 0 + result = &fight.NullOutboundInfo{} + if !player.Service.Egg.StartBreed(malePet, femalePet) { + return nil, errorcode.ErrorCodes.ErrCannotPerformAction + } + + player.Info.Coins -= breedCost + malePet.Generation++ + femalePet.Generation++ + + if grand.Meet(1, 2) { + malePet.Gender = 0 } else { - Female.Gender = 0 + femalePet.Gender = 0 } return result, 0 @@ -113,6 +103,23 @@ func (ctl Controller) StartBreed( // GetEggList 获取精灵蛋数组 // 前端到后端无数据 请求协议 +func canBreedPet(p *model.PetInfo, gender int) bool { + return p != nil && p.Level >= breedMinLevel && p.Gender == gender && p.Generation <= breedMaxGeneration +} + +func buildBreedPetIDSet(ids []int32) map[uint32]struct{} { + ret := make(map[uint32]struct{}, len(ids)) + for _, id := range ids { + ret[uint32(id)] = struct{}{} + } + return ret +} + +func canBreedPair(maleID, femaleID uint32) bool { + _, ok := buildBreedPetIDSet(service.NewEggService().GetData(maleID))[femaleID] + return ok +} + func (ctl Controller) GetEggList( data *pet.C2S_GET_EGG_LIST, player *player.Player) (result *pet.S2C_GET_EGG_LIST, err errorcode.ErrorCode) { //这个时候player应该是空的 @@ -130,6 +137,12 @@ func (ctl Controller) GetEggList( } +const ( + breedMinLevel = 50 + breedMaxGeneration = 9 + breedCost = 1000 +) + var limiter *ratelimit.Rule = ratelimit.NewRule() // 简单规则案例 diff --git a/logic/controller/room_buy.go b/logic/controller/room_buy.go index 434ab9cd0..a02510221 100644 --- a/logic/controller/room_buy.go +++ b/logic/controller/room_buy.go @@ -1,28 +1,26 @@ package controller import ( - "blazing/common/data/xmlres" "blazing/common/socket/errorcode" "blazing/logic/service/player" "blazing/logic/service/room" ) // BuyFitment 购买基地家具 -// data: 包含家具ID和购买数量的输入信息 -// c: 当前玩家对象 -// 返回: 购买结果和错误码 func (h Controller) BuyFitment(data *room.C2S_BUY_FITMENT, c *player.Player) (result *room.S2C_BUY_FITMENT, err errorcode.ErrorCode) { - result = &room.S2C_BUY_FITMENT{} + result = &room.S2C_BUY_FITMENT{Coins: c.Info.Coins} - itemConfig := xmlres.ItemsMAP[int(data.ID)] - totalCost := int64(itemConfig.Price) * int64(data.Count) - - if !c.GetCoins((totalCost)) { - return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016 + bought, err := buySeerdouBackpackItem(c, int64(data.ID), int64(data.Count)) + if err != 0 { + return nil, err } - c.Service.Item.UPDATE(data.ID, int(data.Count)) - c.Info.Coins -= int64(totalCost) + if !bought { + result.Coins = c.Info.Coins + return result, 0 + } + result.ID = data.ID + result.Count = data.Count result.Coins = c.Info.Coins - return + return result, 0 } diff --git a/logic/controller/user_cdk.go b/logic/controller/user_cdk.go index 8684beece..15cc8606a 100644 --- a/logic/controller/user_cdk.go +++ b/logic/controller/user_cdk.go @@ -12,7 +12,12 @@ import ( func (h Controller) CDK(data *user.C2S_GET_GIFT_COMPLETE, player *player.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) { result = &user.S2C_GET_GIFT_COMPLETE{} - r := service.NewCdkService().Get(data.PassText) + cdkService := service.NewCdkService() + rewardPetService := service.NewPetRewardService() + itemRewardService := service.NewItemService() + now := time.Now() + + r := cdkService.Get(data.PassText) if r == nil { return nil, errorcode.ErrorCodes.ErrMolecularCodeNotExists } @@ -20,38 +25,36 @@ func (h Controller) CDK(data *user.C2S_GET_GIFT_COMPLETE, player *player.Player) return nil, errorcode.ErrorCodes.ErrMolecularCodeFrozen } - if r.ValidEndTime.Compare(time.Now()) == -1 { + if r.ValidEndTime.Compare(now) == -1 { return nil, errorcode.ErrorCodes.ErrMolecularCodeExpired } if !player.Service.Cdk.CanGet(uint32(r.ID)) { return } - if !service.NewCdkService().Set(data.PassText) { + if !cdkService.Set(data.PassText) { return nil, errorcode.ErrorCodes.ErrMolecularCodeGiftsGone } result.Flag = 1 - for _, v := range r.ElfRewardIds { - pet := service.NewPetRewardService().Get(v) - if pet != nil { - peti := model.GenPetInfo(int(pet.MonID), int(pet.DV), int(pet.Nature), int(pet.Effect), int(pet.Lv), nil, 0) - player.Service.Pet.PetAdd(peti, 0) - result.PetGift = append(result.PetGift, user.PetGiftInfo{PetID: peti.ID, CacthTime: peti.CatchTime}) + for _, rewardID := range r.ElfRewardIds { + pet := rewardPetService.Get(rewardID) + if pet == nil { + continue } + petInfo := model.GenPetInfo(int(pet.MonID), int(pet.DV), int(pet.Nature), int(pet.Effect), int(pet.Lv), nil, 0) + player.Service.Pet.PetAdd(petInfo, 0) + result.PetGift = append(result.PetGift, user.PetGiftInfo{PetID: petInfo.ID, CacthTime: petInfo.CatchTime}) } - for _, itemID := range r.ItemRewardIds { - iteminfo := service.NewItemService().GetItemCount(itemID) - player.ItemAdd(iteminfo.ItemId, iteminfo.ItemCnt) - - result.GiftList = append(result.GiftList, user.GiftInfo{GiftID: iteminfo.ItemId, Count: iteminfo.ItemCnt}) - + for _, rewardID := range r.ItemRewardIds { + itemInfo := itemRewardService.GetItemCount(rewardID) + player.ItemAdd(itemInfo.ItemId, itemInfo.ItemCnt) + result.GiftList = append(result.GiftList, user.GiftInfo{GiftID: itemInfo.ItemId, Count: itemInfo.ItemCnt}) } if r.TitleRewardIds != 0 { player.Service.Title.Give(r.TitleRewardIds) result.Tile = r.TitleRewardIds - } player.Service.Cdk.Log(uint32(r.ID)) diff --git a/logic/service/common/pack.go b/logic/service/common/pack.go index e356b5c56..520f8aee9 100644 --- a/logic/service/common/pack.go +++ b/logic/service/common/pack.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/lunixbochs/struc" - "github.com/valyala/bytebufferpool" ) // TomeeHeader 结构体字段定义 @@ -17,9 +16,9 @@ type TomeeHeader struct { UserID uint32 `json:"userId"` //Error uint32 `json:"error" struc:"skip"` - Result uint32 `json:"result"` - Data *bytebufferpool.ByteBuffer `json:"data" struc:"skip"` //组包忽略此字段// struc:"skip" - Res []byte `struc:"skip"` + Result uint32 `json:"result"` + Data []byte `json:"data" struc:"skip"` //组包忽略此字段// struc:"skip" + Res []byte `struc:"skip"` //Return []byte `struc:"skip"` //返回记录 } diff --git a/logic/service/fight/effect/795_799.go b/logic/service/fight/effect/795_799.go index f743e0b67..36fe261f0 100644 --- a/logic/service/fight/effect/795_799.go +++ b/logic/service/fight/effect/795_799.go @@ -68,22 +68,20 @@ type Effect796Sub struct { RoundEffectArg0Base } -func (e *Effect796Sub) OnSkill() bool { - if len(e.Args()) < 2 || e.Args()[1].Cmp(alpacadecimal.Zero) <= 0 { - return true +func (e *Effect796Sub) TurnEnd() { + if e.Ctx().Our.CurrentPet.Info.Hp > 0 && e.Ctx().Opp.CurrentPet.Info.Hp > 0 && + len(e.Args()) >= 2 && e.Args()[1].Cmp(alpacadecimal.Zero) > 0 { + damage := e.Ctx().Opp.CurrentPet.GetHP().Div(e.Args()[1]) + if damage.Cmp(alpacadecimal.Zero) > 0 { + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ + Type: info.DamageType.Percent, + Damage: damage, + }) + e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, damage) + } } - damage := e.Ctx().Opp.CurrentPet.GetHP().Div(e.Args()[1]) - if damage.Cmp(alpacadecimal.Zero) <= 0 { - return true - } - - e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ - Type: info.DamageType.Percent, - Damage: damage, - }) - e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, damage) - return true + e.EffectNode.TurnEnd() } // Effect 797: 消除对手回合类效果,消除成功{0}回合内对手无法通过自身技能恢复体力 @@ -166,7 +164,7 @@ type Effect799 struct { node.EffectNode } -func (e *Effect799) OnSkill() bool { +func (e *Effect799) Skill_Use() bool { if len(e.Args()) < 2 || e.Args()[0].Cmp(alpacadecimal.Zero) <= 0 { return true } diff --git a/logic/service/fight/effect/810_814.go b/logic/service/fight/effect/810_814.go index 8d23d4faf..0ec09d899 100644 --- a/logic/service/fight/effect/810_814.go +++ b/logic/service/fight/effect/810_814.go @@ -1,7 +1,6 @@ package effect import ( - "blazing/logic/service/fight/action" "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" "blazing/logic/service/fight/node" diff --git a/logic/service/player/pack.go b/logic/service/player/pack.go index 6f93ffd4e..63c8dfe19 100644 --- a/logic/service/player/pack.go +++ b/logic/service/player/pack.go @@ -20,7 +20,6 @@ import ( "github.com/gogf/gf/v2/os/glog" "github.com/lunixbochs/struc" "github.com/panjf2000/gnet/v2" - "github.com/valyala/bytebufferpool" ) // getUnderlyingValue 递归解析reflect.Value,解包指针、interface{}到底层具体类型 @@ -48,44 +47,73 @@ func getUnderlyingValue(val reflect.Value) (reflect.Value, error) { } } -// XORDecryptU 优化后的异或解密:减少内存分配,支持复用缓冲区 -// XORDecryptU 基于bytebufferpool优化的异或解密函数 -// 保留原有接口,无侵入式优化,高频调用下大幅减少内存分配和GC +// XORDecryptU 原地执行异或解密,避免额外分配和拷贝。 func XORDecryptU(encryptedData []byte, key uint32) []byte { if len(encryptedData) == 0 { return []byte{} } - // 1. 栈上分配密钥字节数组(无GC压力,保留原优化) var keyBytes [4]byte binary.BigEndian.PutUint32(keyBytes[:], key) keyLen := len(keyBytes) - // 2. 从bytebufferpool获取池化缓冲区(替代make分配) - buf := bytebufferpool.Get() - defer bytebufferpool.Put(buf) // 函数结束自动归还缓冲区到池 - - // 3. 调整缓冲区长度,匹配待解密数据(避免扩容) - buf.B = buf.B[:0] // 清空原有数据,保留底层数组 - if cap(buf.B) < len(encryptedData) { - // 若缓冲区容量不足,直接扩容(bytebufferpool会自动管理) - buf.B = make([]byte, len(encryptedData)) - } else { - // 容量足够,直接调整长度 - buf.B = buf.B[:len(encryptedData)] - } - - // 4. 核心异或解密逻辑(直接操作buf.B,无额外内存分配) - decrypted := buf.B for i, b := range encryptedData { - decrypted[i] = b ^ keyBytes[i%keyLen] + encryptedData[i] = b ^ keyBytes[i%keyLen] } - // 5. 拷贝结果(关键:避免返回池化缓冲区,防止被后续调用覆盖) - result := make([]byte, len(decrypted)) - copy(result, decrypted) + return encryptedData +} - return result +var packetDataPoolSizes = [...]int{64, 128, 256, 512, 1024, 2048, 4096} + +var packetDataPools = func() []sync.Pool { + pools := make([]sync.Pool, len(packetDataPoolSizes)) + for i, size := range packetDataPoolSizes { + size := size + pools[i].New = func() any { + return make([]byte, size) + } + } + return pools +}() + +func getPacketData(size int) []byte { + if size <= 0 { + return nil + } + for i, poolSize := range packetDataPoolSizes { + if size <= poolSize { + return packetDataPools[i].Get().([]byte)[:size] + } + } + return make([]byte, size) +} + +func putPacketData(buf []byte) { + if buf == nil { + return + } + for i, poolSize := range packetDataPoolSizes { + if cap(buf) == poolSize { + packetDataPools[i].Put(buf[:poolSize]) + return + } + } +} + +func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) { + var header common.TomeeHeader + header.Len = binary.BigEndian.Uint32(v[0:4]) + header.CMD = binary.BigEndian.Uint32(v[5:9]) + header.UserID = binary.BigEndian.Uint32(v[9:13]) + if dataLen := len(v) - 17; dataLen > 0 { + header.Data = getPacketData(dataLen) + copy(header.Data, v[17:]) + } + + _ = submit(func() { + h.LF.Producer().Write(header) + }) } // 重写 @@ -119,13 +147,13 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) { return } if data.Data != nil { - defer bytebufferpool.Put(data.Data) - data.Res = XORDecryptU(data.Data.Bytes(), h.Player.Hash) + defer putPacketData(data.Data) + data.Res = XORDecryptU(data.Data, h.Player.Hash) } - } else { - defer bytebufferpool.Put(data.Data) - data.Res = data.Data.Bytes() + } else if data.Data != nil { + defer putPacketData(data.Data) + data.Res = data.Data } if cool.Config.ServerInfo.IsDebug != 0 { fmt.Println("接收数据", data.UserID, data.CMD) @@ -172,7 +200,7 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) { // fmt.Println(data.CMD, "接收 变量的地址 ", &t.Info, t.Info.UserID) - params = append(params, ptrValue1, reflect.ValueOf(h.Conn.Context().(*ClientData).Player)) + params = append(params, ptrValue1, reflect.ValueOf(h.Player)) } else { params = append(params, ptrValue1, reflect.ValueOf(h.Conn)) @@ -209,14 +237,10 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) { type ClientData struct { IsCrossDomain sync.Once //是否跨域过 Player *Player //客户实体 - //Mu sync.RWMutex - ERROR_CONNUT int - Wsmsg *WsCodec - Conn gnet.Conn - LF *lockfree.Lockfree[common.TomeeHeader] - //SaveL sync.Once //保存锁 - // MsgChan chan common.TomeeHeader - //SaveDone chan struct{} + ERROR_CONNUT int + Wsmsg *WsCodec + Conn gnet.Conn + LF *lockfree.Lockfree[common.TomeeHeader] } func (p *ClientData) GetPlayer(userid uint32) *Player { //TODO 这里待优化,可能存在内存泄漏问题 @@ -232,42 +256,18 @@ func (p *ClientData) GetPlayer(userid uint32) *Player { //TODO 这里待优化, // return nil } func NewClientData(c gnet.Conn) *ClientData { - // 创建事件处理器 - // 创建消费端串行处理的Lockfree - cd := &ClientData{ - Conn: c, Wsmsg: &WsCodec{}, - // MsgChan: make(chan common.TomeeHeader, 100), } - //cd.LF = make(chan common.TomeeHeader, 8) - // cd.LF = gqueue.NewTQueue[common.TomeeHeader](1) - // go func() { - // for { - // select { - // case queueItem := <-cd.LF.C: - // cd.OnEvent(queueItem) - - // } - // } - - // }() cd.LF = lockfree.NewLockfree( 8, cd, lockfree.NewConditionBlockStrategy(), ) - // // // 启动Lockfree if err := cd.LF.Start(); err != nil { panic(err) } - // 启动每个连接的独立消费协程 - // go cd.consumeMsg() - // // // 启动Lockfree - // // if err := cd.LF.Start(); err != nil { - // // panic(err) - // // } return cd } @@ -305,16 +305,11 @@ func XORDecrypt(encryptedData []byte, keyStr string) []byte { } func (p *ClientData) SendPack(b []byte) error { - cli, ok := p.Conn.Context().(*ClientData) - if !ok { - return fmt.Errorf("链接错误,取消发包") - - } - if cli.Wsmsg == nil { + if p.Wsmsg == nil { return fmt.Errorf("ws空") } - if cli.Wsmsg.Upgraded { + if p.Wsmsg.Upgraded { // This is the echo server wsutil.WriteServerMessage(p.Conn, ws.OpBinary, b) diff --git a/modules/player/service/cdk_log.go b/modules/player/service/cdk_log.go index 4e1ab9190..2fdc72ed8 100644 --- a/modules/player/service/cdk_log.go +++ b/modules/player/service/cdk_log.go @@ -3,19 +3,33 @@ package service import ( "blazing/cool" "blazing/modules/player/model" + "sync" "github.com/gogf/gf/v2/frame/g" ) type CdkService struct { BaseService + + mu sync.RWMutex + claimedCode map[uint32]struct{} + cacheLoaded bool } func (s *CdkService) CanGet(id uint32) bool { - m1, _ := s.dbm(s.Model).Where("code_id", id).Exist() + if s.isClaimed(id) { + return false + } - return !m1 + if err := s.loadClaimedCodes(); err != nil { + exists, _ := s.dbm(s.Model).Where("code_id", id).Exist() + if exists { + s.markClaimed(id) + } + return !exists + } + return !s.isClaimed(id) } func (s *CdkService) Log(id uint32) { @@ -26,17 +40,59 @@ func (s *CdkService) Log(id uint32) { "is_vip": cool.Config.ServerInfo.IsVip, } - m.Data(data).Insert() + if _, err := m.Data(data).Insert(); err == nil { + s.markClaimed(id) + } +} +func (s *CdkService) loadClaimedCodes() error { + s.mu.RLock() + if s.cacheLoaded { + s.mu.RUnlock() + return nil + } + s.mu.RUnlock() + + var logs []model.CdkLog + if err := s.dbm(s.Model).Fields("code_id").Scan(&logs); err != nil { + return err + } + + s.mu.Lock() + defer s.mu.Unlock() + if s.cacheLoaded { + return nil + } + + s.claimedCode = make(map[uint32]struct{}, len(logs)) + for _, log := range logs { + s.claimedCode[log.CodeID] = struct{}{} + } + s.cacheLoaded = true + return nil +} + +func (s *CdkService) isClaimed(id uint32) bool { + s.mu.RLock() + defer s.mu.RUnlock() + _, ok := s.claimedCode[id] + return ok +} + +func (s *CdkService) markClaimed(id uint32) { + s.mu.Lock() + defer s.mu.Unlock() + if s.claimedCode == nil { + s.claimedCode = make(map[uint32]struct{}) + } + s.claimedCode[id] = struct{}{} } func NewCdkService(id uint32) *CdkService { return &CdkService{ - BaseService: BaseService{userid: id, - Service: &cool.Service{Model: model.NewCdkLog()}, }, + claimedCode: make(map[uint32]struct{}), } - } diff --git a/modules/player/service/item.go b/modules/player/service/item.go index a00795a3b..c0dd4735c 100644 --- a/modules/player/service/item.go +++ b/modules/player/service/item.go @@ -26,6 +26,21 @@ func (s *ItemService) Get(min, max uint32) []model.Item { return ttt } + +func (s *ItemService) GetUserItemList(min, max, leftTime uint32) []model.SingleItemInfo { + var items []model.SingleItemInfo + s.dbm(s.Model). + Fields("item_id,item_cnt"). + WhereBetween("item_id", min, max). + Where("item_cnt >", 0). + Scan(&items) + + for i := range items { + items[i].LeftTime = leftTime + } + + return items +} func (s *ItemService) UPDATE(id uint32, count int) error { if cool.Config.ServerInfo.IsVip != 0 && count < 0 {