ace/Source/ACE.Server/WorldObjects/Managers/ContractManager.cs
gmriggs aa9bc542ba
fix some client memory leaks from incorrectly ordered hashtables on login (#3576)
* fix some client memory leaks with appraisals

* fix some client memory leaks from incorrectly ordered hashtables on login

* more organization for PackableHashTable vs. deprecated PHashTable

* fully deprecate PHashTable -- 0 references now

* refactor RestrictionDB.Write() to use PackableHashTable common method. trying to ensure all places in the system that send PackableHashTables go through common method, for consistency and tracing

* cleanup

* finding more references

* update HouseAccess to match client

* add full explanation where numBuckets=89 originates from

* update code for empty collection in AllegianceHierarchy for reference
2021-06-12 16:10:21 -04:00

273 lines
9.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using log4net;
using ACE.Database.Models.Shard;
using ACE.DatLoader;
using ACE.DatLoader.Entity;
using ACE.Entity.Enum;
using ACE.Server.Network.GameEvent.Events;
using ACE.Server.Network.GameMessages.Messages;
using ACE.Server.Network.Structure;
namespace ACE.Server.WorldObjects.Managers
{
public class ContractManager
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public Player Player { get; }
private const int MaxContracts = 100;
public Dictionary<uint, ContractTracker> ContractTrackerTable
{
get
{
var contractIds = Player.Character.GetContractsIds(Player.CharacterDatabaseLock);
return contractIds.ToDictionary(c => c, c => new ContractTracker(Player, c));
}
}
private readonly Dictionary<uint, HashSet<string>> MonitoredQuestFlags = new Dictionary<uint, HashSet<string>>();
public static bool Debug = false;
/// <summary>
/// Constructs a new ContractManager for a Player
/// </summary>
public ContractManager(Player player)
{
Player = player;
RefreshMonitoredQuestFlags();
}
private void RefreshMonitoredQuestFlags()
{
MonitoredQuestFlags.Clear();
var contracts = Player.Character.GetContracts(Player.CharacterDatabaseLock);
foreach (var contract in contracts)
{
var datContract = GetContractFromDat(contract.ContractId);
if (!MonitoredQuestFlags.ContainsKey(contract.ContractId))
MonitoredQuestFlags.Add(contract.ContractId, new HashSet<string>());
if (!string.IsNullOrWhiteSpace(datContract.QuestflagFinished))
MonitoredQuestFlags[datContract.ContractId].Add(datContract.QuestflagFinished.ToLower());
if (!string.IsNullOrWhiteSpace(datContract.QuestflagProgress))
MonitoredQuestFlags[datContract.ContractId].Add(datContract.QuestflagProgress.ToLower());
if (!string.IsNullOrWhiteSpace(datContract.QuestflagRepeatTime))
MonitoredQuestFlags[datContract.ContractId].Add(datContract.QuestflagRepeatTime.ToLower());
if (!string.IsNullOrWhiteSpace(datContract.QuestflagStamped))
MonitoredQuestFlags[datContract.ContractId].Add(datContract.QuestflagStamped.ToLower());
if (!string.IsNullOrWhiteSpace(datContract.QuestflagStarted))
MonitoredQuestFlags[datContract.ContractId].Add(datContract.QuestflagStarted.ToLower());
if (!string.IsNullOrWhiteSpace(datContract.QuestflagTimer))
MonitoredQuestFlags[datContract.ContractId].Add(datContract.QuestflagTimer.ToLower());
}
}
public ContractTracker GetContractTracker(uint contractId)
{
if (GetContract(contractId) != null)
return new ContractTracker(Player, contractId);
return null;
}
/// <summary>
/// Returns a contract from the portal dat file
/// </summary>
public Contract GetContractFromDat(uint contractId)
{
var contractTable = DatManager.PortalDat.ContractTable;
return contractTable.Contracts.TryGetValue(contractId, out var contractData) ? contractData : null;
}
/// <summary>
/// Returns an active or completed contract for this player
/// </summary>
public CharacterPropertiesContractRegistry GetContract(uint contractId)
{
return Player.Character.GetContract(contractId, Player.CharacterDatabaseLock);
}
/// <summary>
/// Returns TRUE if at max capacity for Contracts for this player
/// </summary>
public bool IsFull => Player.Character.GetContractsCount(Player.CharacterDatabaseLock) >= MaxContracts;
/// <summary>
/// Adds a new contract to the player's registry
/// </summary>
public bool Add(int contractId)
{
return Add(Convert.ToUInt32(contractId));
}
/// <summary>
/// Adds a new contract to the player's registry
/// </summary>
public bool Add(uint contractId)
{
var datContract = GetContractFromDat(contractId);
if (datContract == null)
{
if (Debug) Console.WriteLine($"{Player.Name}.ContractManager.Add({contractId}): Contract not found in DAT file.");
return false;
}
if (IsFull)
{
if (Player != null)
{
//Player.Session.Network.EnqueueSend(new GameEventWeenieError(Player.Session, WeenieError.ContractError));
//what happened here in retail?
Player.Session.Network.EnqueueSend(new GameMessageSystemChat($"You currently have the maximum amount of contracts for this character and cannot take on another! You must abandon at least one contract before you can accept the contract for {datContract.ContractName}.", ChatMessageType.Broadcast));
}
return false;
}
var contract = Player.Character.GetOrCreateContract(contractId, Player.CharacterDatabaseLock, out var contractWasCreated);
if (contractWasCreated)
{
// add new contract entry
contract.DeleteContract = false;
contract.SetAsDisplayContract = false;
if (Debug) Console.WriteLine($"{Player.Name}.ContractManager.Add({contractId}): added contract: {datContract.ContractName}");
Player.CharacterChangesDetected = true;
Player.Session.Network.EnqueueSend(new GameEventSendClientContractTracker(Player.Session, contract));
RefreshMonitoredQuestFlags();
}
else
{
if (Debug) Console.WriteLine($"{Player.Name}.ContractManager.Add({contractId}): contract for {datContract.ContractName} already exists in registry.");
// contracts dupes are also successful without actually duping into registry.
//return false;
}
return true;
}
/// <summary>
/// Returns TRUE if a player has a particular contract
/// </summary>
public bool HasContract(uint contractId)
{
var hasContract = GetContract(contractId) != null;
if (Debug)
Console.WriteLine($"{Player.Name}.HasContracts({contractId}): {hasContract}");
return hasContract;
}
/// <summary>
/// Abandon a contract in the Player's registry
/// </summary>
public void Abandon(uint contractId)
{
Erase(contractId);
}
/// <summary>
/// Erase a contract in the Player's registry
/// </summary>
public void Erase(uint contractId)
{
if (Debug)
Console.WriteLine($"{Player.Name}.ContractManager.Erase({contractId})");
Player.Character.EraseContract(contractId, out var contractErased, Player.CharacterDatabaseLock);
if (contractErased != null)
{
contractErased.DeleteContract = true;
Player.CharacterChangesDetected = true;
Player.Session.Network.EnqueueSend(new GameEventSendClientContractTracker(Player.Session, contractErased));
RefreshMonitoredQuestFlags();
}
}
/// <summary>
/// Erase all contracts in registry
/// </summary>
public void EraseAll()
{
if (Debug)
Console.WriteLine($"{Player.Name}.ContractManager.EraseAll");
Player.Character.EraseAllContracts(out var erasedContracts, Player.CharacterDatabaseLock);
foreach (var contract in erasedContracts)
{
contract.DeleteContract = true;
Player.CharacterChangesDetected = true;
Player.Session.Network.EnqueueSend(new GameEventSendClientContractTracker(Player.Session, contract));
}
RefreshMonitoredQuestFlags();
}
public void NotifyOfQuestUpdate(string questName)
{
foreach (var contracts in MonitoredQuestFlags)
{
if (contracts.Value.Contains(questName.ToLower()))
Update(contracts.Key);
}
}
private void Update(uint contractId)
{
if (Debug)
Console.WriteLine($"{Player.Name}.ContractManager.Update");
var contract = GetContract(contractId);
if (contract != null)
Player.Session.Network.EnqueueSend(new GameEventSendClientContractTracker(Player.Session, contract));
}
}
public static class ContractManagerExtensions
{
private static readonly HashComparer hashComparer = new HashComparer(32); // static table size from retail pcaps
public static void Write(this BinaryWriter writer, ContractManager contractManager)
{
writer.Write(contractManager.ContractTrackerTable);
}
public static void Write(this BinaryWriter writer, Dictionary<uint, ContractTracker> contractTable)
{
PackableHashTable.WriteHeader(writer, contractTable.Count, hashComparer.NumBuckets);
var contractTrackers = new SortedDictionary<uint, ContractTracker>(contractTable, hashComparer);
foreach (var contractTracker in contractTrackers)
{
writer.Write(contractTracker.Key);
writer.Write(contractTracker.Value);
}
}
}
}