Run Re-Do
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server receiver for <see cref="BoonPickRequest"/> + the reward-grace AUTO-PICK backstop. A valid pick
|
||||
/// (sender resolved, <c>RunInfo.Lifecycle == RoomReward</c> — the D-F4 gate — <c>Pending == 1</c>, index in
|
||||
/// range, option id known to the catalog) appends ONE <see cref="StatModifier"/> in the run-scoped BOON band
|
||||
/// (<c>Tuning.BoonSourceIdBase + BoonPickCounter++</c> — distinct rows, one range-strip clears the run) and
|
||||
/// clears <c>Pending</c>; the buffer mutation is non-structural and folds through the unchanged
|
||||
/// StatRecomputeSystem on both worlds (rollback-correct). When the reward grace elapses, every still-pending
|
||||
/// EXPEDITION player is auto-dealt <c>Option0</c> (the operator's default un-picked policy — a player always
|
||||
/// gets something) so the run never stalls on an AFK picker.
|
||||
///
|
||||
/// Ordering: <c>[UpdateBefore(RunDirectorSystem)]</c> — ALL RPC receivers sit before the director (the
|
||||
/// ReadyToggle/RouteSelect symmetry). This closes the D-F4 straggler race STRUCTURALLY: on the tick the
|
||||
/// director strips (Returning), a straggler pick is rejected here FIRST (lifecycle is already past RoomReward),
|
||||
/// so nothing can append after the strip; and the auto-pick lands before the director's exit gate reads
|
||||
/// Pending. Requests are ALWAYS destroyed. No CyclePhase edge (the room-chain hard rule).
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
[UpdateBefore(typeof(RunDirectorSystem))]
|
||||
public partial struct BoonApplySystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<BoonCatalog>();
|
||||
state.RequireForUpdate<RunInfo>();
|
||||
state.RequireForUpdate<RunRuntime>();
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var dirEntity = SystemAPI.GetSingletonEntity<RunInfo>();
|
||||
var info = SystemAPI.GetComponent<RunInfo>(dirEntity);
|
||||
var run = SystemAPI.GetComponent<RunRuntime>(dirEntity);
|
||||
bool rewarding = info.Lifecycle == RunLifecycle.RoomReward;
|
||||
|
||||
var catalog = SystemAPI.GetComponent<BoonCatalog>(SystemAPI.GetSingletonEntity<BoonCatalog>());
|
||||
if (!catalog.Value.IsCreated)
|
||||
return;
|
||||
ref var pool = ref catalog.Value.Value;
|
||||
|
||||
bool runDirty = false;
|
||||
|
||||
// ---- explicit picks (drained every tick so stale requests die even outside RoomReward) ----
|
||||
var playerByConn = new NativeHashMap<int, Entity>(8, Allocator.Temp);
|
||||
foreach (var (owner, entity) in
|
||||
SystemAPI.Query<RefRO<GhostOwner>>().WithAll<PlayerTag, BoonOffer, StatModifier>().WithEntityAccess())
|
||||
playerByConn[owner.ValueRO.NetworkId] = entity;
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
foreach (var (receive, req, requestEntity) in
|
||||
SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>, RefRO<BoonPickRequest>>().WithEntityAccess())
|
||||
{
|
||||
var conn = receive.ValueRO.SourceConnection;
|
||||
if (rewarding
|
||||
&& req.ValueRO.Index < 3
|
||||
&& SystemAPI.HasComponent<NetworkId>(conn)
|
||||
&& playerByConn.TryGetValue(SystemAPI.GetComponent<NetworkId>(conn).Value, out var player))
|
||||
{
|
||||
var offer = SystemAPI.GetComponent<BoonOffer>(player);
|
||||
if (offer.Pending == 1)
|
||||
{
|
||||
byte id = req.ValueRO.Index == 2 ? offer.Option2
|
||||
: req.ValueRO.Index == 1 ? offer.Option1 : offer.Option0;
|
||||
if (Apply(ref state, player, id, ref pool, ref run))
|
||||
{
|
||||
offer.Pending = 0;
|
||||
SystemAPI.SetComponent(player, offer);
|
||||
runDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ecb.DestroyEntity(requestEntity);
|
||||
}
|
||||
ecb.Playback(state.EntityManager);
|
||||
playerByConn.Dispose();
|
||||
|
||||
// ---- reward-grace auto-pick backstop (Option0 — the player always gets something) ----
|
||||
if (rewarding && run.RewardGraceTick != 0u)
|
||||
{
|
||||
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
|
||||
if (serverTick.IsValid && !new NetworkTick(run.RewardGraceTick).IsNewerThan(serverTick))
|
||||
{
|
||||
foreach (var (offer, region, entity) in
|
||||
SystemAPI.Query<RefRW<BoonOffer>, RefRO<RegionTag>>()
|
||||
.WithAll<PlayerTag, StatModifier>().WithEntityAccess())
|
||||
{
|
||||
if (offer.ValueRO.Pending != 1 || region.ValueRO.Region != RegionId.Expedition)
|
||||
continue;
|
||||
if (Apply(ref state, entity, offer.ValueRO.Option0, ref pool, ref run))
|
||||
runDirty = true;
|
||||
offer.ValueRW.Pending = 0; // cleared even if the id was unknown — never wedge the gate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runDirty)
|
||||
SystemAPI.SetComponent(dirEntity, run); // the documented BoonPickCounter co-write (band provenance)
|
||||
}
|
||||
|
||||
/// <summary>Append the boon's StatModifier in the run-scoped band. False iff the id is unknown/zero.</summary>
|
||||
static bool Apply(ref SystemState state, Entity player, byte boonId, ref BoonCatalogBlob pool, ref RunRuntime run)
|
||||
{
|
||||
if (boonId == 0)
|
||||
return false;
|
||||
int idx = BoonMath.FindDef(ref pool, boonId);
|
||||
if (idx < 0)
|
||||
return false; // unknown id (catalog drift) — preserve-and-skip, never throw
|
||||
|
||||
var mods = state.EntityManager.GetBuffer<StatModifier>(player);
|
||||
mods.Add(new StatModifier
|
||||
{
|
||||
Target = pool.Defs[idx].Target,
|
||||
Op = pool.Defs[idx].Op,
|
||||
Value = pool.Defs[idx].Value,
|
||||
SourceId = Tuning.BoonSourceIdBase + (run.BoonPickCounter % Tuning.BoonSourceIdSpan),
|
||||
});
|
||||
run.BoonPickCounter += 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user