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

forked from benjivers/val

workbenchthis machine
Files d8171ac61b98b4396aabe0046391dd3bf2c33dd9
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
/** `owed` is how many kicks have been shown ahead of the server saying a shot happened. */
10
export const makeGun = () => ({ kick: 0, mark: 0, ammo: null, owed: 0 });
11
12
/** Recoil and the hit marker both fade the same way: fast, and to zero. */
13
export const fade = (value, dt, life) => Math.max(0, value - dt / life);
14
15
/** A shot happened when the magazine went down, never when it was reloaded. */
16
export const firedNow = (before, ammo, reloading) =>
17
  before !== null && !reloading && ammo < before;
18
19
/** One of my shots landed: it started where I am standing and the server says it hit. */
20
export function hitMarked(shots, me) {
21
  for (const shot of shots) {
22
    if (!shot.hit) continue;
23
    if (Math.hypot(shot.x1 - me.x, shot.y1 - me.y) < 30) return true;
24
  }
25
  return false;
26
}
27
28
/**
29
 * The trigger was pulled here, now. If the gun can fire — alive, loaded, not
30
 * reloading — it kicks at once rather than a round trip later, and the kick
31
 * the server's snapshot would have given for the same shot is owed and not
32
 * shown twice. Whether the shot lands, and whether it even happened, is the
33
 * server's alone: a kick is the hand, not the bullet.
34
 */
35
export function gunFire(gun, snap) {
36
  if (!snap.me.alive || snap.me.reloading || snap.me.ammo <= 0) return false;
37
  if (gun.kick > 0.5) return false; // a held trigger, not a new pull
38
  gun.kick = 1;
39
  gun.owed += 1;
40
  return true;
41
}
42
43
/** Fold a fresh snapshot into the gun's state: kick on a shot, mark on a hit. */
44
export function gunSnapshot(gun, snap) {
45
  if (firedNow(gun.ammo, snap.me.ammo, snap.me.reloading)) {
46
    if (gun.owed > 0) gun.owed -= 1;
47
    else gun.kick = 1;
48
  }
49
  // A kick owed for longer than a shot could take to come back was refused.
50
  if (gun.owed > 0 && gun.kick === 0) gun.owed = 0;
51
  if (snap.me.alive && hitMarked(snap.shots, snap.me)) gun.mark = 1;
52
  gun.ammo = snap.me.ammo;
53
}
54
55
export function gunTick(gun, dt) {
56
  gun.kick = fade(gun.kick, dt, 0.12);
57
  gun.mark = fade(gun.mark, dt, 0.35);
58
}
59
60
/** The crosshair, the weapon and the marker, in that order of importance. */
61
export function drawGun(ctx, gun, width, height, snap) {
62
  const cx = width / 2;
63
  const cy = height / 2;
64
  const gap = 6 + gun.kick * 10;
65
  const arm = 9;
66
  ctx.lineWidth = 2;
67
  ctx.strokeStyle = "rgba(20,24,30,0.8)";
68
  ctx.beginPath();
69
  for (const [dx, dy] of [
70
    [1, 0],
71
    [-1, 0],
72
    [0, 1],
73
    [0, -1],
74
  ]) {
75
    ctx.moveTo(cx + dx * gap, cy + dy * gap);
76
    ctx.lineTo(cx + dx * (gap + arm), cy + dy * (gap + arm));
77
  }
78
  ctx.stroke();
79
  ctx.strokeStyle = snap.me.alive ? "#8ef0c4" : "#888";
80
  ctx.lineWidth = 1.4;
81
  ctx.stroke();
82
83
  if (gun.mark > 0) {
84
    ctx.strokeStyle = `rgba(255,120,110,${gun.mark})`;
85
    ctx.lineWidth = 2.5;
86
    ctx.beginPath();
87
    for (const [dx, dy] of [
88
      [1, 1],
89
      [-1, -1],
90
      [1, -1],
91
      [-1, 1],
92
    ]) {
93
      ctx.moveTo(cx + dx * 7, cy + dy * 7);
94
      ctx.lineTo(cx + dx * 15, cy + dy * 15);
95
    }
96
    ctx.stroke();
97
  }
98
99
  if (!snap.me.alive) return;
100
  // The weapon: a barrel and a body, off to the right, rising with the kick.
101
  const scale = height / 570;
102
  const lift = gun.kick * 22 * scale + (snap.me.reloading ? 40 * scale : 0);
103
  const bx = width * 0.66;
104
  const by = height + lift;
105
  ctx.fillStyle = "#23262e";
106
  ctx.beginPath();
107
  ctx.moveTo(bx - 34 * scale, by);
108
  ctx.lineTo(bx - 20 * scale, by - 130 * scale);
109
  ctx.lineTo(bx + 16 * scale, by - 150 * scale);
110
  ctx.lineTo(bx + 30 * scale, by - 40 * scale);
111
  ctx.lineTo(bx + 60 * scale, by);
112
  ctx.closePath();
113
  ctx.fill();
114
  ctx.fillStyle = "#3b414e";
115
  ctx.fillRect(bx - 8 * scale, by - 160 * scale, 22 * scale, 26 * scale);
116
  if (gun.kick > 0.35) {
117
    ctx.fillStyle = `rgba(255,220,140,${gun.kick})`;
118
    ctx.beginPath();
119
    ctx.arc(bx + 3 * scale, by - 162 * scale, 18 * scale * gun.kick, 0, 7);
120
    ctx.fill();
121
  }
122
}
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts