958 lines
No EOL
38 KiB
C#
958 lines
No EOL
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using PhoenixLib.DAL.Redis.Locks;
|
|
using PhoenixLib.Events;
|
|
using PhoenixLib.Logging;
|
|
using PhoenixLib.ServiceBus;
|
|
using WingsAPI.Communication;
|
|
using WingsAPI.Communication.DbServer.AccountService;
|
|
using WingsAPI.Communication.DbServer.CharacterService;
|
|
using WingsAPI.Communication.Player;
|
|
using WingsAPI.Communication.ServerApi;
|
|
using WingsAPI.Communication.ServerApi.Protocol;
|
|
using WingsAPI.Communication.Sessions;
|
|
using WingsAPI.Communication.Sessions.Model;
|
|
using WingsAPI.Communication.Sessions.Request;
|
|
using WingsAPI.Communication.Sessions.Response;
|
|
using WingsAPI.Data.Account;
|
|
using WingsAPI.Data.Character;
|
|
using WingsAPI.Game.Extensions.Families;
|
|
using WingsAPI.Game.Extensions.MinilandExtensions;
|
|
using WingsAPI.Game.Extensions.Quests;
|
|
using WingsAPI.Game.Extensions.Quicklist;
|
|
using WingsAPI.Packets.Enums;
|
|
using WingsEmu.DTOs.Bonus;
|
|
using WingsEmu.DTOs.Buffs;
|
|
using WingsEmu.DTOs.Inventory;
|
|
using WingsEmu.DTOs.Items;
|
|
using WingsEmu.DTOs.Maps;
|
|
using WingsEmu.DTOs.Mates;
|
|
using WingsEmu.DTOs.Quests;
|
|
using WingsEmu.DTOs.Quicklist;
|
|
using WingsEmu.DTOs.Skills;
|
|
using WingsEmu.DTOs.Titles;
|
|
using WingsEmu.Game;
|
|
using WingsEmu.Game._enum;
|
|
using WingsEmu.Game._i18n;
|
|
using WingsEmu.Game.Algorithm;
|
|
using WingsEmu.Game.Battle;
|
|
using WingsEmu.Game.Buffs;
|
|
using WingsEmu.Game.Characters;
|
|
using WingsEmu.Game.Characters.Events;
|
|
using WingsEmu.Game.Compliments;
|
|
using WingsEmu.Game.Configurations;
|
|
using WingsEmu.Game.Extensions;
|
|
using WingsEmu.Game.Extensions.Mates;
|
|
using WingsEmu.Game.Families;
|
|
using WingsEmu.Game.Families.Configuration;
|
|
using WingsEmu.Game.Helpers.Damages;
|
|
using WingsEmu.Game.Inventory;
|
|
using WingsEmu.Game.Items;
|
|
using WingsEmu.Game.Managers;
|
|
using WingsEmu.Game.Managers.StaticData;
|
|
using WingsEmu.Game.Maps;
|
|
using WingsEmu.Game.Mates;
|
|
using WingsEmu.Game.Mates.Events;
|
|
using WingsEmu.Game.Miniland;
|
|
using WingsEmu.Game.Miniland.Minigames;
|
|
using WingsEmu.Game.Networking;
|
|
using WingsEmu.Game.Quests;
|
|
using WingsEmu.Game.Quests.Event;
|
|
using WingsEmu.Game.Raids.Events;
|
|
using WingsEmu.Game.RainbowBattle.Event;
|
|
using WingsEmu.Game.Revival;
|
|
using WingsEmu.Game.Skills;
|
|
using WingsEmu.Game.TimeSpaces;
|
|
using WingsEmu.Packets.Enums;
|
|
using WingsEmu.Packets.Enums.Chat;
|
|
using WingsEmu.Plugins.DistributedGameEvents.PlayerEvents;
|
|
|
|
namespace WingsEmu.Plugins.BasicImplementations.Event.Characters;
|
|
|
|
public class CharacterLoadEventHandler : IAsyncEventProcessor<CharacterLoadEvent>
|
|
{
|
|
private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
|
|
private readonly IAccountService _accountService;
|
|
private readonly IBCardEffectHandlerContainer _bcardHandlerContainer;
|
|
private readonly IBuffFactory _buffFactory;
|
|
private readonly ICharacterAlgorithm _characterAlgorithm;
|
|
private readonly ICharacterService _characterService;
|
|
private readonly FamilyConfiguration _familyConfiguration;
|
|
private readonly IFamilyManager _familyManager;
|
|
private readonly SerializableGameServer _gameServer;
|
|
private readonly IGameItemInstanceFactory _itemInstanceFactory;
|
|
private readonly IItemsManager _itemsManager;
|
|
private readonly IGameLanguageService _language;
|
|
private readonly IExpirableLockService _lock;
|
|
private readonly IMapManager _mapManager;
|
|
private readonly IMateEntityFactory _mateEntityFactory;
|
|
private readonly IMessagePublisher<PlayerConnectedOnChannelMessage> _messagePublisher;
|
|
private readonly IMinilandManager _minilandManager;
|
|
private readonly IPlayerEntityFactory _playerEntityFactory;
|
|
private readonly IQuestFactory _questFactory;
|
|
private readonly IQuestManager _questManager;
|
|
private readonly IRandomGenerator _randomGenerator;
|
|
private readonly IRankingManager _rankingManager;
|
|
private readonly IReputationConfiguration _reputationConfiguration;
|
|
private readonly IRevivalManager _revivalManager;
|
|
private readonly IServerApiService _serverApiService;
|
|
|
|
|
|
private readonly IServerManager _serverManager;
|
|
private readonly ISessionManager _sessionManager;
|
|
private readonly ISessionService _sessionService;
|
|
private readonly ISubActConfiguration _subActConfiguration;
|
|
private readonly ITimeSpaceConfiguration _timeSpaceConfiguration;
|
|
|
|
public CharacterLoadEventHandler(IServerManager serverManager, IGameLanguageService language, SerializableGameServer gameServer, IBuffFactory buffFactory, ISessionManager sessionManager,
|
|
ICharacterAlgorithm characterAlgorithm, IMinilandManager minilandManager, IMessagePublisher<PlayerConnectedOnChannelMessage> messagePublisher, IRevivalManager revivalManager,
|
|
IQuestManager questManager, FamilyConfiguration familyConfiguration, IServerApiService serverApiService, IRandomGenerator randomGenerator, IMapManager mapManager,
|
|
IReputationConfiguration reputationConfiguration, IItemsManager itemsManager, ISubActConfiguration subActConfiguration, IRankingManager rankingManager,
|
|
IExpirableLockService @lock, ITimeSpaceConfiguration timeSpaceConfiguration, ISessionService sessionService, IGameItemInstanceFactory itemInstanceFactory,
|
|
ICharacterService characterService, IPlayerEntityFactory playerEntityFactory, IMateEntityFactory mateEntityFactory, IFamilyManager familyManager,
|
|
IBCardEffectHandlerContainer bcardHandlerContainer, IQuestFactory questFactory, IAccountService accountService)
|
|
{
|
|
_serverManager = serverManager;
|
|
_language = language;
|
|
_gameServer = gameServer;
|
|
_buffFactory = buffFactory;
|
|
_sessionManager = sessionManager;
|
|
_characterAlgorithm = characterAlgorithm;
|
|
_minilandManager = minilandManager;
|
|
_messagePublisher = messagePublisher;
|
|
_revivalManager = revivalManager;
|
|
_questManager = questManager;
|
|
_familyConfiguration = familyConfiguration;
|
|
_serverApiService = serverApiService;
|
|
_randomGenerator = randomGenerator;
|
|
_mapManager = mapManager;
|
|
_reputationConfiguration = reputationConfiguration;
|
|
_itemsManager = itemsManager;
|
|
_subActConfiguration = subActConfiguration;
|
|
_rankingManager = rankingManager;
|
|
_lock = @lock;
|
|
_timeSpaceConfiguration = timeSpaceConfiguration;
|
|
_sessionService = sessionService;
|
|
_itemInstanceFactory = itemInstanceFactory;
|
|
_characterService = characterService;
|
|
_playerEntityFactory = playerEntityFactory;
|
|
_mateEntityFactory = mateEntityFactory;
|
|
_familyManager = familyManager;
|
|
_bcardHandlerContainer = bcardHandlerContainer;
|
|
_questFactory = questFactory;
|
|
_accountService = accountService;
|
|
}
|
|
|
|
public async Task HandleAsync(CharacterLoadEvent e, CancellationToken cancellation)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
IClientSession session = e.Sender;
|
|
|
|
byte? characterSlot = session.SelectedCharacterSlot;
|
|
|
|
if (characterSlot is null)
|
|
{
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
if (session.HasCurrentMapInstance || session.HasSelectedCharacter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
session.SelectedCharacterSlot = null;
|
|
DbServerGetCharacterResponse response = await _characterService.GetCharacterBySlot(new DbServerGetCharacterFromSlotRequest
|
|
{
|
|
AccountId = session.Account.Id,
|
|
Slot = characterSlot.Value
|
|
});
|
|
|
|
if (response is not { RpcResponseType: RpcResponseType.SUCCESS } || response.CharacterDto is null)
|
|
{
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
SessionResponse sessionResponse = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest { AccountId = session.Account.Id });
|
|
if (sessionResponse is not { ResponseType: RpcResponseType.SUCCESS })
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
|
|
$"[POTENTIAL_DUPE][LOAD] sessionResponse not success => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
Session storedSession = sessionResponse.Session;
|
|
if (storedSession.ChannelId != _serverManager.ChannelId)
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
|
|
$"[POTENTIAL_DUPE][LOAD] Wrong session ChannelId => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
if (storedSession.EncryptionKey != session.SessionId)
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
|
|
$"[POTENTIAL_DUPE][LOAD] Wrong encryption key => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
|
|
AccountPenaltyGetAllResponse penaltiesResponse = null;
|
|
try
|
|
{
|
|
penaltiesResponse = await _accountService.GetAccountPenalties(new AccountPenaltyGetRequest
|
|
{
|
|
AccountId = session.Account.Id
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("[CHARACTER_PRELOAD] Unexpected error: ", ex);
|
|
}
|
|
|
|
if (penaltiesResponse?.ResponseType != RpcResponseType.SUCCESS)
|
|
{
|
|
Log.Error("penaltiesResponse.AccountPenaltyDtos", new Exception());
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
session.Account.Logs = penaltiesResponse.AccountPenaltyDtos == null ? new List<AccountPenaltyDto>() : penaltiesResponse.AccountPenaltyDtos.ToList();
|
|
|
|
IPlayerEntity playerEntity = _playerEntityFactory.CreatePlayerEntity(response.CharacterDto);
|
|
playerEntity.GameStartDate = DateTime.UtcNow;
|
|
playerEntity.MapInstanceId = _mapManager.GetBaseMapInstanceIdByMapId(playerEntity.MapId);
|
|
IMapInstance currentMap = _mapManager.GetMapInstance(playerEntity.MapInstanceId);
|
|
|
|
if (currentMap != null && currentMap.IsBlockedZone(playerEntity.MapX, playerEntity.MapY))
|
|
{
|
|
Position pos = currentMap.GetRandomPosition();
|
|
playerEntity.ChangePosition(pos);
|
|
}
|
|
else
|
|
{
|
|
playerEntity.ChangePosition(new Position(playerEntity.MapX, playerEntity.MapY));
|
|
}
|
|
|
|
playerEntity.Authority = session.Account.Authority;
|
|
|
|
|
|
if (response.CharacterDto.ReturnPoint != null)
|
|
{
|
|
if (response.CharacterDto.ReturnPoint.MapId == 10000 && !session.IsGameMaster())
|
|
{
|
|
response.CharacterDto.ReturnPoint.MapId = 1;
|
|
response.CharacterDto.ReturnPoint.MapX = 1;
|
|
response.CharacterDto.ReturnPoint.MapY = 1;
|
|
}
|
|
}
|
|
|
|
playerEntity.HomeComponent.ChangeReturn(response.CharacterDto.ReturnPoint);
|
|
playerEntity.HomeComponent.ChangeAct5Respawn(response.CharacterDto.Act5RespawnType);
|
|
playerEntity.HomeComponent.ChangeRespawn(response.CharacterDto.RespawnType);
|
|
|
|
LoadQuicklist(playerEntity);
|
|
LoadInventory(playerEntity);
|
|
LoadSkills(playerEntity);
|
|
LoadScripts(playerEntity);
|
|
LoadQuests(playerEntity);
|
|
LoadTitleBCards(playerEntity);
|
|
|
|
sessionResponse = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest { AccountId = session.Account.Id });
|
|
if (sessionResponse is not { ResponseType: RpcResponseType.SUCCESS })
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
|
|
$"[POTENTIAL_DUPE][LOAD] 2nd check sessionResponse not success => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
storedSession = sessionResponse.Session;
|
|
if (storedSession.ChannelId != _serverManager.ChannelId)
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
|
|
$"[POTENTIAL_DUPE][LOAD] 2nd check Wrong session ChannelId => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
if (storedSession.EncryptionKey != session.SessionId)
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
|
|
$"[POTENTIAL_DUPE][LOAD] 2nd check Wrong encryption key => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
IClientSession tmp = _sessionManager.GetSessionByCharacterId(playerEntity.Id);
|
|
if (tmp != null)
|
|
{
|
|
await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] {playerEntity.Name} tried to connect twice on {_serverManager.ChannelId}!");
|
|
tmp.ForceDisconnect();
|
|
session.ForceDisconnect();
|
|
return;
|
|
}
|
|
|
|
// here everything starts
|
|
session.InitializePlayerEntity(playerEntity);
|
|
|
|
playerEntity.Miniland ??= _minilandManager.CreateMinilandByCharacterSession(session);
|
|
|
|
FamilyMembership familyMember = _familyManager.GetFamilyMembershipByCharacterId(session.PlayerEntity.Id);
|
|
playerEntity.SetFamilyMembership(familyMember);
|
|
|
|
foreach (MateDTO mateDto in session.PlayerEntity.NosMates)
|
|
{
|
|
IMateEntity mate = _mateEntityFactory.CreateMateEntity(session.PlayerEntity, mateDto);
|
|
|
|
await session.EmitEventAsync(new MateInitializeEvent
|
|
{
|
|
MateEntity = mate,
|
|
IsOnCharacterEnter = true
|
|
});
|
|
}
|
|
|
|
LoadPartnerWarehouse(playerEntity);
|
|
LoadPartnerInventory(playerEntity);
|
|
LoadPartnerEqBCards(playerEntity);
|
|
|
|
await session.EmitEventAsync(new MinigameRefreshProductionEvent(false));
|
|
await session.EmitEventAsync(new QuestDailyRefreshEvent { Force = false });
|
|
await session.EmitEventAsync(new ComplimentsMonthlyRefreshEvent { Force = false });
|
|
await session.EmitEventAsync(new RainbowBattleLeaverBusterRefreshEvent { Force = false });
|
|
await session.EmitEventAsync(new SpecialistRefreshEvent { Force = false });
|
|
await session.EmitEventAsync(new RaidResetRestrictionEvent());
|
|
|
|
session.CurrentMapInstance = session.PlayerEntity.MapInstance;
|
|
|
|
// need to improve this logic
|
|
if (await PreLoadLogicAndCheckChannelChange(session))
|
|
{
|
|
Log.Warn($"[CHARACTER_LOAD] {session.PlayerEntity.Name} changed to another channel");
|
|
return;
|
|
}
|
|
|
|
IFamily family = session.PlayerEntity.Family;
|
|
|
|
await ExecuteInitialMapJoin(session, family);
|
|
|
|
await TryToSendSomeWarningMessages(session);
|
|
|
|
// todo dump
|
|
_sessionManager.AddOnline(new ClusterCharacterInfo
|
|
{
|
|
ChannelId = (byte?)_gameServer.ChannelId,
|
|
Id = session.PlayerEntity.Id,
|
|
Name = session.PlayerEntity.Name,
|
|
Class = session.PlayerEntity.Class,
|
|
Gender = session.PlayerEntity.Gender,
|
|
HeroLevel = session.PlayerEntity.HeroLevel,
|
|
Level = session.PlayerEntity.Level,
|
|
HardwareId = session.HardwareId
|
|
});
|
|
|
|
if (family != null)
|
|
{
|
|
FamilyPacketExtensions.SendFamilyMembersInfoToMembers(family, _sessionManager, _familyConfiguration);
|
|
}
|
|
|
|
await _messagePublisher.PublishAsync(new PlayerConnectedOnChannelMessage
|
|
{
|
|
ChannelId = _gameServer.ChannelId,
|
|
CharacterId = session.PlayerEntity.Id,
|
|
CharacterName = session.PlayerEntity.Name,
|
|
Class = session.PlayerEntity.Class,
|
|
Gender = session.PlayerEntity.Gender,
|
|
HeroLevel = session.PlayerEntity.HeroLevel,
|
|
Level = session.PlayerEntity.Level,
|
|
FamilyId = family?.Id,
|
|
HardwareId = session.HardwareId
|
|
}, cancellation);
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
private async Task<bool> PreLoadLogicAndCheckChannelChange(IClientSession session)
|
|
{
|
|
await RefreshRevivalInfo(session);
|
|
|
|
await LoadBuffs(session);
|
|
|
|
if (session.CurrentMapInstance != null && !session.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (session.PlayerEntity.Faction == FactionType.Neutral)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GetChannelInfoResponse response = null;
|
|
try
|
|
{
|
|
response = await _serverApiService.GetAct4ChannelInfo(new GetAct4ChannelInfoRequest
|
|
{
|
|
WorldGroup = _serverManager.ServerGroup
|
|
});
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error("[CHARACTER_LOAD] Unexpected error while trying to obtain channel's Act4 information: ", e);
|
|
}
|
|
|
|
SerializableGameServer gameServer = response?.GameServer;
|
|
|
|
if (response?.ResponseType != RpcResponseType.SUCCESS || gameServer == null)
|
|
{
|
|
session.ChangeMap(1, 149, 60);
|
|
return false;
|
|
}
|
|
|
|
if (gameServer.ChannelId == _gameServer.ChannelId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
short mapId = session.PlayerEntity.Faction == FactionType.Angel ? (short)MapIds.ACT4_ANGEL_CITADEL : (short)MapIds.ACT4_DEMON_CITADEL;
|
|
short mapX = (short)(12 + _randomGenerator.RandomNumber(-2, 3));
|
|
short mapY = (short)(40 + _randomGenerator.RandomNumber(-2, 3));
|
|
|
|
if (_mapManager.HasMapFlagByMapId(session.PlayerEntity.MapId, MapFlags.ACT_4))
|
|
{
|
|
mapId = (short)session.PlayerEntity.MapId;
|
|
mapX = session.PlayerEntity.MapX;
|
|
mapY = session.PlayerEntity.MapY;
|
|
}
|
|
|
|
await session.EmitEventAsync(new PlayerChangeChannelEvent(gameServer, ItModeType.ToAct4, mapId, mapX, mapY));
|
|
return true;
|
|
}
|
|
|
|
private async Task LoadBuffs(IClientSession session)
|
|
{
|
|
var toAdd = new List<Buff>();
|
|
var toRemove = new List<CharacterStaticBuffDto>();
|
|
foreach (CharacterStaticBuffDto buff in session.PlayerEntity.StaticBuffs)
|
|
{
|
|
if (buff.RemainingTime <= 0)
|
|
{
|
|
toRemove.Add(buff);
|
|
continue;
|
|
}
|
|
|
|
toAdd.Add(_buffFactory.CreateBuff(buff.CardId, session.PlayerEntity, TimeSpan.FromMilliseconds(buff.RemainingTime), BuffFlag.BIG_AND_KEEP_ON_LOGOUT));
|
|
}
|
|
|
|
await session.PlayerEntity.AddBuffsAsync(toAdd);
|
|
foreach (CharacterStaticBuffDto removeBuff in toRemove)
|
|
{
|
|
session.PlayerEntity.StaticBuffs.Remove(removeBuff);
|
|
}
|
|
}
|
|
|
|
private async Task RefreshRevivalInfo(IClientSession session)
|
|
{
|
|
foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates())
|
|
{
|
|
_revivalManager.TryUnregisterRevival(mate.Id);
|
|
}
|
|
|
|
if (session.PlayerEntity.IsAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
await session.EmitEventAsync(new RevivalReviveEvent(RevivalType.DontPayRevival)
|
|
{
|
|
Forced = ForcedType.Reconnect
|
|
});
|
|
}
|
|
|
|
|
|
private async Task TryToSendSomeWarningMessages(IClientSession session)
|
|
{
|
|
await session.EmitEventAsync(new InventoryExpiredItemsEvent());
|
|
|
|
await session.EmitEventAsync(new CharacterBonusExpiredEvent());
|
|
|
|
if (session.PlayerEntity.HaveStaticBonus(StaticBonusType.BazaarMedalGold) || session.PlayerEntity.HaveStaticBonus(StaticBonusType.BazaarMedalSilver))
|
|
{
|
|
session.SendChatMessage(_language.GetLanguage(GameDialogKey.INFORMATION_CHATMESSAGE_ON_LOGIN_BAZAAR_MEDAL, session.UserLanguage), ChatMessageColorType.Green);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(session.PlayerEntity.Family?.Message))
|
|
{
|
|
session.SendInfo("--- Family Message ---\n" + session.PlayerEntity.Family.Message);
|
|
}
|
|
|
|
foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates())
|
|
{
|
|
session.SendCondMate(mate);
|
|
if (mate.IsTeamMember)
|
|
{
|
|
await session.EmitEventAsync(new MateJoinTeamEvent { MateEntity = mate, IsOnCharacterEnter = true });
|
|
continue;
|
|
}
|
|
|
|
await session.EmitEventAsync(new MateStayInsideMinilandEvent { MateEntity = mate, IsOnCharacterEnter = true });
|
|
}
|
|
|
|
if (session.PlayerEntity.HaveStaticBonus(StaticBonusType.PetBasket))
|
|
{
|
|
session.SendPetBasketPacket(true);
|
|
}
|
|
|
|
if (_gameServer.ChannelType == GameChannelType.ACT_4)
|
|
{
|
|
bool isLosingRep = await _lock.ExistsTemporaryLock($"game:locks:character:{session.PlayerEntity.Id}:act-4-less-rep");
|
|
if (isLosingRep)
|
|
{
|
|
session.PlayerEntity.IsGettingLosingReputation = true;
|
|
session.SendChatMessage(session.GetLanguage(GameDialogKey.ACT4_CHATMESSAGE_LESS_REPUTATION), ChatMessageColorType.Red);
|
|
}
|
|
}
|
|
|
|
AccountPenaltyDto mute = session.Account.Logs.FirstOrDefault(x => x.PenaltyType == PenaltyType.Muted && x.RemainingTime.HasValue);
|
|
if (mute == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DateTime now = DateTime.UtcNow;
|
|
session.PlayerEntity.MuteRemainingTime = TimeSpan.FromSeconds(mute.RemainingTime ?? 0);
|
|
session.PlayerEntity.LastChatMuteMessage = DateTime.MinValue;
|
|
session.PlayerEntity.LastMuteTick = now;
|
|
}
|
|
|
|
private async Task ExecuteInitialMapJoin(IClientSession session, IFamily family)
|
|
{
|
|
session.RefreshSkillList();
|
|
|
|
session.SendRsfiPacket(_subActConfiguration, _timeSpaceConfiguration);
|
|
|
|
IReadOnlyList<CharacterDTO> topReputation = _rankingManager.TopReputation;
|
|
session.RefreshReputation(_reputationConfiguration, topReputation);
|
|
session.RefreshPassiveBCards();
|
|
session.SendTitlePacket();
|
|
session.SendIncreaseRange();
|
|
|
|
session.ShowInventoryExtensions();
|
|
session.SendStaticBonuses();
|
|
session.SendMinilandPrivateInformation(_minilandManager, _language);
|
|
|
|
session.SendTitlePacket();
|
|
|
|
session.SendZzimPacket();
|
|
session.SendTwkPacket();
|
|
|
|
session.RefreshFaction();
|
|
session.SendMessageUnderChat();
|
|
|
|
session.SendScpStcPacket();
|
|
session.SendScpPackets();
|
|
session.SendScnPackets();
|
|
|
|
session.SendStartStartupInventory();
|
|
|
|
session.RefreshGold();
|
|
|
|
session.SendClinitPacket(_rankingManager.TopCompliment);
|
|
session.SendFlinitPacket(topReputation);
|
|
session.SendKdlinitPacket(_rankingManager.TopPoints);
|
|
|
|
session.SendPacket("skyinit 0");
|
|
session.SendPacket("skyinit 1");
|
|
session.SendPacket("skyinit 2");
|
|
session.SendPacket("skyinit 3");
|
|
session.SendPacket("tbf 1.0 2.0 3.0 4.0 5.0");
|
|
|
|
session.SendScpPackets();
|
|
session.SendScnPackets();
|
|
|
|
session.RefreshSpPoint();
|
|
session.SendPacket("rage 0 250000");
|
|
session.SendPacket("rank_cool 0 0 18000");
|
|
|
|
bool changeMap = false;
|
|
if (family != null)
|
|
{
|
|
if ((byte)session.PlayerEntity.Faction != family.Faction)
|
|
{
|
|
await session.EmitEventAsync(new ChangeFactionEvent
|
|
{
|
|
NewFaction = family.Faction == (byte)FactionType.Angel ? FactionType.Angel : FactionType.Demon
|
|
});
|
|
|
|
if (session.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4))
|
|
{
|
|
changeMap = true;
|
|
}
|
|
}
|
|
|
|
session.BroadcastGidx(family, _language);
|
|
session.RefreshFamilyInfo(family, _familyConfiguration);
|
|
session.SendFamilyLogsToMember(family);
|
|
session.RefreshFamilyMembers(_sessionManager, family);
|
|
session.RefreshFamilyMembersMessages(family);
|
|
session.RefreshFamilyMembersExp(family);
|
|
session.SendFmiPacket();
|
|
session.SendFmpPacket(_itemsManager);
|
|
}
|
|
|
|
session.RefreshQuicklist();
|
|
|
|
session.ChangeToLastBaseMap();
|
|
|
|
session.SendPacket("scr 0 0 0 0 0 0");
|
|
|
|
await ExecuteQuestLogic(session);
|
|
|
|
if (!changeMap)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await session.Respawn();
|
|
}
|
|
|
|
private async Task ExecuteQuestLogic(IClientSession session)
|
|
{
|
|
CompletedScriptsDto lastCompletedScriptDto = session.PlayerEntity.GetLastCompletedScript();
|
|
if (lastCompletedScriptDto == null)
|
|
{
|
|
session.SendScriptPacket(1, 10);
|
|
return;
|
|
}
|
|
|
|
|
|
TutorialDto lastCompletedTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedScriptDto.ScriptId, lastCompletedScriptDto.ScriptIndex);
|
|
|
|
|
|
if (lastCompletedTutorial == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TutorialDto nextScript = _questManager.GetScriptTutorialById(lastCompletedTutorial.Id + 1);
|
|
|
|
// We check if it was the last script from that sub-questline, for sending a QnpcPacket instead of a new script
|
|
if (lastCompletedTutorial.ScriptIndex == _questManager.GetScriptsTutorialByScriptId(lastCompletedTutorial.ScriptId).Max(s => s.ScriptIndex))
|
|
{
|
|
QuestNpcDto questNpc = _questManager.GetQuestNpcByScriptId(lastCompletedTutorial.ScriptId + 1);
|
|
session.SendQnpcPacket(questNpc.Level, questNpc.NpcVnum, questNpc.MapId);
|
|
await CheckCompletedQuests(session);
|
|
return;
|
|
}
|
|
|
|
// We check if we have an active quest-script
|
|
CompletedScriptsDto lastCompletedQuestStartScript = session.PlayerEntity.GetLastCompletedScriptByType(TutorialActionType.START_QUEST);
|
|
if (lastCompletedQuestStartScript != null)
|
|
{
|
|
TutorialDto lastCompletedQuestStartTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedQuestStartScript.ScriptId, lastCompletedQuestStartScript.ScriptIndex);
|
|
TutorialDto waitQuestCompletionScript = _questManager.GetScriptsTutorialByType(TutorialActionType.WAIT_FOR_QUEST_COMPLETION)
|
|
.First(s => s.Data == lastCompletedQuestStartTutorial.Data && s.Id > lastCompletedQuestStartTutorial.Id);
|
|
if (!session.PlayerEntity.HasCompletedScriptByIndex(waitQuestCompletionScript.ScriptId, waitQuestCompletionScript.ScriptIndex))
|
|
{
|
|
if (!session.PlayerEntity.HasQuestWithId(lastCompletedQuestStartTutorial.Data))
|
|
{
|
|
session.PlayerEntity.RemoveCompletedScript(lastCompletedQuestStartTutorial.ScriptId, lastCompletedQuestStartTutorial.ScriptIndex);
|
|
var completedScriptsToRemove = session.PlayerEntity.GetCompletedScripts().Where(s => s.ScriptId > lastCompletedQuestStartTutorial.ScriptId ||
|
|
s.ScriptId == lastCompletedQuestStartTutorial.ScriptId && s.ScriptIndex > lastCompletedQuestStartTutorial.ScriptIndex).ToList();
|
|
foreach (CompletedScriptsDto completedScript in completedScriptsToRemove)
|
|
{
|
|
session.PlayerEntity.RemoveCompletedScript(completedScript.ScriptId, completedScript.ScriptIndex);
|
|
}
|
|
|
|
nextScript = lastCompletedQuestStartTutorial;
|
|
}
|
|
else
|
|
{
|
|
nextScript = waitQuestCompletionScript;
|
|
}
|
|
}
|
|
}
|
|
|
|
session.SendScriptPacket(nextScript.ScriptId, nextScript.ScriptIndex);
|
|
await CheckCompletedQuests(session);
|
|
}
|
|
|
|
private async Task CheckCompletedQuests(IClientSession session)
|
|
{
|
|
foreach (CharacterQuest characterQuest in session.PlayerEntity.GetCurrentQuests())
|
|
{
|
|
if (session.PlayerEntity.IsQuestCompleted(characterQuest))
|
|
{
|
|
await session.EmitEventAsync(new QuestCompletedEvent(characterQuest, true));
|
|
}
|
|
}
|
|
|
|
session.RefreshQuestList(_questManager, null);
|
|
session.SendQuestsTargets();
|
|
session.SendSqstPackets(_questManager);
|
|
}
|
|
|
|
private void LoadSkills(IPlayerEntity session)
|
|
{
|
|
foreach (CharacterSkillDTO skillDto in session.LearnedSkills)
|
|
{
|
|
if (session.CharacterSkills.ContainsKey(skillDto.SkillVNum))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var skill = new CharacterSkill
|
|
{
|
|
SkillVNum = skillDto.SkillVNum
|
|
};
|
|
|
|
session.CharacterSkills[skill.SkillVNum] = skill;
|
|
session.Skills.Add(skill);
|
|
|
|
short upgradeSkill = skill.Skill?.UpgradeSkill ?? 0;
|
|
|
|
if (upgradeSkill == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!session.SkillComponent.SkillUpgrades.TryGetValue(upgradeSkill, out HashSet<IBattleEntitySkill> hashSet))
|
|
{
|
|
hashSet = new HashSet<IBattleEntitySkill>();
|
|
session.SkillComponent.SkillUpgrades[upgradeSkill] = hashSet;
|
|
}
|
|
|
|
if (hashSet.Contains(skill))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
hashSet.Add(skill);
|
|
}
|
|
}
|
|
|
|
private void LoadScripts(IPlayerEntity session)
|
|
{
|
|
IEnumerable<CompletedScriptsDto> completedScriptsDtos = session.CompletedScripts;
|
|
foreach (CompletedScriptsDto completedScriptDto in completedScriptsDtos)
|
|
{
|
|
TutorialDto completedScript = _questManager.GetScriptTutorialByIndex(completedScriptDto.ScriptId, completedScriptDto.ScriptIndex);
|
|
|
|
if (completedScript == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
session.SaveScript(completedScript.ScriptId, completedScript.ScriptIndex, completedScript.Type, completedScriptDto.CompletedDate);
|
|
}
|
|
}
|
|
|
|
|
|
private void LoadQuests(IPlayerEntity session)
|
|
{
|
|
IEnumerable<CharacterQuestDto> characterQuests = session.ActiveQuests;
|
|
IEnumerable<CharacterQuestDto> completedPeriodicQuests = session.CompletedPeriodicQuests;
|
|
IEnumerable<CharacterQuestDto> completedQuests = session.CompletedQuests;
|
|
|
|
foreach (CharacterQuestDto characterQuestDto in completedPeriodicQuests)
|
|
{
|
|
CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType);
|
|
characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount;
|
|
session.AddCompletedPeriodicQuest(characterQuest);
|
|
}
|
|
|
|
// This can be removed when the wipe of the server happens.
|
|
// It's gonna contain only the MAIN quests, since we have a "backup" (the scripts), but
|
|
// with new characters will also contain GENERAL and SECONDARY ones
|
|
if (!completedQuests.Any())
|
|
{
|
|
foreach (CompletedScriptsDto completedScript in session.GetCompletedScriptsByType(TutorialActionType.START_QUEST))
|
|
{
|
|
TutorialDto script = _questManager.GetScriptTutorialByIndex(completedScript.ScriptId, completedScript.ScriptIndex);
|
|
|
|
if (script == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (characterQuests.Any(s => s.QuestId == script.Data)) // It's an active one
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, script.Data, QuestSlotType.MAIN);
|
|
session.AddCompletedQuest(characterQuest);
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
foreach (CharacterQuestDto characterQuestDto in completedQuests)
|
|
{
|
|
CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType);
|
|
characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount;
|
|
session.AddCompletedQuest(characterQuest);
|
|
}
|
|
}
|
|
|
|
foreach (CharacterQuestDto characterQuestDto in characterQuests)
|
|
{
|
|
CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType);
|
|
characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount;
|
|
session.AddActiveQuest(characterQuest);
|
|
}
|
|
}
|
|
|
|
private void LoadPartnerEqBCards(IPlayerEntity playerEntity)
|
|
{
|
|
foreach (IMateEntity mate in playerEntity.MateComponent.GetMates(x => x.MateType == MateType.Partner))
|
|
{
|
|
foreach (PartnerInventoryItem partnerInventory in playerEntity.PartnerGetEquippedItems(mate.PetSlot))
|
|
{
|
|
if (partnerInventory?.ItemInstance == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
mate.RefreshEquipmentValues(partnerInventory.ItemInstance, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadTitleBCards(IPlayerEntity playerEntity)
|
|
{
|
|
CharacterTitleDto equippedTitle = playerEntity.Titles.FirstOrDefault(x => x.IsEquipped);
|
|
if (equippedTitle == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
playerEntity.RefreshTitleBCards(_itemsManager, equippedTitle, _bcardHandlerContainer);
|
|
}
|
|
|
|
private void LoadPartnerInventory(IPlayerEntity playerEntity)
|
|
{
|
|
IEnumerable<CharacterPartnerInventoryItemDto> partnerItems = playerEntity.PartnerInventory;
|
|
foreach (CharacterPartnerInventoryItemDto partnerItem in partnerItems)
|
|
{
|
|
if (partnerItem == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(partnerItem.ItemInstance);
|
|
playerEntity.PartnerEquipItem(itemInstance, partnerItem.PartnerSlot);
|
|
}
|
|
}
|
|
|
|
private void LoadQuicklist(IPlayerEntity playerEntity)
|
|
{
|
|
List<CharacterQuicklistEntryDto> inventory = playerEntity.Quicklist;
|
|
foreach (CharacterQuicklistEntryDto item in inventory)
|
|
{
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
playerEntity.QuicklistComponent.AddQuicklist(item);
|
|
}
|
|
}
|
|
|
|
private void LoadInventory(IPlayerEntity playerEntity)
|
|
{
|
|
foreach (CharacterInventoryItemDto item in playerEntity.Inventory)
|
|
{
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CharacterInventoryItemDto inventoryItem = item;
|
|
|
|
GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(inventoryItem.ItemInstance);
|
|
var newItem = new InventoryItem
|
|
{
|
|
CharacterId = inventoryItem.CharacterId,
|
|
InventoryType = inventoryItem.InventoryType,
|
|
IsEquipped = inventoryItem.IsEquipped,
|
|
Slot = inventoryItem.Slot,
|
|
ItemInstance = itemInstance
|
|
};
|
|
|
|
if (newItem.InventoryType == InventoryType.EquippedItems && newItem.ItemInstance != null)
|
|
{
|
|
playerEntity.EquipItem(newItem, newItem.ItemInstance.GameItem.EquipmentSlot, true);
|
|
if (itemInstance.Type == ItemInstanceType.WearableInstance)
|
|
{
|
|
playerEntity.RefreshEquipmentValues(itemInstance);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
playerEntity.AddItemToInventory(newItem);
|
|
}
|
|
|
|
// equipped items
|
|
DateTime date = DateTime.UtcNow;
|
|
foreach (CharacterInventoryItemDto item in playerEntity.EquippedStuffs)
|
|
{
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(item.ItemInstance);
|
|
var newItem = new InventoryItem
|
|
{
|
|
CharacterId = item.CharacterId,
|
|
InventoryType = item.InventoryType,
|
|
IsEquipped = item.IsEquipped,
|
|
Slot = item.Slot,
|
|
ItemInstance = itemInstance
|
|
};
|
|
if (newItem.InventoryType != InventoryType.EquippedItems || newItem.ItemInstance == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (newItem.ItemInstance.ItemDeleteTime <= date)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
playerEntity.EquipItem(newItem, newItem.ItemInstance.GameItem.EquipmentSlot, true);
|
|
if (itemInstance.Type == ItemInstanceType.WearableInstance)
|
|
{
|
|
playerEntity.RefreshEquipmentValues(itemInstance);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadPartnerWarehouse(IPlayerEntity playerEntity)
|
|
{
|
|
IEnumerable<PartnerWarehouseItemDto> items = playerEntity.PartnerWarehouse;
|
|
foreach (PartnerWarehouseItemDto item in items)
|
|
{
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
playerEntity.AddPartnerWarehouseItem(_itemInstanceFactory.CreateItem(item.ItemInstance), item.Slot);
|
|
}
|
|
}
|
|
} |