Release v0.1.4 2026-07-12

This commit is contained in:
Warren H
2026-07-12 13:49:46 -04:00
parent b48b3a4f8f
commit bef71d391a
803 changed files with 3800 additions and 358322 deletions
+68 -75
View File
@@ -1,14 +1,15 @@
import { BOSS_DEFINITIONS } from "./bossCatalog";
import { BOSS_ARCHETYPE_BY_ID, BOSS_DEFINITIONS, type BossArchetype } from "./bossCatalog";
import { clampToArena } from "./arena";
import { advanceCindermawMechanics, createCindermawMotion, createCindermawState, upcomingCindermawMechanic } from "./bosses/cindermaw";
import { advanceCinderbackMechanics, createCinderbackMotion, createCinderbackState, upcomingCinderbackMechanic } from "./bosses/cinderbackRicochet";
import { advanceSkySweeperMechanics, createSkySweeperMotion, createSkySweeperState, upcomingSkySweeperMechanic } from "./bosses/skySweeper";
import { advanceCinderbackMechanics, createCinderbackMotion, createCinderbackState, upcomingCinderbackMechanic } from "./bosses/ricochet";
import { advanceCragclawMechanics, createCragclawMotion, createCragclawState, upcomingCragclawMechanic } from "./bosses/cragclawCrab";
import { advanceCrownshardMechanics, createCrownshardMotion, createCrownshardState, upcomingCrownshardMechanic } from "./bosses/crownshardGolem";
import { advanceEmberMantisMechanics, createEmberMantisMotion, createEmberMantisState, upcomingEmberMantisMechanic } from "./bosses/emberMantis";
import { advanceMournveilMechanics, createMournveilMotion, createMournveilState, upcomingMournveilMechanic } from "./bosses/mournveilGhost";
import { advanceObsidianRamMechanics, createObsidianRamMotion, createObsidianRamState, upcomingObsidianRamMechanic } from "./bosses/obsidianRamGolem";
import { advanceSandglassMechanics, createSandglassMotion, createSandglassState, upcomingSandglassMechanic } from "./bosses/sandglassScorpion";
import { createBaseMotion } from "./bosses/shared";
import { createBaseMotion, returnBossToArenaCenter } from "./bosses/shared";
import { advancePooledBossMechanics, upcomingPooledMechanic } from "./bosses/mechanicPool";
import type { BossMechanicContext, BossMechanicEvent, BossMechanicResult } from "./bosses/types";
import { advanceVexaMechanics, createVexaMotion, createVexaState, dropVexaVenomPool, upcomingVexaMechanic } from "./bosses/vexa";
import { distance, moveToward, pointToSegmentDistance } from "./geometry";
@@ -45,54 +46,46 @@ const CHARGE_TARGET_ORDER: readonly MemberId[] = ["nia", "orin", "vale", "aelia"
const POUNCE_TARGET_ORDER: readonly MemberId[] = ["aelia", "nia", "orin", "vale", "brann"];
export function createBossState(bossId: BossId = "bulldrome"): BossState {
if (bossId === "vexa") return createVexaState();
if (bossId === "cindermaw") return createCindermawState();
if (bossId === "ember-mantis-duelist") return createEmberMantisState();
if (bossId === "obsidian-ram-golem") return createObsidianRamState();
if (bossId === "cinderback-ricochet") return createCinderbackState();
if (bossId === "sandglass-scorpion") return createSandglassState();
if (bossId === "cragclaw-crab") return createCragclawState();
if (bossId === "mournveil-ghost") return createMournveilState();
if (bossId === "crownshard-golem") return createCrownshardState();
if (bossId === "pumpking-king-of-ghosts" || bossId === "blue-eyes-ultimate-dragon") {
const definition = BOSS_DEFINITIONS[bossId];
return {
id: definition.id,
const definition = BOSS_DEFINITIONS[bossId];
const archetype = BOSS_ARCHETYPE_BY_ID[bossId];
let state: BossState;
if (archetype === "web-caster") state = createVexaState(bossId);
else if (archetype === "sky-sweeper") state = createSkySweeperState(bossId);
else if (archetype === "duelist") state = createEmberMantisState(bossId);
else if (archetype === "ram") state = createObsidianRamState(bossId);
else if (archetype === "ricochet") state = createCinderbackState(bossId);
else if (archetype === "burrower") state = createSandglassState();
else if (archetype === "crab") state = createCragclawState();
else if (archetype === "ghost") state = createMournveilState();
else if (archetype === "golem") state = createCrownshardState();
else {
state = {
id: bossId,
name: definition.name,
maxHp: definition.maxHp,
hp: definition.maxHp,
nextMeleeAt: bossId === "pumpking-king-of-ghosts" ? 2.35 : 2.4,
nextNovaAt: Number.POSITIVE_INFINITY,
nextBrandAt: Number.POSITIVE_INFINITY,
nextMeleeAt: BOSS_PERIODIC_MECHANICS.melee.firstAt,
nextNovaAt: BOSS_PERIODIC_MECHANICS.nova.firstAt,
nextBrandAt: BOSS_PERIODIC_MECHANICS.brand.firstAt,
brandCount: 0,
};
}
const definition = BOSS_DEFINITIONS.bulldrome;
return {
id: "bulldrome",
name: definition.name,
maxHp: definition.maxHp,
hp: definition.maxHp,
nextMeleeAt: BOSS_PERIODIC_MECHANICS.melee.firstAt,
nextNovaAt: BOSS_PERIODIC_MECHANICS.nova.firstAt,
nextBrandAt: BOSS_PERIODIC_MECHANICS.brand.firstAt,
brandCount: 0,
};
return { ...state, id: bossId, name: definition.name, maxHp: definition.maxHp, hp: definition.maxHp };
}
export function createBossMotionState(bossId: BossId = "bulldrome"): BossMotionState {
if (bossId === "vexa") return createVexaMotion();
if (bossId === "cindermaw") return createCindermawMotion();
if (bossId === "ember-mantis-duelist") return createEmberMantisMotion();
if (bossId === "obsidian-ram-golem") return createObsidianRamMotion();
if (bossId === "cinderback-ricochet") return createCinderbackMotion();
if (bossId === "sandglass-scorpion") return createSandglassMotion();
if (bossId === "cragclaw-crab") return createCragclawMotion();
if (bossId === "mournveil-ghost") return createMournveilMotion();
if (bossId === "crownshard-golem") return createCrownshardMotion();
if (bossId === "pumpking-king-of-ghosts") return { ...createMournveilMotion(), bossId };
if (bossId === "blue-eyes-ultimate-dragon") return { ...createCrownshardMotion(), bossId };
return {
const archetype = BOSS_ARCHETYPE_BY_ID[bossId];
let motion: BossMotionState;
if (archetype === "web-caster") motion = createVexaMotion(bossId);
else if (archetype === "sky-sweeper") motion = createSkySweeperMotion(bossId);
else if (archetype === "duelist") motion = createEmberMantisMotion(bossId);
else if (archetype === "ram") motion = createObsidianRamMotion(bossId);
else if (archetype === "ricochet") motion = createCinderbackMotion(bossId);
else if (archetype === "burrower") motion = createSandglassMotion();
else if (archetype === "crab") motion = createCragclawMotion();
else if (archetype === "ghost") motion = createMournveilMotion();
else if (archetype === "golem") motion = createCrownshardMotion();
else motion = {
...createBaseMotion("bulldrome"),
mode: "holding",
position: [0, -8.2],
@@ -108,6 +101,11 @@ export function createBossMotionState(bossId: BossId = "bulldrome"): BossMotionS
pounceCenter: [0, 4.5],
pounceCount: 0,
};
return { ...motion, bossId };
}
function mechanicArchetype(bossId: BossId): BossArchetype {
return BOSS_ARCHETYPE_BY_ID[bossId];
}
function chargeEndpoint(start: WorldPosition, target: WorldPosition): WorldPosition {
@@ -152,8 +150,7 @@ function advanceMotionMechanics(
let updatedParty = party;
if (motion.mode === "holding") {
const tank = partyPositions.brann;
motion.position = moveToward(motion.position, [tank[0] + motion.formationOffsetX, tank[1] - 4.25], 1.8 * delta);
returnBossToArenaCenter(motion, delta, 1.8);
if (time >= motion.nextChargeAt) {
const targetId = chooseTarget(updatedParty, CHARGE_TARGET_ORDER, motion.chargeCount);
const target = partyPositions[targetId];
@@ -207,16 +204,12 @@ function advanceMotionMechanics(
motion = { ...motion, position: [motion.chargeEnd[0], motion.chargeEnd[1]], mode: "returning", phaseEndsAt: 0 };
}
} else if (motion.mode === "returning") {
const tank = partyPositions.brann;
const returnPoint: WorldPosition = [tank[0] + motion.formationOffsetX, tank[1] - 4.25];
motion.position = moveToward(motion.position, returnPoint, 4.4 * delta);
if (distance(motion.position, returnPoint) < 0.12) {
if (returnBossToArenaCenter(motion, delta, 4.4)) {
if (motion.chargesSincePounce >= BULL_POUNCE.afterCharges) {
const targetId = chooseTarget(updatedParty, POUNCE_TARGET_ORDER, motion.pounceCount);
const targetName = updatedParty.find((member) => member.id === targetId)?.name ?? targetId;
motion = {
...motion,
position: returnPoint,
mode: "stacking",
phaseEndsAt: time + BULL_POUNCE.stackDuration,
nextChargeAt: Number.POSITIVE_INFINITY,
@@ -235,7 +228,6 @@ function advanceMotionMechanics(
} else {
motion = {
...motion,
position: returnPoint,
mode: "holding",
nextChargeAt: time + BULL_CHARGE.repeatDelay,
};
@@ -383,18 +375,18 @@ function advanceBulldromeMechanics(context: BossMechanicContext): BossMechanicRe
}
export function advanceBossMechanics(context: BossMechanicContext): BossMechanicResult {
if (context.boss.id === "vexa") return advanceVexaMechanics(context);
if (context.boss.id === "cindermaw") return advanceCindermawMechanics(context);
if (context.boss.id === "ember-mantis-duelist") return advanceEmberMantisMechanics(context);
if (context.boss.id === "obsidian-ram-golem") return advanceObsidianRamMechanics(context);
if (context.boss.id === "cinderback-ricochet") return advanceCinderbackMechanics(context);
if (context.boss.id === "sandglass-scorpion") return advanceSandglassMechanics(context);
if (context.boss.id === "cragclaw-crab") return advanceCragclawMechanics(context);
if (context.boss.id === "mournveil-ghost") return advanceMournveilMechanics(context);
if (context.boss.id === "crownshard-golem") return advanceCrownshardMechanics(context);
if (context.boss.id === "pumpking-king-of-ghosts") return advanceMournveilMechanics(context);
if (context.boss.id === "blue-eyes-ultimate-dragon") return advanceCrownshardMechanics(context);
return advanceBulldromeMechanics(context);
const archetype = mechanicArchetype(context.boss.id);
const result = archetype === "web-caster" ? advanceVexaMechanics(context)
: archetype === "sky-sweeper" ? advanceSkySweeperMechanics(context)
: archetype === "duelist" ? advanceEmberMantisMechanics(context)
: archetype === "ram" ? advanceObsidianRamMechanics(context)
: archetype === "ricochet" ? advanceCinderbackMechanics(context)
: archetype === "burrower" ? advanceSandglassMechanics(context)
: archetype === "crab" ? advanceCragclawMechanics(context)
: archetype === "ghost" ? advanceMournveilMechanics(context)
: archetype === "golem" ? advanceCrownshardMechanics(context)
: advanceBulldromeMechanics(context);
return advancePooledBossMechanics(context, result);
}
export function handleBossDispel(
@@ -405,7 +397,7 @@ export function handleBossDispel(
time: number,
debuffNames: readonly string[],
) {
if (bossId === "vexa" && debuffNames.includes("Widow Venom")) {
if (mechanicArchetype(bossId) === "web-caster" && debuffNames.includes("Widow Venom")) {
return {
motion: dropVexaVenomPool(motion, memberId, [position[0], position[1]], time),
message: "Widow Venom purged. A venom pool forms where the target stood.",
@@ -415,17 +407,18 @@ export function handleBossDispel(
}
export function upcomingMechanic(boss: BossState, motion: BossMotionState, time: number) {
if (boss.id === "vexa") return upcomingVexaMechanic(boss, motion, time);
if (boss.id === "cindermaw") return upcomingCindermawMechanic(boss, motion, time);
if (boss.id === "ember-mantis-duelist") return upcomingEmberMantisMechanic(boss, motion, time);
if (boss.id === "obsidian-ram-golem") return upcomingObsidianRamMechanic(boss, motion, time);
if (boss.id === "cinderback-ricochet") return upcomingCinderbackMechanic(boss, motion, time);
if (boss.id === "sandglass-scorpion") return upcomingSandglassMechanic(boss, motion, time);
if (boss.id === "cragclaw-crab") return upcomingCragclawMechanic(boss, motion, time);
if (boss.id === "mournveil-ghost") return upcomingMournveilMechanic(boss, motion, time);
if (boss.id === "crownshard-golem") return upcomingCrownshardMechanic(boss, motion, time);
if (boss.id === "pumpking-king-of-ghosts") return upcomingMournveilMechanic(boss, motion, time);
if (boss.id === "blue-eyes-ultimate-dragon") return upcomingCrownshardMechanic(boss, motion, time);
const pooled = upcomingPooledMechanic(motion, time);
if (pooled) return pooled;
const archetype = mechanicArchetype(boss.id);
if (archetype === "web-caster") return upcomingVexaMechanic(boss, motion, time);
if (archetype === "sky-sweeper") return upcomingSkySweeperMechanic(boss, motion, time);
if (archetype === "duelist") return upcomingEmberMantisMechanic(boss, motion, time);
if (archetype === "ram") return upcomingObsidianRamMechanic(boss, motion, time);
if (archetype === "ricochet") return upcomingCinderbackMechanic(boss, motion, time);
if (archetype === "burrower") return upcomingSandglassMechanic(boss, motion, time);
if (archetype === "crab") return upcomingCragclawMechanic(boss, motion, time);
if (archetype === "ghost") return upcomingMournveilMechanic(boss, motion, time);
if (archetype === "golem") return upcomingCrownshardMechanic(boss, motion, time);
if (motion.mode === "telegraph") {
return { name: "Bull Charge", remaining: Math.max(0, motion.phaseEndsAt - time), cycle: BULL_CHARGE.telegraphDuration, urgent: true };
}