Import art/VFX asset packs + game-feel systems; normalize texture extensions to lowercase for LFS

Add BefourStudios SciFi environment packs, Gabriel Aguiar VFX, and the
ShaderCrew Toon Shader embedded packages, plus combat/enemy/wave/death
gameplay systems and supporting vault docs/screenshots.

Rename 11 vendor textures from uppercase .PNG/.HDR to lowercase so the
case-sensitive Git LFS filters (*.png/*.hdr) match on case-sensitive
filesystems (Linux CI, case-sensitive macOS), not just locally where
core.ignorecase=true masks the gap. Each .meta moved with its asset so
GUID references are preserved. All ~1000 binaries tracked via LFS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 22:50:43 -07:00
parent dd0064c377
commit e362aaeb43
4830 changed files with 1293057 additions and 38 deletions
@@ -0,0 +1,80 @@
using ProjectM.Simulation;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
namespace ProjectM.Server
{
/// <summary>
/// Server-authoritative player death→respawn timing. The "is dead" GATE is derived every predicted tick from
/// replicated Health by <see cref="PlayerDeathStateSystem"/> (so movement/aim/fire stop on both server and
/// owner-client); THIS system owns the timer + the authoritative recovery. Runs server-only in the plain
/// <see cref="SimulationSystemGroup"/> AFTER the predicted group, so it observes this tick's post-damage Health.
/// On first seeing Health&lt;=0 it schedules a respawn tick; once due it refills Health to the effective max and
/// repositions the player to its deterministic base spawn slot (<see cref="PlayerSpawnMath"/>). Health.Current
/// (GhostField) + LocalTransform replicate, so the recovery reaches clients and the derived Dead clears.
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(PredictedSimulationSystemGroup))]
public partial struct PlayerRespawnSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate<PlayerSpawner>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
if (!serverTick.IsValid)
return;
uint now = serverTick.TickIndexForValidTick;
var spawner = SystemAPI.GetSingleton<PlayerSpawner>();
float3 center = spawner.SpawnPoint;
if (SystemAPI.TryGetSingleton<BaseAnchor>(out var baseAnchor))
center = BaseGridMath.PlotCenter(baseAnchor);
foreach (var (health, respawn, invuln, xform, owner, eff) in
SystemAPI.Query<RefRW<Health>, RefRW<RespawnState>, RefRW<RespawnInvuln>, RefRW<LocalTransform>,
RefRO<GhostOwner>, RefRO<EffectiveCharacterStats>>()
.WithAll<PlayerTag>())
{
if (health.ValueRO.Current > 0f)
{
respawn.ValueRW.RespawnTick = 0; // alive: clear any pending schedule
continue;
}
// Dead this tick.
if (respawn.ValueRO.RespawnTick == 0)
{
// Just died: schedule the recovery.
respawn.ValueRW.RespawnTick = RespawnMath.RespawnTick(now, respawn.ValueRO.DelayTicks);
}
else if (RespawnMath.IsDue(now, respawn.ValueRO.RespawnTick))
{
// Recover: full health at the deterministic base spawn slot.
float maxHealth = eff.ValueRO.MaxHealth > 0f ? eff.ValueRO.MaxHealth : health.ValueRO.Max;
health.ValueRW.Current = maxHealth;
float3 pos = center + PlayerSpawnMath.SpawnOffset(
owner.ValueRO.NetworkId, spawner.SpawnRingRadius, spawner.RingSlots);
xform.ValueRW.Position = pos;
// Grant brief post-respawn damage immunity so the swarm can't instantly re-kill.
invuln.ValueRW.UntilTick = TickUtil.NonZero(now + (uint)math.max(0, respawn.ValueRO.InvulnTicks));
respawn.ValueRW.RespawnTick = 0;
}
}
}
}
}