[lane B+C] C. difficile: the kill that isn't clean
A foe whose corpse keeps hurting — the definitive colon enemy. C. diff drifts with a corrosive aura like any floater, but on death it leaves a SPORE_CLOUD: a static acid-green zone that damages anything in it for ~5s, then disperses. Killing it costs you the ground it died on (shoot the cloud to clear it, or fly around). - enemies.js: two archetypes. `cdiff` (amber octahedron, shares the floater drift+aura branch) drops a `spore_cloud` in kill(), placed at the death spot. `spore_cloud` (acid-green per the readability law, foe:false so the phage ignores it and clearing it never scores) is static, damages on contact, and counts down a `life`. Entities gained a `life` field (only the cloud uses it). - levels/enemies.js: ARCHETYPES += cdiff; catalogue `cdiff_bloom` (biome large_intestine — registered for L5's encounter pass, not shoehorned into the esophagus). Verified: killing a cdiff dropped a cloud at its death spot; sitting in it dropped coat 100->89; the cloud expired after its life. Amber octahedron vs acid-green blob — clean read. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
aae1ebdcfd
commit
b0abbaf76b
@ -79,6 +79,18 @@ export const BALANCE = {
|
|||||||
// payoff for tending flora. escort* = where it loiters when no foe is in `huntS` range.
|
// payoff for tending flora. escort* = where it loiters when no foe is in `huntS` range.
|
||||||
phage: { hp: 8, radius: 1.1, score: 0, speed: 26, turn: 3.2, punch: 20, bursts: 2,
|
phage: { hp: 8, radius: 1.1, score: 0, speed: 26, turn: 3.2, punch: 20, bursts: 2,
|
||||||
huntS: 70, escortLead: 10, escortR: 5, pool: 8 },
|
huntS: 70, escortLead: 10, escortR: 5, pool: 8 },
|
||||||
|
|
||||||
|
// cdiff (C. difficile) — a foe floater (drift + corrosive aura) whose corpse ISN'T clean:
|
||||||
|
// on death it leaves a spore_cloud (enemies.js kill()). Killing it costs you the ground it
|
||||||
|
// died on. A colon (L5) enemy; registered now, placed when L5 gets its encounter pass.
|
||||||
|
cdiff: { hp: 26, radius: 2.3, score: 140, drift: 1.2, bob: 0.45,
|
||||||
|
auraRadius: 4.0, auraDps: 14, auraTick: 0.2, pool: 12 },
|
||||||
|
|
||||||
|
// spore_cloud — C. diff's parting gift. A STATIC corrosive zone (acid-green, readability law)
|
||||||
|
// that damages on contact and expires after `life`s. Not scored (foe:false), but shootable:
|
||||||
|
// low hp so you CAN clear it with the cannon — spend heat to disperse it, or fly around.
|
||||||
|
spore_cloud: { hp: 14, radius: 3.2, score: 0, auraRadius: 4.5, auraDps: 22, auraTick: 0.25,
|
||||||
|
life: 5, pool: 12 },
|
||||||
},
|
},
|
||||||
|
|
||||||
darts: { pool: 48, radius: 0.45 },
|
darts: { pool: 48, radius: 0.45 },
|
||||||
|
|||||||
@ -72,6 +72,21 @@ const ARCHETYPES = {
|
|||||||
color: EMISSIVE.neutral,
|
color: EMISSIVE.neutral,
|
||||||
foe: false,
|
foe: false,
|
||||||
},
|
},
|
||||||
|
// cdiff (C. difficile) — a foe floater whose corpse leaves a spore_cloud (kill()). Amber,
|
||||||
|
// an angular octahedron so it reads distinct from the round yeast cluster.
|
||||||
|
cdiff: {
|
||||||
|
cfg: B.enemies.cdiff,
|
||||||
|
geo: () => new THREE.OctahedronGeometry(B.enemies.cdiff.radius, 0),
|
||||||
|
color: EMISSIVE.hostile,
|
||||||
|
},
|
||||||
|
// spore_cloud — the lingering corrosive zone C. diff drops on death. Acid-green (readability
|
||||||
|
// law: acid/corrosive), foe:false so the phage ignores it and clearing it never scores.
|
||||||
|
spore_cloud: {
|
||||||
|
cfg: B.enemies.spore_cloud,
|
||||||
|
geo: () => new THREE.IcosahedronGeometry(B.enemies.spore_cloud.radius, 1),
|
||||||
|
color: EMISSIVE.acid,
|
||||||
|
foe: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
||||||
@ -148,7 +163,7 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
|||||||
hp: cfg.hp, radius: cfg.radius, alive: true,
|
hp: cfg.hp, radius: cfg.radius, alive: true,
|
||||||
pos: new THREE.Vector3(), flash: 0, phase: rand() * Math.PI * 2,
|
pos: new THREE.Vector3(), flash: 0, phase: rand() * Math.PI * 2,
|
||||||
cooldown: rand() * (cfg.fireEvery ?? 1), // desync turret volleys
|
cooldown: rand() * (cfg.fireEvery ?? 1), // desync turret volleys
|
||||||
auraT: 0,
|
auraT: 0, life: cfg.life ?? 0, // life: only spore_cloud counts down
|
||||||
};
|
};
|
||||||
live.push(e);
|
live.push(e);
|
||||||
pool.slots.push(e);
|
pool.slots.push(e);
|
||||||
@ -178,6 +193,10 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
|||||||
onDeath?.(e);
|
onDeath?.(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (e.type === 'cdiff') { // the corpse keeps hurting: leave a spore cloud
|
||||||
|
const sp = spawn('spore_cloud', { s: e.s });
|
||||||
|
if (sp) { sp.x = e.x; sp.y = e.y; } // at the death spot, not a random arc
|
||||||
|
}
|
||||||
comboN++; comboT = B.combo.window;
|
comboN++; comboT = B.combo.window;
|
||||||
bus.emit('enemy:die', { id: e.id, type: e.type, s: e.s, score: e.cfg.score, kind });
|
bus.emit('enemy:die', { id: e.id, type: e.type, s: e.s, score: e.cfg.score, kind });
|
||||||
bus.emit('combo', { n: comboN });
|
bus.emit('combo', { n: comboN });
|
||||||
@ -221,9 +240,23 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
|||||||
e.flash = Math.max(0, e.flash - dt * 6);
|
e.flash = Math.max(0, e.flash - dt * 6);
|
||||||
const cfg = e.cfg;
|
const cfg = e.cfg;
|
||||||
|
|
||||||
if (e.type === 'floater' || e.type === 'spore_pod') {
|
if (e.type === 'spore_cloud') {
|
||||||
// drifts and bobs; taxes the lane it occupies rather than chasing. spore_pod shares
|
// A lingering corrosive spore cloud (C. diff's parting gift). Static — it does NOT move;
|
||||||
// this behaviour exactly — it's a floater with its own pool/mesh (a yeast cluster).
|
// it just damages on contact and expires after `life`. Killing the C. diff isn't clean.
|
||||||
|
e.life -= dt;
|
||||||
|
if (e.life <= 0) { kill(e, 'expire'); continue; }
|
||||||
|
if (ps.alive) {
|
||||||
|
const d = Math.hypot(e.s - ps.s, e.x - ps.x, e.y - ps.y);
|
||||||
|
e.auraT -= dt;
|
||||||
|
if (d < cfg.auraRadius + player.radius && e.auraT <= 0) {
|
||||||
|
player.damage(cfg.auraDps * cfg.auraTick, 'spore');
|
||||||
|
e.auraT = cfg.auraTick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.type === 'floater' || e.type === 'spore_pod' || e.type === 'cdiff') {
|
||||||
|
// drifts and bobs; taxes the lane it occupies rather than chasing. spore_pod (yeast) and
|
||||||
|
// cdiff (C. difficile) share this exactly — floaters with their own pool/mesh. cdiff's
|
||||||
|
// twist is only at death (kill() drops a spore_cloud), never in how it moves.
|
||||||
e.phase += dt * cfg.bob;
|
e.phase += dt * cfg.bob;
|
||||||
e.s += Math.sin(e.phase) * cfg.drift * dt;
|
e.s += Math.sin(e.phase) * cfg.drift * dt;
|
||||||
e.x += Math.cos(e.phase * 0.7) * cfg.drift * dt;
|
e.x += Math.cos(e.phase * 0.7) * cfg.drift * dt;
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
* Round-2 microbe pass added `flora` (the first friend), `spore_pod` (a distinct-mesh foe),
|
* Round-2 microbe pass added `flora` (the first friend), `spore_pod` (a distinct-mesh foe),
|
||||||
* `phage` (the first ally — a friendly seeker that hunts foes), and `mucus_grazer` (a commensal
|
* `phage` (the first ally — a friendly seeker that hunts foes), and `mucus_grazer` (a commensal
|
||||||
* that trades your coat for faster standing). */
|
* that trades your coat for faster standing). */
|
||||||
export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage', 'mucus_grazer'];
|
export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage', 'mucus_grazer', 'cdiff'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fields:
|
* Fields:
|
||||||
@ -109,6 +109,14 @@ export const ENEMIES = Object.freeze({
|
|||||||
'allies quicker. Cyan (a commensal), amoeba-blob shape so the coat cost reads as a ' +
|
'allies quicker. Cyan (a commensal), amoeba-blob shape so the coat cost reads as a ' +
|
||||||
'different bug, not a betrayal by the healing rod.',
|
'different bug, not a betrayal by the healing rod.',
|
||||||
},
|
},
|
||||||
|
cdiff_bloom: {
|
||||||
|
archetype: 'cdiff', biome: 'large_intestine', tint: 0xff7a2a, wall: false,
|
||||||
|
note: 'Clostridioides difficile — waits for antibiotics to clear the neighbours, then blooms ' +
|
||||||
|
'and paints the colon in pseudomembrane, leaving armoured SPORES. In-game a floater ' +
|
||||||
|
'with a corrosive aura whose corpse ISN\'T clean: killing it drops a spore_cloud that ' +
|
||||||
|
'keeps hurting the ground it died on (shoot it to disperse, or fly around). The colon\'s ' +
|
||||||
|
'definitive foe; belongs to L5, registered so C can place it when L5 gets its pass.',
|
||||||
|
},
|
||||||
|
|
||||||
// ---- passthrough: bare archetype names --------------------------------------------
|
// ---- passthrough: bare archetype names --------------------------------------------
|
||||||
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user