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:
@@ -0,0 +1,42 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Enableable, LOCAL (not replicated) "is dead" gate for a player. Derived every predicted tick from the
|
||||
/// replicated <see cref="Health"/> by <see cref="PlayerDeathStateSystem"/> (Dead == Health.Current <= 0) —
|
||||
/// exactly the derive-don't-replicate idiom of <see cref="EffectiveCharacterStats"/>/<c>StatRecomputeSystem</c>,
|
||||
/// so it is identical on server + owner-predicted client and rollback-correct with no replicated enabled bit.
|
||||
/// Baked DISABLED (the player spawns alive). Movement/aim/fire systems query <c>.WithDisabled<Dead>()</c>
|
||||
/// so a dead player is frozen and can't act.
|
||||
/// </summary>
|
||||
public struct Dead : IComponentData, IEnableableComponent { }
|
||||
|
||||
/// <summary>
|
||||
/// Server-only respawn timer for a player. NOT replicated — recovery is server-authoritative and the refilled
|
||||
/// <see cref="Health"/> (GhostField) + repositioned LocalTransform replicate instead. <see cref="RespawnTick"/>
|
||||
/// == 0 means "no respawn pending / alive"; <see cref="DelayTicks"/> is the baked down-time before recovery.
|
||||
/// </summary>
|
||||
public struct RespawnState : IComponentData
|
||||
{
|
||||
/// <summary>Raw server tick at which to respawn; 0 = none pending.</summary>
|
||||
public uint RespawnTick;
|
||||
|
||||
/// <summary>Ticks the player stays down before recovering (~60 ticks/sec).</summary>
|
||||
public int DelayTicks;
|
||||
|
||||
/// <summary>Ticks of post-respawn damage immunity granted on recovery (~60 ticks/sec).</summary>
|
||||
public int InvulnTicks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replicated post-respawn damage-immunity window. <c>UntilTick</c> = the raw server tick until which the
|
||||
/// player ignores damage; 0 = none. Set server-side by <c>PlayerRespawnSystem</c> on recovery, enforced by
|
||||
/// <c>HealthApplyDamageSystem</c>, and a <c>[GhostField]</c> so the client HUD can show a SHIELDED cue.
|
||||
/// </summary>
|
||||
public struct RespawnInvuln : IComponentData
|
||||
{
|
||||
[GhostField] public uint UntilTick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bd1e4b63cf826446b85d11124bc291b
|
||||
@@ -22,7 +22,7 @@ namespace ProjectM.Simulation
|
||||
{
|
||||
foreach (var (facing, transform, input) in
|
||||
SystemAPI.Query<RefRW<PlayerFacing>, RefRW<LocalTransform>, RefRO<PlayerInput>>()
|
||||
.WithAll<Simulate>())
|
||||
.WithAll<Simulate>().WithDisabled<Dead>())
|
||||
{
|
||||
float2 aim = input.ValueRO.Aim;
|
||||
if (math.lengthsq(aim) < 1e-6f)
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace ProjectM.Simulation
|
||||
{
|
||||
foreach (var (control, input, stats) in
|
||||
SystemAPI.Query<RefRW<CharacterControl>, RefRO<PlayerInput>, RefRO<EffectiveCharacterStats>>()
|
||||
.WithAll<Simulate>())
|
||||
.WithAll<Simulate>().WithDisabled<Dead>())
|
||||
{
|
||||
control.ValueRW.MoveVelocity =
|
||||
CharacterControlMath.DesiredMovement(input.ValueRO.Move, stats.ValueRO.MoveSpeed);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Derives the LOCAL enableable <see cref="Dead"/> gate from the replicated <see cref="Health"/> every predicted
|
||||
/// tick (Dead == Health.Current <= 0). Runs in BOTH worlds inside
|
||||
/// <see cref="PredictedSimulationSystemGroup"/>, BEFORE movement/aim/fire, so a dead player is excluded from
|
||||
/// those systems (which query <c>.WithDisabled<Dead>()</c>) on the server AND the owner-predicting client.
|
||||
/// Because it is a pure function of the already-replicated, reconciled Health (the same derive-don't-replicate
|
||||
/// pattern as <see cref="StatRecomputeSystem"/>), the gate is identical across server, owner-client, and rollback
|
||||
/// — no replicated enabled bit required. Also zeroes <see cref="CharacterControl.MoveVelocity"/> while dead so
|
||||
/// the kinematic character holds still (the movement system is skipped and would otherwise coast on stale
|
||||
/// velocity). The authoritative recovery (Health refill + reposition) is owned server-side by
|
||||
/// <c>PlayerRespawnSystem</c>. Visits dead players too via <c>.WithPresent<Dead>()</c> (required to write
|
||||
/// the enabled bit on an entity whose Dead is currently disabled).
|
||||
/// </summary>
|
||||
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
|
||||
[UpdateBefore(typeof(PlayerControlSystem))]
|
||||
[UpdateBefore(typeof(PlayerAimSystem))]
|
||||
[BurstCompile]
|
||||
public partial struct PlayerDeathStateSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
foreach (var (health, control, deadEnabled) in
|
||||
SystemAPI.Query<RefRO<Health>, RefRW<CharacterControl>, EnabledRefRW<Dead>>()
|
||||
.WithAll<PlayerTag, Simulate>()
|
||||
.WithPresent<Dead>())
|
||||
{
|
||||
bool isDead = health.ValueRO.Current <= 0f;
|
||||
deadEnabled.ValueRW = isDead;
|
||||
if (isDead)
|
||||
control.ValueRW.MoveVelocity = float3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65930c35d657ce84fbce6f1130efb441
|
||||
@@ -0,0 +1,36 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure, deterministic respawn-timer math (no RNG, no wall-clock) — unit-testable in EditMode without a netcode
|
||||
/// world (mirrors <see cref="PlayerSpawnMath"/> / <see cref="EnemyAIMath"/>). Ticks are the server's monotonic
|
||||
/// simulation ticks; a stored value of 0 means "no respawn pending / alive". Comparisons use a wrap-safe signed-delta (modular) compare — matching
|
||||
/// <see cref="Unity.NetCode.NetworkTick.IsNewerThan"/> semantics — so they hold across the uint tick wraparound.
|
||||
/// </summary>
|
||||
public static class RespawnMath
|
||||
{
|
||||
/// <summary>
|
||||
/// The tick at which a death at <paramref name="deathTick"/> should respawn, given
|
||||
/// <paramref name="delayTicks"/> (clamped to >= 1). Never returns 0 (0 is the "no respawn pending"
|
||||
/// sentinel), so a death exactly at tick 0 still schedules a recovery.
|
||||
/// </summary>
|
||||
public static uint RespawnTick(uint deathTick, int delayTicks)
|
||||
{
|
||||
uint delay = (uint)math.max(1, delayTicks);
|
||||
uint t = deathTick + delay;
|
||||
return t == 0u ? 1u : t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True when <paramref name="now"/> has reached/passed a scheduled <paramref name="respawnTick"/> (and one
|
||||
/// is actually scheduled, i.e. non-zero).
|
||||
/// </summary>
|
||||
public static bool IsDue(uint now, uint respawnTick)
|
||||
{
|
||||
// Wrap-safe modular compare (signed delta), NOT a raw `now >= respawnTick` (which is unsafe at the
|
||||
// uint tick wraparound). Matches NetworkTick.IsNewerThan; keeps this helper pure-uint + unit-testable.
|
||||
return respawnTick != 0u && (int)(now - respawnTick) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 128d07bd94a16ab40b8a71bdf4f3e4cf
|
||||
Reference in New Issue
Block a user