TryTransferItem(SimpleCommodity) checks the target can take the part and stores the part removed, not the stack left behind (it duplicated and lost units on every partial move, the player's commodity buy among them). TryTransferItems goes. CargoTransfer.Step owns a give's progress and end; PilotCommand.Give and StopGiving are applied by PilotApplier; TransferRate is the new tunable, DockingDistance the range. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
124 lines
4.6 KiB
C#
124 lines
4.6 KiB
C#
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
using System.Linq;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
|
|
// Why a give ended. Notification only: nothing reads it to decide anything.
|
|
public enum GiveEnd
|
|
{
|
|
Done,
|
|
Stopped,
|
|
OutOfRange,
|
|
ReceiverGone,
|
|
ReceiverFull,
|
|
NothingLeft,
|
|
GiverDocked
|
|
}
|
|
|
|
// A pilot's standing offer of goods to a ship alongside. Runtime only: nothing about a give is saved.
|
|
public sealed class GiveOrder
|
|
{
|
|
public readonly Entity Receiver;
|
|
// A commodity order is for any stack of this item's design in the giver's bays; a crafted order is for this instance.
|
|
public readonly ItemInstance Item;
|
|
// Units still to move.
|
|
public int Remaining { get; internal set; }
|
|
// Units earned and not yet moved, fractional between steps.
|
|
internal double Budget;
|
|
|
|
internal GiveOrder(Entity receiver, ItemInstance item, int quantity)
|
|
{
|
|
Receiver = receiver;
|
|
Item = item;
|
|
Remaining = quantity;
|
|
}
|
|
}
|
|
|
|
// The one owner of a give's progress and its end (ruling tender-transfer-verb). A ship's own pilot asks for a give through
|
|
// PilotCommand.Give and PilotApplier starts it here; nothing else moves goods out of a hold in space (ruling
|
|
// transfer-is-give-only). Every unit moves through EquippedCargoBay.TryTransferItem, the one conserving bay-to-bay commit.
|
|
public static class CargoTransfer
|
|
{
|
|
internal static void Begin(Entity giver, GiveOrder order)
|
|
{
|
|
End(giver, GiveEnd.Stopped);
|
|
giver.Giving = order;
|
|
}
|
|
|
|
// Ends the giver's give, if it has one, for the reason.
|
|
internal static void End(Entity giver, GiveEnd reason)
|
|
{
|
|
if (giver.Giving == null) return;
|
|
giver.Giving = null;
|
|
giver.LastGiveEnd = reason;
|
|
}
|
|
|
|
// One step of every give in the zone: the receiver is still here and alongside, and the rate earns units, each moved
|
|
// into the first receiver bay that takes it.
|
|
public static void Step(Zone zone, float deltaTime)
|
|
{
|
|
foreach (var giver in zone.Entities.ToArray())
|
|
{
|
|
var order = giver.Giving;
|
|
if (order == null) continue;
|
|
var settings = giver.ItemManager.GameplaySettings;
|
|
var range = settings.DockingDistance;
|
|
if (!zone.Entities.Contains(order.Receiver))
|
|
{
|
|
End(giver, GiveEnd.ReceiverGone);
|
|
continue;
|
|
}
|
|
if (lengthsq(order.Receiver.Position.xz - giver.Position.xz) > range * range)
|
|
{
|
|
End(giver, GiveEnd.OutOfRange);
|
|
continue;
|
|
}
|
|
|
|
order.Budget += (double) settings.TransferRate * deltaTime;
|
|
while (order.Budget >= 1 && order.Remaining > 0)
|
|
{
|
|
var source = Source(giver, order);
|
|
if (source.Bay == null)
|
|
{
|
|
End(giver, GiveEnd.NothingLeft);
|
|
break;
|
|
}
|
|
if (!Move(source.Bay, source.Item, order.Receiver))
|
|
{
|
|
End(giver, GiveEnd.ReceiverFull);
|
|
break;
|
|
}
|
|
order.Budget -= 1;
|
|
order.Remaining--;
|
|
}
|
|
if (giver.Giving == order && order.Remaining == 0) End(giver, GiveEnd.Done);
|
|
}
|
|
}
|
|
|
|
// Where the next unit comes from: the first stack of the order's design in the giver's bays, or the crafted instance.
|
|
private static (EquippedCargoBay Bay, ItemInstance Item) Source(Entity giver, GiveOrder order)
|
|
{
|
|
foreach (var bay in giver.CargoBays)
|
|
{
|
|
if (order.Item is CraftedItemInstance crafted)
|
|
{
|
|
if (bay.Cargo.ContainsKey(crafted)) return (bay, crafted);
|
|
}
|
|
else if (bay.ItemsOfType.TryGetValue(order.Item.Data.Key, out var stacks))
|
|
{
|
|
var stack = stacks.OfType<SimpleCommodity>().FirstOrDefault(s => s.Quantity > 0);
|
|
if (stack != null) return (bay, stack);
|
|
}
|
|
}
|
|
return (null, null);
|
|
}
|
|
|
|
// One unit of a commodity, or the crafted item, into the first receiver bay that takes it.
|
|
private static bool Move(EquippedCargoBay from, ItemInstance item, Entity receiver) =>
|
|
item is SimpleCommodity commodity
|
|
? receiver.CargoBays.Any(bay => from.TryTransferItem(bay, commodity, 1))
|
|
: receiver.CargoBays.Any(bay => from.TryTransferItem(bay, (CraftedItemInstance) item));
|
|
}
|