Back to Stick Brawl
SOURCE / PINNED RELEASE

Made of little things.

Stick Brawl

Release
732bb70ea856…
Author-recorded commit
918970bb749a…
License
LICENSE
Author’s source reference
nostr://npub1ny7l44q0au5xkv3mjgt6zks5f0e2ydefslq4nhh2ve2xypgc2mqqat8qx2/wss%3A%2F%2Fgit.napplet.soy%2F/n-554b329db93

Archive hash verified: 67f16c1148706bbd…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/game/gear.ts
import type { Joints, Point } from './figure.js';

/**
 * The player's weapons. Everything here is drawn in the stickman's own space:
 * measured from his feet, with +x pointing the way he faces.
 */

/** Turn so that +x points along the forearm (from elbow to hand). */
function alongForearm(ctx: CanvasRenderingContext2D, joints: Joints): void {
  const { elbowF, handF } = joints;
  ctx.translate(handF.x, handF.y);
  ctx.rotate(Math.atan2(handF.y - elbowF.y, handF.x - elbowF.x));
}

/** Where the end of the revolver's barrel is, for firing bullets. */
export function muzzlePoint(joints: Joints): Point {
  const { elbowF, handF } = joints;
  const angle = Math.atan2(handF.y - elbowF.y, handF.x - elbowF.x);
  // The barrel sits a little above the fist and sticks out 10 units.
  return {
    x: handF.x + Math.cos(angle) * 10 + Math.sin(angle) * 2.6,
    y: handF.y + Math.sin(angle) * 10 - Math.cos(angle) * 2.6,
  };
}

/** Where the tip of the sword is, for the swoosh trail. */
export function swordTip(joints: Joints): Point {
  const { elbowF, handF } = joints;
  const angle = Math.atan2(handF.y - elbowF.y, handF.x - elbowF.x);
  return { x: handF.x + Math.cos(angle) * 25, y: handF.y + Math.sin(angle) * 25 };
}

/** The revolver gripped in the fist: the handle is in the hand and the barrel points along the arm. */
export function drawRevolverInHand(ctx: CanvasRenderingContext2D, joints: Joints, flash: boolean): void {
  ctx.save();
  alongForearm(ctx, joints);
  // Handle, angled back down through the fist.
  ctx.fillStyle = '#6b4a2f';
  ctx.beginPath();
  ctx.moveTo(-1.6, -2.2);
  ctx.lineTo(1.4, -2.2);
  ctx.lineTo(0.2, 3.6);
  ctx.lineTo(-2.6, 3.0);
  ctx.closePath();
  ctx.fill();
  // Trigger guard.
  ctx.strokeStyle = '#4d535b';
  ctx.lineWidth = 0.7;
  ctx.beginPath();
  ctx.arc(2.4, -1.4, 1.3, 0, Math.PI);
  ctx.stroke();
  // Cylinder and barrel.
  ctx.fillStyle = '#6c737c';
  ctx.fillRect(0.6, -4.2, 4, 2.8);
  ctx.fillStyle = '#9aa3ad';
  ctx.fillRect(4.4, -3.7, 6, 1.8);
  if (flash) {
    ctx.fillStyle = '#ffe27a';
    ctx.beginPath();
    ctx.moveTo(10.4, -5);
    ctx.lineTo(17, -2.8);
    ctx.lineTo(10.4, -0.6);
    ctx.closePath();
    ctx.fill();
  }
  ctx.restore();
}

/** The holster on the hip, with the revolver's handle sticking out when it's put away. */
export function drawHolster(ctx: CanvasRenderingContext2D, joints: Joints, gunInside: boolean): void {
  const { hip } = joints;
  ctx.save();
  ctx.translate(hip.x + 1.5, hip.y + 1);
  ctx.rotate(0.25);
  if (gunInside) {
    ctx.fillStyle = '#6b4a2f';
    ctx.fillRect(-1.2, -3.4, 2.6, 3.6);
  }
  ctx.fillStyle = '#4a3423';
  ctx.fillRect(-1.8, 0, 3.6, 6.5);
  ctx.restore();
}

function drawSword(ctx: CanvasRenderingContext2D): void {
  // Handle.
  ctx.fillStyle = '#5a3d27';
  ctx.fillRect(-4.5, -1, 6, 2);
  ctx.fillStyle = '#c9a227';
  ctx.beginPath();
  ctx.arc(-4.8, 0, 1.3, 0, Math.PI * 2);
  ctx.fill();
  // Cross guard.
  ctx.fillRect(1.3, -3.2, 1.6, 6.4);
  // Blade with a shiny edge.
  ctx.fillStyle = '#cfd6de';
  ctx.beginPath();
  ctx.moveTo(2.9, -1.3);
  ctx.lineTo(22.5, -1.1);
  ctx.lineTo(25, 0);
  ctx.lineTo(22.5, 1.1);
  ctx.lineTo(2.9, 1.3);
  ctx.closePath();
  ctx.fill();
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(3.5, -1.1, 18.5, 0.6);
}

/** The sword held in the fist, pointing along the arm. */
export function drawSwordInHand(ctx: CanvasRenderingContext2D, joints: Joints): void {
  ctx.save();
  alongForearm(ctx, joints);
  drawSword(ctx);
  ctx.restore();
}

/** The sword strapped across the back, handle up behind the shoulder. */
export function drawSwordOnBack(ctx: CanvasRenderingContext2D, joints: Joints): void {
  const { shoulder, hip } = joints;
  // Work out "down the spine" and "out of the back", so the sword tilts with the body.
  const length = Math.hypot(hip.x - shoulder.x, hip.y - shoulder.y) || 1;
  const down = { x: (hip.x - shoulder.x) / length, y: (hip.y - shoulder.y) / length };
  const back = { x: -down.y, y: down.x };
  ctx.save();
  // Handle sticks up just behind the shoulder; the blade runs down past the hip.
  ctx.translate(shoulder.x + back.x * 3 - down.x * 3, shoulder.y + back.y * 3 - down.y * 3);
  ctx.rotate(Math.atan2(down.y + back.y * 0.55, down.x + back.x * 0.55));
  ctx.translate(2.5, 0);
  drawSword(ctx);
  ctx.restore();
}