Economy: base-local mining loop (mine at base, any attack harvests, scheduled sieges)
Consolidate the divorced combat + economy halves into one base-local loop. BaseFieldSpawnSystem tops up RegionTag{Base} Ore nodes around the plot; harvest routes Base->shared ledger and Expedition/untagged->personal inventory for BOTH the projectile (ResourceHarvestSystem) and melee (MeleeComboSystem server-only block, writes Remaining back for VFX). Activate the reserved Schedule source in ThreatDirectorSystem so base sieges arm WITHOUT an expedition trip (the loop-closer: previously zero waves ever attacked a base-only player). Region-filter the ExpeditionFieldSystem teardown so it no longer wipes the permanent base field. HudSystem shows phase-aware loop copy. See DR-031.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,11 +49,15 @@ namespace ProjectM.Simulation
|
||||
public partial struct MeleeComboSystem : ISystem
|
||||
{
|
||||
ComponentLookup<KnockbackState> m_KnockbackLookup;
|
||||
ComponentLookup<RegionTag> m_RegionLookup;
|
||||
BufferLookup<InventorySlot> m_InvLookup;
|
||||
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
m_KnockbackLookup = state.GetComponentLookup<KnockbackState>(isReadOnly: false);
|
||||
m_RegionLookup = state.GetComponentLookup<RegionTag>(isReadOnly: true);
|
||||
m_InvLookup = state.GetBufferLookup<InventorySlot>(isReadOnly: false);
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
}
|
||||
|
||||
@@ -166,6 +170,50 @@ namespace ProjectM.Simulation
|
||||
enemyEntities.Add(enemyEntity);
|
||||
enemyPositions.Add(xform.ValueRO.Position);
|
||||
}
|
||||
// Gather harvest targets (resource nodes + Blight clutter) ONCE so "any attack harvests": a swing
|
||||
// depletes every node/clutter in its cone, crediting the shared ResourceLedger (the build currency
|
||||
// pool) just like a base projectile hit. SERVER-ONLY (this whole block) — interpolated node ghosts
|
||||
// are never rolled back, so the deposit + destroy fire exactly once per swing.
|
||||
bool haveLedger = SystemAPI.TryGetSingletonEntity<ResourceLedger>(out var ledgerEntity);
|
||||
m_RegionLookup.Update(ref state);
|
||||
m_InvLookup.Update(ref state);
|
||||
var meleePlayerByConn = new NativeHashMap<int, Entity>(8, Allocator.Temp);
|
||||
foreach (var (po, pe) in SystemAPI.Query<RefRO<GhostOwner>>().WithAll<PlayerTag, InventorySlot>().WithEntityAccess())
|
||||
meleePlayerByConn[po.ValueRO.NetworkId] = pe;
|
||||
var harvEntity = new NativeList<Entity>(Allocator.Temp);
|
||||
var harvPos = new NativeList<float3>(Allocator.Temp);
|
||||
var harvRemaining = new NativeList<int>(Allocator.Temp);
|
||||
var harvYieldId = new NativeList<byte>(Allocator.Temp);
|
||||
var harvPerHit = new NativeList<float>(Allocator.Temp);
|
||||
var harvIsClutter = new NativeList<bool>(Allocator.Temp);
|
||||
var harvVariant = new NativeList<byte>(Allocator.Temp);
|
||||
var harvToLedger = new NativeList<bool>(Allocator.Temp);
|
||||
foreach (var (hx, node, he) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<ResourceNode>>().WithEntityAccess())
|
||||
{
|
||||
harvEntity.Add(he);
|
||||
harvPos.Add(hx.ValueRO.Position);
|
||||
harvRemaining.Add(node.ValueRO.Remaining);
|
||||
harvYieldId.Add(node.ValueRO.ResourceId);
|
||||
harvPerHit.Add(node.ValueRO.HarvestPerHit);
|
||||
harvIsClutter.Add(false);
|
||||
harvVariant.Add(0);
|
||||
harvToLedger.Add(m_RegionLookup.HasComponent(he) && m_RegionLookup[he].Region == RegionId.Base);
|
||||
}
|
||||
foreach (var (hx, clutter, he) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<BlightClutter>>().WithEntityAccess())
|
||||
{
|
||||
harvEntity.Add(he);
|
||||
harvPos.Add(hx.ValueRO.Position);
|
||||
harvRemaining.Add(clutter.ValueRO.Remaining);
|
||||
harvYieldId.Add(clutter.ValueRO.ScrapResourceId);
|
||||
harvPerHit.Add(clutter.ValueRO.ScrapPerHit);
|
||||
harvIsClutter.Add(true);
|
||||
harvVariant.Add(clutter.ValueRO.Variant);
|
||||
harvToLedger.Add(m_RegionLookup.HasComponent(he) && m_RegionLookup[he].Region == RegionId.Base);
|
||||
}
|
||||
var harvDestroyed = new NativeArray<bool>(harvEntity.Length, Allocator.Temp);
|
||||
|
||||
|
||||
m_KnockbackLookup.Update(ref state);
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
@@ -192,10 +240,85 @@ namespace ProjectM.Simulation
|
||||
}
|
||||
}
|
||||
}
|
||||
// HARVEST: deplete every node/clutter in each swing's cone, crediting the shared ledger; write
|
||||
// Remaining back so the [GhostField] replicates -> WorldFeedbackSystem chips fire on melee mining.
|
||||
for (int s = 0; s < cleaves.Length; s++)
|
||||
{
|
||||
var hc = cleaves[s];
|
||||
for (int i = 0; i < harvEntity.Length; i++)
|
||||
{
|
||||
if (harvDestroyed[i])
|
||||
continue;
|
||||
if (!MeleeConeMath.InCone(hc.From, hc.Face, hc.Range, cosHalf, harvPos[i]))
|
||||
continue;
|
||||
|
||||
int amount = math.max(1, (int)harvPerHit[i]);
|
||||
byte yieldId = harvYieldId[i];
|
||||
// Route by region: Base nodes credit the shared ledger DIRECTLY (the build pool); an
|
||||
// expedition / un-tagged target goes to the swinging player's PERSONAL inventory (spill to
|
||||
// ledger), mirroring ResourceHarvestSystem. Only deplete if the yield landed somewhere —
|
||||
// never consume a node for zero credit (e.g. no ledger singleton present).
|
||||
int remainder = amount;
|
||||
bool deposited = false;
|
||||
if (!harvToLedger[i]
|
||||
&& meleePlayerByConn.TryGetValue(hc.OwnerId, out var meleePlayer)
|
||||
&& m_InvLookup.HasBuffer(meleePlayer))
|
||||
{
|
||||
var inv = m_InvLookup[meleePlayer];
|
||||
remainder = InventoryMath.Deposit(inv, yieldId, amount, Tuning.DefaultStackMax, Tuning.InventoryMaxSlots);
|
||||
deposited = true;
|
||||
}
|
||||
if (remainder > 0 && haveLedger)
|
||||
{
|
||||
var ledger = SystemAPI.GetBuffer<StorageEntry>(ledgerEntity);
|
||||
StorageMath.Deposit(ledger, yieldId, remainder);
|
||||
deposited = true;
|
||||
}
|
||||
if (!deposited)
|
||||
continue;
|
||||
|
||||
int rem = harvRemaining[i] - amount;
|
||||
harvRemaining[i] = rem;
|
||||
if (rem <= 0)
|
||||
{
|
||||
harvDestroyed[i] = true;
|
||||
ecb.DestroyEntity(harvEntity[i]);
|
||||
}
|
||||
else if (harvIsClutter[i])
|
||||
{
|
||||
ecb.SetComponent(harvEntity[i], new BlightClutter
|
||||
{
|
||||
Remaining = rem,
|
||||
Variant = harvVariant[i],
|
||||
ScrapResourceId = yieldId,
|
||||
ScrapPerHit = harvPerHit[i],
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ecb.SetComponent(harvEntity[i], new ResourceNode
|
||||
{
|
||||
ResourceId = yieldId,
|
||||
Remaining = rem,
|
||||
HarvestPerHit = harvPerHit[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
enemyEntities.Dispose();
|
||||
enemyPositions.Dispose();
|
||||
harvEntity.Dispose();
|
||||
harvPos.Dispose();
|
||||
harvRemaining.Dispose();
|
||||
harvYieldId.Dispose();
|
||||
harvPerHit.Dispose();
|
||||
harvIsClutter.Dispose();
|
||||
harvVariant.Dispose();
|
||||
harvDestroyed.Dispose();
|
||||
harvToLedger.Dispose();
|
||||
meleePlayerByConn.Dispose();
|
||||
}
|
||||
|
||||
if (cleaves.IsCreated)
|
||||
|
||||
Reference in New Issue
Block a user