Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Multiplayer/Components/IdMonoBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ public void Register(T id)
indexToObject[id] = this;
}

/// <summary>
/// Takes a fresh id from the pool. Used when an existing object becomes network
/// relevant after its Awake has already run (e.g. an inventory item dropped back
/// into the world by the host).
/// </summary>
public void AssignNewId()
{
Register(idPool.NextId);
}

protected virtual void OnDestroy()
{
idPool.ReleaseId(NetId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using DV.Interaction;
using DV.InventorySystem;
using DV.Shops;
using DV.Utils;
using Multiplayer.Networking.Data;
using Multiplayer.Networking.Managers.Server;
using Multiplayer.Networking.Packets.Common;
Expand Down Expand Up @@ -138,12 +139,23 @@ private void CullingManager_PlayerEnteredActivationRegion(ServerPlayer serverPla
}
}

//Scan modules can be metres from the register itself (shelves spread around the shop),
//so scans are validated against the module position rather than the register position.
private const float MAX_SCAN_DISTANCE_SQR = 10f * 10f;

public void Server_ProcessCashRegisterAction(ServerPlayer player, CommonCashRegisterWithModulesActionPacket packet)
{
bool success = false;
CashRegisterAction response = CashRegisterAction.RejectGeneric;

NetworkLifecycle.Instance.Server?.LogDebug(() => $"NetworkedCashRegisterWithModules.Server_ProcessAction({player.Username}, {packet.Action}, {packet.Amount})");

if (packet.Action == CashRegisterAction.ScanItem)
{
Server_ProcessScanItem(player, packet);
return;
}

if (transform.PlayerCanReach(player, 1))
{
processingAction = true;
Expand All @@ -169,6 +181,8 @@ public void Server_ProcessCashRegisterAction(ServerPlayer player, CommonCashRegi
}
else
{
// Record the buyer so items spawned by the purchase can be stamped with their owner
Patches.World.GlobalShopControllerPatch.PurchasingPlayerId = player.PlayerId;
success = CashRegister?.Buy() ?? false;
}

Expand Down Expand Up @@ -235,15 +249,92 @@ public void Server_ProcessCashRegisterAction(ServerPlayer player, CommonCashRegi
processingAction = false;
}

private void Server_ProcessScanItem(ServerPlayer player, CommonCashRegisterWithModulesActionPacket packet)
{
if (!TryGetModule(packet.ModuleIndex, out ScanItemCashRegisterModule module))
{
NetworkLifecycle.Instance.Server?.LogWarning($"NetworkedCashRegisterWithModules.Server_ProcessScanItem() Module index {packet.ModuleIndex} not found on {CashRegister.GetObjectPath()}");
SendRejection(player, CashRegisterAction.RejectGeneric);
return;
}

if ((player.WorldPosition - module.transform.position).sqrMagnitude > MAX_SCAN_DISTANCE_SQR)
{
NetworkLifecycle.Instance.Server?.LogDebug(() => $"Player \"{player.Username}\" tried to scan an item, but they are too far away");
SendRejection(player, CashRegisterAction.RejectGeneric);
return;
}

processingAction = true;

//AddItemsToBuy validates stock and increments the basket; on success the
//ScanItemCashRegisterModulePatch postfix broadcasts the new basket state to all clients
if (!module.AddItemsToBuy())
SendRejection(player, CashRegisterAction.RejectGeneric);

processingAction = false;
}

private void SendRejection(ServerPlayer player, CashRegisterAction response)
{
NetworkLifecycle.Instance.Server.SendCashRegisterAction
(
new CommonCashRegisterWithModulesActionPacket
{
NetId = NetId,
Action = response,
Amount = CashRegister.DepositedCash
},
[player]
);
}

public bool TryGetModule(byte moduleIndex, out ScanItemCashRegisterModule module)
{
module = null;

if (CashRegister == null || CashRegister.registerModules == null || moduleIndex >= CashRegister.registerModules.Length)
return false;

module = CashRegister.registerModules[moduleIndex] as ScanItemCashRegisterModule;
return module != null;
}

public bool TryGetModuleIndex(CashRegisterModule module, out byte moduleIndex)
{
moduleIndex = 0;

if (CashRegister == null || CashRegister.registerModules == null)
return false;

int index = Array.IndexOf(CashRegister.registerModules, module);
if (index < 0 || index > byte.MaxValue)
return false;

moduleIndex = (byte)index;
return true;
}

#endregion

#region Client

public void Client_ProcessCashRegisterAction(CashRegisterAction action, double amount)
public void Client_ProcessCashRegisterAction(CommonCashRegisterWithModulesActionPacket packet)
{
CashRegisterAction action = packet.Action;
double amount = packet.Amount;

NetworkLifecycle.Instance.Client?.LogDebug(() => $"NetworkedCashRegisterWithModules.Client_ProcessCashRegisterAction({action}, {amount}) isBuying: {isBuying}, isCancelling: {isCancelling}");
switch (action)
{
case CashRegisterAction.SetBasket:
if (TryGetModule(packet.ModuleIndex, out ScanItemCashRegisterModule scanModule))
scanModule.SetUnitsToBuy((float)amount);
else
Multiplayer.LogWarning($"NetworkedCashRegisterWithModules.Client_ProcessCashRegisterAction({action}) Module index {packet.ModuleIndex} not found on {CashRegister.GetObjectPath()}");

break;

case CashRegisterAction.Cancel:

isCancelling = false;
Expand Down
39 changes: 34 additions & 5 deletions Multiplayer/Components/Networking/World/NetworkedItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ public static bool TryGetNetId(ItemBase item, out ushort netID)
//Handle ownership
public sbyte OwnerId { get; private set; } = -1; // 0 means no owner

// Last player to hold/pocket this item; not cleared on drop so systems like
// Lost & Found can tell whose item this is. 0 means no player has owned it.
public byte LastOwnerId { get; private set; }

public void SetLastOwner(byte playerId)
{
LastOwnerId = playerId;
}

//public void SetOwner(ushort playerId)
//{
// if (OwnerId != playerId)
Expand Down Expand Up @@ -130,9 +139,10 @@ protected void Start()
if (!initialised)
Register();

// Mark registration as complete for items that don't need tracked values
// Items without tracked values complete registration here; FinaliseTrackedValues
// also applies any snapshots that were received before Start ran
if (!registrationComplete && !UsefulItem)
registrationComplete = true;
FinaliseTrackedValues();
}

public T GetTrackedItem<T>() where T : Component
Expand Down Expand Up @@ -549,9 +559,15 @@ private void HandleDroppedOrThrownState(ItemUpdateData snapshot)

//resolve ownership
if (NetworkLifecycle.Instance.IsHost())
{
if (NetworkLifecycle.Instance.Server.TryGetServerPlayer(snapshot.Player, out ServerPlayer player) && player.OwnsItem(NetId))
player.RemoveOwnedItem(NetId);

//the item is back in the shared world; track it in world storage so it saves with the world
if (Item.InventorySpecs != null && Item.InventorySpecs.BelongsToPlayer)
StorageController.Instance.AddItemToWorldStorage(Item);
}

//activate and relocate item
gameObject.SetActive(true);
transform.position = snapshot.ItemPosition + WorldMover.currentMove;
Expand Down Expand Up @@ -615,8 +631,19 @@ private void HandleInventoryOrHandState(ItemUpdateData snapshot)
}

if (NetworkLifecycle.Instance.IsHost())
if (NetworkLifecycle.Instance.Server.TryGetServerPlayer(snapshot.Player, out ServerPlayer player) && !player.OwnsItem(NetId))
player.AddOwnedItem(NetId);
{
if (NetworkLifecycle.Instance.Server.TryGetServerPlayer(snapshot.Player, out ServerPlayer player))
{
if (!player.OwnsItem(NetId))
player.AddOwnedItem(NetId);

SetLastOwner(player.PlayerId);
}

//the item is in a player's possession; take it out of the world storages so
//it is not saved (and not summonable) as a world item
StorageController.Instance.RemoveItemFromStorageItemList(Item);
}

//todo add to player model's hand
this.gameObject.SetActive(false);
Expand All @@ -635,7 +662,9 @@ protected override void OnDestroy()
return;
}

if (NetworkLifecycle.Instance.IsHost())
// NetId 0 means this was a per-player inventory copy, never a world item. Announcing
// its destruction would make other clients discard whatever they have under id 0.
if (NetworkLifecycle.Instance.IsHost() && NetId != 0)
{
var updateData = CreateUpdateData(ItemUpdateData.ItemUpdateType.Destroy);
if (updateData != null)
Expand Down
Loading