Split The code is public; the app answers only to the crew.

Killed by whom, with what — but not how close you came: the death notice has no killer health #238

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/cs:scout/204-killed-by-whom-with-what-but-not-how-clo → main 1 commit fork

  • Merged. scout/204-killed-by-whom-with-what-but-not-how-clo was merged into main as bc0b071.

Review

Sign in to review this change.
8 files changed +64 −10
README.md +5 −2
@@ -495,8 +495,11 @@ be sitting on the radar, the health or the ammo.
495495
496496 When you die the camera moves to a teammate who is still up and the HUD becomes
497497 theirs — their health, armour, ammo and gun, no crosshair — over a line naming
498 who killed you and with what. Clicking, or the fire key, moves on to the next
499 teammate. Whoever dies stays on the floor where they fell, lying the way they
498 who killed you, with what, and how much health they had left, so a lost duel
499 says whether you were outplayed or one bullet away. That last number is a
500 fact about the moment you died, not a live reading of the killer, and it is
501 silent when nobody else's death caused yours (a fall, the bomb, your own
502 grenade). Clicking, or the fire key, moves on to the next teammate. Whoever dies stays on the floor where they fell, lying the way they
500503 were facing and tinted their side, until the next round starts — so the rifle
501504 dropped in a doorway has a body under it and a fight leaves a record of itself.
502505 A body is decoration and nothing more: bullets go through it, it blocks nobody,
src/client/bots.test.ts +2 −2
@@ -559,7 +559,7 @@ test("a call outranks the rotation a counter-terrorist would have made on its ow
559559 fallen.pos = [...DE_FORGE.sites.B] as [number, number, number];
560560 world.feed.push({
561561 at: world.time, killer: "them", killerId: 9, victim: fallen.name, victimId: fallen.id,
562 weapon: "ak47", headshot: false, killerTeam: "t",
562 weapon: "ak47", headshot: false, killerTeam: "t", killerHealth: 100,
563563 });
564564
565565 const standing = cts.filter((p) => p.alive);
@@ -741,7 +741,7 @@ test("a call turns a rotating defender round inside its repath window", () => {
741741 fallen.pos = [...DE_FORGE.sites.B] as [number, number, number];
742742 world.feed.push({
743743 at: world.time, killer: "them", killerId: 9, victim: fallen.name, victimId: fallen.id,
744 weapon: "ak47", headshot: false, killerTeam: "t",
744 weapon: "ak47", headshot: false, killerTeam: "t", killerHealth: 100,
745745 });
746746 thinkAll(world);
747747 const rotated = defenders(world).filter((p) => p.alive && p.brain?.site === "A");
src/client/hud.test.ts +17 −3
@@ -255,7 +255,7 @@ const spectatedWorld = (): World => {
255255 world.me.health = 0;
256256 world.feed.push({
257257 at: world.time, killer: "Krait", killerId: 9, victim: world.me.name, victimId: world.me.id,
258 weapon: "ak47", headshot: true, killerTeam: "t",
258 weapon: "ak47", headshot: true, killerTeam: "t", killerHealth: 23,
259259 });
260260 return world;
261261 };
@@ -274,6 +274,7 @@ test("dead, the HUD is the watched player's: their numbers, no crosshair, and wh
274274 expect(texts.join("|")).toContain(`Watching ${mate.name}`);
275275 expect(texts.join("|")).toContain("Krait killed you with AK-47");
276276 expect(texts.join("|")).toContain("headshot");
277 expect(texts.join("|")).toContain("23 hp left"); // how close you came
277278 // The crosshair is four strokes out from the middle; a spectator gets none.
278279 expect(frame.calls.filter((c) => c.name === "moveTo" && c.args[1] === H / 2).length).toBe(0);
279280 });
@@ -282,7 +283,7 @@ test("a fall that killed you says so, rather than naming you as your own killer"
282283 const world = spectatedWorld();
283284 world.feed.push({
284285 at: world.time, killer: world.me.name, killerId: world.me.id, victim: world.me.name, victimId: world.me.id,
285 weapon: "fall", headshot: false, killerTeam: "ct",
286 weapon: "fall", headshot: false, killerTeam: "ct", killerHealth: null,
286287 });
287288 const mate = world.players.find((p) => p.alive && p.team === "ct") as Player;
288289 const frame = recorder();
@@ -292,6 +293,19 @@ test("a fall that killed you says so, rather than naming you as your own killer"
292293 expect(texts).toContain(`${world.me.name} Fall ${world.me.name}`);
293294 });
294295
296 test("a self-kill says nothing about health: there is no one else's hp to report", () => {
297 const world = spectatedWorld();
298 world.feed.push({
299 at: world.time, killer: world.me.name, killerId: world.me.id, victim: world.me.name, victimId: world.me.id,
300 weapon: "bomb", headshot: false, killerTeam: "ct", killerHealth: null,
301 });
302 const mate = world.players.find((p) => p.alive && p.team === "ct") as Player;
303 const frame = recorder();
304 draw(frame, world, mate);
305 const texts = frame.calls.filter((c) => c.name === "fillText").map((c) => c.text).join("|");
306 expect(texts).not.toContain("hp left");
307 });
308
295309 test("a spectator does not inherit the dead player's hit markers", () => {
296310 const world = spectatedWorld();
297311 const mate = world.players.find((p) => p.alive && p.team === "ct") as Player;
@@ -723,7 +737,7 @@ test("reduced motion leaves the hit marker, the kill feed and the crosshair exac
723737 world.effects.push({ kind: "hitmark", at: world.time - 0.1, headshot: true, killed: true });
724738 world.feed.push({
725739 at: world.time, killer: "me", killerId: world.me.id, killerTeam: "ct", victim: "them",
726 weapon: "ak47", headshot: true, victimId: 5,
740 weapon: "ak47", headshot: true, victimId: 5, killerHealth: 80,
727741 });
728742 // Every call, argument for argument and alpha for alpha: the whole of the
729743 // rest of the HUD is information, and a setting that moved any of it would
src/client/hud.ts +2 −1
@@ -606,8 +606,9 @@ export function drawHud(
606606 if (notice !== null) {
607607 ctx.font = mid;
608608 ctx.fillStyle = "#ff8f86";
609 const hp = notice.killerHealth === null ? "" : `, ${Math.round(clamp(notice.killerHealth, 0, 100))} hp left`;
609610 const how = notice.weapon === "fall" ? "You fell too far" :
610 `${notice.killer} killed you with ${causeName(notice.weapon)}` + (notice.headshot ? " — headshot" : "");
611 `${notice.killer} killed you with ${causeName(notice.weapon)}` + (notice.headshot ? " — headshot" : "") + hp;
611612 ctx.fillText(how, width / 2, top + Math.round(30 * scale));
612613 }
613614 ctx.font = small;
src/client/saved.test.ts +13
@@ -82,6 +82,19 @@ test("snapshot reads only what world.ts already exports, and parses back", () =>
8282 expect(parseSaved(JSON.stringify(saved))).toEqual(saved);
8383 });
8484
85 test("the kill feed, killer health included, is not part of a snapshot", () => {
86 const world: World = createWorld(SHORT, "scout", "ct", 5, 9);
87 const before = snapshot(world, crypto.randomUUID());
88 world.feed.push({
89 at: world.time, killer: "them", killerId: 1, victim: world.me.name, victimId: world.me.id,
90 weapon: "ak47", headshot: false, killerTeam: "t", killerHealth: 23,
91 });
92 const after = snapshot(world, before.matchId);
93 expect(after).toEqual(before);
94 expect(JSON.stringify(after)).not.toMatch(/killerHealth/);
95 expect(parseSaved(JSON.stringify(after))).toEqual(after);
96 });
97
8598 test("snapshot carries nothing mid-round: no positions, no health, no bomb", () => {
8699 const world: World = createWorld(SHORT, "scout", "ct", 5, 8);
87100 const saved = snapshot(world, crypto.randomUUID());
src/client/spectate.test.ts +2 −2
@@ -56,12 +56,12 @@ test("the kill feed says who killed you, and nothing when it has no line for you
5656 const bot = world.players[1] as Player;
5757 world.feed.push({
5858 at: world.time, killer: bot.name, killerId: bot.id, victim: world.me.name, victimId: world.me.id,
59 weapon: "ak47", headshot: true, killerTeam: bot.team,
59 weapon: "ak47", headshot: true, killerTeam: bot.team, killerHealth: 42,
6060 });
6161 // A namesake killed somewhere else does not become your killer: ids decide.
6262 world.feed.push({
6363 at: world.time, killer: "someone", killerId: 8, victim: world.me.name, victimId: 7,
64 weapon: "usp", headshot: false, killerTeam: "ct",
64 weapon: "usp", headshot: false, killerTeam: "ct", killerHealth: 100,
6565 });
6666 const notice = deathNotice(world, world.me);
6767 expect(notice?.killer).toBe(bot.name);
src/client/world.test.ts +15
@@ -732,6 +732,7 @@ test("a bomb kill runs the same path as any other: a hurt, a named feed line, a
732732 // Nobody planted this for a kill: the victim stands in for the killer,
733733 // the same way a suicide by your own grenade does.
734734 expect(entry?.killerId).toBe(world.me.id);
735 expect(entry?.killerHealth).toBe(null); // no one else's hp to report on a suicide
735736
736737 expect(world.bodies.length).toBe(1);
737738 });
@@ -1820,6 +1821,20 @@ test("a bot's knife in your back ends it in one, and is credited like any kill",
18201821 expect(damageDealt(world, bot.id)[0].entry.damage).toBe(100);
18211822 });
18221823
1824 test("the feed keeps the killer's health as it was at the kill, not as it is now", () => {
1825 const world = createWorld(SHORT, "me", "ct", 5, 402);
1826 const bot = world.players.find((p) => p.team === "t") as Player;
1827 bot.health = 23;
1828 knifeRange(world, bot, world.me, 0);
1829 expect(shoot(world, bot)).toBe(true);
1830 expect(world.me.alive).toBe(false);
1831 const line = world.feed[world.feed.length - 1];
1832 expect(line.killerId).toBe(bot.id);
1833 expect(line.killerHealth).toBe(23);
1834 bot.health = 5; // hurt after the fact, in another fight entirely
1835 expect(world.feed[world.feed.length - 1].killerHealth).toBe(23);
1836 });
1837
18231838 test("the same bot stabbing you in the chest leaves you standing", () => {
18241839 const world = createWorld(SHORT, "me", "ct", 5, 403);
18251840 const bot = world.players.find((p) => p.team === "t") as Player;
src/client/world.ts +8
@@ -157,6 +157,13 @@ export type FeedEntry = {
157157 weapon: Cause;
158158 headshot: boolean;
159159 killerTeam: Team;
160 /**
161 * The killer's health the instant the kill landed, taken once here and
162 * never re-read: a corpse's feed line must not change as the world moves
163 * on. Null when the killer is the victim (a fall, a bomb, your own
164 * grenade) — there is no "how close" to a death nobody else caused.
165 */
166 killerHealth: number | null;
160167 };
161168
162169 /** One pairing in the round's damage ledger: what one player did to another. */
@@ -990,6 +997,7 @@ function onKill(world: World, killer: Player, victim: Player, weapon: Cause, hea
990997 killer: killer.name, killerId: killer.id,
991998 victim: victim.name, victimId: victim.id,
992999 weapon, headshot, killerTeam: killer.team,
1000 killerHealth: killer === victim ? null : killer.health,
9931001 });
9941002 if (world.feed.length > 5) world.feed.shift();
9951003 if (victim.hasBomb) {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs