mirror of
https://github.com/ACEmulator/ACE
synced 2026-08-17 12:26:06 -04:00
473 lines
18 KiB
C#
473 lines
18 KiB
C#
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
|
|
using ACE.Database;
|
|
using ACE.Entity;
|
|
using ACE.Entity.Enum;
|
|
using ACE.Entity.Models;
|
|
using ACE.Server.Managers;
|
|
using ACE.Server.Entity.Actions;
|
|
using ACE.Server.Network;
|
|
using ACE.Server.Network.GameEvent.Events;
|
|
using ACE.Server.Network.GameMessages.Messages;
|
|
|
|
namespace ACE.Server.WorldObjects
|
|
{
|
|
partial class Player
|
|
{
|
|
public HashSet<ObjectGuid> ItemsInTradeWindow = new HashSet<ObjectGuid>();
|
|
|
|
public ObjectGuid TradePartner;
|
|
|
|
public bool IsTrading { get; private set; }
|
|
|
|
private bool TradeAccepted;
|
|
|
|
public bool TradeTransferInProgress;
|
|
|
|
public void HandleActionOpenTradeNegotiations(uint tradePartnerGuid, bool initiator = false)
|
|
{
|
|
if (IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameMessageSystemChat($"As a mindless engine of destruction an Olthoi cannot participate in trade negotiations!", ChatMessageType.Magic));
|
|
return;
|
|
}
|
|
|
|
var tradePartner = PlayerManager.GetOnlinePlayer(tradePartnerGuid);
|
|
if (tradePartner == null) return;
|
|
|
|
//Check to see if potential trading partner is an Olthoi player
|
|
if (initiator && tradePartner.IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameMessageSystemChat($"The Olthoi's hunger for destruction is too great to understand a request for trade negotiations!", ChatMessageType.Broadcast));
|
|
return;
|
|
}
|
|
|
|
//Check to see if partner is not allowing trades
|
|
if (initiator && tradePartner.GetCharacterOption(CharacterOption.IgnoreAllTradeRequests))
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeIgnoringRequests));
|
|
return;
|
|
}
|
|
|
|
//Check to see if either party is already part of an in process trade session
|
|
if (IsTrading || tradePartner.IsTrading)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeAlreadyTrading));
|
|
return;
|
|
}
|
|
|
|
//Check to see if either party is in combat mode
|
|
if (CombatMode != CombatMode.NonCombat || tradePartner.CombatMode != CombatMode.NonCombat)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeNonCombatMode));
|
|
return;
|
|
}
|
|
|
|
//Check to see if trade partner is in range, if so, rotate and move to
|
|
if (initiator)
|
|
{
|
|
CreateMoveToChain(tradePartner, (success) =>
|
|
{
|
|
if (!success)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeMaxDistanceExceeded));
|
|
return;
|
|
}
|
|
|
|
Session.Network.EnqueueSend(new GameEventRegisterTrade(Session, Guid, tradePartner.Guid));
|
|
|
|
tradePartner.HandleActionOpenTradeNegotiations(Guid.Full, false);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
IsTrading = true;
|
|
tradePartner.IsTrading = true;
|
|
TradeTransferInProgress = false;
|
|
tradePartner.TradeTransferInProgress = false;
|
|
|
|
TradePartner = tradePartner.Guid;
|
|
tradePartner.TradePartner = Guid;
|
|
|
|
ItemsInTradeWindow.Clear();
|
|
tradePartner.ItemsInTradeWindow.Clear();
|
|
|
|
Session.Network.EnqueueSend(new GameEventRegisterTrade(Session, tradePartner.Guid, tradePartner.Guid));
|
|
}
|
|
}
|
|
|
|
public void HandleActionCloseTradeNegotiations(EndTradeReason endTradeReason = EndTradeReason.Normal)
|
|
{
|
|
if (TradeTransferInProgress) return;
|
|
|
|
IsTrading = false;
|
|
TradeAccepted = false;
|
|
TradeTransferInProgress = false;
|
|
ItemsInTradeWindow.Clear();
|
|
TradePartner = ObjectGuid.Invalid;
|
|
|
|
Session.Network.EnqueueSend(new GameEventCloseTrade(Session, endTradeReason));
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeClosed));
|
|
}
|
|
|
|
public void HandleActionAddToTrade(uint itemGuid, uint tradeWindowSlotNumber)
|
|
{
|
|
if (TradeTransferInProgress)
|
|
return;
|
|
|
|
TradeAccepted = false;
|
|
|
|
var target = PlayerManager.GetOnlinePlayer(TradePartner);
|
|
|
|
if (target == null || itemGuid == 0)
|
|
return;
|
|
|
|
target.TradeAccepted = false;
|
|
|
|
WorldObject wo = GetInventoryItem(itemGuid);
|
|
|
|
if (wo == null)
|
|
{
|
|
wo = GetEquippedItem(itemGuid);
|
|
|
|
if (wo == null)
|
|
return;
|
|
}
|
|
|
|
if (wo.IsAttunedOrContainsAttuned)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, "You cannot trade that!"));
|
|
Session.Network.EnqueueSend(new GameEventTradeFailure(Session, itemGuid, WeenieError.AttunedItem));
|
|
return;
|
|
}
|
|
|
|
if (wo is PetDevice petDevice && petDevice.Pet is not null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, "You must unsummon your pet before you can trade this item!"));
|
|
Session.Network.EnqueueSend(new GameEventTradeFailure(Session, itemGuid, WeenieError.AttunedItem));
|
|
return;
|
|
}
|
|
|
|
if (wo.IsUniqueOrContainsUnique && !target.CheckUniques(wo, this))
|
|
{
|
|
// WeenieError.TooManyUniqueItems / WeenieErrorWithString._CannotCarryAnymore?
|
|
Session.Network.EnqueueSend(new GameEventTradeFailure(Session, itemGuid, WeenieError.None));
|
|
return;
|
|
}
|
|
|
|
ItemsInTradeWindow.Add(new ObjectGuid(itemGuid));
|
|
|
|
Session.Network.EnqueueSend(new GameEventAddToTrade(Session, itemGuid, TradeSide.Self));
|
|
|
|
target.AddKnownTradeObj(Guid, wo.Guid);
|
|
target.TrackObject(wo);
|
|
|
|
var actionChain = new ActionChain();
|
|
actionChain.AddDelaySeconds(0.001f);
|
|
actionChain.AddAction(target, () =>
|
|
{
|
|
target.Session.Network.EnqueueSend(new GameEventAddToTrade(target.Session, itemGuid, TradeSide.Partner));
|
|
});
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
public void HandleActionResetTrade(ObjectGuid whoReset)
|
|
{
|
|
if (TradeTransferInProgress)
|
|
return;
|
|
|
|
ItemsInTradeWindow.Clear();
|
|
TradeAccepted = false;
|
|
|
|
Session.Network.EnqueueSend(new GameEventResetTrade(Session, whoReset));
|
|
}
|
|
|
|
public void ClearTradeAcceptance()
|
|
{
|
|
ItemsInTradeWindow.Clear();
|
|
TradeAccepted = false;
|
|
|
|
Session.Network.EnqueueSend(new GameEventClearTradeAcceptance(Session));
|
|
}
|
|
|
|
public void HandleActionAcceptTrade()
|
|
{
|
|
if (TradeTransferInProgress)
|
|
return;
|
|
|
|
TradeAccepted = true;
|
|
|
|
Session.Network.EnqueueSend(new GameEventAcceptTrade(Session, Guid));
|
|
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, "You have accepted the offer"));
|
|
|
|
var target = PlayerManager.GetOnlinePlayer(TradePartner);
|
|
|
|
if (target == null)
|
|
return;
|
|
|
|
target.Session.Network.EnqueueSend(new GameEventAcceptTrade(target.Session, Guid));
|
|
target.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(target.Session, $"{Name} has accepted the offer"));
|
|
|
|
if (target.TradeAccepted)
|
|
FinalizeTrade(target);
|
|
}
|
|
|
|
private void FinalizeTrade(Player target)
|
|
{
|
|
if (!VerifyTrade_BusyState(target) || !VerifyTrade_Inventory(target))
|
|
return;
|
|
|
|
IsBusy = true;
|
|
target.IsBusy = true;
|
|
|
|
TradeTransferInProgress = true;
|
|
target.TradeTransferInProgress = true;
|
|
|
|
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, "The items are being traded"));
|
|
target.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(target.Session, "The items are being traded"));
|
|
|
|
var tradedItems = new Collection<(Biota biota, ReaderWriterLockSlim rwLock)>();
|
|
|
|
var myEscrow = new List<WorldObject>();
|
|
var targetEscrow = new List<WorldObject>();
|
|
|
|
foreach (ObjectGuid itemGuid in ItemsInTradeWindow)
|
|
{
|
|
if (TryRemoveFromInventoryWithNetworking(itemGuid, out var wo, RemoveFromInventoryAction.TradeItem) || TryDequipObjectWithNetworking(itemGuid, out wo, DequipObjectAction.TradeItem))
|
|
{
|
|
targetEscrow.Add(wo);
|
|
|
|
tradedItems.Add((wo.Biota, wo.BiotaDatabaseLock));
|
|
}
|
|
}
|
|
|
|
foreach (ObjectGuid itemGuid in target.ItemsInTradeWindow)
|
|
{
|
|
if (target.TryRemoveFromInventoryWithNetworking(itemGuid, out var wo, RemoveFromInventoryAction.TradeItem) || target.TryDequipObjectWithNetworking(itemGuid, out wo, DequipObjectAction.TradeItem))
|
|
{
|
|
myEscrow.Add(wo);
|
|
|
|
tradedItems.Add((wo.Biota, wo.BiotaDatabaseLock));
|
|
}
|
|
}
|
|
|
|
var actionChain = new ActionChain();
|
|
actionChain.AddDelaySeconds(0.5f);
|
|
actionChain.AddAction(CurrentLandblock, () =>
|
|
{
|
|
foreach (var wo in myEscrow)
|
|
TryCreateInInventoryWithNetworking(wo);
|
|
|
|
foreach (var wo in targetEscrow)
|
|
target.TryCreateInInventoryWithNetworking(wo);
|
|
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeComplete));
|
|
target.Session.Network.EnqueueSend(new GameEventWeenieError(target.Session, WeenieError.TradeComplete));
|
|
|
|
TradeTransferInProgress = false;
|
|
target.TradeTransferInProgress = false;
|
|
|
|
IsBusy = false;
|
|
target.IsBusy = false;
|
|
|
|
DatabaseManager.Shard.SaveBiotasInParallel(tradedItems, null);
|
|
|
|
HandleActionResetTrade(Guid);
|
|
target.HandleActionResetTrade(target.Guid);
|
|
});
|
|
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
private bool GetItemsInTradeWindow(Player player, out List<WorldObject> itemsToBeTraded)
|
|
{
|
|
itemsToBeTraded = null;
|
|
var results = new List<WorldObject>();
|
|
|
|
foreach (ObjectGuid itemGuid in player.ItemsInTradeWindow)
|
|
{
|
|
var wo = player.GetInventoryItem(itemGuid); // look in inventory for item
|
|
|
|
if (wo == null) // if item is equipped, it won't be found above, so if not found, look in equipped objects
|
|
wo = player.GetEquippedItem(itemGuid);
|
|
|
|
if (wo != null)
|
|
results.Add(wo);
|
|
else // item was not found in inventory or equipped
|
|
return false;
|
|
}
|
|
|
|
itemsToBeTraded = results;
|
|
return true;
|
|
}
|
|
|
|
public void HandleActionDeclineTrade(Session session)
|
|
{
|
|
if (session.Player.TradeTransferInProgress) return;
|
|
|
|
session.Player.TradeAccepted = false;
|
|
|
|
session.Network.EnqueueSend(new GameEventDeclineTrade(session,session.Player.Guid));
|
|
session.Network.EnqueueSend(new GameEventCommunicationTransientString(session, "Trade confirmation failed..."));
|
|
|
|
var target = PlayerManager.GetOnlinePlayer(session.Player.TradePartner);
|
|
|
|
if (target != null)
|
|
{
|
|
target.Session.Network.EnqueueSend(new GameEventDeclineTrade(target.Session, session.Player.Guid));
|
|
target.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(target.Session, "Trade confirmation failed..."));
|
|
}
|
|
}
|
|
|
|
public void HandleActionTradeSwitchToCombatMode(Session session)
|
|
{
|
|
if (session.Player.CombatMode != CombatMode.NonCombat && session.Player.IsTrading)
|
|
{
|
|
var target = PlayerManager.GetOnlinePlayer(session.Player.TradePartner);
|
|
|
|
session.Network.EnqueueSend(new GameEventWeenieError(session, WeenieError.TradeNonCombatMode));
|
|
session.Player.HandleActionCloseTradeNegotiations(EndTradeReason.EnteredCombat);
|
|
|
|
if (target != null)
|
|
{
|
|
target.Session.Network.EnqueueSend(new GameEventWeenieError(target.Session, WeenieError.TradeNonCombatMode));
|
|
target.HandleActionCloseTradeNegotiations(EndTradeReason.EnteredCombat);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool VerifyTrade_BusyState(Player partner)
|
|
{
|
|
if (!IsBusy && !partner.IsBusy)
|
|
return true;
|
|
|
|
var selfBusy = "You are too busy to complete the trade!";
|
|
var otherBusy = "Your trading partner is too busy to complete the trade!";
|
|
|
|
var selfMsg = IsBusy ? selfBusy : otherBusy;
|
|
var partnerMsg = IsBusy ? otherBusy : selfBusy;
|
|
|
|
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, selfMsg));
|
|
partner.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(partner.Session, partnerMsg));
|
|
|
|
ClearTradeAcceptance();
|
|
partner.ClearTradeAcceptance();
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool VerifyTrade_Inventory(Player partner)
|
|
{
|
|
var selfItemsVerified = GetItemsInTradeWindow(this, out var self_items);
|
|
var partnerItemsVerified = GetItemsInTradeWindow(partner, out var partner_items);
|
|
|
|
if (!selfItemsVerified)
|
|
{
|
|
HandleActionDeclineTrade(Session);
|
|
return false;
|
|
}
|
|
|
|
if (!partnerItemsVerified)
|
|
{
|
|
partner.HandleActionDeclineTrade(partner.Session);
|
|
return false;
|
|
}
|
|
|
|
var playerACanAddToInventory = CanAddToInventory(partner_items, out var selfEncumbered, out var selfPackSpace);
|
|
var playerBCanAddToInventory = partner.CanAddToInventory(self_items, out var partnerEncumbered, out var partnerPackSpace);
|
|
|
|
if (playerACanAddToInventory && playerBCanAddToInventory)
|
|
return true;
|
|
|
|
var selfReason = "";
|
|
var partnerReason = "";
|
|
|
|
if (!playerACanAddToInventory)
|
|
{
|
|
selfReason = "You ";
|
|
partnerReason = "Your trading partner ";
|
|
|
|
if (selfEncumbered)
|
|
{
|
|
selfReason += "are too encumbered to complete the trade!";
|
|
partnerReason += "is too encumbered to complete the trade!";
|
|
}
|
|
else if (selfPackSpace)
|
|
{
|
|
selfReason += "do not have enough free slots to complete the trade!";
|
|
partnerReason += "does not have enough free slots to complete the trade!";
|
|
}
|
|
}
|
|
else if (!playerBCanAddToInventory)
|
|
{
|
|
selfReason = "Your trading partner ";
|
|
partnerReason = "You ";
|
|
|
|
if (partnerEncumbered)
|
|
{
|
|
selfReason += "is too encumbered to complete the trade!";
|
|
partnerReason += "are too encumbered to complete the trade!";
|
|
}
|
|
else if (partnerPackSpace)
|
|
{
|
|
selfReason += "does not have enough free slots to complete the trade!";
|
|
partnerReason += "do not have enough free slots to complete the trade!";
|
|
}
|
|
}
|
|
|
|
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, selfReason));
|
|
partner.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(partner.Session, partnerReason));
|
|
|
|
ClearTradeAcceptance();
|
|
partner.ClearTradeAcceptance();
|
|
|
|
return false;
|
|
}
|
|
|
|
public Dictionary<ObjectGuid, HashSet<ObjectGuid>> KnownTradeObjs = new Dictionary<ObjectGuid, HashSet<ObjectGuid>>();
|
|
|
|
public void AddKnownTradeObj(ObjectGuid playerGuid, ObjectGuid itemGuid)
|
|
{
|
|
if (!KnownTradeObjs.TryGetValue(playerGuid, out var knownTradeItems))
|
|
{
|
|
knownTradeItems = new HashSet<ObjectGuid>();
|
|
KnownTradeObjs.Add(playerGuid, knownTradeItems);
|
|
}
|
|
knownTradeItems.Add(itemGuid);
|
|
}
|
|
|
|
public Player GetKnownTradeObj(ObjectGuid itemGuid)
|
|
{
|
|
if (KnownTradeObjs.Count() == 0)
|
|
return null;
|
|
|
|
PruneKnownTradeObjs();
|
|
|
|
foreach (var knownTradeObj in KnownTradeObjs)
|
|
{
|
|
if (knownTradeObj.Value.Contains(itemGuid))
|
|
{
|
|
var playerGuid = knownTradeObj.Key;
|
|
var player = ObjMaint.GetKnownObject(playerGuid.Full)?.WeenieObj?.WorldObject as Player;
|
|
if (player != null && player.Location != null && Location.DistanceTo(player.Location) <= LocalBroadcastRange)
|
|
return player;
|
|
else
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void PruneKnownTradeObjs()
|
|
{
|
|
foreach (var playerGuid in KnownTradeObjs.Keys.ToList())
|
|
{
|
|
if (ObjMaint.GetKnownObject(playerGuid.Full) == null)
|
|
KnownTradeObjs.Remove(playerGuid);
|
|
}
|
|
}
|
|
}
|
|
}
|