Files
the-sims-online-2d/js/core.js
T

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