Fix: barrel fuse shows its blast radius + burns ~1.5s (was 0.6s)
Operator feel report: the explosion radius was unreadable and the fuse too short to react to. Two changes: - Tuning.BarrelFuseTicks 36 -> 90 (~1.5s): long enough to READ the danger and walk out, and better for baiting enemies onto a lit barrel. - WorldFeedbackSystem draws a pulsing ground DISC at exactly Tuning.BarrelExplodeRadius under every lit fuse (keyed on the same replicated Remaining=0-on-a-live-barrel cue as the boom split; one shared pulsing material; removed via the existing prune path). Verified live: disc appeared 0.07s after the pop (first fuse snapshot), persisted to the boom at 1.54s, scale read exactly 3.25 = the true detonation radius; 470/470 EditMode; console clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,9 @@ namespace ProjectM.Client
|
|||||||
AudioClip _chipClip;
|
AudioClip _chipClip;
|
||||||
AudioClip _clearClip;
|
AudioClip _clearClip;
|
||||||
AudioClip _boomClip;
|
AudioClip _boomClip;
|
||||||
|
readonly Dictionary<Entity, GameObject> _fuseRings = new(); // blast-radius discs, one per lit fuse
|
||||||
|
Mesh _fuseDiscMesh;
|
||||||
|
Material _fuseMat;
|
||||||
|
|
||||||
protected override void OnCreate()
|
protected override void OnCreate()
|
||||||
{
|
{
|
||||||
@@ -52,11 +55,19 @@ namespace ProjectM.Client
|
|||||||
var mat = MakeParticleMaterial();
|
var mat = MakeParticleMaterial();
|
||||||
_chipFx = MakeBurst(_fxRoot, "HarvestChips", mat, new Color(2.6f, 1.9f, 0.7f), 0.10f, 5f, 0.30f, 256, 0.25f, 0.10f, 0.2f);
|
_chipFx = MakeBurst(_fxRoot, "HarvestChips", mat, new Color(2.6f, 1.9f, 0.7f), 0.10f, 5f, 0.30f, 256, 0.25f, 0.10f, 0.2f);
|
||||||
_clearFx = MakeBurst(_fxRoot, "ClutterClear", mat, new Color(3.0f, 1.1f, 0.25f), 0.16f, 7f, 0.45f, 512, 0.25f, 0.10f, 0.2f);
|
_clearFx = MakeBurst(_fxRoot, "ClutterClear", mat, new Color(3.0f, 1.1f, 0.25f), 0.16f, 7f, 0.45f, 512, 0.25f, 0.10f, 0.2f);
|
||||||
|
// Blast-radius disc for lit barrel fuses (operator 07-10: the radius was unreadable) — a flat ground
|
||||||
|
// disc at EXACTLY Tuning.BarrelExplodeRadius, pulsing via the shared material's alpha.
|
||||||
|
_fuseMat = MakeParticleMaterial();
|
||||||
|
_fuseMat.name = "BarrelFuseDisc";
|
||||||
|
_fuseMat.color = new Color(2.8f, 0.55f, 0.18f, 0.35f);
|
||||||
|
_fuseDiscMesh = BuildDisc(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
{
|
{
|
||||||
if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject);
|
if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject); // fuse rings are children -> die with it
|
||||||
|
if (_fuseMat != null) Object.Destroy(_fuseMat);
|
||||||
|
if (_fuseDiscMesh != null) Object.Destroy(_fuseDiscMesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
@@ -93,6 +104,15 @@ namespace ProjectM.Client
|
|||||||
{
|
{
|
||||||
_seen.Add(e);
|
_seen.Add(e);
|
||||||
Observe(e, clutter.ValueRO.Remaining, xf.ValueRO.Position, true, WorldFeelConfig.WildTint, clutter.ValueRO.Variant, haveLocal && math.distancesq(xf.ValueRO.Position, localPos) <= WorldFeelConfig.ProximityRange * WorldFeelConfig.ProximityRange);
|
Observe(e, clutter.ValueRO.Remaining, xf.ValueRO.Position, true, WorldFeelConfig.WildTint, clutter.ValueRO.Variant, haveLocal && math.distancesq(xf.ValueRO.Position, localPos) <= WorldFeelConfig.ProximityRange * WorldFeelConfig.ProximityRange);
|
||||||
|
UpdateFuseRing(e, clutter.ValueRO, xf.ValueRO.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// blast-radius discs pulse in sync (one shared material)
|
||||||
|
if (_fuseRings.Count > 0 && _fuseMat != null)
|
||||||
|
{
|
||||||
|
float fa = 0.30f + 0.22f * Mathf.Abs(Mathf.Sin(UnityEngine.Time.time * 9f));
|
||||||
|
var fc = _fuseMat.color;
|
||||||
|
_fuseMat.color = new Color(fc.r, fc.g, fc.b, fa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune: a despawn = the server destroyed it (node depleted / clutter shattered). Gate on proximity so
|
// Prune: a despawn = the server destroyed it (node depleted / clutter shattered). Gate on proximity so
|
||||||
@@ -131,6 +151,11 @@ namespace ProjectM.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_fuseRings.TryGetValue(_stale[i], out var ring))
|
||||||
|
{
|
||||||
|
if (ring != null) Object.Destroy(ring);
|
||||||
|
_fuseRings.Remove(_stale[i]);
|
||||||
|
}
|
||||||
_cache.Remove(_stale[i]);
|
_cache.Remove(_stale[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +176,49 @@ void Observe(Entity e, int remaining, float3 pos, bool isClutter, Color tint, by
|
|||||||
_cache[e] = new Cache { Remaining = remaining, Pos = pos, IsClutter = isClutter, Tint = tint, Variant = variant };
|
_cache[e] = new Cache { Remaining = remaining, Pos = pos, IsClutter = isClutter, Tint = tint, Variant = variant };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lit fuse (Variant 3, Remaining 0 on a live barrel) -> a flat ground disc at EXACTLY the detonation
|
||||||
|
// radius so the player reads WHERE not to stand. Removed via the prune path (a fused barrel can only
|
||||||
|
// despawn — detonation or teardown).
|
||||||
|
void UpdateFuseRing(Entity e, BlightClutter clutter, float3 pos)
|
||||||
|
{
|
||||||
|
if (clutter.Variant != 3 || clutter.Remaining > 0) return;
|
||||||
|
if (!_fuseRings.TryGetValue(e, out var go) || go == null)
|
||||||
|
{
|
||||||
|
go = new GameObject("BarrelBlastRadius");
|
||||||
|
go.transform.SetParent(_fxRoot, false);
|
||||||
|
var mf = go.AddComponent<MeshFilter>();
|
||||||
|
mf.sharedMesh = _fuseDiscMesh;
|
||||||
|
var mr = go.AddComponent<MeshRenderer>();
|
||||||
|
mr.sharedMaterial = _fuseMat;
|
||||||
|
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||||
|
mr.receiveShadows = false;
|
||||||
|
go.transform.localScale = new Vector3(Tuning.BarrelExplodeRadius, 1f, Tuning.BarrelExplodeRadius);
|
||||||
|
_fuseRings[e] = go;
|
||||||
|
}
|
||||||
|
go.transform.position = new Vector3(pos.x, 0.06f, pos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit-radius upward-facing disc fan; scaled per ring to the blast radius.
|
||||||
|
static Mesh BuildDisc(int segments)
|
||||||
|
{
|
||||||
|
var m = new Mesh { name = "FuseDisc" };
|
||||||
|
var v = new Vector3[segments + 1];
|
||||||
|
var tris = new int[segments * 3];
|
||||||
|
v[0] = Vector3.zero;
|
||||||
|
for (int i = 0; i < segments; i++)
|
||||||
|
{
|
||||||
|
float a = i / (float)segments * Mathf.PI * 2f;
|
||||||
|
v[i + 1] = new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a));
|
||||||
|
int n = (i + 1) % segments;
|
||||||
|
tris[i * 3] = 0; tris[i * 3 + 1] = n + 1; tris[i * 3 + 2] = i + 1;
|
||||||
|
}
|
||||||
|
m.vertices = v;
|
||||||
|
m.triangles = tris;
|
||||||
|
m.RecalculateBounds();
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static Color TintForResource(byte resourceId)
|
static Color TintForResource(byte resourceId)
|
||||||
{
|
{
|
||||||
if (resourceId == ResourceId.Ore) return WorldFeelConfig.OreTint;
|
if (resourceId == ResourceId.Ore) return WorldFeelConfig.OreTint;
|
||||||
|
|||||||
@@ -109,9 +109,10 @@ namespace ProjectM.Simulation
|
|||||||
|
|
||||||
// ---- Exploding barrels (Phase 1.5 hazard; server-only detonation consts — see Exploding_Barrels_Build_Spec) ----
|
// ---- Exploding barrels (Phase 1.5 hazard; server-only detonation consts — see Exploding_Barrels_Build_Spec) ----
|
||||||
|
|
||||||
/// <summary>Fuse ticks between the pop (Remaining hits 0) and detonation (~0.6 s @60): long enough for the
|
/// <summary>Fuse ticks between the pop (Remaining hits 0) and detonation (~1.5 s @60; operator feel call
|
||||||
/// popper to step out + for the replicated Remaining=0 to straddle snapshots (the client's fuse cue).</summary>
|
/// 07-10 — the 0.6 s fuse popped before the danger could be read): long enough to READ the blast-radius
|
||||||
public const uint BarrelFuseTicks = 36;
|
/// disc + walk out, and for the replicated Remaining=0 to straddle snapshots (the client's fuse cue).</summary>
|
||||||
|
public const uint BarrelFuseTicks = 90;
|
||||||
|
|
||||||
/// <summary>Detonation radius (world units, XZ). Above melee reach so point-blank pops are punished, but a
|
/// <summary>Detonation radius (world units, XZ). Above melee reach so point-blank pops are punished, but a
|
||||||
/// fuse-length walk clears it comfortably.</summary>
|
/// fuse-length walk clears it comfortably.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user