diff --git a/README.md b/README.md index aedf648f..1717d467 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ hk4e game server * mongodb * nats-server -1. 启动节点服务器 `cmd/node && go run .` -2. 启动http登录服务器 `cmd/dispatch && go run .` -3. 启动网关服务器 `cd cmd/gate && go run .` -4. 启动战斗服务器 `cmd/fight && go run .` -5. 启动寻路服务器 `cmd/pathfinding && go run .` -6. 启动游戏服务器 `cd cmd/gs && go run .` -7. 启动游戏管理服务器 `cmd/gm && go run .` +1. 启动节点服务器(仅单节点) `cmd/node && go run .` +2. 启动http登录服务器(可多节点) `cmd/dispatch && go run .` +3. 启动网关服务器(可多节点) `cd cmd/gate && go run .` +4. 启动战斗服务器(可多节点) `cmd/fight && go run .` +5. 启动寻路服务器(可多节点) `cmd/pathfinding && go run .` +6. 启动游戏服务器(可多节点) `cd cmd/gs && go run .` +7. 启动游戏管理服务器(仅单节点) `cmd/gm && go run .` diff --git a/cmd/fight/application.toml b/cmd/fight/application.toml index c4df3002..6b4485e4 100644 --- a/cmd/fight/application.toml +++ b/cmd/fight/application.toml @@ -1,3 +1,6 @@ +[hk4e] +client_proto_proxy_enable = false + [logger] level = "DEBUG" mode = "BOTH" diff --git a/cmd/gs/application.toml b/cmd/gs/application.toml index 61757b14..05ae0d18 100644 --- a/cmd/gs/application.toml +++ b/cmd/gs/application.toml @@ -1,4 +1,5 @@ [hk4e] +client_proto_proxy_enable = false resource_path = "./GameDataConfigTable" game_data_config_path = "./game_data_config" gacha_history_server = "https://hk4e.flswld.com/api/v1" diff --git a/common/utils/utils.go b/common/utils/utils.go new file mode 100644 index 00000000..77ad0923 --- /dev/null +++ b/common/utils/utils.go @@ -0,0 +1,34 @@ +package utils + +import ( + "hk4e/common/config" + "hk4e/pkg/logger" + "hk4e/pkg/object" + + pb "google.golang.org/protobuf/proto" +) + +func UnmarshalProtoObj(serverProtoObj pb.Message, clientProtoObj pb.Message, data []byte) bool { + if config.CONF.Hk4e.ClientProtoProxyEnable { + err := pb.Unmarshal(data, clientProtoObj) + if err != nil { + logger.Error("parse client proto obj error: %v", err) + return false + } + delList, err := object.CopyProtoBufSameField(serverProtoObj, clientProtoObj) + if err != nil { + logger.Error("copy proto obj error: %v", err) + return false + } + if len(delList) != 0 { + logger.Error("delete field name list: %v", delList) + } + } else { + err := pb.Unmarshal(data, serverProtoObj) + if err != nil { + logger.Error("parse server proto obj error: %v", err) + return false + } + } + return true +} diff --git a/fight/engine/fight_engine.go b/fight/engine/fight_engine.go index a9ce4269..8e4c2ca1 100644 --- a/fight/engine/fight_engine.go +++ b/fight/engine/fight_engine.go @@ -1,10 +1,14 @@ package engine import ( + "reflect" "time" + "hk4e/common/config" "hk4e/common/constant" "hk4e/common/mq" + "hk4e/common/utils" + "hk4e/gate/client_proto" "hk4e/pkg/logger" "hk4e/protocol/cmd" "hk4e/protocol/proto" @@ -19,6 +23,7 @@ type FightEngine struct { func NewFightEngine(messageQueue *mq.MessageQueue) (r *FightEngine) { r = new(FightEngine) r.messageQueue = messageQueue + initClientCmdProtoMap() go r.fightHandle() return r } @@ -223,9 +228,13 @@ func (f *FightRoutine) attackHandle(gameMsg *mq.GameMsg) { continue } hitInfo := new(proto.EvtBeingHitInfo) - err := pb.Unmarshal(entry.CombatData, hitInfo) - if err != nil { - logger.Error("parse combat invocations entity hit info error: %v", err) + clientProtoObj := GetClientProtoObjByName("EvtBeingHitInfo") + if clientProtoObj == nil { + logger.Error("get client proto obj is nil") + return + } + ok := utils.UnmarshalProtoObj(hitInfo, clientProtoObj, entry.CombatData) + if !ok { continue } attackResult := hitInfo.AttackResult @@ -278,3 +287,23 @@ func (f *FightRoutine) getAllPlayer(entityMap map[uint32]*Entity) []uint32 { } return uidList } + +var ClientCmdProtoMap *client_proto.ClientCmdProtoMap +var ClientCmdProtoMapRefValue reflect.Value + +func initClientCmdProtoMap() { + if config.CONF.Hk4e.ClientProtoProxyEnable { + ClientCmdProtoMap = client_proto.NewClientCmdProtoMap() + ClientCmdProtoMapRefValue = reflect.ValueOf(ClientCmdProtoMap) + } +} + +func GetClientProtoObjByName(protoObjName string) pb.Message { + if !config.CONF.Hk4e.ClientProtoProxyEnable { + return &proto.NullMsg{} + } + clientProtoObj := ClientCmdProtoMapRefValue.MethodByName( + "GetClientProtoObjByName", + ).Call([]reflect.Value{reflect.ValueOf(protoObjName)})[0].Interface().(pb.Message) + return clientProtoObj +} diff --git a/gate/client_proto/README.md b/gate/client_proto/README.md new file mode 100644 index 00000000..612555ff --- /dev/null +++ b/gate/client_proto/README.md @@ -0,0 +1,16 @@ +# 客户端协议代理功能 + +## 功能介绍 + +### 开启本功能后,网关服务器以及游戏服务器等其他服务器,将预先对客户端上行和服务器下行的协议数据做前置转换,采用任意版本的协议文件(必要字段名必须与现有的协议保持一致)均可,避免了因协议序号混淆等频繁变动,而造成游戏服务器代码不必要的频繁改动 + +## 使用方法 + +1. 在此目录下建立bin目录和proto目录 +2. 将client_cmd.csv文件放到bin目录下 +3. 将对应版本的proto协议文件复制到proto目录下并编译成pb.go +4. 将client_proto_gen_test.go的TestClientProtoGen方法添加运行配置 +5. 将运行配置输出目录和工作目录都设置为bin目录 +6. 运行并生成client_proto_gen.go +7. 将client_cmd.csv放入gate和gs和fight服务器的运行目录下 +8. 将gate和gs和fight服务器的配置文件中开启client_proto_proxy_enable客户端协议代理功能 diff --git a/gate/client_proto/client_proto_gen_test.go b/gate/client_proto/client_proto_gen_test.go index 9bef96bb..aed077cb 100644 --- a/gate/client_proto/client_proto_gen_test.go +++ b/gate/client_proto/client_proto_gen_test.go @@ -2,11 +2,23 @@ package client_proto import ( "os" + "strings" "testing" ) func TestClientProtoGen(t *testing.T) { - clientCmdProtoMap := NewClientCmdProtoMap() + dir, err := os.ReadDir("../proto") + if err != nil { + panic(err) + } + nameList := make([]string, 0) + for _, entry := range dir { + split := strings.Split(entry.Name(), ".") + if len(split) != 2 { + panic("file name error") + } + nameList = append(nameList, split[0]) + } fileData := "package client_proto\n" fileData += "\n" @@ -15,10 +27,10 @@ func TestClientProtoGen(t *testing.T) { fileData += "pb \"google.golang.org/protobuf/proto\"\n" fileData += ")\n" fileData += "\n" - fileData += "func (c *ClientCmdProtoMap) GetClientProtoObjByCmdName(cmdName string) pb.Message {\n" - fileData += "switch cmdName {\n" - for cmdName := range clientCmdProtoMap.clientCmdNameCmdIdMap { - fileData += "case \"" + cmdName + "\":\nreturn new(proto." + cmdName + ")\n" + fileData += "func (c *ClientCmdProtoMap) GetClientProtoObjByName(protoObjName string) pb.Message {\n" + fileData += "switch protoObjName {\n" + for _, protoObjName := range nameList { + fileData += "case \"" + protoObjName + "\":\nreturn new(proto." + protoObjName + ")\n" } fileData += "default:\n" fileData += "return nil\n" @@ -26,7 +38,7 @@ func TestClientProtoGen(t *testing.T) { fileData += "}\n" fileData += "\n" - err := os.WriteFile("../client_proto_gen.go", []byte(fileData), 0644) + err = os.WriteFile("../client_proto_gen.go", []byte(fileData), 0644) if err != nil { panic(err) } diff --git a/gate/net/kcp_connect_manager.go b/gate/net/kcp_connect_manager.go index 8ea2ecfa..f276005b 100644 --- a/gate/net/kcp_connect_manager.go +++ b/gate/net/kcp_connect_manager.go @@ -53,8 +53,10 @@ func NewKcpConnectManager(messageQueue *mq.MessageQueue, discovery *rpc.Discover r.kcpEventInput = make(chan *KcpEvent, 1000) r.kcpEventOutput = make(chan *KcpEvent, 1000) r.serverCmdProtoMap = cmd.NewCmdProtoMap() - r.clientCmdProtoMap = client_proto.NewClientCmdProtoMap() - r.clientCmdProtoMapRefValue = reflect.ValueOf(r.clientCmdProtoMap) + if config.CONF.Hk4e.ClientProtoProxyEnable { + r.clientCmdProtoMap = client_proto.NewClientCmdProtoMap() + r.clientCmdProtoMapRefValue = reflect.ValueOf(r.clientCmdProtoMap) + } r.messageQueue = messageQueue r.localMsgOutput = make(chan *ProtoMsg, 1000) r.createSessionChan = make(chan *Session, 1000) diff --git a/gate/net/proto_endecode.go b/gate/net/proto_endecode.go index 11a62641..579f0254 100644 --- a/gate/net/proto_endecode.go +++ b/gate/net/proto_endecode.go @@ -30,21 +30,38 @@ func (k *KcpConnectManager) protoDecode(kcpMsg *KcpMsg) (protoMsgList []*ProtoMs clientCmdId := kcpMsg.CmdId clientProtoData := kcpMsg.ProtoData cmdName := k.clientCmdProtoMap.GetClientCmdNameByCmdId(clientCmdId) - clientProtoObj := k.clientCmdProtoMapRefValue.MethodByName( - "GetClientProtoObjByCmdName", - ).Call([]reflect.Value{reflect.ValueOf(cmdName)})[0].Interface().(pb.Message) + if cmdName == "" { + logger.Error("get cmdName is nil, clientCmdId: %v", clientCmdId) + return protoMsgList + } + clientProtoObj := k.getClientProtoObjByName(cmdName) + if clientProtoObj == nil { + logger.Error("get client proto obj is nil, cmdName: %v", cmdName) + return protoMsgList + } err := pb.Unmarshal(clientProtoData, clientProtoObj) if err != nil { logger.Error("unmarshal client proto error: %v", err) return protoMsgList } serverCmdId := k.serverCmdProtoMap.GetCmdIdByCmdName(cmdName) + if serverCmdId == 0 { + logger.Error("get server cmdId is nil, cmdName: %v", cmdName) + return protoMsgList + } serverProtoObj := k.serverCmdProtoMap.GetProtoObjByCmdId(serverCmdId) - err = object.CopyProtoBufSameField(serverProtoObj, clientProtoObj) + if serverProtoObj == nil { + logger.Error("get server proto obj is nil, serverCmdId: %v", serverCmdId) + return protoMsgList + } + delList, err := object.CopyProtoBufSameField(serverProtoObj, clientProtoObj) if err != nil { logger.Error("copy proto obj error: %v", err) return protoMsgList } + if len(delList) != 0 { + logger.Error("delete field name list: %v, cmdName: %v", delList, cmdName) + } serverProtoData, err := pb.Marshal(serverProtoObj) if err != nil { logger.Error("marshal server proto error: %v", err) @@ -117,6 +134,50 @@ func (k *KcpConnectManager) protoDecodePayloadLoop(cmdId uint16, protoData []byt return } for _, unionCmd := range unionCmdNotify.GetCmdList() { + if config.CONF.Hk4e.ClientProtoProxyEnable { + clientCmdId := uint16(unionCmd.MessageId) + clientProtoData := unionCmd.Body + cmdName := k.clientCmdProtoMap.GetClientCmdNameByCmdId(clientCmdId) + if cmdName == "" { + logger.Error("get cmdName is nil, clientCmdId: %v", clientCmdId) + continue + } + clientProtoObj := k.getClientProtoObjByName(cmdName) + if clientProtoObj == nil { + logger.Error("get client proto obj is nil, cmdName: %v", cmdName) + continue + } + err := pb.Unmarshal(clientProtoData, clientProtoObj) + if err != nil { + logger.Error("unmarshal client proto error: %v", err) + continue + } + serverCmdId := k.serverCmdProtoMap.GetCmdIdByCmdName(cmdName) + if serverCmdId == 0 { + logger.Error("get server cmdId is nil, cmdName: %v", cmdName) + continue + } + serverProtoObj := k.serverCmdProtoMap.GetProtoObjByCmdId(serverCmdId) + if serverProtoObj == nil { + logger.Error("get server proto obj is nil, serverCmdId: %v", serverCmdId) + continue + } + delList, err := object.CopyProtoBufSameField(serverProtoObj, clientProtoObj) + if err != nil { + logger.Error("copy proto obj error: %v", err) + continue + } + if len(delList) != 0 { + logger.Error("delete field name list: %v, cmdName: %v", delList, cmdName) + } + serverProtoData, err := pb.Marshal(serverProtoObj) + if err != nil { + logger.Error("marshal server proto error: %v", err) + continue + } + unionCmd.MessageId = uint32(serverCmdId) + unionCmd.Body = serverProtoData + } k.protoDecodePayloadLoop(uint16(unionCmd.MessageId), unionCmd.Body, protoMessageList) } } @@ -165,24 +226,43 @@ func (k *KcpConnectManager) protoEncode(protoMsg *ProtoMsg) (kcpMsg *KcpMsg) { serverCmdId := kcpMsg.CmdId serverProtoData := kcpMsg.ProtoData serverProtoObj := k.serverCmdProtoMap.GetProtoObjByCmdId(serverCmdId) + if serverProtoObj == nil { + logger.Error("get server proto obj is nil, serverCmdId: %v", serverCmdId) + return nil + } err := pb.Unmarshal(serverProtoData, serverProtoObj) if err != nil { logger.Error("unmarshal server proto error: %v", err) + return nil } cmdName := k.serverCmdProtoMap.GetCmdNameByCmdId(serverCmdId) - clientProtoObj := k.clientCmdProtoMapRefValue.MethodByName( - "GetClientProtoObjByCmdName", - ).Call([]reflect.Value{reflect.ValueOf(cmdName)})[0].Interface().(pb.Message) - err = object.CopyProtoBufSameField(clientProtoObj, serverProtoObj) + if cmdName == "" { + logger.Error("get cmdName is nil, serverCmdId: %v", serverCmdId) + return nil + } + clientProtoObj := k.getClientProtoObjByName(cmdName) + if clientProtoObj == nil { + logger.Error("get client proto obj is nil, cmdName: %v", cmdName) + return nil + } + delList, err := object.CopyProtoBufSameField(clientProtoObj, serverProtoObj) if err != nil { logger.Error("copy proto obj error: %v", err) return nil } + if len(delList) != 0 { + logger.Error("delete field name list: %v, cmdName: %v", delList, cmdName) + } clientProtoData, err := pb.Marshal(clientProtoObj) if err != nil { logger.Error("marshal client proto error: %v", err) + return nil } clientCmdId := k.clientCmdProtoMap.GetClientCmdIdByCmdName(cmdName) + if clientCmdId == 0 { + logger.Error("get client cmdId is nil, cmdName: %v", cmdName) + return nil + } kcpMsg.CmdId = clientCmdId kcpMsg.ProtoData = clientProtoData } @@ -213,3 +293,10 @@ func (k *KcpConnectManager) encodeProtoToPayload(protoObj pb.Message) (cmdId uin } return cmdId, protoData } + +func (k *KcpConnectManager) getClientProtoObjByName(protoObjName string) pb.Message { + clientProtoObj := k.clientCmdProtoMapRefValue.MethodByName( + "GetClientProtoObjByName", + ).Call([]reflect.Value{reflect.ValueOf(protoObjName)})[0].Interface().(pb.Message) + return clientProtoObj +} diff --git a/gs/game/game_manager.go b/gs/game/game_manager.go index b2246785..7724086f 100644 --- a/gs/game/game_manager.go +++ b/gs/game/game_manager.go @@ -1,9 +1,12 @@ package game import ( + "reflect" "time" + appConfig "hk4e/common/config" "hk4e/common/mq" + "hk4e/gate/client_proto" "hk4e/gate/kcp" "hk4e/gs/dao" "hk4e/gs/model" @@ -25,9 +28,11 @@ var TICK_MANAGER *TickManager = nil var COMMAND_MANAGER *CommandManager = nil type GameManager struct { - dao *dao.Dao - messageQueue *mq.MessageQueue - snowflake *alg.SnowflakeWorker + dao *dao.Dao + messageQueue *mq.MessageQueue + snowflake *alg.SnowflakeWorker + clientCmdProtoMap *client_proto.ClientCmdProtoMap + clientCmdProtoMapRefValue reflect.Value } func NewGameManager(dao *dao.Dao, messageQueue *mq.MessageQueue, gsId uint32) (r *GameManager) { @@ -35,6 +40,10 @@ func NewGameManager(dao *dao.Dao, messageQueue *mq.MessageQueue, gsId uint32) (r r.dao = dao r.messageQueue = messageQueue r.snowflake = alg.NewSnowflakeWorker(int64(gsId)) + if appConfig.CONF.Hk4e.ClientProtoProxyEnable { + r.clientCmdProtoMap = client_proto.NewClientCmdProtoMap() + r.clientCmdProtoMapRefValue = reflect.ValueOf(r.clientCmdProtoMap) + } GAME_MANAGER = r LOCAL_EVENT_MANAGER = NewLocalEventManager() ROUTE_MANAGER = NewRouteManager() @@ -266,3 +275,13 @@ func (g *GameManager) DisconnectPlayer(userId uint32, reason uint32) { }) // g.SendMsg(cmd.ServerDisconnectClientNotify, userId, 0, new(proto.ServerDisconnectClientNotify)) } + +func (g *GameManager) GetClientProtoObjByName(protoObjName string) pb.Message { + if !appConfig.CONF.Hk4e.ClientProtoProxyEnable { + return &proto.NullMsg{} + } + clientProtoObj := g.clientCmdProtoMapRefValue.MethodByName( + "GetClientProtoObjByName", + ).Call([]reflect.Value{reflect.ValueOf(protoObjName)})[0].Interface().(pb.Message) + return clientProtoObj +} diff --git a/gs/game/user_fight_sync.go b/gs/game/user_fight_sync.go index 9ee3f303..fe92bd29 100644 --- a/gs/game/user_fight_sync.go +++ b/gs/game/user_fight_sync.go @@ -1,6 +1,7 @@ package game import ( + "hk4e/common/utils" "hk4e/gs/model" "hk4e/pkg/logger" "hk4e/pkg/reflection" @@ -103,9 +104,13 @@ func (g *GameManager) CombatInvocationsNotify(player *model.Player, payloadMsg p continue case proto.CombatTypeArgument_COMBAT_TYPE_ARGUMENT_ENTITY_MOVE: entityMoveInfo := new(proto.EntityMoveInfo) - err := pb.Unmarshal(entry.CombatData, entityMoveInfo) - if err != nil { - logger.Error("parse EntityMoveInfo error: %v", err) + clientProtoObj := g.GetClientProtoObjByName("EntityMoveInfo") + if clientProtoObj == nil { + logger.Error("get client proto obj is nil") + return + } + ok := utils.UnmarshalProtoObj(entityMoveInfo, clientProtoObj, entry.CombatData) + if !ok { continue } motionInfo := entityMoveInfo.MotionInfo @@ -152,9 +157,14 @@ func (g *GameManager) CombatInvocationsNotify(player *model.Player, payloadMsg p player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) case proto.CombatTypeArgument_COMBAT_TYPE_ARGUMENT_ANIMATOR_STATE_CHANGED: evtAnimatorStateChangedInfo := new(proto.EvtAnimatorStateChangedInfo) - err := pb.Unmarshal(entry.CombatData, evtAnimatorStateChangedInfo) - if err != nil { - logger.Error("parse EvtAnimatorStateChangedInfo error: %v", err) + clientProtoObj := g.GetClientProtoObjByName("EvtAnimatorStateChangedInfo") + if clientProtoObj == nil { + logger.Error("get client proto obj is nil") + return + } + ok := utils.UnmarshalProtoObj(evtAnimatorStateChangedInfo, clientProtoObj, entry.CombatData) + if !ok { + continue } logger.Debug("EvtAnimatorStateChangedInfo: %v", entry, player.PlayerID) player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) @@ -240,9 +250,13 @@ func (g *GameManager) ClientAbilityChangeNotify(player *model.Player, payloadMsg switch abilityInvokeEntry.ArgumentType { case proto.AbilityInvokeArgument_ABILITY_INVOKE_ARGUMENT_META_ADD_NEW_ABILITY: abilityMetaAddAbility := new(proto.AbilityMetaAddAbility) - err := pb.Unmarshal(abilityInvokeEntry.AbilityData, abilityMetaAddAbility) - if err != nil { - logger.Error("%v", err) + clientProtoObj := g.GetClientProtoObjByName("AbilityMetaAddAbility") + if clientProtoObj == nil { + logger.Error("get client proto obj is nil") + return + } + ok := utils.UnmarshalProtoObj(abilityMetaAddAbility, clientProtoObj, abilityInvokeEntry.AbilityData) + if !ok { continue } worldAvatar := world.GetWorldAvatarByEntityId(abilityInvokeEntry.EntityId) @@ -252,9 +266,13 @@ func (g *GameManager) ClientAbilityChangeNotify(player *model.Player, payloadMsg worldAvatar.abilityList = append(worldAvatar.abilityList, abilityMetaAddAbility.Ability) case proto.AbilityInvokeArgument_ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE: abilityMetaModifierChange := new(proto.AbilityMetaModifierChange) - err := pb.Unmarshal(abilityInvokeEntry.AbilityData, abilityMetaModifierChange) - if err != nil { - logger.Error("%v", err) + clientProtoObj := g.GetClientProtoObjByName("AbilityMetaModifierChange") + if clientProtoObj == nil { + logger.Error("get client proto obj is nil") + return + } + ok := utils.UnmarshalProtoObj(abilityMetaModifierChange, clientProtoObj, abilityInvokeEntry.AbilityData) + if !ok { continue } abilityAppliedModifier := &proto.AbilityAppliedModifier{ diff --git a/gs/game/user_multiplayer.go b/gs/game/user_multiplayer.go index 6ecf0d5f..d2c589ad 100644 --- a/gs/game/user_multiplayer.go +++ b/gs/game/user_multiplayer.go @@ -141,7 +141,7 @@ func (g *GameManager) JoinPlayerSceneReq(player *model.Player, payloadMsg pb.Mes g.SendMsg(cmd.LeaveWorldNotify, player.PlayerID, player.ClientSeq, new(proto.LeaveWorldNotify)) - g.LoginNotify(player.PlayerID, player, 0) + // g.LoginNotify(player.PlayerID, player, 0) if hostPlayer.SceneLoadState == model.SceneEnterDone { delete(hostWorld.waitEnterPlayerMap, player.PlayerID) diff --git a/gs/game/user_stamina.go b/gs/game/user_stamina.go index 437db0b7..41a2b665 100644 --- a/gs/game/user_stamina.go +++ b/gs/game/user_stamina.go @@ -5,6 +5,7 @@ import ( "time" "hk4e/common/constant" + "hk4e/common/utils" "hk4e/gdconf" "hk4e/gs/model" "hk4e/pkg/endec" @@ -21,9 +22,13 @@ func (g *GameManager) HandleAbilityStamina(player *model.Player, entry *proto.Ab case proto.AbilityInvokeArgument_ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA: // 大剑重击 或 持续技能 耐力消耗 costStamina := new(proto.AbilityMixinCostStamina) - err := pb.Unmarshal(entry.AbilityData, costStamina) - if err != nil { - logger.Error("unmarshal ability data err: %v", err) + clientProtoObj := g.GetClientProtoObjByName("AbilityMixinCostStamina") + if clientProtoObj == nil { + logger.Error("get client proto obj is nil") + return + } + ok := utils.UnmarshalProtoObj(costStamina, clientProtoObj, entry.AbilityData) + if !ok { return } // 处理持续耐力消耗 diff --git a/pkg/object/object.go b/pkg/object/object.go index 0a805b63..a0622c10 100644 --- a/pkg/object/object.go +++ b/pkg/object/object.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/pkg/errors" "github.com/vmihailenco/msgpack/v5" "google.golang.org/protobuf/encoding/protojson" pb "google.golang.org/protobuf/proto" @@ -37,45 +38,56 @@ func FastDeepCopy(dst, src any) error { return nil } -func CopyProtoBufSameField(dst, src pb.Message) error { +func CopyProtoBufSameField(dst, src pb.Message) ([]string, error) { date, err := protojson.Marshal(src) if err != nil { - return err - } - jsonObj := make(map[string]any) - err = json.Unmarshal(date, &jsonObj) - if err != nil { - return err + return nil, err } + delList := make([]string, 0) + loopCount := 0 for { - jsonData, err := json.Marshal(jsonObj) - if err != nil { - return err + loopCount++ + if loopCount > 1000 { + return nil, errors.New("loop count limit") } - err = protojson.Unmarshal(jsonData, dst) + err = protojson.Unmarshal(date, dst) if err != nil { if !strings.Contains(err.Error(), "unknown field") { - return err + return nil, err } split := strings.Split(err.Error(), "\"") if len(split) != 3 { - return err + return nil, err } fieldName := split[1] + jsonObj := make(map[string]any) + err = json.Unmarshal(date, &jsonObj) + if err != nil { + return nil, err + } DeleteAllKeyNameFromStringAnyMap(jsonObj, fieldName) + delList = append(delList, fieldName) + date, err = json.Marshal(jsonObj) + if err != nil { + return nil, err + } continue } else { break } } - return nil + return delList, nil } func DeleteAllKeyNameFromStringAnyMap(src map[string]any, keyName string) { for key, value := range src { - v, ok := value.(map[string]any) + vm, ok := value.(map[string]any) if ok { - DeleteAllKeyNameFromStringAnyMap(v, keyName) + DeleteAllKeyNameFromStringAnyMap(vm, keyName) + } + vs, ok := value.([]any) + if ok { + DeleteAllKeyNameFromAnyList(vs, keyName) } if key == keyName { delete(src, key) @@ -83,6 +95,19 @@ func DeleteAllKeyNameFromStringAnyMap(src map[string]any, keyName string) { } } +func DeleteAllKeyNameFromAnyList(src []any, keyName string) { + for _, value := range src { + vm, ok := value.(map[string]any) + if ok { + DeleteAllKeyNameFromStringAnyMap(vm, keyName) + } + vs, ok := value.([]any) + if ok { + DeleteAllKeyNameFromAnyList(vs, keyName) + } + } +} + func ConvBoolToInt64(v bool) int64 { if v { return 1