using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using PhoenixLib.Events; using PhoenixLib.Logging; using WingsAPI.Communication.ServerApi.Protocol; using WingsAPI.Game.Extensions.Families; using WingsAPI.Game.Extensions.Groups; using WingsAPI.Game.Extensions.MinilandExtensions; using WingsAPI.Game.Extensions.PacketGeneration; using WingsEmu.DTOs.Maps; using WingsEmu.Game; using WingsEmu.Game._enum; using WingsEmu.Game._i18n; using WingsEmu.Game._ItemUsage.Configuration; using WingsEmu.Game._packetHandling; using WingsEmu.Game.Algorithm; using WingsEmu.Game.Battle; using WingsEmu.Game.Buffs; using WingsEmu.Game.Characters; using WingsEmu.Game.Characters.Events; using WingsEmu.Game.Configurations; using WingsEmu.Game.Entities; using WingsEmu.Game.Entities.Extensions; using WingsEmu.Game.Extensions; using WingsEmu.Game.Extensions.Mates; using WingsEmu.Game.Families; using WingsEmu.Game.Groups; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Managers; using WingsEmu.Game.Maps; using WingsEmu.Game.Maps.Event; using WingsEmu.Game.Mates; using WingsEmu.Game.Mates.Events; using WingsEmu.Game.Miniland; using WingsEmu.Game.Networking; using WingsEmu.Game.Networking.Broadcasting; using WingsEmu.Game.Portals; using WingsEmu.Game.Raids; using WingsEmu.Game.TimeSpaces; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Chat; namespace WingsEmu.Plugins.BasicImplementations.Event.Maps; public class JoinMapEventHandler : IAsyncEventProcessor { private readonly IBuffFactory _buffFactory; private readonly ICharacterAlgorithm _characterAlgorithm; private readonly IGameLanguageService _languageService; private readonly IMapManager _mapManager; private readonly IMinilandManager _minilandManager; private readonly IPortalFactory _portalFactory; private readonly IRankingManager _rankingManager; private readonly IReputationConfiguration _reputationConfiguration; private readonly SerializableGameServer _serializableGameServer; private readonly ISpPartnerConfiguration _spPartnerConfiguration; public JoinMapEventHandler(ICharacterAlgorithm characterAlgorithm, IGameLanguageService languageService, ISpPartnerConfiguration spPartnerConfiguration, IBuffFactory buffFactory, IMinilandManager minilandManager, IMapManager mapManager, IReputationConfiguration reputationConfiguration, IPortalFactory portalFactory, IRankingManager rankingManager, SerializableGameServer serializableGameServer) { _characterAlgorithm = characterAlgorithm; _languageService = languageService; _spPartnerConfiguration = spPartnerConfiguration; _buffFactory = buffFactory; _minilandManager = minilandManager; _mapManager = mapManager; _reputationConfiguration = reputationConfiguration; _portalFactory = portalFactory; _rankingManager = rankingManager; _serializableGameServer = serializableGameServer; } public async Task HandleAsync(JoinMapEvent e, CancellationToken cancellation) { IClientSession session = e.Sender; IMapInstance mapInstance; if (e.JoinedMapInstance != default) { mapInstance = e.JoinedMapInstance; } else if (e.JoinedMapGuid != default) { mapInstance = _mapManager.GetMapInstance(e.JoinedMapGuid); } else { mapInstance = _mapManager.GetBaseMapInstanceByMapId(e.JoinedMapId); } if (mapInstance == default) { /*if (_serializableGameServer.ChannelType == GameChannelType.ACT_4 && e.JoinedMapId is 145) { await session.EmitEventAsync(new PlayerReturnFromAct4Event()); return; }*/ Log.Error( "Seems like the provided information for the Map to join doesn't equal to any known instance." + $"JoinedMapInstance: {e.JoinedMapInstance?.Id}|{e.JoinedMapInstance?.MapId}" + $"JoinedMapGuid: {e.JoinedMapGuid}" + $"JoinedMapId: {e.JoinedMapId}" + $"ChannelType: {_serializableGameServer.ChannelType} | ChannelId: {_serializableGameServer.ChannelId}", new ArgumentNullException("MapInstance")); //session.ChangeToLastBaseMap(); return; } Stopwatch watch = null; if (session.DebugMode) { watch = Stopwatch.StartNew(); } if (session.CurrentMapInstance != default) { await session.EmitEventAsync(new LeaveMapEvent()); } ServerSideJoinMap(e, mapInstance); IFamily family = session.PlayerEntity.Family; await SendJoinMapInformation(e, family); await BroadcastAndReceiveJoinInformation(e, family); await session.EmitEventAsync(new JoinMapEndEvent(mapInstance)); await session.PlayerEntity.CheckAct52Buff(_buffFactory); await session.PlayerEntity.CheckAct4Buff(_buffFactory); await session.PlayerEntity.CheckPvPBuff(); await session.EmitEventAsync(new VehicleCheckMapSpeedEvent()); session.PlayerEntity.ShadowAppears(false); session.PlayerEntity.LastMapChange = DateTime.UtcNow; if (session.DebugMode && watch != null) { watch.Stop(); session.SendDebugMessage($"The map change took: {watch.ElapsedMilliseconds.ToString()}ms"); } if (session.PlayerEntity.IsRaidLeader(session.PlayerEntity.Id)) { if (session.PlayerEntity.Raid?.Instance?.RaidSubInstances == null) { return; } if (session.PlayerEntity.Raid.Instance.RaidSubInstances.TryGetValue(session.CurrentMapInstance.Id, out RaidSubInstance subInstance) && subInstance != null) { subInstance.IsDiscoveredByLeader = true; } } } private void ServerSideJoinMap(JoinMapEvent e, IMapInstance mapInstance) { IClientSession session = e.Sender; if (mapInstance.HasMapFlag(MapFlags.IS_BASE_MAP) && mapInstance.MapInstanceType != MapInstanceType.TimeSpaceInstance) { session.PlayerEntity.MapId = mapInstance.MapId; } if (mapInstance.HasMapFlag(MapFlags.IS_BASE_MAP) && mapInstance.MapInstanceType != MapInstanceType.TimeSpaceInstance && e.X != null && e.Y != null) { session.PlayerEntity.MapX = e.X.Value; session.PlayerEntity.MapY = e.Y.Value; } if (e.X != null && e.Y != null) { session.PlayerEntity.Position = new Position(e.X.Value, e.Y.Value); } switch (mapInstance.MapInstanceType) { case MapInstanceType.Miniland: Game.Configurations.Miniland.Miniland minilandConfiguration = _minilandManager.GetMinilandConfiguration(mapInstance); session.PlayerEntity.Position = new Position(minilandConfiguration.ArrivalSerializablePosition.X, minilandConfiguration.ArrivalSerializablePosition.Y); if (mapInstance.Id == session.PlayerEntity.Miniland.Id) { foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates(x => !x.IsTeamMember)) { session.SendCondMate(mate); if (mapInstance.GetMateById(mate.Id) != null) { continue; } mapInstance.AddMate(mate); } } break; case MapInstanceType.ArenaInstance: session.PlayerEntity.ArenaImmunity = DateTime.UtcNow; session.SendArenaStatistics(false, session.PlayerEntity.GetGroup()); break; case MapInstanceType.TimeSpaceInstance: List partners = session.PlayerEntity.TimeSpaceComponent.Partners; if (!partners.Any()) { break; } foreach (INpcEntity partner in partners) { partner.ChangeMapInstance(mapInstance); mapInstance.AddNpc(partner); partner.Position = session.PlayerEntity.Position; } break; default: session.PlayerEntity.ArenaDeaths = 0; session.PlayerEntity.ArenaKills = 0; session.PlayerEntity.TimeSpaceComponent.Partners.Clear(); break; } session.PlayerEntity.MapInstanceId = mapInstance.Id; session.CurrentMapInstance = session.PlayerEntity.MapInstance; session.CurrentMapInstance.RegisterSession(session); HashSet toRemovePlayer = new(); foreach (Guid id in session.PlayerEntity.AggroedEntities) { IMonsterEntity monster = mapInstance.GetMonsterByUniqueId(id); if (monster != null) { continue; } toRemovePlayer.Add(id); } foreach (Guid remove in toRemovePlayer) { session.PlayerEntity.AggroedEntities.Remove(remove); } foreach (IMateEntity mate in session.PlayerEntity.MateComponent.TeamMembers()) { HashSet toRemoveMate = new(); foreach (Guid id in mate.AggroedEntities) { IMonsterEntity monster = mapInstance.GetMonsterByUniqueId(id); if (monster != null) { continue; } toRemoveMate.Add(id); } foreach (Guid remove in toRemoveMate) { mate.AggroedEntities.Remove(remove); } } } private async Task SendJoinMapInformation(JoinMapEvent e, IFamily family) { IClientSession session = e.Sender; session.SendCInfoPacket(family, _reputationConfiguration, _rankingManager.TopReputation); session.SendCModePacket(); session.RefreshEquipment(); session.RefreshLevel(_characterAlgorithm); session.RefreshStat(); session.SendAtPacket(); session.SendGidxPacket(family, _languageService); session.SendCondPacket(); session.SendCondPacket(); session.SendTitInfoPacket(); session.SendEqPacket(); session.SendCMapPacket(true); if (session.CurrentMapInstance.MapId == (int)MapIds.HATUS_BOSS_MAP) { session.SendEmptyHatusHeads(); } session.RefreshStatChar(); if (session.CurrentMapInstance.MapInstanceType != MapInstanceType.TimeSpaceInstance) { session.SendRsfpPacket(); } session.RefreshFairy(); session.SendCharConstBuffEffect(); session.RefreshZoom(); await SendPartyInfo(e); session.SendPacket(session.GenerateAct6EmptyPacket()); session.SendCondPacket(); session.SendPacket(session.CurrentMapInstance.GenerateMapDesignObjects()); foreach (MapDesignObject mapObject in session.CurrentMapInstance.MapDesignObjects) { session.SendPacket(mapObject.GenerateEffect(false)); } if (session.CurrentMapInstance.MapInstanceType == MapInstanceType.TimeSpaceInstance && session.PlayerEntity.TimeSpaceComponent.IsInTimeSpaceParty) { IClientSession[] timeSpaceMembers = session.PlayerEntity.TimeSpaceComponent.TimeSpace.Members.ToArray(); foreach (IClientSession member in timeSpaceMembers) { member.SendPacket(e.JoinedMapInstance.GenerateRsfn(isVisit: true)); } if (session.PlayerEntity.TimeSpaceComponent.TimeSpace.Started) { session.SendRsfpPacket(); } } session.SendPackets(session.CurrentMapInstance.GetEntitiesOnMapPackets()); foreach (IPortalEntity p in session.CurrentMapInstance.GenerateMinilandEntryPortals(session.PlayerEntity.Miniland, _portalFactory)) { session.SendPacket(p.GenerateGp()); } session.SendTimeSpacePortals(); session.TrySendScalPacket(); session.SendDancePacket(session.CurrentMapInstance.IsDance); if (session.PlayerEntity.ShowRaidDeathInfo) { session.PlayerEntity.ShowRaidDeathInfo = false; session.SendInfo(session.GetLanguage(GameDialogKey.RAID_INFO_NO_LIVES_LEFT)); } List partners = session.PlayerEntity.TimeSpaceComponent.Partners; if (partners.Any()) { foreach (INpcEntity partner in partners) { session.PlayerEntity.MapInstance.Broadcast(x => partner.GenerateIn()); session.SendMateControl(partner); session.SendCondMate(partner); } } await MinilandInfoSend(e); } private async Task MinilandInfoSend(PlayerEvent e) { if (e.Sender.CurrentMapInstance?.MapInstanceType != MapInstanceType.Miniland) { return; } IClientSession session = e.Sender; IClientSession minilandOwner = _minilandManager.GetSessionByMiniland(session.CurrentMapInstance); if (minilandOwner == default) { return; } if (session.PlayerEntity.Miniland.Id != minilandOwner.PlayerEntity.Miniland.Id) { if (minilandOwner.PlayerEntity.MinilandState == MinilandState.LOCK && !session.IsGameMaster() || minilandOwner.PlayerEntity.MinilandState == MinilandState.PRIVATE && !session.PlayerEntity.IsFriend(minilandOwner.PlayerEntity.Id) && !session.PlayerEntity.IsMarried(minilandOwner.PlayerEntity.Id) && !session.IsGameMaster()) { session.ChangeToLastBaseMap(); session.SendMsg(_languageService.GetLanguage(GameDialogKey.MINILAND_SHOUTMESSAGE_CLOSED, session.UserLanguage), MsgMessageType.Middle); return; } int count = minilandOwner.PlayerEntity.Miniland.Sessions.Count(x => x.PlayerEntity.Id != minilandOwner.PlayerEntity.Id && !x.GmMode); int capacity = _minilandManager.GetMinilandMaximumCapacity(minilandOwner.PlayerEntity.Id); if (count > capacity) { session.ChangeToLastBaseMap(); session.SendMsg(_languageService.GetLanguage(GameDialogKey.MINILAND_SHOUTMESSAGE_FULL, session.UserLanguage), MsgMessageType.Middle); return; } session.SendMsg(minilandOwner.GetMinilandCleanMessage(_languageService), MsgMessageType.Middle); if (!session.IsGameMaster()) { await _minilandManager.IncreaseMinilandVisitCounter(minilandOwner.PlayerEntity.Id); } session.SendMinilandPublicInformation(_minilandManager, _languageService); minilandOwner.SendMinilandPrivateInformation(_minilandManager, _languageService); } else { session.SendMinilandPrivateInformation(_minilandManager, _languageService); } foreach (IMateEntity mate in minilandOwner.PlayerEntity.MateComponent.GetMates()) { if (!mate.IsAlive() && session.PlayerEntity.Id == minilandOwner.PlayerEntity.Id) { await session.EmitEventAsync(new MateReviveEvent(mate, false)); continue; } if (!mate.IsTeamMember) { session.SendPacket(mate.GenerateIn(_languageService, session.UserLanguage, _spPartnerConfiguration)); } } long visitCount = await _minilandManager.GetMinilandVisitCounter(session.PlayerEntity.Id); session.SendInformationChatMessage(_languageService.GetLanguageFormat(GameDialogKey.MINILAND_MESSAGE_VISITOR, session.UserLanguage, session.PlayerEntity.LifetimeStats.TotalMinilandVisits, visitCount)); } private async Task SendPartyInfo(PlayerEvent e) { IClientSession session = e.Sender; session.SendPstPackets(); if (!session.PlayerEntity.IsOnVehicle && !session.PlayerEntity.CheatComponent.IsInvisible) { foreach (IMateEntity mate in session.PlayerEntity.MateComponent.TeamMembers()) { if (!mate.IsAlive()) { continue; } if (mate.IsSitting) { await session.EmitEventAsync(new MateRestEvent { MateEntity = mate, Force = true }); } mate.TeleportNearCharacter(); session.SendPacket(mate.GenerateIn(_languageService, session.UserLanguage, _spPartnerConfiguration)); session.TrySendScalPacket(mate); session.SendMateControl(mate); session.SendTargetConstBuffEffects(mate); session.SendCondMate(mate); } } session.RefreshParty(_spPartnerConfiguration); if (!session.PlayerEntity.IsInGroup()) { return; } PlayerGroup group = session.PlayerEntity.GetGroup(); foreach (IPlayerEntity member in group.Members) { if (session.PlayerEntity.Id == member.Id) { continue; } member.Session.RefreshParty(_spPartnerConfiguration); } session.CurrentMapInstance?.Broadcast(session.GeneratePidx(), new ExceptSessionBroadcast(session)); } private async Task BroadcastAndReceiveJoinInformation(JoinMapEvent e, IFamily family) { IClientSession session = e.Sender; foreach (IClientSession currentPlayerOnMap in session.CurrentMapInstance.Sessions) { if (currentPlayerOnMap.PlayerEntity.Id == session.PlayerEntity.Id) { continue; } if (!currentPlayerOnMap.PlayerEntity.CheatComponent.IsInvisible) { await TargetedSendJoinInformation(session, currentPlayerOnMap, currentPlayerOnMap.PlayerEntity.Family, true); } if (!session.PlayerEntity.CheatComponent.IsInvisible) { await TargetedSendJoinInformation(currentPlayerOnMap, session, family, false); } } } private async Task TargetedSendJoinInformation(IClientSession receiverSession, IClientSession targetSession, IFamily family, bool showInEffect) { bool isAnonymous = targetSession.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4) && receiverSession.PlayerEntity.Faction != targetSession.PlayerEntity.Faction && !receiverSession.IsGameMaster(); receiverSession.SendTargetInPacket(targetSession, _reputationConfiguration, _rankingManager.TopReputation, isAnonymous, showInEffect); if (!isAnonymous) { receiverSession.SendTargetGidxPacket(targetSession, family, _languageService); } receiverSession.SendTargetTitInfoPacket(targetSession); receiverSession.SendTargetConstBuffEffects(targetSession.PlayerEntity); if (targetSession.PlayerEntity.HasShopOpened) { receiverSession.SendPlayerShopTitle(targetSession); receiverSession.SendPacket(targetSession.GeneratePlayerFlag((long)DialogVnums.SHOP_PLAYER)); } if (targetSession.PlayerEntity.IsOnVehicle) { return; } foreach (IMateEntity mate in targetSession.PlayerEntity.MateComponent.TeamMembers()) { string inPacket = mate.GenerateIn(_languageService, receiverSession.UserLanguage, _spPartnerConfiguration, isAnonymous); receiverSession.SendPacket(inPacket); receiverSession.SendTargetConstBuffEffects(mate); } } }