diff --git a/cool/config.go b/common/cool/config.go similarity index 100% rename from cool/config.go rename to common/cool/config.go diff --git a/cool/const.go b/common/cool/const.go similarity index 100% rename from cool/const.go rename to common/cool/const.go diff --git a/cool/controller-simple.go b/common/cool/controller-simple.go similarity index 100% rename from cool/controller-simple.go rename to common/cool/controller-simple.go diff --git a/cool/controller.go b/common/cool/controller.go similarity index 100% rename from cool/controller.go rename to common/cool/controller.go diff --git a/cool/cool.go b/common/cool/cool.go similarity index 100% rename from cool/cool.go rename to common/cool/cool.go diff --git a/cool/coolconfig/config.go b/common/cool/coolconfig/config.go similarity index 100% rename from cool/coolconfig/config.go rename to common/cool/coolconfig/config.go diff --git a/cool/cooldb/cooldb.go b/common/cool/cooldb/cooldb.go similarity index 100% rename from cool/cooldb/cooldb.go rename to common/cool/cooldb/cooldb.go diff --git a/cool/coolfile/coolfile.go b/common/cool/coolfile/coolfile.go similarity index 100% rename from cool/coolfile/coolfile.go rename to common/cool/coolfile/coolfile.go diff --git a/cool/ctx.go b/common/cool/ctx.go similarity index 100% rename from cool/ctx.go rename to common/cool/ctx.go diff --git a/cool/db.go b/common/cool/db.go similarity index 100% rename from cool/db.go rename to common/cool/db.go diff --git a/cool/file.go b/common/cool/file.go similarity index 100% rename from cool/file.go rename to common/cool/file.go diff --git a/cool/func.go b/common/cool/func.go similarity index 100% rename from cool/func.go rename to common/cool/func.go diff --git a/cool/go.mod b/common/cool/go.mod similarity index 100% rename from cool/go.mod rename to common/cool/go.mod diff --git a/cool/go.sum b/common/cool/go.sum similarity index 100% rename from cool/go.sum rename to common/cool/go.sum diff --git a/cool/initdb.go b/common/cool/initdb.go similarity index 100% rename from cool/initdb.go rename to common/cool/initdb.go diff --git a/cool/middleware.go b/common/cool/middleware.go similarity index 100% rename from cool/middleware.go rename to common/cool/middleware.go diff --git a/cool/middleware_handler_response.go b/common/cool/middleware_handler_response.go similarity index 100% rename from cool/middleware_handler_response.go rename to common/cool/middleware_handler_response.go diff --git a/cool/model.go b/common/cool/model.go similarity index 100% rename from cool/model.go rename to common/cool/model.go diff --git a/cool/service.go b/common/cool/service.go similarity index 100% rename from cool/service.go rename to common/cool/service.go diff --git a/common/core/info/LoginUserInfo.go b/common/core/info/LoginUserInfo.go new file mode 100644 index 000000000..05a4d223c --- /dev/null +++ b/common/core/info/LoginUserInfo.go @@ -0,0 +1,217 @@ +package info + +// Point 表示坐标结构 +type Point struct { + X uint32 + Y uint32 +} + +// TeamInfo 战队信息 +type TeamInfo struct { + // 此处应包含TeamInfo的具体字段,原Java代码中未给出详细定义 + // 请根据实际需求补充 +} + +// TeamPKInfo 战队PK信息 +type TeamPKInfo struct { + // 此处应包含TeamPKInfo的具体字段,原Java代码中未给出详细定义 + // 请根据实际需求补充 +} + +// PeopleItemInfo 人物物品信息 +type PeopleItemInfo struct { + // 此处应包含PeopleItemInfo的具体字段,原Java代码中未给出详细定义 + // 请根据实际需求补充 +} + +// PetInfo 精灵信息 +type PetInfo struct { + // 此处应包含PetInfo的具体字段,原Java代码中未给出详细定义 + // 请根据实际需求补充 +} + +// LoginUserInfo 登录用户信息结构体 +type LoginUserInfo struct { + // 米米号 通过sid拿到 + UserId uint64 + // 注册时间(按秒的时间戳) + RegisterTime uint64 + // 16字节昵称 + Nick [16]byte `array_serialize:"fixed_length,16"` + // 暂时不明建议先给固定值0 + Vip uint16 `ushort:"true"` + // 暂时不明建议先给固定值15 + Viped uint16 `ushort:"true"` + // 暂时不明建议先给固定值0 + DsFlag uint64 + // 机器人人物颜色 00 rgb + Color uint64 + // 暂时不明建议先给固定值0 + Texture uint64 + // 暂时不明建议先给固定值3000 + Energy uint64 `default:"3000"` + // 赛尔豆 + Coins uint64 + // 暂时不明建议先给固定值0 + FightBadge uint64 + // 上线的地图id + MapID uint64 + // 上线的坐标 2个uint + Pos Point `array_serialize:"fixed_length,8"` + // 已经消耗掉的时间(秒为单位) + TimeToday uint64 + // 总电池限制(秒为单位) + TimeLimit uint64 + // 暂时不明感觉是某种活动建议先给固定值0(只能0或1) + IsClothHalfDay byte + // 暂时不明感觉是某种活动建议先给固定值0(只能0或1) + IsRoomHalfDay byte + // 暂时不明感觉是某种活动建议先给固定值0(只能0或1) + IFortressHalfDay byte + // 暂时不明感觉是某种活动建议先给固定值0(只能0或1) + IsHQHalfDay byte + // 暂时不明建议先给固定值0 + LoginCount uint64 + // 邀请活动建议先给固定值0 + Inviter uint64 + // 邀请活动建议先给固定值0 + NewInviteeCount uint64 + // 超no等级建议固定8 + VipLevel uint64 `default:"8"` + // 超no的vip值建议固定80000 + VipValue uint64 `default:"80000"` + // 超no的外形等级建议固定1(暂定) + VipStage uint64 `default:"1"` + // nono是否自动充电 建议固定1 + AutoCharge uint64 `default:"1"` + // 超no的结束时间建议尽可能大 + VipEndTime uint64 `default:"4294967295"` + // 邀请活动建议先给固定值0 + FreshManBonus uint64 + // 超no芯片列表*(80字节) + NonoChipList [80]byte `array_serialize:"fixed_length,80"` + // 50字节,默认值为3 + DailyResArr [50]byte `array_serialize:"fixed_length,50"` + // 教官id + TeacherID uint64 + // 学员id + StudentID uint64 + // 毕业人数 + GraduationCount uint64 + // 默认值为0 + MaxPuniLv uint64 `default:"0"` + // 精灵的最高等级 + PetMaxLevel uint64 + // 所有的精灵的数量 + AllPetNumber uint64 + // 精灵王之战胜场 + MonKingWin uint64 + // 勇者之塔当前到达的层数 + CurrentStage uint64 + // 试炼之塔最大胜利的层数 + MaxStage uint64 + // 试炼之塔当前到达的层数 + CurrentFreshStage uint64 + // 试炼之塔最大胜利的层数 + MaxFreshStage uint64 + // 星际擂台连胜 + MaxArenaWins uint64 + // 未知默认0 + TwoTimes uint64 `default:"0"` + // 未知默认0 + ThreeTimes uint64 `default:"0"` + // 是否自动战斗(未知默认值0) + AutoFight uint64 `default:"0"` + // 自动战斗剩余的场次(未知默认值0) + AutoFightTime uint64 `default:"0"` + // 能量吸收仪剩余次数(未知待定默认值0) + EnergyTime uint64 `default:"0"` + // 学习力吸收仪剩余次数(未知待定默认值0) + LearnTimes uint64 `default:"0"` + // 未知默认0 + MonBattleMedal uint64 `default:"0"` + // 未知默认0 + RecordCount uint64 `default:"0"` + // 未知默认0 + ObtainTm uint64 `default:"0"` + // 当前在孵化的元神珠id + SoulBeadItemID uint64 + // 未知默认0 + ExpireTm uint64 `default:"0"` + // 未知默认0 + FuseTimes uint64 `default:"0"` + // 玩家有没有nono + HasNono uint64 `default:"1"` + // 玩家有没有超能nono + SuperNono uint64 `default:"1"` + // 默认值-1 + NonoState uint64 `default:"4294967295"` + // nono的颜色 + NonoColor uint64 + // nono的名字 必须要补齐到16位 + NonoNick [16]byte `array_serialize:"fixed_length,16"` + // 猜测为战队信息24字节 + TeamInfo TeamInfo `array_serialize:"fixed_length,24"` + // 8字节 + TeamPkInfo TeamPKInfo `array_serialize:"fixed_length,8"` + // 1字节 无内容 + Reserved byte + // 默认值为0 + Badge uint64 `default:"0"` + // 未知(27字节,默认值为3) + Reserved1 [27]byte `array_serialize:"fixed_length,27"` + // 任务状态数组(500字节,3为已经完成,建议默认值为3) + TaskList [500]byte `array_serialize:"fixed_length,500"` + // 精灵背包内的信息由于特性精灵的存在精灵背包不定长 如果有特性占199字节 如果没特性 一个精灵占175字节 + PetList []PetInfo + // 穿戴装备 8字节 + Clothes []PeopleItemInfo +} + +// NewLoginUserInfo 创建新的登录用户信息实例,设置默认值 +func NewLoginUserInfo() *LoginUserInfo { + info := &LoginUserInfo{ + Vip: 0, + Viped: 15, + Energy: 3000, + VipLevel: 8, + VipValue: 80000, + VipStage: 1, + AutoCharge: 1, + VipEndTime: uint64(^uint32(0)), + TwoTimes: 0, + ThreeTimes: 0, + AutoFight: 0, + AutoFightTime: 0, + EnergyTime: 0, + LearnTimes: 0, + MonBattleMedal: 0, + RecordCount: 0, + ObtainTm: 0, + ExpireTm: 0, + FuseTimes: 0, + HasNono: 1, + SuperNono: 1, + NonoState: uint64(^uint32(0)), + Badge: 0, + } + + // 初始化固定长度数组 + for i := range info.NonoChipList { + info.NonoChipList[i] = 0 + } + for i := range info.DailyResArr { + info.DailyResArr[i] = 3 + } + for i := range info.Reserved1 { + info.Reserved1[i] = 3 + } + for i := range info.TaskList { + info.TaskList[i] = 3 + } + + // 初始化nono昵称 + copy(info.NonoNick[:], "nono") + + return info +} diff --git a/common/core/info/ServerInfo.go b/common/core/info/ServerInfo.go new file mode 100644 index 000000000..5639e12c1 --- /dev/null +++ b/common/core/info/ServerInfo.go @@ -0,0 +1,41 @@ +package info + +// ServerInfo 服务器信息结构体 +type ServerInfo struct { + // 连接ID, 即服务器序号 + OnlineID uint32 + // 当前服务器玩家在线数量, 供SWF显示 + UserCnt uint32 + // 服务器IP, 16字节UTF-8, 不足16补齐到16 + IP []byte `v:"FIXED_LENGTH|length:16"` + // 端口 + Port uint16 + // 好友在线的个数 + Friends uint32 +} + +// NewServerInfo 创建新的服务器信息实例 +func NewServerInfo() *ServerInfo { + return &ServerInfo{ + OnlineID: 0, + UserCnt: 0, + IP: []byte{}, + Port: 0, + Friends: 0, + } +} + +// // SetIP 设置IP地址并自动填充到16字节 +// func (s *ServerInfo) SetIP(ip string) { +// copy(s.IP[:], ip) +// if len(ip) < 16 { +// for i := len(ip); i < 16; i++ { +// s.IP[i] = 0 // 用0填充剩余字节 +// } +// } +// } + +// // GetIP 获取IP地址(去除填充的0) +// func (s *ServerInfo) GetIP() string { +// return strings.TrimRight(string(s.IP[:]), "\x00") +// } diff --git a/common/go.mod b/common/go.mod index 0ce39b06a..393060b42 100644 --- a/common/go.mod +++ b/common/go.mod @@ -7,6 +7,7 @@ require github.com/panjf2000/gnet v1.6.7 require ( github.com/panjf2000/ants/v2 v2.11.3 // indirect github.com/panjf2000/gnet/v2 v2.5.0 // indirect + github.com/tnnmigga/enum v1.0.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect diff --git a/common/go.sum b/common/go.sum index 7b73e262f..aee57ed50 100644 --- a/common/go.sum +++ b/common/go.sum @@ -23,6 +23,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tnnmigga/enum v1.0.2 h1:Yvchx0Esc01X5HiphW78sKzH/RXKttdFsfPO1ARiOa4= +github.com/tnnmigga/enum v1.0.2/go.mod h1:QaBFBwGJi/2GAM34b2pz6UL2NRtl2TRZ8lXp4vGwqhA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= diff --git a/common/player/Server.go b/common/player/Server.go deleted file mode 100644 index a18b5b289..000000000 --- a/common/player/Server.go +++ /dev/null @@ -1,360 +0,0 @@ -package socket - -import ( - "context" - "database/sql" - "errors" - "fmt" - "log" - "sync" - "time" - -) - -// Server 游戏服务器核心类,管理玩家、星球和游戏逻辑 -type Server struct { - players map[int64]*entity.Player - planets map[int64]*planet.Planet - mutex sync.RWMutex - gameRepo repo.GameResourceRepo - serverRepo repo.ServerRepo - accountRepo repo.AccountRepo - playerInfoRepo repo.PlayerInfoRepo - playerItemRepo repo.PlayerItemInfoRepo - petRepo repo.PetRepo -} - -// NewServer 创建新的游戏服务器实例 -func NewServer( - gameRepo repo.GameResourceRepo, - serverRepo repo.ServerRepo, - accountRepo repo.AccountRepo, - playerInfoRepo repo.PlayerInfoRepo, - playerItemRepo repo.PlayerItemInfoRepo, - petRepo repo.PetRepo, -) *Server { - s := &Server{ - players: make(map[int64]*entity.Player), - planets: make(map[int64]*planet.Planet), - gameRepo: gameRepo, - serverRepo: serverRepo, - accountRepo: accountRepo, - playerInfoRepo: playerInfoRepo, - playerItemRepo: playerItemRepo, - petRepo: petRepo, - } - s.initializePlanets() - return s -} - -// initializePlanets 初始化所有星球 -func (s *Server) initializePlanets() { - maps := s.gameRepo.GetAllMaps() - for _, config := range maps { - planet := s.generatePlanet(config) - s.planets[planet.GetId()] = planet - } -} - -// generatePlanet 根据地图配置生成星球 -func (s *Server) generatePlanet(config *mapInfo.MapXmlModel) *planet.Planet { - entries := config.GetEntries() - positions := make(map[int64]structs.Point) - - if len(entries) == 0 { - positions = make(map[int64]structs.Point) - } else { - for _, entry := range entries { - positions[entry.GetFromMap()] = s.generatePoint(entry) - } - } - - return planet.NewPlanet( - s, - int64(config.GetId()), - config.GetName(), - structs.Point{X: config.GetX(), Y: config.GetY()}, - positions, - s.gameRepo.CanMapRefresh(config.GetId()), - ) -} - -// generatePoint 从入口配置生成点坐标 -func (s *Server) generatePoint(xml *mapInfo.EntryXmlModel) structs.Point { - return structs.Point{X: xml.GetPosX(), Y: xml.GetPosY()} -} - -// GetPlayer 获取玩家信息 -func (s *Server) GetPlayer(accountID int64) (*entity.Player, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - player, exists := s.players[accountID] - if !exists { - return nil, errors.New("玩家不存在") - } - return player, nil -} - -// GetPlanet 获取星球信息 -func (s *Server) GetPlanet(planetID int64) (*planet.Planet, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - planet, exists := s.planets[planetID] - if !exists { - return nil, errors.New("星球不存在") - } - return planet, nil -} - -// GetDefaultPlanet 获取默认星球 -func (s *Server) GetDefaultPlanet() (*planet.Planet, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - planet, exists := s.planets[1] // 假设1是默认星球ID - if !exists { - return nil, errors.New("未找到默认星球") - } - return planet, nil -} - -// PlayerEnter 玩家加入服务器 -func (s *Server) PlayerEnter(player *entity.Player) { - s.mutex.Lock() - defer s.mutex.Unlock() - - s.players[player.GetAccountID()] = player -} - -// PlayerOffline 玩家离线处理 -func (s *Server) PlayerOffline(player *entity.Player) { - s.mutex.Lock() - defer s.mutex.Unlock() - - // 清理缓存会话数据 - if err := s.accountRepo.RemoveSessionID(player.GetSessionID()); err != nil { - log.Printf("清除会话ID失败: %v", err) - } - - // 清理登录绑定服务器 - if err := s.serverRepo.CancelRecordAccount(player.GetAccountID()); err != nil { - log.Printf("取消账号绑定失败: %v", err) - } - - // 从玩家列表中移除 - delete(s.players, player.GetAccountID()) -} - -// BroadcastMessage 广播消息给所有在线玩家 -func (s *Server) BroadcastMessage(message *net.OutboundMessage) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - for _, player := range s.players { - if player.IsOnline() { - player.SendMessage(message) - } - } -} - -// BroadcastMessageWithFilter 按条件广播消息给在线玩家 -func (s *Server) BroadcastMessageWithFilter(message *net.OutboundMessage, filter func(*entity.Player) bool) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - for _, player := range s.players { - if player.IsOnline() && filter(player) { - player.SendMessage(message) - } - } -} - -// GeneratePetEntity 生成宠物实体 -func (s *Server) GeneratePetEntity( - playerID int64, - petTypeID int, - individualValue int16, - nature int, - abilityTypeEnum int, - isShiny bool, - level int, -) (*pet.PetEntity, error) { - if level < 1 || level > 100 { - return nil, fmt.Errorf("精灵等级必须在1到100之间, level: %d", level) - } - - petInfo, err := s.gameRepo.GetMonsterByID(petTypeID) - if err != nil { - return nil, fmt.Errorf("无效的精灵ID, pet_id: %d, 错误: %v", petTypeID, err) - } - - firstSkillInfo, err := s.gameRepo.GetPetFirstSkillID(petTypeID, level) - if err != nil { - return nil, fmt.Errorf("精灵没有初始技能, pet_id: %d, 错误: %v", petTypeID, err) - } - - zero := int16(0) - - pet := pet.NewPetEntityBuilder() - .WithAsset(petInfo) - .WithLevelToExp(s.gameRepo.GetLevelToExp()) - .WithPlayerID(playerID) - .WithCapturePlayerID(playerID) - .WithCaptureTime(time.Now().Unix()) - .WithCaptureMap(0) // 假设0是默认地图ID - .WithCaptureRect(0) // 假设0是默认区域 - .WithCaptureLevel(level) - .WithIndividualValue(individualValue) - .WithNature(nature) - .WithAbilityTypeEnum(abilityTypeEnum) - .WithIsShiny(isShiny) - .WithLevel(level) - .WithCurrentExp(0) - .WithEvHp(zero) - .WithEvAttack(zero) - .WithEvDefense(zero) - .WithEvSpecialAttack(zero) - .WithEvSpecialDefense(zero) - .WithEvSpeed(zero) - .WithSkill1ID(firstSkillInfo[0].GetId()) - .WithSkill1Pp(firstSkillInfo[0].GetMaxPp()) - .WithSkill2ID(firstSkillInfo[1].GetId()) - .WithSkill2Pp(firstSkillInfo[1].GetMaxPp()) - .WithSkill3ID(firstSkillInfo[2].GetId()) - .WithSkill3Pp(firstSkillInfo[2].GetMaxPp()) - .WithSkill4ID(firstSkillInfo[3].GetId()) - .WithSkill4Pp(firstSkillInfo[3].GetMaxPp()) - .WithIndividualGuarantee(0) - .WithNatureGuarantee(0) - .Build() - - if err := s.CalculatePetPanel(pet); err != nil { - return nil, err - } - - return pet, nil -} - -// CalculatePetPanel 计算宠物面板属性 -func (s *Server) CalculatePetPanel(petEntity *pet.PetEntity) error { - natureInfo, err := s.gameRepo.GetNatureInfoByID(petEntity.GetNature()) - if err != nil { - return fmt.Errorf("无效的性格ID, nature: %d, 错误: %v", petEntity.GetNature(), err) - } - - hp := util.CalculatePetHPPanelSize( - petEntity.GetAsset().GetHp(), - petEntity.GetIndividualValue(), - petEntity.GetLevel(), - petEntity.GetEvHp(), - ) - - attack := util.CalculatePetPanelSize( - petEntity.GetAsset().GetAtk(), - petEntity.GetIndividualValue(), - petEntity.GetLevel(), - petEntity.GetEvHp(), - natureInfo.GetAttackCorrect(), - ) - - defense := util.CalculatePetPanelSize( - petEntity.GetAsset().GetDef(), - petEntity.GetIndividualValue(), - petEntity.GetLevel(), - petEntity.GetEvHp(), - natureInfo.GetDefenseCorrect(), - ) - - specialAttack := util.CalculatePetPanelSize( - petEntity.GetAsset().GetSpAtk(), - petEntity.GetIndividualValue(), - petEntity.GetLevel(), - petEntity.GetEvHp(), - natureInfo.GetSaCorrect(), - ) - - specialDefense := util.CalculatePetPanelSize( - petEntity.GetAsset().GetSpDef(), - petEntity.GetIndividualValue(), - petEntity.GetLevel(), - petEntity.GetEvHp(), - natureInfo.GetSdCorrect(), - ) - - speed := util.CalculatePetPanelSize( - petEntity.GetAsset().GetSpd(), - petEntity.GetIndividualValue(), - petEntity.GetLevel(), - petEntity.GetEvHp(), - natureInfo.GetSpeedCorrect(), - ) - - petEntity.SetMaxHp(hp) - petEntity.SetCurrentHp(hp) - petEntity.SetAttack(attack) - petEntity.SetDefense(defense) - petEntity.SetSpecialAttack(specialAttack) - petEntity.SetSpecialDefense(specialDefense) - petEntity.SetSpeed(speed) - - return nil -} - -// SavePlayer 保存玩家信息到数据库 -func (s *Server) SavePlayer(player *entity.Player) (bool, error) { - ctx := context.Background() - tx, err := s.playerInfoRepo.BeginTransaction(ctx) - if err != nil { - return false, fmt.Errorf("开始事务失败: %v", err) - } - defer func() { - if r := recover(); r != nil { - tx.Rollback() - log.Printf("保存玩家时发生panic: %v", r) - } else if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - playerID := player.GetGameID() - pets := player.GetPetBag().GetUsedPets() - items := player.GetItemBag().GetItems() - - // 保存玩家信息 - if err := s.playerInfoRepo.Save(ctx, tx, player); err != nil { - return false, fmt.Errorf("保存玩家信息失败: %v", err) - } - - // 保存精灵信息 - if err := s.petRepo.SaveAll(ctx, tx, playerID, pets); err != nil { - return false, fmt.Errorf("保存精灵信息失败: %v", err) - } - - // 保存玩家背包信息 - if err := s.playerItemRepo.SaveAll(ctx, tx, playerID, items); err != nil { - return false, fmt.Errorf("保存背包信息失败: %v", err) - } - - return true, nil -} - -// Destroy 销毁服务器,清理资源 -func (s *Server) Destroy() error { - // 保存所有玩家数据 - s.mutex.RLock() - for _, player := range s.players { - go func(p *entity.Player) { - if _, err := s.SavePlayer(p); err != nil { - log.Printf("保存玩家 %d 数据失败: %v", p.GetAccountID(), err) - } - }(player) - } - s.mutex.RUnlock() - - log.Println("Destroying server ...") - return nil -} diff --git a/common/serialize/ArraySerialize.go b/common/serialize/ArraySerialize.go new file mode 100644 index 000000000..1c8ea2efe --- /dev/null +++ b/common/serialize/ArraySerialize.go @@ -0,0 +1,10 @@ +package serialize + +type ArraySerialize struct { + OrderId int64 `v:"order-exist"` + ProductName string + Amount int64 + // ... +} +var paddedContent byte=0//长度不足时填充的内容, 默认填充0 + diff --git a/common/bytearray/bytearray.go b/common/serialize/bytearray/bytearray.go similarity index 100% rename from common/bytearray/bytearray.go rename to common/serialize/bytearray/bytearray.go diff --git a/common/bytearray/bytearray_test.go b/common/serialize/bytearray/bytearray_test.go similarity index 100% rename from common/bytearray/bytearray_test.go rename to common/serialize/bytearray/bytearray_test.go diff --git a/common/serialize/cheak.go b/common/serialize/cheak.go new file mode 100644 index 000000000..aa8b3ff21 --- /dev/null +++ b/common/serialize/cheak.go @@ -0,0 +1,20 @@ +package serialize + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/util/gvalid" +) + +type Request struct { + OrderId int64 `v:"order-exist"` + ProductName string `v:"order-exist"` + Amount int64 + // ... +} + +func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error { + fmt.Println(in) + return nil +} diff --git a/common/serialize/cheak_test.go b/common/serialize/cheak_test.go new file mode 100644 index 000000000..51eed8bb4 --- /dev/null +++ b/common/serialize/cheak_test.go @@ -0,0 +1,22 @@ +package serialize + +import ( + "fmt" + "testing" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func Test_main(t *testing.T) { + var ( + ctx = gctx.New() + req = &Request{ + OrderId: 65535, + ProductName: "HikingShoe", + Amount: 10000, + } + ) + err := g.Validator().RuleFunc("order-exist", RuleOrderExist).Data(req).Run(ctx) + fmt.Println(err) +} diff --git a/common/serialize/ser.go b/common/serialize/ser.go new file mode 100644 index 000000000..a71f66392 --- /dev/null +++ b/common/serialize/ser.go @@ -0,0 +1,236 @@ +package serialize + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "reflect" + "strings" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/gvalid" + "github.com/tnnmigga/enum" +) + +var lengthtype = enum.New[struct { + LENGTH_FIRST int + FIXED_LENGTH int +}]() + +// PacketSerializer 定义序列化函数类型,将数据转换为字节切片 +type PacketSerializer[T any] func(data T) ([]byte, error) + +// PacketDeserializer 定义反序列化函数类型,将字节切片转换为数据 +type PacketDeserializer[T any] func(data []byte) (T, error) + +// PacketHandler 封装序列化和反序列化处理函数 +type PacketHandler[T any] struct { + Serialize PacketSerializer[T] // 序列化函数 + Deserialize PacketDeserializer[T] // 反序列化函数 +} + + + +func serializebase[T any](field reflect.StructField, buf *bytes.Buffer, writedata any) error { + + + if field.Type.Kind() == reflect.Slice { //|| field.Type.Kind() == reflect.Array} + + datatype := make(chan int, 1) + FIXED_LENGTH := func(ctx context.Context, in gvalid.RuleFuncInput) error { + if in.Field == field.Name { //判断相同 + datatype <- lengthtype.FIXED_LENGTH + + } + return nil + } + LENGTH_FIRST := func(ctx context.Context, in gvalid.RuleFuncInput) error { + datatype <- lengthtype.FIXED_LENGTH + // fmt.Println(in) + return nil + } + length := func(ctx context.Context, in gvalid.RuleFuncInput) error { + tem := <-datatype + close(datatype) + writelen := 0 + if parts := strings.Split(in.Rule, ":"); len(parts) > 1 { + writelen = gconv.Int(strings.TrimSpace(parts[1])) + + } + switch tem { + case lengthtype.FIXED_LENGTH: //in.value + tempslice := gconv.SliceAny(writedata) + temp := make([]byte, writelen-len(tempslice)) + + for i := 0; i < len(tempslice); i++ { + tempdata := tempslice[i] + fmt.Println(i) + serializebase[T]( field, buf, tempdata) //todo递归序列化 + // copy(temp, date1) + // if err := binary.Write(buf, binary.BigEndian, temp); err != nil { + // return nil + // } + } + + if err := binary.Write(buf, binary.BigEndian, temp); err != nil { + return nil + } + case lengthtype.LENGTH_FIRST: + // temp := make([]byte, writelen) + // date1 := []byte(writedata.(string)) + // copy(temp, date1) + // if err := binary.Write(buf, binary.BigEndian, temp); err != nil { + // return nil + // } + } + + return nil + } + rules := make(map[string]gvalid.RuleFunc, 3) + rules["LENGTH_FIRST"] = LENGTH_FIRST + rules["FIXED_LENGTH"] = FIXED_LENGTH + rules["length"] = length + g.Validator().RuleFuncMap(rules).Data(writedata).Run(gctx.New()) + // serializeslice[T](field, buf, writedata) + } else { + if err := binary.Write(buf, binary.BigEndian, writedata); err != nil { + + + } + } + return nil +} + +// DefaultPacketSerializer 默认序列化实现,使用大端序写入数据 +func DefaultPacketSerializer[T any]() PacketSerializer[T] { + return func(data T) ([]byte, error) { + var buf bytes.Buffer + // 使用大端序写入数据 + // 1. 使用reflect获取结构体类型 + typ := reflect.TypeOf(data) + fmt.Println("结构体类型名称:", typ.Name()) + fmt.Println("字段数量:", typ.NumField()) + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fmt.Printf("字段名: %s, 类型: %s", + field.Name, field.Type) + fmt.Println("字段值:", reflect.ValueOf(data).Field(i).Interface()) + + writedata := reflect.ValueOf(data).Field(i).Interface() + fmt.Println(field.Type.Kind()) + + serializebase[T]( field, &buf, writedata) + + } + + return buf.Bytes(), nil + } +} + +// DefaultPacketDeserializer 默认反序列化实现,使用大端序读取数据 +func DefaultPacketDeserializer[T any]() PacketDeserializer[T] { + return func(data []byte) (T, error) { + var result T + reader := bytes.NewReader(data) + // 使用大端序读取数据 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + var zero T + return zero, err + } + return result, nil + } +} + +// NewDefaultPacketHandler 创建默认的数据包处理句柄 +func NewDefaultPacketHandler[T any]() *PacketHandler[T] { + return &PacketHandler[T]{ + Serialize: DefaultPacketSerializer[T](), + Deserialize: DefaultPacketDeserializer[T](), + } +} + +// 示例:使用自定义类型演示序列化与反序列化 +type ExampleData struct { + ID int32 + Name string + Data []byte +} + +// 自定义序列化函数(处理结构体类型) +func CustomSerializer() PacketSerializer[ExampleData] { + return func(data ExampleData) ([]byte, error) { + var buf bytes.Buffer + + // 先序列化基本类型字段 + if err := binary.Write(&buf, binary.BigEndian, data.ID); err != nil { + return nil, err + } + + // 序列化字符串长度和内容 + lenName := int32(len(data.Name)) + if err := binary.Write(&buf, binary.BigEndian, lenName); err != nil { + return nil, err + } + if _, err := buf.Write([]byte(data.Name)); err != nil { + return nil, err + } + + // 序列化字节数组长度和内容 + lenData := int32(len(data.Data)) + if err := binary.Write(&buf, binary.BigEndian, lenData); err != nil { + return nil, err + } + if _, err := buf.Write(data.Data); err != nil { + return nil, err + } + + return buf.Bytes(), nil + } +} + +// 自定义反序列化函数(处理结构体类型) +func CustomDeserializer() PacketDeserializer[ExampleData] { + return func(data []byte) (ExampleData, error) { + var result ExampleData + reader := bytes.NewReader(data) + + // 读取基本类型字段 + if err := binary.Read(reader, binary.BigEndian, &result.ID); err != nil { + return result, err + } + + // 读取字符串长度和内容 + var lenName int32 + if err := binary.Read(reader, binary.BigEndian, &lenName); err != nil { + return result, err + } + if lenName > int32(reader.Len()) { + return result, errors.New("invalid name length") + } + nameBuf := make([]byte, lenName) + if _, err := reader.Read(nameBuf); err != nil { + return result, err + } + result.Name = string(nameBuf) + + // 读取字节数组长度和内容 + var lenData int32 + if err := binary.Read(reader, binary.BigEndian, &lenData); err != nil { + return result, err + } + if lenData > int32(reader.Len()) { + return result, errors.New("invalid data length") + } + result.Data = make([]byte, lenData) + if _, err := reader.Read(result.Data); err != nil { + return result, err + } + + return result, nil + } +} diff --git a/common/socket/ServerEvent.go b/common/socket/ServerEvent.go index ca8fc5f7e..c4626b85c 100644 --- a/common/socket/ServerEvent.go +++ b/common/socket/ServerEvent.go @@ -5,6 +5,7 @@ import ( "github.com/panjf2000/gnet/v2" "github.com/panjf2000/gnet/v2/pkg/logging" + "blazing/common/data/entity" ) func (s *Server) Boot() error { @@ -35,19 +36,18 @@ func (s *Server) OnBoot(eng gnet.Engine) gnet.Action { func (s *Server) OnTraffic(conn gnet.Conn) (action gnet.Action) { - + conn.SetContext(entity.NewClientData())//注入data if s.network == "tcp" { return s.handleTcp(conn) } - - return gnet.None } - func (s *Server) handleTcp(conn gnet.Conn) (action gnet.Action) { + for { + data, err := s.codec.Decode(conn) if err != nil { break @@ -73,11 +73,11 @@ func (s *Server) handleTcp(conn gnet.Conn) (action gnet.Action) { } func (s *Server) parser(c gnet.Conn, line []byte) { - + //todo 这里待实现注入player实体 s.handler.Handle(line) } func (s *Server) Start() { - + err := gnet.Run(s, s.network+"://"+s.addr, gnet.WithMulticore(s.multicore)) logging.Infof("server exits with error: %v", err) } diff --git a/common/socket/cmd/cmd.go b/common/socket/cmd/cmd.go new file mode 100644 index 000000000..1574a7dcf --- /dev/null +++ b/common/socket/cmd/cmd.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "github.com/tnnmigga/enum" +) + +// Enum 辅助类型定义 +type EnumValue struct { + Value int + Name string +} + +// MessageCommandIDRegistry 消息命令ID注册表 +var MessageCommandIDRegistry = enum.New[struct { + // 在线相关命令 + Commend_OnLine int `enum:"105"` // 在线命令 + Login_In int `enum:"1001"` // 玩家登录 + System_Time int `enum:"1002"` // 返回当前时间戳 + Map_Hot int `enum:"1004"` // 显示地图热度(此地图内玩家数量) + Gold_Online_Check_Remain int `enum:"1106"` // 返回玩家金豆数量 + + // 地图相关命令 + Enter_Map int `enum:"2001"` // 告知后端玩家进入新地图 + Leave_Map int `enum:"2002"` // 玩家离开地图 + List_Map_Player int `enum:"2003"` // 返回当前地图玩家列表 + Map_Ogre_List int `enum:"2004"` // 精灵刷新 + + // 用户信息相关命令 + Get_Sim_UserInfo int `enum:"2051"` // 返回邮人物简单信息 + Get_More_UserInfo int `enum:"2052"` // 返回人物详细信息 + Change_Nick_Name int `enum:"2061"` // 修改玩家名字 + + // 动作相关命令 + People_Walk int `enum:"2101"` // 玩家走路包 + Chat int `enum:"2102"` // 公屏聊天 + Aimat int `enum:"2104"` // 射击 + + // 精灵相关命令 + Get_Pet_Info int `enum:"2301"` // 获取精灵详细信息 + Get_Pet_List int `enum:"2303"` // 获取所有的精灵列表 + Pet_Release int `enum:"2304"` // 精灵加入背包或放回仓库 + Pet_Show int `enum:"2305"` // 展示精灵 + Pet_Cure int `enum:"2306"` // 恢复精灵状态,NONO恢复所有精灵 + Pet_Study_Skill int `enum:"2307"` // 升级学习替换技能 + Pet_Default int `enum:"2308"` // 设置精灵首发 + Pet_One_Cure int `enum:"2310"` // 恢复精灵状态,精灵恢复单只精灵 + Pet_Skill_Switch int `enum:"2312"` // 切换精灵技能 + Pet_Set_Exp int `enum:"2318"` // 分配精灵经验 + Pet_Get_Exp int `enum:"2319"` // 获取积累经验 + Pet_Room int `enum:"2325"` // 跟随精灵获取信息 + Get_Soul_Bead_List int `enum:"2354"` // 返回元神珠信息 + + // 战斗相关命令 + Ready_To_Fight int `enum:"2404"` // 客户端通知服务端可以开始战斗, 此包表示客户端可以开始战斗, 无需服务端回复此包内容 + Use_Skill int `enum:"2405"` // 使用技能 + Change_Pet int `enum:"2407"` // 切换精灵 + Fight_NPC_Monster int `enum:"2408"` // 与野怪对战的申请进入战斗 + Catch_Monster int `enum:"2409"` // 捕捉精灵 + Challenge_Boss int `enum:"2411"` // 与当前地图的Boss类型野怪进入战斗 + Note_ReadyTo_Fight int `enum:"2503"` // 通知客户端已经可以开始战斗 + Note_Start_Fight int `enum:"2504"` // 在客户端告知服务端可以开始战斗后, 服务端回给客户端战斗开始 + Note_Use_Skill int `enum:"2505"` // 通知使用技能 + Fight_Over int `enum:"2506"` // 战斗结束 + Note_Update_Skill int `enum:"2507"` // 升级获得学习技能 + Note_Update_Prop int `enum:"2508"` // 返回升级后的信息 + + // 装备物品相关命令 + Change_Cloth int `enum:"2604"` // 修改玩家装备 + Item_List int `enum:"2605"` // 返回玩家物品列表 + + // 奖励相关命令 + Talk_Count int `enum:"2701"` // 玩家领取奖励的次数(挖矿,礼包等) + Talk_Cate int `enum:"2702"` // 领取奖品的内容 + Mail_Get_Unread int `enum:"2757"` // 返回邮件数量 + + // 系统相关命令 + System_Message int `enum:"8002"` // 后端主动发送面板消息 + Get_Boss_Monster int `enum:"8004"` // 返回战斗结束后的奖励包或主动发放奖励 + + // NONO相关命令 + Nono_Info int `enum:"9003"` // 通过米米号获取nono信息 + Nono_Follow_Or_Home int `enum:"9019"` // nono跟随或回家 + + // 特殊命令 + Get_Quadruple_Exe_Time int `enum:"50007"` // 返回已使用四倍剩余时间 + + // 暂未处理的包 + Item_Buy int `enum:"2601"` // 物品购买 + Item_Sale int `enum:"2602"` // 物品出售 + Friend_Add int `enum:"2151"` // 添加好友 + Friend_Remove int `enum:"2153"` // 移除好友 + Invite_To_Fight int `enum:"2401"` // 邀请战斗 + Escape_Fight int `enum:"2410"` // 逃离战斗 + Join_Game int `enum:"5001"` // 加入游戏 + Game_Over int `enum:"5002"` // 游戏结束 + Leave_Game int `enum:"5003"` // 离开游戏 +}]() + diff --git a/common/socket/cmd/cmd_test.go b/common/socket/cmd/cmd_test.go new file mode 100644 index 000000000..e18bca649 --- /dev/null +++ b/common/socket/cmd/cmd_test.go @@ -0,0 +1,14 @@ +package cmd + +import ( + "testing" +) + +func BenchmarkCmd(b *testing.B) { + b.ReportAllocs() + //写入 + for i := 0; i < b.N; i++ { + b.Log(MessageCommandIDRegistry.Change_Cloth) + + } +} diff --git a/common/socket/codec/CrossDomain.go b/common/socket/codec/CrossDomain.go deleted file mode 100644 index ea7b1088e..000000000 --- a/common/socket/codec/CrossDomain.go +++ /dev/null @@ -1,33 +0,0 @@ -package codec - -import ( - "log" - - "github.com/panjf2000/gnet/v2" -) - -// CROSS_DOMAIN 定义跨域策略文件内容 -const CROSS_DOMAIN = "\x00" - -// TEXT 定义跨域请求的文本格式 -const TEXT = "\x00" - -// Handle 处理网络连接 -func Handle(conn gnet.Conn) error { - // 读取数据并检查是否为跨域请求 - data, err := conn.Peek(len(TEXT)) - if err != nil { - log.Printf("Error reading cross-domain request: %v", err) - return err - } - - if string(data) == TEXT { //判断是否是跨域请求 - log.Printf("Received cross-domain request from %s", conn.RemoteAddr()) - // 处理跨域请求 - conn.Write([]byte(CROSS_DOMAIN)) - conn.Discard(len(TEXT)) - return nil - } - - return nil -} diff --git a/common/socket/codec/SocketCodec.go b/common/socket/codec/SocketCodec.go index 79d974174..78600cf00 100644 --- a/common/socket/codec/SocketCodec.go +++ b/common/socket/codec/SocketCodec.go @@ -10,4 +10,5 @@ type SocketCodec interface { Encode([]byte) ([]byte, error) Decode(gnet.Conn) ([]byte, error) + } \ No newline at end of file diff --git a/common/socket/codec/SocketCodec_Tomee.go b/common/socket/codec/SocketCodec_Tomee.go index 37745ceef..44c36b6e2 100644 --- a/common/socket/codec/SocketCodec_Tomee.go +++ b/common/socket/codec/SocketCodec_Tomee.go @@ -1,13 +1,21 @@ package codec import ( + "blazing/common/data/entity" "encoding/binary" "errors" "io" + "log" "github.com/panjf2000/gnet/v2" ) +// CROSS_DOMAIN 定义跨域策略文件内容 +const CROSS_DOMAIN = "\x00" + +// TEXT 定义跨域请求的文本格式 +const TEXT = "\x00" + var ErrIncompletePacket = errors.New("incomplete packet") // TomeeSocketCodec 协议格式: @@ -23,26 +31,54 @@ var ErrIncompletePacket = errors.New("incomplete packet") // * | ... ... | // * +-----------+ type TomeeSocketCodec struct{} -var _ SocketCodec = (*TomeeSocketCodec)(nil) +var _ SocketCodec = (*TomeeSocketCodec)(nil) func NewTomeeSocketCodec() *TomeeSocketCodec { return &TomeeSocketCodec{} } + +func handle(c gnet.Conn) { + clientdata:=c.Context().(*entity.ClientData) + if(clientdata.IsCrossDomain){ + return + + } + // 读取数据并检查是否为跨域请求 + 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)) + + clientdata.IsCrossDomain=true + return + } + + return +} func (codec TomeeSocketCodec) Encode(buf []byte) ([]byte, error) { bodyLen := len(buf) data := make([]byte, 4+bodyLen) - + // 写入4字节的包长度 binary.BigEndian.PutUint32(data[:4], uint32(bodyLen)) // 写入包体 copy(data[4:], buf) - + return data, nil } func (codec TomeeSocketCodec) Decode(c gnet.Conn) ([]byte, error) { + + handle(c) // 先读取4字节的包长度 lenBuf, err := c.Peek(4) if err != nil { @@ -51,10 +87,10 @@ func (codec TomeeSocketCodec) Decode(c gnet.Conn) ([]byte, error) { } return nil, err } - + bodyLen := binary.BigEndian.Uint32(lenBuf) totalLen := 4 + int(bodyLen) - + // 检查整个包是否完整 buf, err := c.Peek(totalLen) if err != nil { @@ -63,13 +99,13 @@ func (codec TomeeSocketCodec) Decode(c gnet.Conn) ([]byte, error) { } return nil, err } - + // 提取包体 body := make([]byte, bodyLen) copy(body, buf[4:totalLen]) - + // 从缓冲区中丢弃已读取的数据 _, _ = c.Discard(totalLen) - + return body, nil } diff --git a/go.work b/go.work index af1928640..b273ddd4f 100644 --- a/go.work +++ b/go.work @@ -4,7 +4,7 @@ use ( ./common ./common/contrib/drivers/mysql ./common/contrib/files/local - ./cool + ./common/cool ./logic ./login ./modules/base diff --git a/logic/main.go b/logic/main.go index d94fbe4bc..a9f0c225b 100644 --- a/logic/main.go +++ b/logic/main.go @@ -1,7 +1,21 @@ package main -import "blazing/common/socket" +import ( + "blazing/common/bytearray/serialize" + "blazing/common/core/info" + "blazing/common/socket" + "fmt" +) func main() { + + + tt:=info.NewServerInfo() + tt.OnlineID=99 + tt.IP=[]byte("127.0.0.1") + tt1:=serialize.NewDefaultPacketHandler[info.ServerInfo]() + tg,_:=tt1.Serialize(*tt) + + fmt.Println(tg) socket.NewServer(socket.WithPort("9999")).Start() }