package game import ( "hk4e/common/constant" "hk4e/gs/model" "hk4e/pkg/logger" "hk4e/pkg/reflection" "hk4e/protocol/cmd" "hk4e/protocol/proto" pb "google.golang.org/protobuf/proto" ) var cmdProtoMap *cmd.CmdProtoMap = nil func DoForward[IET model.InvokeEntryType](player *model.Player, invokeHandler *model.InvokeHandler[IET], cmdId uint16, newNtf pb.Message, forwardField string, srcNtf pb.Message, copyFieldList []string) { if cmdProtoMap == nil { cmdProtoMap = cmd.NewCmdProtoMap() } world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { return } if srcNtf != nil && copyFieldList != nil { for _, fieldName := range copyFieldList { reflection.CopyStructField(newNtf, srcNtf, fieldName) } } if invokeHandler.AllLen() == 0 && invokeHandler.AllExceptCurLen() == 0 && invokeHandler.HostLen() == 0 { return } if invokeHandler.AllLen() > 0 { reflection.SetStructFieldValue(newNtf, forwardField, invokeHandler.EntryListForwardAll) GAME.SendToWorldA(world, cmdId, player.ClientSeq, newNtf) } if invokeHandler.AllExceptCurLen() > 0 { reflection.SetStructFieldValue(newNtf, forwardField, invokeHandler.EntryListForwardAllExceptCur) GAME.SendToWorldAEC(world, cmdId, player.ClientSeq, newNtf, player.PlayerID) } if invokeHandler.HostLen() > 0 { reflection.SetStructFieldValue(newNtf, forwardField, invokeHandler.EntryListForwardHost) GAME.SendToWorldH(world, cmdId, player.ClientSeq, newNtf) } } func (g *Game) UnionCmdNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.UnionCmdNotify) _ = req if player.SceneLoadState != model.SceneEnterDone { return } DoForward[proto.CombatInvokeEntry](player, player.CombatInvokeHandler, cmd.CombatInvocationsNotify, new(proto.CombatInvocationsNotify), "InvokeList", nil, nil) DoForward[proto.AbilityInvokeEntry](player, player.AbilityInvokeHandler, cmd.AbilityInvocationsNotify, new(proto.AbilityInvocationsNotify), "Invokes", nil, nil) player.CombatInvokeHandler.Clear() player.AbilityInvokeHandler.Clear() } func (g *Game) MassiveEntityElementOpBatchNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.MassiveEntityElementOpBatchNotify) if player.SceneLoadState != model.SceneEnterDone { return } world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { return } scene := world.GetSceneById(player.SceneId) if scene == nil { logger.Error("scene is nil, sceneId: %v", player.SceneId) return } req.OpIdx = scene.GetMeeoIndex() scene.SetMeeoIndex(scene.GetMeeoIndex() + 1) g.SendToWorldA(world, cmd.MassiveEntityElementOpBatchNotify, player.ClientSeq, req) } func (g *Game) CombatInvocationsNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.CombatInvocationsNotify) if player.SceneLoadState != model.SceneEnterDone { return } world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { return } scene := world.GetSceneById(player.SceneId) if scene == nil { logger.Error("scene is nil, sceneId: %v", player.SceneId) return } for _, entry := range req.InvokeList { switch entry.ArgumentType { case proto.CombatTypeArgument_COMBAT_EVT_BEING_HIT: evtBeingHitInfo := new(proto.EvtBeingHitInfo) err := pb.Unmarshal(entry.CombatData, evtBeingHitInfo) if err != nil { logger.Error("parse EvtBeingHitInfo error: %v", err) continue } // logger.Debug("EvtBeingHitInfo: %v, ForwardType: %v", evtBeingHitInfo, entry.ForwardType) attackResult := evtBeingHitInfo.AttackResult if attackResult == nil { logger.Error("attackResult is nil") continue } target := scene.GetEntity(attackResult.DefenseId) if target == nil { logger.Error("could not found target, defense id: %v", attackResult.DefenseId) continue } attackResult.Damage *= 100 damage := attackResult.Damage attackerId := attackResult.AttackerId _ = attackerId currHp := float32(0) fightProp := target.GetFightProp() if fightProp != nil { currHp = fightProp[constant.FIGHT_PROP_CUR_HP] currHp -= damage if currHp < 0 { currHp = 0 } fightProp[constant.FIGHT_PROP_CUR_HP] = currHp } g.EntityFightPropUpdateNotifyBroadcast(world, target) if currHp == 0 && target.GetEntityType() == constant.ENTITY_TYPE_MONSTER { g.KillEntity(player, scene, target.GetId(), proto.PlayerDieType_PLAYER_DIE_GM) } combatData, err := pb.Marshal(evtBeingHitInfo) if err != nil { logger.Error("create combat invocations entity hit info error: %v", err) } entry.CombatData = combatData player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) case proto.CombatTypeArgument_ENTITY_MOVE: entityMoveInfo := new(proto.EntityMoveInfo) err := pb.Unmarshal(entry.CombatData, entityMoveInfo) if err != nil { logger.Error("parse EntityMoveInfo error: %v", err) continue } // logger.Debug("EntityMoveInfo: %v, ForwardType: %v", entityMoveInfo, entry.ForwardType) motionInfo := entityMoveInfo.MotionInfo if motionInfo.Pos == nil || motionInfo.Rot == nil { continue } sceneEntity := scene.GetEntity(entityMoveInfo.EntityId) if sceneEntity == nil { continue } if sceneEntity.GetEntityType() == constant.ENTITY_TYPE_AVATAR { // 玩家实体在移动 g.AoiPlayerMove(player, player.Pos, &model.Vector{ X: float64(motionInfo.Pos.X), Y: float64(motionInfo.Pos.Y), Z: float64(motionInfo.Pos.Z), }) // 场景区域触发器检测 g.SceneRegionTriggerCheck(player, scene, player.Pos, &model.Vector{ X: float64(motionInfo.Pos.X), Y: float64(motionInfo.Pos.Y), Z: float64(motionInfo.Pos.Z), }, sceneEntity.GetId()) // 更新玩家的位置信息 player.Pos.X = float64(motionInfo.Pos.X) player.Pos.Y = float64(motionInfo.Pos.Y) player.Pos.Z = float64(motionInfo.Pos.Z) player.Rot.X = float64(motionInfo.Rot.X) player.Rot.Y = float64(motionInfo.Rot.Y) player.Rot.Z = float64(motionInfo.Rot.Z) // 处理耐力消耗 g.ImmediateStamina(player, motionInfo.State) } else { // 非玩家实体在移动 // 更新场景实体的位置信息 pos := sceneEntity.GetPos() pos.X = float64(motionInfo.Pos.X) pos.Y = float64(motionInfo.Pos.Y) pos.Z = float64(motionInfo.Pos.Z) rot := sceneEntity.GetRot() rot.X = float64(motionInfo.Rot.X) rot.Y = float64(motionInfo.Rot.Y) rot.Z = float64(motionInfo.Rot.Z) if sceneEntity.GetEntityType() == constant.ENTITY_TYPE_GADGET { // 载具耐力消耗 gadgetEntity := sceneEntity.GetGadgetEntity() if gadgetEntity.GetGadgetVehicleEntity() != nil { // 处理耐力消耗 g.ImmediateStamina(player, motionInfo.State) // 处理载具销毁请求 g.VehicleDestroyMotion(player, sceneEntity, motionInfo.State) } } } sceneEntity.SetMoveState(uint16(motionInfo.State)) sceneEntity.SetLastMoveSceneTimeMs(entityMoveInfo.SceneTime) sceneEntity.SetLastMoveReliableSeq(entityMoveInfo.ReliableSeq) // 众里寻他千百度 蓦然回首 那人却在灯火阑珊处 if motionInfo.State == proto.MotionState_MOTION_NOTIFY || motionInfo.State == proto.MotionState_MOTION_FIGHT { // 只要转发了这两个包的其中之一 客户端的动画就会被打断 continue } player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) case proto.CombatTypeArgument_COMBAT_ANIMATOR_PARAMETER_CHANGED: evtAnimatorParameterInfo := new(proto.EvtAnimatorParameterInfo) err := pb.Unmarshal(entry.CombatData, evtAnimatorParameterInfo) if err != nil { logger.Error("parse EvtAnimatorParameterInfo error: %v", err) continue } // logger.Debug("EvtAnimatorParameterInfo: %v, ForwardType: %v", evtAnimatorParameterInfo, entry.ForwardType) player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) case proto.CombatTypeArgument_COMBAT_ANIMATOR_STATE_CHANGED: evtAnimatorStateChangedInfo := new(proto.EvtAnimatorStateChangedInfo) err := pb.Unmarshal(entry.CombatData, evtAnimatorStateChangedInfo) if err != nil { logger.Error("parse EvtAnimatorStateChangedInfo error: %v", err) continue } // logger.Debug("EvtAnimatorStateChangedInfo: %v, ForwardType: %v", evtAnimatorStateChangedInfo, entry.ForwardType) player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) default: player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry) } } } func (g *Game) AoiPlayerMove(player *model.Player, oldPos *model.Vector, newPos *model.Vector) { world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { logger.Error("get player world is nil, uid: %v", player.PlayerID) return } scene := world.GetSceneById(player.SceneId) sceneBlockAoiMap := WORLD_MANAGER.GetSceneBlockAoiMap() aoiManager, exist := sceneBlockAoiMap[player.SceneId] if !exist { logger.Error("get scene block aoi is nil, sceneId: %v, uid: %v", player.SceneId, player.PlayerID) return } oldGid := aoiManager.GetGidByPos(float32(oldPos.X), 0.0, float32(oldPos.Z)) newGid := aoiManager.GetGidByPos(float32(newPos.X), 0.0, float32(newPos.Z)) if oldGid != newGid { // 跨越了block格子 logger.Debug("player cross grid, oldGid: %v, newGid: %v, uid: %v", oldGid, newGid, player.PlayerID) } // 加载和卸载的group oldNeighborGroupMap := g.GetNeighborGroup(player.SceneId, oldPos) newNeighborGroupMap := g.GetNeighborGroup(player.SceneId, newPos) for groupId, groupConfig := range oldNeighborGroupMap { _, exist := newNeighborGroupMap[groupId] if exist { continue } // 旧有新没有的group即为卸载的 if !world.GetMultiplayer() { // 处理多人世界不同玩家不同位置的group卸载情况 g.RemoveSceneGroup(player, scene, groupConfig) } } for groupId, groupConfig := range newNeighborGroupMap { _, exist := oldNeighborGroupMap[groupId] if exist { continue } // 新有旧没有的group即为加载的 g.AddSceneGroup(player, scene, groupConfig) } // 消失和出现的场景实体 oldVisionEntityMap := g.GetVisionEntity(scene, oldPos) newVisionEntityMap := g.GetVisionEntity(scene, newPos) delEntityIdList := make([]uint32, 0) for entityId := range oldVisionEntityMap { _, exist := newVisionEntityMap[entityId] if exist { continue } // 旧有新没有的实体即为消失的 delEntityIdList = append(delEntityIdList, entityId) } addEntityIdList := make([]uint32, 0) for entityId := range newVisionEntityMap { _, exist := oldVisionEntityMap[entityId] if exist { continue } // 新有旧没有的实体即为出现的 addEntityIdList = append(addEntityIdList, entityId) } // 同步客户端消失和出现的场景实体 if len(delEntityIdList) > 0 { g.RemoveSceneEntityNotifyToPlayer(player, proto.VisionType_VISION_MISS, delEntityIdList) } if len(addEntityIdList) > 0 { g.AddSceneEntityNotify(player, proto.VisionType_VISION_MEET, addEntityIdList, false, false) } } func (g *Game) AbilityInvocationsNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.AbilityInvocationsNotify) if player.SceneLoadState != model.SceneEnterDone { return } world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { return } for _, entry := range req.Invokes { // logger.Debug("AbilityInvocationsNotify: %v", entry, player.PlayerID) switch entry.ArgumentType { case proto.AbilityInvokeArgument_ABILITY_META_MODIFIER_CHANGE: worldAvatar := world.GetWorldAvatarByEntityId(entry.EntityId) if worldAvatar != nil { for _, ability := range worldAvatar.abilityList { if ability.InstancedAbilityId == entry.Head.InstancedAbilityId { // logger.Error("A: %v", ability) } } for _, modifier := range worldAvatar.modifierList { if modifier.InstancedAbilityId == entry.Head.InstancedAbilityId { // logger.Error("B: %v", modifier) } } for _, modifier := range worldAvatar.modifierList { if modifier.InstancedModifierId == entry.Head.InstancedModifierId { // logger.Error("C: %v", modifier) } } } } // 处理耐力消耗 g.HandleAbilityStamina(player, entry) player.AbilityInvokeHandler.AddEntry(entry.ForwardType, entry) } } func (g *Game) ClientAbilityInitFinishNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.ClientAbilityInitFinishNotify) if player.SceneLoadState != model.SceneEnterDone { return } invokeHandler := model.NewInvokeHandler[proto.AbilityInvokeEntry]() for _, entry := range req.Invokes { // logger.Debug("ClientAbilityInitFinishNotify: %v", entry) invokeHandler.AddEntry(entry.ForwardType, entry) } DoForward[proto.AbilityInvokeEntry](player, invokeHandler, cmd.ClientAbilityInitFinishNotify, new(proto.ClientAbilityInitFinishNotify), "Invokes", req, []string{"EntityId"}) } func (g *Game) ClientAbilityChangeNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.ClientAbilityChangeNotify) if player.SceneLoadState != model.SceneEnterDone { return } invokeHandler := model.NewInvokeHandler[proto.AbilityInvokeEntry]() for _, entry := range req.Invokes { // logger.Debug("ClientAbilityChangeNotify: %v", entry) invokeHandler.AddEntry(entry.ForwardType, entry) } DoForward[proto.AbilityInvokeEntry](player, invokeHandler, cmd.ClientAbilityChangeNotify, new(proto.ClientAbilityChangeNotify), "Invokes", req, []string{"IsInitHash", "EntityId"}) world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { return } for _, abilityInvokeEntry := range req.Invokes { switch abilityInvokeEntry.ArgumentType { case proto.AbilityInvokeArgument_ABILITY_META_ADD_NEW_ABILITY: abilityMetaAddAbility := new(proto.AbilityMetaAddAbility) err := pb.Unmarshal(abilityInvokeEntry.AbilityData, abilityMetaAddAbility) if err != nil { logger.Error("parse AbilityMetaAddAbility error: %v", err) continue } worldAvatar := world.GetWorldAvatarByEntityId(abilityInvokeEntry.EntityId) if worldAvatar == nil { continue } if abilityMetaAddAbility.Ability == nil { continue } abilityList := worldAvatar.GetAbilityList() abilityList = append(abilityList, abilityMetaAddAbility.Ability) worldAvatar.SetAbilityList(abilityList) case proto.AbilityInvokeArgument_ABILITY_META_MODIFIER_CHANGE: abilityMetaModifierChange := new(proto.AbilityMetaModifierChange) err := pb.Unmarshal(abilityInvokeEntry.AbilityData, abilityMetaModifierChange) if err != nil { logger.Error("parse AbilityMetaModifierChange error: %v", err) continue } abilityAppliedModifier := &proto.AbilityAppliedModifier{ ModifierLocalId: abilityMetaModifierChange.ModifierLocalId, ParentAbilityEntityId: 0, ParentAbilityName: abilityMetaModifierChange.ParentAbilityName, ParentAbilityOverride: abilityMetaModifierChange.ParentAbilityOverride, InstancedAbilityId: abilityInvokeEntry.Head.InstancedAbilityId, InstancedModifierId: abilityInvokeEntry.Head.InstancedModifierId, ExistDuration: 0, AttachedInstancedModifier: abilityMetaModifierChange.AttachedInstancedModifier, ApplyEntityId: abilityMetaModifierChange.ApplyEntityId, IsAttachedParentAbility: abilityMetaModifierChange.IsAttachedParentAbility, ModifierDurability: nil, SbuffUid: 0, IsServerbuffModifier: abilityInvokeEntry.Head.IsServerbuffModifier, } worldAvatar := world.GetWorldAvatarByEntityId(abilityInvokeEntry.EntityId) if worldAvatar == nil { continue } modifierList := worldAvatar.GetModifierList() modifierList = append(modifierList, abilityAppliedModifier) worldAvatar.SetModifierList(modifierList) default: } } } func (g *Game) EvtDoSkillSuccNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtDoSkillSuccNotify) if player.SceneLoadState != model.SceneEnterDone { return } logger.Debug("EvtDoSkillSuccNotify: %v", req) // 处理技能开始的耐力消耗 g.SkillStartStamina(player, req.CasterId, req.SkillId) g.TriggerQuest(player, constant.QUEST_FINISH_COND_TYPE_SKILL, "", int32(req.SkillId)) } func (g *Game) EvtAvatarEnterFocusNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtAvatarEnterFocusNotify) if player.SceneLoadState != model.SceneEnterDone { return } // logger.Debug("EvtAvatarEnterFocusNotify: %v", req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) g.SendToWorldA(world, cmd.EvtAvatarEnterFocusNotify, player.ClientSeq, req) } func (g *Game) EvtAvatarUpdateFocusNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtAvatarUpdateFocusNotify) if player.SceneLoadState != model.SceneEnterDone { return } // logger.Debug("EvtAvatarUpdateFocusNotify: %v", req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) g.SendToWorldA(world, cmd.EvtAvatarUpdateFocusNotify, player.ClientSeq, req) } func (g *Game) EvtAvatarExitFocusNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtAvatarExitFocusNotify) if player.SceneLoadState != model.SceneEnterDone { return } // logger.Debug("EvtAvatarExitFocusNotify: %v", req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) g.SendToWorldA(world, cmd.EvtAvatarExitFocusNotify, player.ClientSeq, req) } func (g *Game) EvtEntityRenderersChangedNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtEntityRenderersChangedNotify) if player.SceneLoadState != model.SceneEnterDone { return } // logger.Debug("EvtEntityRenderersChangedNotify: %v", req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) g.SendToWorldA(world, cmd.EvtEntityRenderersChangedNotify, player.ClientSeq, req) } func (g *Game) EvtCreateGadgetNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtCreateGadgetNotify) if player.SceneLoadState != model.SceneEnterDone { return } // logger.Debug("EvtCreateGadgetNotify: %v", req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { logger.Error("world is nil, WorldId: %v", player.WorldId) return } scene := world.GetSceneById(player.SceneId) if req.InitPos == nil { return } scene.CreateEntityGadgetClient(&model.Vector{ X: float64(req.InitPos.X), Y: float64(req.InitPos.Y), Z: float64(req.InitPos.Z), }, &model.Vector{ X: float64(req.InitEulerAngles.X), Y: float64(req.InitEulerAngles.Y), Z: float64(req.InitEulerAngles.Z), }, req.EntityId, req.ConfigId, req.CampId, req.CampType, req.OwnerEntityId, req.TargetEntityId, req.PropOwnerEntityId) g.AddSceneEntityNotify(player, proto.VisionType_VISION_BORN, []uint32{req.EntityId}, true, true) } func (g *Game) EvtDestroyGadgetNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtDestroyGadgetNotify) if player.SceneLoadState != model.SceneEnterDone { return } // logger.Debug("EvtDestroyGadgetNotify: %v", req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) if world == nil { logger.Error("world is nil, worldId: %v", player.WorldId) return } scene := world.GetSceneById(player.SceneId) scene.DestroyEntity(req.EntityId) g.RemoveSceneEntityNotifyBroadcast(scene, proto.VisionType_VISION_MISS, []uint32{req.EntityId}) } func (g *Game) EvtAiSyncSkillCdNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtAiSyncSkillCdNotify) _ = req } func (g *Game) EvtAiSyncCombatThreatInfoNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EvtAiSyncCombatThreatInfoNotify) _ = req } func (g *Game) EntityConfigHashNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EntityConfigHashNotify) _ = req } func (g *Game) MonsterAIConfigHashNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.MonsterAIConfigHashNotify) _ = req } func (g *Game) SetEntityClientDataNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.SetEntityClientDataNotify) g.SendMsg(cmd.SetEntityClientDataNotify, player.PlayerID, player.ClientSeq, req) } func (g *Game) EntityAiSyncNotify(player *model.Player, payloadMsg pb.Message) { req := payloadMsg.(*proto.EntityAiSyncNotify) entityAiSyncNotify := &proto.EntityAiSyncNotify{ InfoList: make([]*proto.AiSyncInfo, 0), } for _, monsterId := range req.LocalAvatarAlertedMonsterList { entityAiSyncNotify.InfoList = append(entityAiSyncNotify.InfoList, &proto.AiSyncInfo{ EntityId: monsterId, HasPathToTarget: true, IsSelfKilling: false, }) } g.SendMsg(cmd.EntityAiSyncNotify, player.PlayerID, player.ClientSeq, entityAiSyncNotify) }