ace/Source/ACE.Server/Network/Handlers/CharacterHandler.cs
Ty Conner ab54cf9002
Add support for client DAT patching from server (#3979)
* Update DatFile.cs

* Update DatFileType.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Create DDDManager.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Create GameMessageDDDBeginDDD.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Create GameMessageDDDDataMessage.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Update DDDHandler.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Update WorldManager.cs

* Update Program.cs

* committing changes provided

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* more wip changes

* Update DDDManager.cs

* Update DDDManager.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Update CMostlyConsecutiveIntSet.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Update WorldManager.cs

* Update DDDHandler.cs

* Update Session.cs

* Update DDDManager.cs

* Update Session.cs

* Update DDDManager.cs

* Update DDDManager.cs

* Update GameMessageDDDDataMessage.cs

* Update DDDHandler.cs

* Update Session.cs

* Update CAllIterationList.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Update CMostlyConsecutiveIntSet.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Create PTaggedIterationList.cs

Co-Authored-By: OptimShi <OptimShi@users.noreply.github.com>

* Update Session.cs

* Update ConnectionListener.cs

* Create DDDConfiguration.cs

* Update MasterConfiguration.cs

* Update Config.js.example

* Update DDDManager.cs

* Update DDDHandler.cs

* Update Program.cs

* Update PropertyManager.cs

* Update WorldManager.cs

* Update SessionTerminationReason.cs

* Update DDDHandler.cs

* Update DDDManager.cs

* Update ConnectionListener.cs

* Update SessionTerminationReason.cs

* Update GameMessageDDDDataMessage.cs

* Update AuthenticationHandler.cs

* Update CharacterHandler.cs

* Update PacketInboundLoginRequest.cs

* Update Session.cs

* Update DatFileType.cs

---------

Co-authored-by: OptimShi <OptimShi@users.noreply.github.com>
2023-06-09 13:15:10 -04:00

387 lines
16 KiB
C#

using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using log4net;
using ACE.Common;
using ACE.Common.Extensions;
using ACE.Database;
using ACE.DatLoader;
using ACE.Entity;
using ACE.Entity.Enum;
using ACE.Entity.Models;
using ACE.Server.Factories;
using ACE.Server.Managers;
using ACE.Server.Network.Enum;
using ACE.Server.Network.GameMessages;
using ACE.Server.Network.GameMessages.Messages;
namespace ACE.Server.Network.Handlers
{
public static class CharacterHandler
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
[GameMessage(GameMessageOpcode.CharacterCreate, SessionState.AuthConnected)]
public static void CharacterCreate(ClientMessage message, Session session)
{
string clientString = message.Payload.ReadString16L();
if (clientString != session.Account)
return;
if (ServerManager.ShutdownInProgress)
{
session.SendCharacterError(CharacterError.LogonServerFull);
return;
}
if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Open || session.AccessLevel > AccessLevel.Player)
CharacterCreateEx(message, session);
else
session.SendCharacterError(CharacterError.LogonServerFull);
}
private static void CharacterCreateEx(ClientMessage message, Session session)
{
var characterCreateInfo = new CharacterCreateInfo();
characterCreateInfo.Unpack(message.Payload);
if (PropertyManager.GetBool("taboo_table").Item && DatManager.PortalDat.TabooTable.ContainsBadWord(characterCreateInfo.Name.ToLowerInvariant()))
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameBanned);
return;
}
if (PropertyManager.GetBool("creature_name_check").Item && DatabaseManager.World.IsCreatureNameInWorldDatabase(characterCreateInfo.Name))
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameBanned);
return;
}
DatabaseManager.Shard.IsCharacterNameAvailable(characterCreateInfo.Name, isAvailable =>
{
if (!isAvailable)
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse);
return;
}
});
if ((characterCreateInfo.Heritage == HeritageGroup.Olthoi || characterCreateInfo.Heritage == HeritageGroup.OlthoiAcid) && PropertyManager.GetBool("olthoi_play_disabled").Item)
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Pending);
return;
}
Weenie weenie;
if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions)
{
if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin)
weenie = DatabaseManager.World.GetCachedWeenie("admin");
else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy)
weenie = DatabaseManager.World.GetCachedWeenie("sentinel");
else
weenie = DatabaseManager.World.GetCachedWeenie("human");
if (characterCreateInfo.Heritage == HeritageGroup.Olthoi && weenie.WeenieType == WeenieType.Admin)
weenie = DatabaseManager.World.GetCachedWeenie("olthoiadmin");
if (characterCreateInfo.Heritage == HeritageGroup.OlthoiAcid && weenie.WeenieType == WeenieType.Admin)
weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidadmin");
}
else
weenie = DatabaseManager.World.GetCachedWeenie("human");
if (characterCreateInfo.Heritage == HeritageGroup.Olthoi && weenie.WeenieType == WeenieType.Creature)
weenie = DatabaseManager.World.GetCachedWeenie("olthoiplayer");
if (characterCreateInfo.Heritage == HeritageGroup.OlthoiAcid && weenie.WeenieType == WeenieType.Creature)
weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidplayer");
if (characterCreateInfo.IsSentinel && session.AccessLevel >= AccessLevel.Sentinel)
weenie = DatabaseManager.World.GetCachedWeenie("sentinel");
if (characterCreateInfo.IsAdmin && session.AccessLevel >= AccessLevel.Developer)
weenie = DatabaseManager.World.GetCachedWeenie("admin");
if (weenie == null)
weenie = DatabaseManager.World.GetCachedWeenie("human"); // Default catch-all
if (weenie == null) // If it is STILL null after the above catchall, the database is missing critical data and cannot continue with character creation.
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.DatabaseDown);
log.Error("Database does not contain the weenie for human (1). Characters cannot be created until the missing weenie is restored.");
return;
}
var guid = GuidManager.NewPlayerGuid();
var weenieType = weenie.WeenieType;
// If Database didn't have Sentinel/Admin weenies, alter the weenietype coming in.
if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions)
{
if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin && weenieType != WeenieType.Admin)
weenieType = WeenieType.Admin;
else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy && weenieType != WeenieType.Sentinel)
weenieType = WeenieType.Sentinel;
}
var result = PlayerFactory.Create(characterCreateInfo, weenie, guid, session.AccountId, weenieType, out var player);
if (result != PlayerFactory.CreateResult.Success || player == null)
{
if (result == PlayerFactory.CreateResult.ClientServerSkillsMismatch)
{
session.Terminate(SessionTerminationReason.ClientVersionIncorrect, new GameMessageBootAccount(" because your client is not the correct version for this server. Please visit http://play.emu.ac/ to update to latest client"));
return;
}
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Corrupt);
return;
}
DatabaseManager.Shard.IsCharacterNameAvailable(characterCreateInfo.Name, isAvailable =>
{
if (!isAvailable)
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse);
return;
}
var possessions = player.GetAllPossessions();
var possessedBiotas = new Collection<(Biota biota, ReaderWriterLockSlim rwLock)>();
foreach (var possession in possessions)
possessedBiotas.Add((possession.Biota, possession.BiotaDatabaseLock));
// We must await here --
DatabaseManager.Shard.AddCharacterInParallel(player.Biota, player.BiotaDatabaseLock, possessedBiotas, player.Character, player.CharacterDatabaseLock, saveSuccess =>
{
if (!saveSuccess)
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.DatabaseDown);
return;
}
PlayerManager.AddOfflinePlayer(player);
session.Characters.Add(player.Character);
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Ok, player.Guid, characterCreateInfo.Name);
});
});
}
private static void SendCharacterCreateResponse(Session session, CharacterGenerationVerificationResponse response, ObjectGuid guid = default(ObjectGuid), string charName = "")
{
session.Network.EnqueueSend(new GameMessageCharacterCreateResponse(response, guid, charName));
}
[GameMessage(GameMessageOpcode.CharacterEnterWorldRequest, SessionState.AuthConnected)]
public static void CharacterEnterWorldRequest(ClientMessage message, Session session)
{
if (ServerManager.ShutdownInProgress)
{
session.SendCharacterError(CharacterError.LogonServerFull);
return;
}
if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Open || session.AccessLevel > AccessLevel.Player)
session.Network.EnqueueSend(new GameMessageCharacterEnterWorldServerReady());
else
session.SendCharacterError(CharacterError.LogonServerFull);
}
[GameMessage(GameMessageOpcode.CharacterEnterWorld, SessionState.AuthConnected)]
public static void CharacterEnterWorld(ClientMessage message, Session session)
{
var guid = message.Payload.ReadUInt32();
string clientString = message.Payload.ReadString16L();
if (ServerManager.ShutdownInProgress)
{
session.SendCharacterError(CharacterError.LogonServerFull);
return;
}
if (clientString != session.Account)
{
session.SendCharacterError(CharacterError.EnterGameCharacterNotOwned);
return;
}
var character = session.Characters.SingleOrDefault(c => c.Id == guid);
if (character == null)
{
session.SendCharacterError(CharacterError.EnterGameCharacterNotOwned);
return;
}
if (character.IsDeleted || character.DeleteTime > 0)
{
session.SendCharacterError(CharacterError.EnterGameCharacterNotOwned);
return;
}
if (PlayerManager.GetOnlinePlayer(guid) != null)
{
// If this happens, it could be that the previous session for this Player terminated in a way that didn't transfer the player to offline via PlayerManager properly.
session.SendCharacterError(CharacterError.EnterGameCharacterInWorld);
return;
}
var offlinePlayer = PlayerManager.GetOfflinePlayer(guid);
if (offlinePlayer == null)
{
// This would likely only happen if the account tried to log in a character that didn't exist.
session.SendCharacterError(CharacterError.EnterGameGeneric);
return;
}
if (offlinePlayer.IsDeleted || offlinePlayer.IsPendingDeletion)
{
session.SendCharacterError(CharacterError.EnterGameCharacterNotOwned);
return;
}
if ((offlinePlayer.Heritage == (int)HeritageGroup.Olthoi || offlinePlayer.Heritage == (int)HeritageGroup.OlthoiAcid) && PropertyManager.GetBool("olthoi_play_disabled").Item)
{
session.SendCharacterError(CharacterError.EnterGameCouldntPlaceCharacter);
return;
}
session.InitSessionForWorldLogin();
session.State = SessionState.WorldConnected;
WorldManager.PlayerEnterWorld(session, character);
}
[GameMessage(GameMessageOpcode.CharacterLogOff, SessionState.WorldConnected)]
public static void CharacterLogOff(ClientMessage message, Session session)
{
session.LogOffPlayer();
}
[GameMessage(GameMessageOpcode.CharacterDelete, SessionState.AuthConnected)]
public static void CharacterDelete(ClientMessage message, Session session)
{
string clientString = message.Payload.ReadString16L();
uint characterSlot = message.Payload.ReadUInt32();
if (ServerManager.ShutdownInProgress)
{
session.SendCharacterError(CharacterError.Delete);
return;
}
if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Closed && session.AccessLevel < AccessLevel.Advocate)
{
session.SendCharacterError(CharacterError.LogonServerFull);
return;
}
if (clientString != session.Account)
{
session.SendCharacterError(CharacterError.Delete);
return;
}
var character = session.Characters[(int)characterSlot];
if (character == null)
{
session.SendCharacterError(CharacterError.Delete);
return;
}
var offlinePlayer = PlayerManager.GetOfflinePlayer(session.Characters[(int)characterSlot].Id);
if (offlinePlayer == null || offlinePlayer.IsDeleted || offlinePlayer.IsPendingDeletion)
{
session.SendCharacterError(CharacterError.Delete);
return;
}
session.Network.EnqueueSend(new GameMessageCharacterDelete());
var charRestoreTime = PropertyManager.GetLong("char_delete_time", 3600).Item;
character.DeleteTime = (ulong)(Time.GetUnixTime() + charRestoreTime);
character.IsDeleted = false;
DatabaseManager.Shard.SaveCharacter(character, new ReaderWriterLockSlim(), result =>
{
if (result)
{
session.Network.EnqueueSend(new GameMessageCharacterList(session.Characters, session));
PlayerManager.HandlePlayerDelete(character.Id);
}
else
session.SendCharacterError(CharacterError.Delete);
});
}
[GameMessage(GameMessageOpcode.CharacterRestore, SessionState.AuthConnected)]
public static void CharacterRestore(ClientMessage message, Session session)
{
var guid = message.Payload.ReadUInt32();
if (ServerManager.ShutdownInProgress)
{
session.SendCharacterError(CharacterError.EnterGameCouldntPlaceCharacter);
return;
}
if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Closed && session.AccessLevel < AccessLevel.Advocate)
{
session.SendCharacterError(CharacterError.LogonServerFull);
return;
}
var character = session.Characters.SingleOrDefault(c => c.Id == guid);
if (character == null)
return;
if (Time.GetUnixTime() > character.DeleteTime || character.IsDeleted)
{
session.SendCharacterError(CharacterError.EnterGameCharacterNotOwned);
return;
}
DatabaseManager.Shard.IsCharacterNameAvailable(character.Name, isAvailable =>
{
if (!isAvailable)
{
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse);
}
else
{
character.DeleteTime = 0;
character.IsDeleted = false;
DatabaseManager.Shard.SaveCharacter(character, new ReaderWriterLockSlim(), result =>
{
var name = character.Name;
if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions && session.AccessLevel > AccessLevel.Advocate)
name = "+" + name;
else if (!ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions && character.IsPlussed)
name = "+" + name;
if (result)
session.Network.EnqueueSend(new GameMessageCharacterRestore(guid, name, 0u));
else
SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Corrupt);
});
}
});
}
}
}