wren/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine
Files cdce38683437375e2145e657f55d282173240782
1
/**
2
 * The gun in your hands: a crosshair, a shape at the bottom of the screen
3
 * that kicks when you fire, and a marker when a shot of yours lands.
4
 *
5
 * The client never decides whether it hit anything — it reads that off the
6
 * shots the server sent, the same ones the top-down view draws.
7
 */
8
9
export const makeGun = () => ({ kick: 0, mark: 0, ammo: null });
10
11
/** Recoil and the hit marker both fade the same way: fast, and to zero. */
12
export const fade = (value, dt, life) => Math.max(0, value - dt / life);
13
14
/** A shot happened when the magazine went down, never when it was reloaded. */
15
export const firedNow = (before, ammo, reloading) =>
16
  before !== null && !reloading && ammo < before;
17
18
/** One of my shots landed: it started where I am standing and the server says it hit. */
19
export function hitMarked(shots, me) {
20
  for (const shot of shots) {
21
    if (!shot.hit) continue;
22
    if (Math.hypot(shot.x1 - me.x, shot.y1 - me.y) < 30) return true;
23
  }
24
  return false;
25
}
26
27
/** Fold a fresh snapshot into the gun's state: kick on a shot, mark on a hit. */
28
export function gunSnapshot(gun, snap) {
29
  if (firedNow(gun.ammo, snap.me.ammo, snap.me.reloading)) gun.kick = 1;
30
  if (snap.me.alive && hitMarked(snap.shots, snap.me)) gun.mark = 1;
31
  gun.ammo = snap.me.ammo;
32
}
33
34
export function gunTick(gun, dt) {
35
  gun.kick = fade(gun.kick, dt, 0.12);
36
  gun.mark = fade(gun.mark, dt, 0.35);
37
}
38
39
/** The crosshair, the weapon and the marker, in that order of importance. */
40
export function drawGun(ctx, gun, width, height, snap) {
41
  const cx = width / 2;
42
  const cy = height / 2;
43
  const gap = 6 + gun.kick * 10;
44
  const arm = 9;
45
  ctx.lineWidth = 2;
46
  ctx.strokeStyle = "rgba(20,24,30,0.8)";
47
  ctx.beginPath();
48
  for (const [dx, dy] of [
49
    [1, 0],
50
    [-1, 0],
51
    [0, 1],
52
    [0, -1],
53
  ]) {
54
    ctx.moveTo(cx + dx * gap, cy + dy * gap);
55
    ctx.lineTo(cx + dx * (gap + arm), cy + dy * (gap + arm));
56
  }
57
  ctx.stroke();
58
  ctx.strokeStyle = snap.me.alive ? "#8ef0c4" : "#888";
59
  ctx.lineWidth = 1.4;
60
  ctx.stroke();
61
62
  if (gun.mark > 0) {
63
    ctx.strokeStyle = `rgba(255,120,110,${gun.mark})`;
64
    ctx.lineWidth = 2.5;
65
    ctx.beginPath();
66
    for (const [dx, dy] of [
67
      [1, 1],
68
      [-1, -1],
69
      [1, -1],
70
      [-1, 1],
71
    ]) {
72
      ctx.moveTo(cx + dx * 7, cy + dy * 7);
73
      ctx.lineTo(cx + dx * 15, cy + dy * 15);
74
    }
75
    ctx.stroke();
76
  }
77
78
  if (!snap.me.alive) return;
79
  // The weapon: a barrel and a body, off to the right, rising with the kick.
80
  const scale = height / 570;
81
  const lift = gun.kick * 22 * scale + (snap.me.reloading ? 40 * scale : 0);
82
  const bx = width * 0.66;
83
  const by = height + lift;
84
  ctx.fillStyle = "#23262e";
85
  ctx.beginPath();
86
  ctx.moveTo(bx - 34 * scale, by);
87
  ctx.lineTo(bx - 20 * scale, by - 130 * scale);
88
  ctx.lineTo(bx + 16 * scale, by - 150 * scale);
89
  ctx.lineTo(bx + 30 * scale, by - 40 * scale);
90
  ctx.lineTo(bx + 60 * scale, by);
91
  ctx.closePath();
92
  ctx.fill();
93
  ctx.fillStyle = "#3b414e";
94
  ctx.fillRect(bx - 8 * scale, by - 160 * scale, 22 * scale, 26 * scale);
95
  if (gun.kick > 0.35) {
96
    ctx.fillStyle = `rgba(255,220,140,${gun.kick})`;
97
    ctx.beginPath();
98
    ctx.arc(bx + 3 * scale, by - 162 * scale, 18 * scale * gun.kick, 0, 7);
99
    ctx.fill();
100
  }
101
}
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts