Release v0.1.19 2026-07-19

This commit is contained in:
Warren H
2026-07-19 14:15:30 -04:00
parent 7b522e3bc8
commit 437e70fc58
13 changed files with 113 additions and 8 deletions
+4 -1
View File
@@ -21,6 +21,7 @@ import {
HOCKEY_PVP_GOAL_Z,
HOCKEY_PVP_SIDE_OFFSET_Z,
} from "../game/hockeyHealingPvp";
import { useHockeyPvpCountdownSeconds } from "../game/useHockeyPvpCountdown";
import { bottomTabsFor, cycleBottomTab } from "../game/bottomTabs";
import {
BLOCKBREAKER_BREACH_DAMAGE,
@@ -251,6 +252,8 @@ function BriefingPanel() {
const blockbreakerMode = activityMode === "blockbreaker";
const aetherMode = activityMode === "aether-assault";
const opponentName = useGameStore((state) => state.hockeyPvp.opponentName);
const countdownEndsAtMs = useGameStore((state) => state.hockeyPvp.countdownEndsAtMs);
const pvpCountdownSeconds = useHockeyPvpCountdownSeconds(pvpMode, countdownEndsAtMs);
return (
<div className="briefing-panel">
<div className="briefing-class">
@@ -258,7 +261,7 @@ function BriefingPanel() {
<span>Chosen discipline</span>
<h2>{healer.specialization}</h2>
<p>{hockeyMode ? "Defend the wide goal while healing through two bosses. Left-stick direction sets every puck return; the moving enemy paddle tracks it and strikes it back. Each fallen boss awards loot and rolls its pet chance before replacement." : pvpMode ? `Face ${opponentName}. Both parties use normalized base gear and fight the same boss order. Every boss kill adds 5% global healing Dampening. Aim returns with left stick. A goal deals ${HOCKEY_PVP_GOAL_DAMAGE} damage to every member of the conceding party. Boss kills still award loot and pet chances.` : blockbreakerMode ? `Aim the puck into advancing five-column color rows while healing through two bosses. Orthogonal matching clusters break together. Rows start every 10 seconds and accelerate. Misses safely re-serve; each breach deals ${BLOCKBREAKER_BREACH_DAMAGE} damage to every party member.` : aetherMode ? "Move freely through the full runway while your arcane focus fires automatically. Heal with your normal kit. Dodge enemy volleys and diving ships; ship hits only threaten the healer. Bosses and rewards continue independently." : <>{healer.description} {definitions.map((boss) => boss.briefing).join(" ")}</>}</p>
<button className="start-button" onClick={startEncounter}><span>{hockeyMode ? "Begin Hockey Healing" : pvpMode ? `Face ${opponentName}` : blockbreakerMode ? "Begin Blockbreaker" : aetherMode ? "Begin Aether Assault" : `Face ${bossNames}`}</span><small>{DEFAULT_CONTROLLER_GLYPHS.start} / ENTER</small></button>
<button className="start-button" onClick={startEncounter} disabled={pvpMode}><span>{hockeyMode ? "Begin Hockey Healing" : pvpMode ? pvpCountdownSeconds > 0 ? `Match starts in ${pvpCountdownSeconds}` : "Match starting now" : blockbreakerMode ? "Begin Blockbreaker" : aetherMode ? "Begin Aether Assault" : `Face ${bossNames}`}</span><small>{pvpMode ? "Automatic start" : `${DEFAULT_CONTROLLER_GLYPHS.start} / ENTER`}</small></button>
</div>
<div className="briefing-kit">
<div className="section-label"><span>Prepared skills</span><small>6 equipped</small></div>
+4
View File
@@ -51,6 +51,7 @@ import { DualDisplayFrame } from "./DualDisplayFrame";
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs";
import {
HOCKEY_PVP_GOAL_DAMAGE,
HOCKEY_PVP_COUNTDOWN_MS,
HOCKEY_PVP_QUEUE_TIMEOUT_MS,
hockeyPvpBossAt,
randomHockeyPvpCpuName,
@@ -1593,6 +1594,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
seed: Math.max(1, Math.floor(Math.random() * 0xffffffff)),
opponentName: randomHockeyPvpCpuName(),
role: "cpu",
countdownEndsAtMs: Date.now() + HOCKEY_PVP_COUNTDOWN_MS,
});
};
const cancelPvpQueue = () => {
@@ -1624,6 +1626,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
completePvpQueue({
matchId: joined.match.id,
seed: joined.match.seed,
countdownEndsAtMs: joined.match.countdownEndsAtMs,
opponentName: joined.match.opponentName,
role: joined.match.role,
});
@@ -1638,6 +1641,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
completePvpQueue({
matchId: result.match.id,
seed: result.match.seed,
countdownEndsAtMs: result.match.countdownEndsAtMs,
opponentName: result.match.opponentName,
role: result.match.role,
});
+17 -3
View File
@@ -7,6 +7,7 @@ import { tankAuraProtects } from "../game/partyCombat";
import { BuffDraftPanel } from "./BuffDraftPanel";
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs";
import { hockeyPvpDampeningPercent } from "../game/hockeyHealingPvp";
import { useHockeyPvpCountdownSeconds } from "../game/useHockeyPvpCountdown";
import { blockbreakerTimeMultiplier } from "../game/blockbreaker";
import { RpgRunOverlay } from "./rpgRoguelike/RpgRunOverlay";
import type { CharacterAppearanceV1 } from "../game/characterAppearance";
@@ -168,6 +169,10 @@ function PhaseOverlay() {
const blockbreaker = useGameStore((state) => state.blockbreaker);
const aetherAssault = useGameStore((state) => state.aetherAssault);
const hockeyPvp = useGameStore((state) => state.hockeyPvp);
const pvpCountdownSeconds = useHockeyPvpCountdownSeconds(
activityMode === "hockey-healing-pvp" && phase === "briefing",
hockeyPvp.countdownEndsAtMs,
);
const singleScreen = isSingleScreenLayout();
if (runMode === "rpg-roguelike") return null;
if (phase === "intermission") return <BuffDraftPanel className="top-buff-draft" />;
@@ -223,15 +228,24 @@ function PhaseOverlay() {
? `Run record: ${aetherAssault.score.toLocaleString()} points, wave ${aetherAssault.wave}, ${aetherAssault.kills} ships, and ${endlessBossKills} boss kills.`
: pvpMode ? `${hockeyPvp.opponentName} kept their party standing.`
: endlessDefeat ? `Run record: ${endlessBossKills} bosses defeated after the trio finale.` : definitions.map((boss) => boss.failure).join(" ");
const briefingPrompt = pvpMode
? pvpCountdownSeconds > 0
? `Match starts automatically in ${pvpCountdownSeconds}`
: "Match starting now"
: singleScreen
? "Press Start / Enter to begin"
: "Begin from lower display";
return (
<div className={`phase-overlay phase-${phase}`}>
<div className="phase-sigil"></div>
<span>{eyebrow}</span>
<h1>{title}</h1>
<p>{copy}</p>
<small>{singleScreen
? phase === "briefing" ? "Press Start / Enter to begin" : showEndlessChoice ? "Choose Endless Mode or Quit" : pvpMode ? "Press Start for the next match" : "Press Start / Enter to restart"
: phase === "briefing" ? "Begin from lower display" : showEndlessChoice ? "Choose Endless Mode or Quit on lower display" : pvpMode ? "Choose next match from lower display" : "Restart from lower display"}</small>
<small>{phase === "briefing"
? briefingPrompt
: singleScreen
? showEndlessChoice ? "Choose Endless Mode or Quit" : pvpMode ? "Press Start for the next match" : "Press Start / Enter to restart"
: showEndlessChoice ? "Choose Endless Mode or Quit on lower display" : pvpMode ? "Choose next match from lower display" : "Restart from lower display"}</small>
</div>
);
}