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.
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();
}
