Phase 1 A/C/D: void room fixed, authored portal + spawn FX, verbs finally taught
SLICE A - rooms become places: ExpeditionBiome duplicated to sub-slot 1 (X+1500) - ground, 205 decor children, rim cliffs, ambience; collider ring + 3 landmark colliders duplicated in the subscene; NEW in-room cover at BOTH slots (4 collidable rocks each: Environment-layer box colliders in the subscene + matching Synty rock meshes in Game.unity, placed clear of the landing point, portal spot, spawn ring and boss spawn). Half of all rooms previously played on a literal void. SLICE C - owned FX wired: VFXConfig gains Portal + EnemySpawn slots (FX_Portal_Round_01 / FX_Dust_Big_01 from PolygonParticleFX); the portal beacon prefers the authored effect (procedural pillar stays as fallback); enemies get a spawn-emerge cue on the ghost add-edge (primed-scan guard skips the connect flood). SLICE D (teaching) - the two undiscoverable verbs are now taught: dash on the Move step, class ability + attack on the Rooms step (scheme-aware glyphs); How-to-Play Controls adds Class ability + Dash rows and fixes the wrong RT glyph; Loop step 1 mentions class pick + prep; the stale '50 starting Ore' now interpolates Tuning.StartingOre. 456/456 EditMode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,117 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e44b4d3f96f40294d86af33480634e84
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -107,11 +107,11 @@ namespace ProjectM.Client
|
|||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case Welcome: return "CLEAR 2 EXPEDITIONS to charge the Engine, then survive the FINAL SIEGE. (Space to continue · Esc → How to Play)";
|
case Welcome: return "CLEAR 2 EXPEDITIONS to charge the Engine, then survive the FINAL SIEGE. (Space to continue · Esc → How to Play)";
|
||||||
case Move: return move + " — Move";
|
case Move: return move + " — Move " + (gamepad ? "B" : "LShift") + " — Dash (brief invulnerability)";
|
||||||
case Build: return build + " — open Build, place a Turret by your Core (your 50 starting Ore covers it)";
|
case Build: return build + " — open Build, place a Turret by your Core (your " + ProjectM.Simulation.Tuning.StartingOre + " starting Ore covers it)";
|
||||||
case Fabricator: return "Build a Fabricator — turrets need Charge (Ore → ammo)";
|
case Fabricator: return "Build a Fabricator — turrets need Charge (Ore → ammo)";
|
||||||
case ReadyUp: return "Press T (or click READY UP) — when everyone is ready, the party launches";
|
case ReadyUp: return "Press T (or click READY UP) — when everyone is ready, the party launches";
|
||||||
case Rooms: return "Fight through the rooms — attack the glowing crystals to haul resources home";
|
case Rooms: return (gamepad ? "X attack · LT class ability" : "LMB attack · RMB class ability") + " — clear the room; shoot the glowing crystals for your haul";
|
||||||
case Boon: return "Clear the room — pick 1 of 3 BOONS, then choose your path on the map";
|
case Boon: return "Clear the room — pick 1 of 3 BOONS, then choose your path on the map";
|
||||||
case Return: return "Fell the ALPHA HUSK, then return home — your haul + the Engine charge (+1) come with you";
|
case Return: return "Fell the ALPHA HUSK, then return home — your haul + the Engine charge (+1) come with you";
|
||||||
case Defend: return "Defend the Core! — a completed run provokes a retaliation siege";
|
case Defend: return "Defend the Core! — a completed run provokes a retaliation siege";
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ namespace ProjectM.Client
|
|||||||
struct FxCache { public float Hp; public float MaxHp; public float3 Pos; public bool IsEnemy; public uint Windup; }
|
struct FxCache { public float Hp; public float MaxHp; public float3 Pos; public bool IsEnemy; public uint Windup; }
|
||||||
|
|
||||||
readonly Dictionary<Entity, FxCache> _cache = new();
|
readonly Dictionary<Entity, FxCache> _cache = new();
|
||||||
|
bool _scanPrimed; // Phase 1: first health-scan completed -> new cache entries are true spawns, not the connect flood
|
||||||
|
GameObject _portalFx; // Phase 1: authored portal effect (VFXConfig.Portal) replacing the procedural pillar when wired
|
||||||
readonly HashSet<Entity> _seen = new();
|
readonly HashSet<Entity> _seen = new();
|
||||||
readonly List<Entity> _stale = new();
|
readonly List<Entity> _stale = new();
|
||||||
readonly List<FloatingNumber> _numbers = new();
|
readonly List<FloatingNumber> _numbers = new();
|
||||||
@@ -294,9 +296,19 @@ namespace ProjectM.Client
|
|||||||
PrototypeCameraRig.AddShake(isLocalPlayer ? FeelConfig.PlayerDeathShake : FeelConfig.RemotePlayerDeathShake);
|
PrototypeCameraRig.AddShake(isLocalPlayer ? FeelConfig.PlayerDeathShake : FeelConfig.RemotePlayerDeathShake);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (isEnemy && _scanPrimed && cur >= health.ValueRO.Max - 0.001f)
|
||||||
|
{
|
||||||
|
// Phase 1 spawn-emerge cue: FIRST sighting of a fresh (full-HP) enemy ghost = its spawn
|
||||||
|
// (rooms drip-spawn AFTER the player lands, so the add-edge IS the spawn moment). The primed
|
||||||
|
// guard skips the initial-connect flood; a relevancy re-entry of an undamaged enemy may
|
||||||
|
// rarely re-cue - accepted juice noise, never gameplay.
|
||||||
|
Burst(_hitFx, cfg != null ? cfg.EnemySpawn : null, (Vector3)p + Vector3.up * 0.15f, 8);
|
||||||
|
PlayClip(_telegraphClip, (Vector3)p, 0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
_cache[entity] = new FxCache { Hp = cur, MaxHp = health.ValueRO.Max, Pos = p, IsEnemy = isEnemy, Windup = windup };
|
_cache[entity] = new FxCache { Hp = cur, MaxHp = health.ValueRO.Max, Pos = p, IsEnemy = isEnemy, Windup = windup };
|
||||||
}
|
}
|
||||||
|
_scanPrimed = true; // Phase 1: everything cached after the first scan; later add-edges are true spawns
|
||||||
|
|
||||||
// Prune despawned ghosts. A Husk that vanished was killed -> death VFX at its last position.
|
// Prune despawned ghosts. A Husk that vanished was killed -> death VFX at its last position.
|
||||||
if (_cache.Count != _seen.Count)
|
if (_cache.Count != _seen.Count)
|
||||||
@@ -1003,9 +1015,25 @@ void TriggerSlash(Vector3 pos, float2 facing, float range, float halfAngle, int
|
|||||||
if (!inExplore || !SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
|
if (!inExplore || !SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
|
||||||
{
|
{
|
||||||
if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
|
if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
|
||||||
|
if (_portalFx != null && _portalFx.activeSelf) _portalFx.SetActive(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float3 pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(ri.CurrentRoom & 1));
|
float3 pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(ri.CurrentRoom & 1));
|
||||||
|
// Phase 1: prefer the authored portal effect (VFXConfig.Portal, PolygonParticleFX) over the
|
||||||
|
// procedural pillar; the pillar remains the asset-free fallback.
|
||||||
|
var vfx = VFXConfig.Instance;
|
||||||
|
if (vfx != null && vfx.Portal != null)
|
||||||
|
{
|
||||||
|
if (_portalFx == null)
|
||||||
|
{
|
||||||
|
_portalFx = Object.Instantiate(vfx.Portal, _fxRoot, false);
|
||||||
|
_portalFx.name = "~RoomPortalFx";
|
||||||
|
}
|
||||||
|
_portalFx.transform.position = new Vector3(pos.x, 0f, pos.z); // terrain y=0 (pos.y is the capsule plane)
|
||||||
|
if (!_portalFx.activeSelf) _portalFx.SetActive(true);
|
||||||
|
if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_portalBeacon == null)
|
if (_portalBeacon == null)
|
||||||
{
|
{
|
||||||
_portalBeacon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
_portalBeacon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ namespace ProjectM.Client
|
|||||||
public GameObject EnemyDeath;
|
public GameObject EnemyDeath;
|
||||||
[Tooltip("Spawned when the local player drops to 0 HP (falls back to EnemyDeath if null).")]
|
[Tooltip("Spawned when the local player drops to 0 HP (falls back to EnemyDeath if null).")]
|
||||||
public GameObject PlayerDeath;
|
public GameObject PlayerDeath;
|
||||||
|
[Tooltip("Looping portal effect anchored at the room-exit during RoomExplore (falls back to the procedural pillar if null).")]
|
||||||
|
public GameObject Portal;
|
||||||
|
[Tooltip("One-shot at each enemy's spawn position (first sighting of a fresh ghost).")]
|
||||||
|
public GameObject EnemySpawn;
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ namespace ProjectM.Client
|
|||||||
Head(c, ranger ? "CLASS: Ranger (ranged anchor)" : "CLASS: Warrior (melee anchor)");
|
Head(c, ranger ? "CLASS: Ranger (ranged anchor)" : "CLASS: Warrior (melee anchor)");
|
||||||
Body(c, "Move — WASD / Left Stick");
|
Body(c, "Move — WASD / Left Stick");
|
||||||
Body(c, "Aim — Mouse cursor / Right Stick");
|
Body(c, "Aim — Mouse cursor / Right Stick");
|
||||||
Body(c, "Attack — LMB / RT (your primary verb)");
|
Body(c, "Attack — LMB / X (3-hit melee combo — the finisher hits hard)");
|
||||||
|
Body(c, ranger ? "Class ability — RMB / LT (aimed power shot)" : "Class ability — RMB / LT (cone slam with knockback)");
|
||||||
|
Body(c, "Dash — LShift / B (brief invulnerability — dodge THROUGH attacks)");
|
||||||
Body(c, "Build menu — Tab / Y");
|
Body(c, "Build menu — Tab / Y");
|
||||||
Body(c, "Place / Cancel (in build) — LMB / RMB (A / B on gamepad)");
|
Body(c, "Place / Cancel (in build) — LMB / RMB (A / B on gamepad)");
|
||||||
Body(c, "Deposit at base — G");
|
Body(c, "Deposit at base — G");
|
||||||
@@ -84,7 +86,7 @@ namespace ProjectM.Client
|
|||||||
break;
|
break;
|
||||||
case 1: // The Loop
|
case 1: // The Loop
|
||||||
Head(c, "THE LOOP — expedition RUNS are how you win");
|
Head(c, "THE LOOP — expedition RUNS are how you win");
|
||||||
Body(c, "1. BASE — build Turrets + a Fabricator around your Engine Core; spend Aether on PERMANENT class upgrades.");
|
Body(c, "1. BASE — pick your CLASS + buy PREP buffs (this run only); build Turrets + a Fabricator; spend Aether on PERMANENT class upgrades.");
|
||||||
Body(c, "2. READY UP [T] — when everyone is ready, the party launches into a multi-room run together.");
|
Body(c, "2. READY UP [T] — when everyone is ready, the party launches into a multi-room run together.");
|
||||||
Body(c, "3. RUN — clear each room, pick 1 of 3 BOONS (this run only), choose your path on the map, mine the crystals.");
|
Body(c, "3. RUN — clear each room, pick 1 of 3 BOONS (this run only), choose your path on the map, mine the crystals.");
|
||||||
Body(c, "4. BOSS — fell it and the party returns with the haul: +1 on the Engine meter. A wipe banks nothing.");
|
Body(c, "4. BOSS — fell it and the party returns with the haul: +1 on the Engine meter. A wipe banks nothing.");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user