[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.
|
||||
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 },
|
||||
|
||||
// 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 },
|
||||
|
||||
@ -72,6 +72,21 @@ const ARCHETYPES = {
|
||||
color: EMISSIVE.neutral,
|
||||
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 }) {
|
||||
@ -148,7 +163,7 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
||||
hp: cfg.hp, radius: cfg.radius, alive: true,
|
||||
pos: new THREE.Vector3(), flash: 0, phase: rand() * Math.PI * 2,
|
||||
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);
|
||||
pool.slots.push(e);
|
||||
@ -178,6 +193,10 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
||||
onDeath?.(e);
|
||||
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;
|
||||
bus.emit('enemy:die', { id: e.id, type: e.type, s: e.s, score: e.cfg.score, kind });
|
||||
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);
|
||||
const cfg = e.cfg;
|
||||
|
||||
if (e.type === 'floater' || e.type === 'spore_pod') {
|
||||
// drifts and bobs; taxes the lane it occupies rather than chasing. spore_pod shares
|
||||
// this behaviour exactly — it's a floater with its own pool/mesh (a yeast cluster).
|
||||
if (e.type === 'spore_cloud') {
|
||||
// A lingering corrosive spore cloud (C. diff's parting gift). Static — it does NOT move;
|
||||
// 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.s += Math.sin(e.phase) * 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),
|
||||
* `phage` (the first ally — a friendly seeker that hunts foes), and `mucus_grazer` (a commensal
|
||||
* 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:
|
||||
@ -109,6 +109,14 @@ export const ENEMIES = Object.freeze({
|
||||
'allies quicker. Cyan (a commensal), amoeba-blob shape so the coat cost reads as a ' +
|
||||
'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 --------------------------------------------
|
||||
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
||||
|
||||
Loading…
Reference in New Issue
Block a user