98 lines
4.0 KiB
JavaScript
98 lines
4.0 KiB
JavaScript
/* ============================================================
|
|
* core.js — utilities, isometric math, event bus
|
|
* ============================================================ */
|
|
'use strict';
|
|
|
|
const TW = 64, TH = 32; // iso tile width/height
|
|
const WALL_H = 46; // wall pixel height
|
|
const LOT_W = 34, LOT_H = 34; // lot size in tiles
|
|
|
|
/* ---------- tiny helpers ---------- */
|
|
function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
|
|
function lerp(a, b, t) { return a + (b - a) * t; }
|
|
function dist2(ax, ay, bx, by) { const dx = ax - bx, dy = ay - by; return dx * dx + dy * dy; }
|
|
let __uid = 1;
|
|
function uid() { return __uid++; }
|
|
function rand(a, b) { return a + Math.random() * (b - a); }
|
|
function randi(a, b) { return Math.floor(rand(a, b + 1)); }
|
|
function choice(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
|
|
function chance(p) { return Math.random() < p; }
|
|
|
|
/** Deterministic-ish name pools for CAS random & visitors */
|
|
const FIRST_NAMES_M = ['Mortimer','Bob','Michael','Gunther','Mickey','Goopy','Skip','Dustin','Dirk','Romeo','Puck','Tybalt','Mercutio','Patrizio','Don','Victor'];
|
|
const FIRST_NAMES_F = ['Bella','Cassandra','Dina','Nina','Cornelia','Riley','Kaylynn','Jenny','Brandi','Angela','Lilith','Hermia','Juliet','Isabel','Kayla','Molly'];
|
|
const LAST_NAMES = ['Goth','Bachelor','Dreamer','Pleasant','Caliente','Broke','Oldie','Rosalie','Monty','Capp','Summerdream','Smith','Newbie','Grunt','Jacquet','Freshe','Tricou','Moore','Oasis','Lothario'];
|
|
|
|
/* ---------- isometric transforms ----------
|
|
* World coordinates: tile floats (x,y). Screen coords pre-camera. */
|
|
function isoToScreen(x, y) {
|
|
return [ (x - y) * TW / 2, (x + y) * TH / 2 ];
|
|
}
|
|
/** screen (pre-camera) -> world tile float */
|
|
function screenToIso(sx, sy) {
|
|
const x = (sx / (TW / 2) + sy / (TH / 2)) / 2;
|
|
const y = (sy / (TH / 2) - sx / (TW / 2)) / 2;
|
|
return [x, y];
|
|
}
|
|
/** camera helpers attached to game object G */
|
|
function worldToPx(wx, wy) {
|
|
const [sx, sy] = isoToScreen(wx, wy);
|
|
return [ sx * G.cam.zoom + G.cam.x, sy * G.cam.zoom + G.cam.y ];
|
|
}
|
|
function pxToWorld(px, py) {
|
|
const sx = (px - G.cam.x) / G.cam.zoom, sy = (py - G.cam.y) / G.cam.zoom;
|
|
return screenToIso(sx, sy);
|
|
}
|
|
|
|
/* Facing directions: 0=S(+y), 1=W(-x), 2=N(-y), 3=E(+x) — matches sprite art */
|
|
const DIRS = [
|
|
{ dx: 0, dy: 1 }, // S
|
|
{ dx: -1, dy: 0 }, // W
|
|
{ dx: 0, dy: -1 }, // N
|
|
{ dx: 1, dy: 0 }, // E
|
|
];
|
|
function dirFromDelta(dx, dy) {
|
|
if (Math.abs(dx) >= Math.abs(dy)) return dx > 0 ? 3 : 1;
|
|
return dy > 0 ? 0 : 2;
|
|
}
|
|
|
|
/* ---------- colors ---------- */
|
|
const SKINS = ['#f6d7c4','#eeb98f','#c98a5e','#8d5a3b'];
|
|
const HAIRS = ['#2a1c12','#5a3a1e','#a56a2f','#d9b24a','#b23a2a','#777777','#101010','#e8e0cf'];
|
|
const SHIRTS = ['#d94a4a','#4a72d9','#43b05a','#e8a33d','#8a52c9','#3ab5b0','#e06aa8','#556077','#f0f0f0','#22262e'];
|
|
const PANTS = ['#2e3542','#3d5a99','#6b4226','#7a7f88','#274832','#803040'];
|
|
|
|
/* ---------- event bus ---------- */
|
|
const Bus = {
|
|
map: {},
|
|
on(ev, fn) { (this.map[ev] ||= []).push(fn); },
|
|
emit(ev, data) { (this.map[ev] || []).forEach(fn => fn(data)); },
|
|
};
|
|
|
|
/* ---------- money formatting ---------- */
|
|
function fmtMoney(n) { return '§' + Math.round(n).toLocaleString('en-US'); }
|
|
|
|
/** trace a rounded-rectangle path (caller fills/strokes) */
|
|
function roundRect(ctx, x, y, w, h, r) {
|
|
r = Math.min(r, w / 2, h / 2);
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + r, y);
|
|
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
ctx.arcTo(x, y + h, x, y, r);
|
|
ctx.arcTo(x, y, x + w, y, r);
|
|
ctx.closePath();
|
|
}
|
|
|
|
/* ---------- time helpers ---------- */
|
|
function hourTo12(h) {
|
|
h = ((h % 24) + 24) % 24;
|
|
const ampm = h < 12 ? 'AM' : 'PM';
|
|
let hh = h % 12; if (hh === 0) hh = 12;
|
|
return hh + ':' + String(G.time.min).padStart(2, '0') + ' ' + ampm;
|
|
}
|
|
const DAY_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
|
|
/* ---------- deep merge for save/load safety ---------- */
|
|
function isObj(v) { return v && typeof v === 'object' && !Array.isArray(v); }
|