diff --git a/gdconf/avatar_level_data.go b/gdconf/avatar_level_data.go new file mode 100644 index 00000000..519f2f06 --- /dev/null +++ b/gdconf/avatar_level_data.go @@ -0,0 +1,31 @@ +package gdconf + +import ( + "fmt" + "hk4e/pkg/logger" + + "github.com/jszwec/csvutil" +) + +// 角色等级配置表 + +type AvatarLevelData struct { + Level int32 `csv:"Level"` // 等级 + Exp int32 `csv:"Exp,omitempty"` // 升到下一级所需经验 +} + +func (g *GameDataConfig) loadAvatarLevelData() { + g.AvatarLevelDataMap = make(map[int32]*AvatarLevelData) + data := g.readCsvFileData("AvatarLevelData.csv") + var avatarLevelDataList []*AvatarLevelData + err := csvutil.Unmarshal(data, &avatarLevelDataList) + if err != nil { + info := fmt.Sprintf("parse file error: %v", err) + panic(info) + } + for _, avatarLevelData := range avatarLevelDataList { + // list -> map + g.AvatarLevelDataMap[avatarLevelData.Level] = avatarLevelData + } + logger.Info("AvatarLevelData count: %v", len(g.AvatarLevelDataMap)) +} diff --git a/gdconf/avatar_promote_data.go b/gdconf/avatar_promote_data.go new file mode 100644 index 00000000..08f61d94 --- /dev/null +++ b/gdconf/avatar_promote_data.go @@ -0,0 +1,32 @@ +package gdconf + +import ( + "fmt" + "hk4e/pkg/logger" + + "github.com/jszwec/csvutil" +) + +// 角色突破配置表 + +type AvatarPromoteData struct { + PromoteId int32 `csv:"PromoteId"` // 角色突破ID + PromoteLevel int32 `csv:"PromoteLevel,omitempty"` // 突破等级 + LevelLimit int32 `csv:"LevelLimit,omitempty"` // 解锁等级上限 +} + +func (g *GameDataConfig) loadAvatarPromoteData() { + g.AvatarPromoteDataMap = make(map[int32]*AvatarPromoteData) + data := g.readCsvFileData("AvatarPromoteData.csv") + var avatarPromoteDataList []*AvatarPromoteData + err := csvutil.Unmarshal(data, &avatarPromoteDataList) + if err != nil { + info := fmt.Sprintf("parse file error: %v", err) + panic(info) + } + for _, avatarPromoteData := range avatarPromoteDataList { + // list -> map + g.AvatarPromoteDataMap[avatarPromoteData.PromoteLevel] = avatarPromoteData + } + logger.Info("AvatarPromoteData count: %v", len(g.AvatarPromoteDataMap)) +} diff --git a/gdconf/game_data_config.go b/gdconf/game_data_config.go index 9d240175..61a81156 100644 --- a/gdconf/game_data_config.go +++ b/gdconf/game_data_config.go @@ -38,6 +38,8 @@ type GameDataConfig struct { FetterDataMap map[int32]*FetterData // 角色资料解锁 FetterDataAvatarIdMap map[int32][]int32 // 角色资料解锁角色id索引 ItemDataMap map[int32]*ItemData // 统一道具 + AvatarLevelDataMap map[int32]*AvatarLevelData // 角色等级 + AvatarPromoteDataMap map[int32]*AvatarPromoteData // 角色突破 } func InitGameDataConfig() { @@ -97,6 +99,8 @@ func (g *GameDataConfig) load() { g.loadGatherData() // 采集物 g.loadFetterData() // 角色资料解锁 g.loadItemData() // 统一道具 + g.loadAvatarLevelData() // 角色等级 + g.loadAvatarPromoteData() // 角色突破 } func (g *GameDataConfig) readCsvFileData(fileName string) []byte { diff --git a/gdconf/table_struct_mapping.json b/gdconf/table_struct_mapping.json index 400762f1..c618d1e4 100644 --- a/gdconf/table_struct_mapping.json +++ b/gdconf/table_struct_mapping.json @@ -588,5 +588,40 @@ "origin_name": "数值用类型" } ] + }, + { + "table_name": "AvatarLevelData", + "field_list": [ + { + "field_name": "Level", + "field_type": "int32", + "origin_name": "等级" + }, + { + "field_name": "Exp", + "field_type": "int32", + "origin_name": "升到下一级所需经验" + } + ] + }, + { + "table_name": "AvatarPromoteData", + "field_list": [ + { + "field_name": "PromoteId", + "field_type": "int32", + "origin_name": "角色突破ID" + }, + { + "field_name": "PromoteLevel", + "field_type": "int32", + "origin_name": "突破等级" + }, + { + "field_name": "LevelLimit", + "field_type": "int32", + "origin_name": "解锁等级上限" + } + ] } ] diff --git a/gs/game/player_avatar.go b/gs/game/player_avatar.go index 62c64b78..7e0c274c 100644 --- a/gs/game/player_avatar.go +++ b/gs/game/player_avatar.go @@ -66,6 +66,107 @@ func (g *GameManager) AddUserAvatar(userId uint32, avatarId uint32) { g.SendMsg(cmd.AvatarAddNotify, userId, player.ClientSeq, avatarAddNotify) } +// AvatarUpgradeReq 角色升级请求 +func (g *GameManager) AvatarUpgradeReq(player *model.Player, payloadMsg pb.Message) { + logger.Debug("user upgrade, uid: %v", player.PlayerID) + req := payloadMsg.(*proto.AvatarUpgradeReq) + // 是否拥有角色 + avatar, ok := player.AvatarMap[player.GetAvatarIdByGuid(req.AvatarGuid)] + if !ok { + logger.Error("avatar error, avatarGuid: %v", req.AvatarGuid) + g.CommonRetError(cmd.AvatarUpgradeRsp, player, &proto.AvatarUpgradeRsp{}, proto.Retcode_RET_CAN_NOT_FIND_AVATAR) + return + } + // 经验书数量是否足够 + if player.GetItemCount(req.ItemId) < req.Count { + logger.Error("item count not enough, itemCount: %v", req.Count) + g.CommonRetError(cmd.AvatarUpgradeRsp, player, &proto.AvatarUpgradeRsp{}, proto.Retcode_RET_ITEM_COUNT_NOT_ENOUGH) + return + } + // TODO 摩拉数量是否足够 + // 获取角色突破配置表 + avatarPromoteConfig, ok := gdconf.CONF.AvatarPromoteDataMap[int32(avatar.Promote)] + if !ok { + logger.Error("avatar promote config error, promoteLevel: %v", avatar.Promote) + g.CommonRetError(cmd.AvatarUpgradeRsp, player, &proto.AvatarUpgradeRsp{}) + return + } + // 角色等级是否达到限制 + if avatar.Level >= uint8(avatarPromoteConfig.LevelLimit) { + logger.Error("avatar promote config error, promoteLevel: %v", avatar.Promote) + g.CommonRetError(cmd.AvatarUpgradeRsp, player, &proto.AvatarUpgradeRsp{}) + return + } + // 消耗升级材料并升级角色 + GAME_MANAGER.CostUserItem(player.PlayerID, []*UserItem{ + { + ItemId: req.ItemId, + ChangeCount: req.Count, + }, + }) + // 角色升级前的信息 + oldLevel := avatar.Level + oldFightPropMap := make(map[uint32]float32, len(avatar.FightPropMap)) + for propType, propValue := range avatar.FightPropMap { + oldFightPropMap[propType] = propValue + } + // 角色增加经验 + avatar.Exp += req.Count * 20000 // TODO 根据物品id给予相应的经验 + // 角色升级 + for { + // 获取角色等级配置表 + avatarLevelConfig, ok := gdconf.CONF.AvatarLevelDataMap[int32(avatar.Level)] + if !ok { + logger.Error("avatar level config error, level: %v", avatar.Level) + g.CommonRetError(cmd.AvatarUpgradeRsp, player, &proto.AvatarUpgradeRsp{}) + return + } + // 角色当前等级未突破则跳出循环 + if avatar.Level >= uint8(avatarPromoteConfig.LevelLimit) { + break + } + // 角色经验小于升级所需的经验则跳出循环 + if avatar.Exp < uint32(avatarLevelConfig.Exp) { + break + } + // 角色等级提升 + avatar.Exp -= uint32(avatarLevelConfig.Exp) + avatar.Level++ + } + // 角色更新面板 + player.InitAvatarFightProp(avatar) + // 角色属性表更新通知 + g.SendMsg(cmd.AvatarPropNotify, player.PlayerID, player.ClientSeq, g.PacketAvatarPropNotify(avatar)) + avatarUpgradeRsp := &proto.AvatarUpgradeRsp{ + CurLevel: uint32(avatar.Level), + OldLevel: uint32(oldLevel), + OldFightPropMap: oldFightPropMap, + CurFightPropMap: avatar.FightPropMap, + AvatarGuid: req.AvatarGuid, + } + g.SendMsg(cmd.AvatarUpgradeRsp, player.PlayerID, player.ClientSeq, avatarUpgradeRsp) +} + +// PacketAvatarPropNotify 角色属性表更新通知 +func (g *GameManager) PacketAvatarPropNotify(avatar *model.Avatar) *proto.AvatarPropNotify { + avatarPropNotify := &proto.AvatarPropNotify{ + PropMap: make(map[uint32]int64, 5), + AvatarGuid: avatar.Guid, + } + // 角色等级 + avatarPropNotify.PropMap[uint32(constant.PlayerPropertyConst.PROP_LEVEL)] = int64(avatar.Level) + // 角色经验 + avatarPropNotify.PropMap[uint32(constant.PlayerPropertyConst.PROP_EXP)] = int64(avatar.Exp) + // 角色突破等级 + avatarPropNotify.PropMap[uint32(constant.PlayerPropertyConst.PROP_BREAK_LEVEL)] = int64(avatar.Promote) + // 角色饱食度 + avatarPropNotify.PropMap[uint32(constant.PlayerPropertyConst.PROP_SATIATION_VAL)] = int64(avatar.Satiation) + // 角色饱食度溢出 + avatarPropNotify.PropMap[uint32(constant.PlayerPropertyConst.PROP_SATIATION_PENALTY_TIME)] = int64(avatar.SatiationPenalty) + + return avatarPropNotify +} + func (g *GameManager) WearEquipReq(player *model.Player, payloadMsg pb.Message) { logger.Debug("user wear equip, uid: %v", player.PlayerID) req := payloadMsg.(*proto.WearEquipReq) @@ -73,13 +174,13 @@ func (g *GameManager) WearEquipReq(player *model.Player, payloadMsg pb.Message) equipGuid := req.EquipGuid avatar, ok := player.GameObjectGuidMap[avatarGuid].(*model.Avatar) if !ok { - logger.Error("avatar error, avatar guid: %v", avatarGuid) - g.CommonRetError(cmd.WearEquipRsp, player, &proto.WearEquipRsp{}) + logger.Error("avatar error, avatarGuid: %v", avatarGuid) + g.CommonRetError(cmd.WearEquipRsp, player, &proto.WearEquipRsp{}, proto.Retcode_RET_CAN_NOT_FIND_AVATAR) return } weapon, ok := player.GameObjectGuidMap[equipGuid].(*model.Weapon) if !ok { - logger.Error("equip error, equip guid: %v", equipGuid) + logger.Error("equip error, equipGuid: %v", equipGuid) g.CommonRetError(cmd.WearEquipRsp, player, &proto.WearEquipRsp{}) return } @@ -219,7 +320,7 @@ func (g *GameManager) AvatarWearFlycloakReq(player *model.Player, payloadMsg pb. func (g *GameManager) PacketAvatarEquipChangeNotify(avatar *model.Avatar, weapon *model.Weapon, entityId uint32) *proto.AvatarEquipChangeNotify { itemDataConfig, ok := gdconf.CONF.ItemDataMap[int32(weapon.ItemId)] if !ok { - logger.Error("item data config error, item id: %v") + logger.Error("item data config error, itemId: %v") return new(proto.AvatarEquipChangeNotify) } avatarEquipChangeNotify := &proto.AvatarEquipChangeNotify{ diff --git a/gs/game/route_manager.go b/gs/game/route_manager.go index 77558427..c681f3ef 100644 --- a/gs/game/route_manager.go +++ b/gs/game/route_manager.go @@ -129,6 +129,7 @@ func (r *RouteManager) initRoute() { r.registerRouter(cmd.GCGInitFinishReq, GAME_MANAGER.GCGInitFinishReq) r.registerRouter(cmd.GCGOperationReq, GAME_MANAGER.GCGOperationReq) r.registerRouter(cmd.ObstacleModifyNotify, GAME_MANAGER.ObstacleModifyNotify) + r.registerRouter(cmd.AvatarUpgradeReq, GAME_MANAGER.AvatarUpgradeReq) } func (r *RouteManager) RouteHandle(netMsg *mq.NetMsg) { diff --git a/gs/model/avatar.go b/gs/model/avatar.go index 880ef44d..fc719230 100644 --- a/gs/model/avatar.go +++ b/gs/model/avatar.go @@ -41,12 +41,23 @@ func (p *Player) InitAllAvatar() { } func (p *Player) InitAvatar(avatar *Avatar) { + // 角色战斗属性 + p.InitAvatarFightProp(avatar) + // guid + avatar.Guid = p.GetNextGameObjectGuid() + p.GameObjectGuidMap[avatar.Guid] = GameObject(avatar) + avatar.EquipGuidList = make(map[uint64]uint64) + p.AvatarMap[avatar.AvatarId] = avatar + return +} + +// InitAvatarFightProp 初始化角色面板 +func (p *Player) InitAvatarFightProp(avatar *Avatar) { avatarDataConfig, ok := gdconf.CONF.AvatarDataMap[int32(avatar.AvatarId)] if !ok { logger.Error("avatarDataConfig error, avatarId: %v", avatar.AvatarId) return } - // 角色战斗属性 avatar.FightPropMap = make(map[uint32]float32) avatar.FightPropMap[uint32(constant.FightPropertyConst.FIGHT_PROP_NONE)] = 0.0 // 白字攻防血 @@ -65,12 +76,6 @@ func (p *Player) InitAvatar(avatar *Avatar) { // 元素充能 avatar.FightPropMap[uint32(constant.FightPropertyConst.FIGHT_PROP_CHARGE_EFFICIENCY)] = 1.0 p.SetCurrEnergy(avatar, avatar.CurrEnergy, true) - // guid - avatar.Guid = p.GetNextGameObjectGuid() - p.GameObjectGuidMap[avatar.Guid] = GameObject(avatar) - avatar.EquipGuidList = make(map[uint64]uint64) - p.AvatarMap[avatar.AvatarId] = avatar - return } func (p *Player) GetAvatarIdByGuid(guid uint64) uint32 { diff --git a/protocol/cmd/cmd_id_proto_obj_map.go b/protocol/cmd/cmd_id_proto_obj_map.go index 6b43cb4f..9f239315 100644 --- a/protocol/cmd/cmd_id_proto_obj_map.go +++ b/protocol/cmd/cmd_id_proto_obj_map.go @@ -221,6 +221,9 @@ func (c *CmdProtoMap) registerAllMessage() { c.registerMessage(AvatarWearFlycloakRsp, &proto.AvatarWearFlycloakRsp{}) // 角色换风之翼响应 c.registerMessage(AvatarFlycloakChangeNotify, &proto.AvatarFlycloakChangeNotify{}) // 角色换风之翼通知 c.registerMessage(AvatarLifeStateChangeNotify, &proto.AvatarLifeStateChangeNotify{}) // 角色存活状态改变通知 + c.registerMessage(AvatarUpgradeReq, &proto.AvatarUpgradeReq{}) // 角色升级请求 + c.registerMessage(AvatarUpgradeRsp, &proto.AvatarUpgradeRsp{}) // 角色升级通知 + c.registerMessage(AvatarPropNotify, &proto.AvatarPropNotify{}) // 角色属性表更新通知 // 背包与道具 c.registerMessage(PlayerStoreNotify, &proto.PlayerStoreNotify{}) // 玩家背包数据通知