feat(qualification): render civilian targets
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
dc9d2f4605
commit
6c7a9fb5e2
1 changed files with 42 additions and 11 deletions
|
|
@ -37,6 +37,7 @@ export type AimTrainerGameState = {
|
|||
startedAt: number;
|
||||
headshots: number;
|
||||
torsoHits: number;
|
||||
civilianHits: number;
|
||||
shotsFired: number;
|
||||
despawnedUnhitTargets: number;
|
||||
feedback: AimTrainerPointFeedback[];
|
||||
|
|
@ -51,6 +52,7 @@ export type AimTrainerLiveStats = {
|
|||
readonly shotsFired: number;
|
||||
readonly headshots: number;
|
||||
readonly torsoHits: number;
|
||||
readonly civilianHits: number;
|
||||
readonly despawnedUnhitTargets: number;
|
||||
};
|
||||
|
||||
|
|
@ -61,6 +63,7 @@ export function createAimTrainerGameState(): AimTrainerGameState {
|
|||
startedAt: 0,
|
||||
headshots: 0,
|
||||
torsoHits: 0,
|
||||
civilianHits: 0,
|
||||
shotsFired: 0,
|
||||
despawnedUnhitTargets: 0,
|
||||
feedback: [],
|
||||
|
|
@ -77,6 +80,7 @@ export function emptyAimTrainerLiveStats(): AimTrainerLiveStats {
|
|||
shotsFired: 0,
|
||||
headshots: 0,
|
||||
torsoHits: 0,
|
||||
civilianHits: 0,
|
||||
despawnedUnhitTargets: 0,
|
||||
};
|
||||
}
|
||||
|
|
@ -89,11 +93,13 @@ export function getAimTrainerLiveStats(game: AimTrainerGameState): AimTrainerLiv
|
|||
game.torsoHits,
|
||||
game.shotsFired,
|
||||
game.despawnedUnhitTargets,
|
||||
game.civilianHits,
|
||||
),
|
||||
accuracy: calculateAimTrainerAccuracy(hits, game.shotsFired, game.despawnedUnhitTargets),
|
||||
shotsFired: game.shotsFired,
|
||||
headshots: game.headshots,
|
||||
torsoHits: game.torsoHits,
|
||||
civilianHits: game.civilianHits,
|
||||
despawnedUnhitTargets: game.despawnedUnhitTargets,
|
||||
};
|
||||
}
|
||||
|
|
@ -141,12 +147,37 @@ function drawRange(ctx: CanvasRenderingContext2D): void {
|
|||
ctx.fillText("QUALIFICATION COURSE", AIM_TRAINER_CANVAS_WIDTH - 18, 16);
|
||||
}
|
||||
|
||||
const STANDARD_TARGET_PALETTE = {
|
||||
torsoStops: ["#9b956d", "#706f52", "#4d5542"] as const,
|
||||
lowerShade: "rgba(29, 38, 30, 0.42)",
|
||||
straps: "rgba(217, 207, 151, 0.32)",
|
||||
torsoOutline: "rgba(226, 217, 166, 0.6)",
|
||||
headFill: "#afa678",
|
||||
headShade: "rgba(48, 55, 40, 0.48)",
|
||||
headOutline: "rgba(226, 217, 166, 0.72)",
|
||||
crosshair: "rgba(235, 226, 175, 0.32)",
|
||||
boundsBox: "rgba(211, 202, 149, 0.12)",
|
||||
};
|
||||
|
||||
const CIVILIAN_TARGET_PALETTE = {
|
||||
torsoStops: ["#8fb4dd", "#5b87b8", "#3a6191"] as const,
|
||||
lowerShade: "rgba(16, 32, 52, 0.42)",
|
||||
straps: "rgba(235, 245, 255, 0.35)",
|
||||
torsoOutline: "rgba(190, 215, 240, 0.6)",
|
||||
headFill: "#b7d0ec",
|
||||
headShade: "rgba(30, 58, 92, 0.48)",
|
||||
headOutline: "rgba(190, 215, 240, 0.72)",
|
||||
crosshair: "rgba(215, 235, 255, 0.35)",
|
||||
boundsBox: "rgba(167, 197, 232, 0.14)",
|
||||
};
|
||||
|
||||
function drawTarget(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
activeTarget: AimTrainerActiveTarget,
|
||||
frame: AimTrainerSceneFrame,
|
||||
): void {
|
||||
const { target, spawnedAt } = activeTarget;
|
||||
const palette = target.kind === "civilian" ? CIVILIAN_TARGET_PALETTE : STANDARD_TARGET_PALETTE;
|
||||
const age = Math.max(0, frame.now - spawnedAt);
|
||||
const fadeDuration = Math.min(
|
||||
AIM_TRAINER_TARGET_FADE_DURATION_MS,
|
||||
|
|
@ -173,35 +204,35 @@ function drawTarget(
|
|||
ctx.shadowColor = "transparent";
|
||||
|
||||
const torso = ctx.createLinearGradient(-target.torsoW / 2, 0, target.torsoW / 2, target.torsoH);
|
||||
torso.addColorStop(0, "#9b956d");
|
||||
torso.addColorStop(0.5, "#706f52");
|
||||
torso.addColorStop(1, "#4d5542");
|
||||
torso.addColorStop(0, palette.torsoStops[0]);
|
||||
torso.addColorStop(0.5, palette.torsoStops[1]);
|
||||
torso.addColorStop(1, palette.torsoStops[2]);
|
||||
ctx.fillStyle = torso;
|
||||
ctx.fillRect(-target.torsoW / 2, 0, target.torsoW, target.torsoH);
|
||||
ctx.fillStyle = "rgba(29, 38, 30, 0.42)";
|
||||
ctx.fillStyle = palette.lowerShade;
|
||||
ctx.fillRect(-target.torsoW / 2, target.torsoH * 0.54, target.torsoW, target.torsoH * 0.46);
|
||||
ctx.fillStyle = "rgba(217, 207, 151, 0.32)";
|
||||
ctx.fillStyle = palette.straps;
|
||||
ctx.fillRect(-target.torsoW * 0.28, target.torsoH * 0.16, target.torsoW * 0.56, 3);
|
||||
ctx.fillRect(-target.torsoW * 0.28, target.torsoH * 0.42, target.torsoW * 0.56, 3);
|
||||
ctx.strokeStyle = "rgba(226, 217, 166, 0.6)";
|
||||
ctx.strokeStyle = palette.torsoOutline;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.strokeRect(-target.torsoW / 2, 0, target.torsoW, target.torsoH);
|
||||
|
||||
ctx.fillStyle = "#afa678";
|
||||
ctx.fillStyle = palette.headFill;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, -target.headRadius, target.headRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "rgba(48, 55, 40, 0.48)";
|
||||
ctx.fillStyle = palette.headShade;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, -target.headRadius - 2, target.headRadius * 0.72, Math.PI, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "rgba(226, 217, 166, 0.72)";
|
||||
ctx.strokeStyle = palette.headOutline;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, -target.headRadius, target.headRadius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.strokeStyle = "rgba(235, 226, 175, 0.32)";
|
||||
ctx.strokeStyle = palette.crosshair;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -target.headRadius - target.headRadius - 9);
|
||||
|
|
@ -213,7 +244,7 @@ function drawTarget(
|
|||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
ctx.strokeStyle = "rgba(211, 202, 149, 0.12)";
|
||||
ctx.strokeStyle = palette.boundsBox;
|
||||
ctx.setLineDash([4, 5]);
|
||||
ctx.strokeRect(torsoX, target.y, target.torsoW * pulse, target.torsoH);
|
||||
ctx.setLineDash([]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue