133 lines
4.9 KiB
C#
133 lines
4.9 KiB
C#
using NeonSprawl.Server.Game.Items;
|
|
using NeonSprawl.Server.Game.Mastery;
|
|
using NeonSprawl.Server.Game.Skills;
|
|
|
|
namespace NeonSprawl.Server.Game.Gathering;
|
|
|
|
/// <summary>
|
|
/// Orchestrates gather side effects: capacity pre-check, inventory grant, skill XP, depletion commit (NEO-62).
|
|
/// Interact wiring is NEO-63.
|
|
/// </summary>
|
|
public static class GatherOperations
|
|
{
|
|
private static readonly GatherGrantApplied[] EmptyGrants = [];
|
|
|
|
/// <summary>
|
|
/// Resolves one gather for <paramref name="playerId"/> at <paramref name="interactableId"/>.
|
|
/// Mutations commit in order: inventory add → XP grant → depletion decrement (last).
|
|
/// </summary>
|
|
public static GatherResult TryGather(
|
|
string playerId,
|
|
string interactableId,
|
|
IResourceNodeDefinitionRegistry nodeRegistry,
|
|
IItemDefinitionRegistry itemRegistry,
|
|
IPlayerInventoryStore inventoryStore,
|
|
ISkillDefinitionRegistry skillRegistry,
|
|
IPlayerSkillProgressionStore xpStore,
|
|
ISkillLevelCurve levelCurve,
|
|
PerkUnlockEngine perkUnlockEngine,
|
|
IResourceNodeInstanceStore instanceStore)
|
|
{
|
|
var key = ResourceNodeInstanceIds.Normalize(interactableId);
|
|
if (key.Length == 0 ||
|
|
!nodeRegistry.TryGetDefinition(key, out var definition) ||
|
|
!nodeRegistry.TryGetYield(key, out var yield))
|
|
{
|
|
return Deny(GatherReasonCodes.UnknownNode, null);
|
|
}
|
|
|
|
if (!instanceStore.TryEnsureInitialized(key, definition.MaxGathers))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Failed to initialize resource node instance '{key}' with maxGathers {definition.MaxGathers}.");
|
|
}
|
|
|
|
if (!instanceStore.TryGetRemainingGathers(key, out var capacity) || capacity.RemainingGathers <= 0)
|
|
{
|
|
var remaining = capacity.RemainingGathers;
|
|
return Deny(GatherReasonCodes.NodeDepleted, remaining);
|
|
}
|
|
|
|
var inventoryOutcome = PlayerInventoryOperations.TryAddStack(
|
|
playerId,
|
|
yield.ItemId,
|
|
yield.Quantity,
|
|
itemRegistry,
|
|
inventoryStore);
|
|
|
|
if (inventoryOutcome.Kind == PlayerInventoryMutationKind.StoreMissing)
|
|
{
|
|
return Deny(GatherReasonCodes.InventoryStoreMissing, capacity.RemainingGathers);
|
|
}
|
|
|
|
if (inventoryOutcome.Kind == PlayerInventoryMutationKind.Denied)
|
|
{
|
|
return Deny(inventoryOutcome.ReasonCode ?? GatherReasonCodes.InventoryFull, capacity.RemainingGathers);
|
|
}
|
|
|
|
var xpOutcome = SkillProgressionGrantOperations.TryApplyGrant(
|
|
playerId,
|
|
GatherSkillXpConstants.SalvageSkillId,
|
|
GatherSkillXpConstants.ActivityXpPerResourceNodeGather,
|
|
GatherSkillXpConstants.ActivitySourceKind,
|
|
skillRegistry,
|
|
xpStore,
|
|
levelCurve,
|
|
perkUnlockEngine);
|
|
|
|
if (xpOutcome.Kind != SkillProgressionGrantApplyKind.Granted)
|
|
{
|
|
var xpReason = xpOutcome.Kind == SkillProgressionGrantApplyKind.ProgressionStoreMissing
|
|
? GatherReasonCodes.ProgressionStoreMissing
|
|
: xpOutcome.Response?.ReasonCode ?? GatherReasonCodes.ProgressionStoreMissing;
|
|
|
|
_ = PlayerInventoryOperations.TryRemoveStack(
|
|
playerId,
|
|
yield.ItemId,
|
|
yield.Quantity,
|
|
itemRegistry,
|
|
inventoryStore);
|
|
|
|
return Deny(xpReason, capacity.RemainingGathers);
|
|
}
|
|
|
|
var depletionOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(
|
|
key,
|
|
nodeRegistry,
|
|
instanceStore);
|
|
|
|
if (depletionOutcome.Kind != ResourceNodeInstanceMutationKind.Applied)
|
|
{
|
|
_ = PlayerInventoryOperations.TryRemoveStack(
|
|
playerId,
|
|
yield.ItemId,
|
|
yield.Quantity,
|
|
itemRegistry,
|
|
inventoryStore);
|
|
|
|
var depletedRemaining = depletionOutcome.Snapshot?.RemainingGathers ?? 0;
|
|
return Deny(
|
|
depletionOutcome.ReasonCode ?? GatherReasonCodes.NodeDepleted,
|
|
depletedRemaining);
|
|
}
|
|
|
|
return new GatherResult(
|
|
Success: true,
|
|
ReasonCode: null,
|
|
GrantsApplied: [new GatherGrantApplied(yield.ItemId, yield.Quantity)],
|
|
RemainingGathers: depletionOutcome.Snapshot!.Value.RemainingGathers,
|
|
XpGrantSummary: new GatherXpGrantSummary(
|
|
GatherSkillXpConstants.SalvageSkillId,
|
|
GatherSkillXpConstants.ActivityXpPerResourceNodeGather,
|
|
GatherSkillXpConstants.ActivitySourceKind));
|
|
}
|
|
|
|
private static GatherResult Deny(string reasonCode, int? remainingGathers) =>
|
|
new(
|
|
Success: false,
|
|
ReasonCode: reasonCode,
|
|
GrantsApplied: EmptyGrants,
|
|
RemainingGathers: remainingGathers,
|
|
XpGrantSummary: null);
|
|
}
|