Init Homebase

This commit is contained in:
2026-06-02 18:28:23 -07:00
parent 2ee30c01fd
commit dd0064c377
48 changed files with 1934 additions and 12 deletions
@@ -34,6 +34,12 @@ namespace ProjectM.Server
public void OnUpdate(ref SystemState state)
{
var spawner = SystemAPI.GetSingleton<PlayerSpawner>();
// M5 home base: re-root the spawn ring on the baked BaseAnchor when present; fall back
// to the spawner's SpawnPoint if the base subscene hasn't streamed in yet.
var center = spawner.SpawnPoint;
if (SystemAPI.TryGetSingleton<BaseAnchor>(out var baseAnchor))
center = BaseGridMath.PlotCenter(baseAnchor);
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (receive, requestEntity) in
@@ -45,7 +51,7 @@ namespace ProjectM.Server
var networkId = SystemAPI.GetComponent<NetworkId>(connection);
var player = ecb.Instantiate(spawner.PlayerPrefab);
ecb.SetComponent(player, LocalTransform.FromPosition(spawner.SpawnPoint + PlayerSpawnMath.SpawnOffset(networkId.Value, spawner.SpawnRingRadius, spawner.RingSlots)));
ecb.SetComponent(player, LocalTransform.FromPosition(center + PlayerSpawnMath.SpawnOffset(networkId.Value, spawner.SpawnRingRadius, spawner.RingSlots)));
ecb.SetComponent(player, new GhostOwner { NetworkId = networkId.Value });
// Auto-despawn the player when its owning connection is removed.
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9b28010c047f72f4b8d242ee1f4351cd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
using ProjectM.Simulation;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
namespace ProjectM.Server
{
/// <summary>
/// Server-only, one-shot spawner for the shared home-base storage container (mirrors
/// UpgradePickupSpawnSystem). On its first update it reads the baked <see cref="StorageSpawner"/>
/// singleton and the <see cref="BaseAnchor"/>, instantiates the container ghost at the cell center
/// (<see cref="BaseGridMath.CellToWorld"/>), then destroys the spawner singleton so the system idles
/// (spawned exactly once). Runs in the default SimulationSystemGroup (NOT the prediction loop); the
/// container replicates to clients as an ownerless interpolated ghost. The container is intentionally
/// NOT linked to any connection's LinkedEntityGroup, so it persists across player disconnects.
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial struct SharedStorageSpawnSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<StorageSpawner>();
state.RequireForUpdate<BaseAnchor>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var spawnerEntity = SystemAPI.GetSingletonEntity<StorageSpawner>();
var spawner = SystemAPI.GetComponent<StorageSpawner>(spawnerEntity);
var anchor = SystemAPI.GetSingleton<BaseAnchor>();
var ecb = new EntityCommandBuffer(Allocator.Temp);
if (spawner.Prefab != Entity.Null)
{
var container = ecb.Instantiate(spawner.Prefab);
var position = BaseGridMath.CellToWorld(anchor, spawner.Cell);
ecb.SetComponent(container, LocalTransform.FromPosition(position));
}
// One-shot: remove the spawner so RequireForUpdate fails and the system idles.
ecb.DestroyEntity(spawnerEntity);
ecb.Playback(state.EntityManager);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c60c2c14e48ea0c45858cf0054c1663f
@@ -0,0 +1,54 @@
using ProjectM.Simulation;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Server
{
/// <summary>
/// Server-authoritative handler for <see cref="StorageOpRequest"/> RPCs (deposit/withdraw on the
/// shared storage container). Resolves the single <see cref="SharedStorageContainer"/> as a singleton,
/// applies the op to its replicated <see cref="StorageEntry"/> buffer via <see cref="StorageMath"/>,
/// and destroys the request entity. Runs in the default SimulationSystemGroup (NOT the prediction
/// loop), so a server event is applied exactly once (no rollback double-apply). Op is read as a byte
/// (see <see cref="StorageOp"/>); the buffer mutation auto-replicates to all clients via GhostField.
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial struct StorageOpReceiveSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<SharedStorageContainer>();
var builder = new EntityQueryBuilder(Allocator.Temp)
.WithAll<StorageOpRequest, ReceiveRpcCommandRequest>();
state.RequireForUpdate(state.GetEntityQuery(builder));
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var containerEntity = SystemAPI.GetSingletonEntity<SharedStorageContainer>();
var contents = SystemAPI.GetBuffer<StorageEntry>(containerEntity);
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (request, requestEntity) in
SystemAPI.Query<RefRO<StorageOpRequest>>().WithAll<ReceiveRpcCommandRequest>().WithEntityAccess())
{
var op = request.ValueRO;
if (op.Op == StorageOp.Withdraw)
StorageMath.Withdraw(contents, op.ItemId, op.Count);
else
StorageMath.Deposit(contents, op.ItemId, op.Count);
ecb.DestroyEntity(requestEntity);
}
ecb.Playback(state.EntityManager);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6739144c8fa1bd040ad766919f9535f3