|
|
|
@ -896,6 +896,73 @@ export class GameRenderer {
|
|
|
|
gfx.fill({ color: 0x5a4a3a, alpha: 0.9 });
|
|
|
|
gfx.fill({ color: 0x5a4a3a, alpha: 0.9 });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Small plaza fountain (procedural towns; server towns use decoration.well at center). */
|
|
|
|
|
|
|
|
private _drawTownFountain(gfx: Graphics, cx: number, cy: number, s: number): void {
|
|
|
|
|
|
|
|
gfx.ellipse(cx, cy + 2 * s, 14 * s, 7 * s);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x4a5a6a, alpha: 0.85 });
|
|
|
|
|
|
|
|
gfx.stroke({ color: 0x3a4550, width: 1.2, alpha: 0.65 });
|
|
|
|
|
|
|
|
gfx.ellipse(cx, cy + 2 * s, 10 * s, 5 * s);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x5a8aaa, alpha: 0.45 });
|
|
|
|
|
|
|
|
gfx.rect(cx - 3 * s, cy - 14 * s, 6 * s, 16 * s);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x7a7a88, alpha: 0.9 });
|
|
|
|
|
|
|
|
gfx.rect(cx - 5 * s, cy - 16 * s, 10 * s, 3 * s);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x6a6a78, alpha: 0.88 });
|
|
|
|
|
|
|
|
gfx.circle(cx, cy - 10 * s, 2.2 * s);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0xaaddff, alpha: 0.35 });
|
|
|
|
|
|
|
|
gfx.arc(cx, cy - 10 * s, 3 * s, -Math.PI * 0.85, -Math.PI * 0.15);
|
|
|
|
|
|
|
|
gfx.stroke({ color: 0x88ccff, width: 1.2, alpha: 0.4 });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Paved town square at the settlement center (under well / fountain and civic building).
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private _drawTownPlaza(
|
|
|
|
|
|
|
|
gfx: Graphics,
|
|
|
|
|
|
|
|
tx: number,
|
|
|
|
|
|
|
|
ty: number,
|
|
|
|
|
|
|
|
groundW: number,
|
|
|
|
|
|
|
|
groundH: number,
|
|
|
|
|
|
|
|
): void {
|
|
|
|
|
|
|
|
const pw = groundW * 0.42;
|
|
|
|
|
|
|
|
const ph = groundH * 0.42;
|
|
|
|
|
|
|
|
gfx.ellipse(tx, ty, pw, ph);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x7a7878, alpha: 0.55 });
|
|
|
|
|
|
|
|
gfx.stroke({ color: 0x5a5858, width: 1.2, alpha: 0.45 });
|
|
|
|
|
|
|
|
const step = Math.max(10, pw * 0.14);
|
|
|
|
|
|
|
|
for (let dx = -pw + step * 0.3; dx < pw; dx += step) {
|
|
|
|
|
|
|
|
for (let dy = -ph + step * 0.25; dy < ph; dy += step * 0.85) {
|
|
|
|
|
|
|
|
if ((dx * dx) / (pw * pw) + (dy * dy) / (ph * ph) > 0.82) continue;
|
|
|
|
|
|
|
|
const h = ((Math.floor(dx / step) * 31) ^ (Math.floor(dy / step) * 17)) & 1;
|
|
|
|
|
|
|
|
gfx.rect(tx + dx - step * 0.08, ty + dy - step * 0.08, step * 0.45, step * 0.38);
|
|
|
|
|
|
|
|
gfx.fill({ color: h ? 0x6a686e : 0x757278, alpha: 0.35 });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Single civic building (hall / notice board) facing the plaza — not an NPC home. */
|
|
|
|
|
|
|
|
private _drawCivicBuilding(gfx: Graphics, cx: number, cy: number, s: number): void {
|
|
|
|
|
|
|
|
const w = 52 * s;
|
|
|
|
|
|
|
|
const h = 38 * s;
|
|
|
|
|
|
|
|
const rh = 26 * s;
|
|
|
|
|
|
|
|
gfx.rect(cx - w / 2, cy - h, w, h);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x8a9098, alpha: 0.95 });
|
|
|
|
|
|
|
|
gfx.stroke({ color: 0x4a5058, width: 1.2, alpha: 0.55 });
|
|
|
|
|
|
|
|
gfx.poly([
|
|
|
|
|
|
|
|
cx - w / 2 - 4 * s, cy - h,
|
|
|
|
|
|
|
|
cx + w / 2 + 4 * s, cy - h,
|
|
|
|
|
|
|
|
cx + w / 2, cy - h - rh,
|
|
|
|
|
|
|
|
cx - w / 2, cy - h - rh,
|
|
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x4a5560, alpha: 0.92 });
|
|
|
|
|
|
|
|
gfx.rect(cx - 8 * s, cy - h * 0.65, 16 * s, 22 * s);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x2a3540, alpha: 0.75 });
|
|
|
|
|
|
|
|
gfx.stroke({ color: 0x1a2530, width: 0.8, alpha: 0.5 });
|
|
|
|
|
|
|
|
gfx.rect(cx - w / 2 + 6 * s, cy - h - rh * 0.35, 4 * s, rh * 0.55);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x6a7580, alpha: 0.85 });
|
|
|
|
|
|
|
|
gfx.rect(cx + w / 2 - 10 * s, cy - h - rh * 0.35, 4 * s, rh * 0.55);
|
|
|
|
|
|
|
|
gfx.fill({ color: 0x6a7580, alpha: 0.85 });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Draw a signpost decoration. */
|
|
|
|
/** Draw a signpost decoration. */
|
|
|
|
private _drawSignpost(gfx: Graphics, cx: number, cy: number, s: number): void {
|
|
|
|
private _drawSignpost(gfx: Graphics, cx: number, cy: number, s: number): void {
|
|
|
|
gfx.rect(cx - 1 * s, cy - 16 * s, 2 * s, 16 * s);
|
|
|
|
gfx.rect(cx - 1 * s, cy - 16 * s, 2 * s, 16 * s);
|
|
|
|
@ -931,7 +998,8 @@ export class GameRenderer {
|
|
|
|
const r3 = ((hash * 7 + i * 13) & 0xff) / 0xff;
|
|
|
|
const r3 = ((hash * 7 + i * 13) & 0xff) / 0xff;
|
|
|
|
|
|
|
|
|
|
|
|
const angle = (i / houseCount) * Math.PI * 2 + r1 * 0.4;
|
|
|
|
const angle = (i / houseCount) * Math.PI * 2 + r1 * 0.4;
|
|
|
|
const dist = spread * (0.2 + r2 * 0.65);
|
|
|
|
// Keep town center clear for plaza + fountain/well + civic building
|
|
|
|
|
|
|
|
const dist = spread * (0.36 + r2 * 0.52);
|
|
|
|
const dx = Math.cos(angle) * dist;
|
|
|
|
const dx = Math.cos(angle) * dist;
|
|
|
|
const dy = Math.sin(angle) * dist * 0.5;
|
|
|
|
const dy = Math.sin(angle) * dist * 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
@ -955,7 +1023,7 @@ export class GameRenderer {
|
|
|
|
const stallCount = houseCount >= 10 ? 2 : 1;
|
|
|
|
const stallCount = houseCount >= 10 ? 2 : 1;
|
|
|
|
for (let si = 0; si < stallCount; si++) {
|
|
|
|
for (let si = 0; si < stallCount; si++) {
|
|
|
|
const stallAngle = (si + 0.5) * Math.PI + (townSeed & 0xf) * 0.1;
|
|
|
|
const stallAngle = (si + 0.5) * Math.PI + (townSeed & 0xf) * 0.1;
|
|
|
|
const stallDist = spread * 0.35;
|
|
|
|
const stallDist = spread * 0.42;
|
|
|
|
this._drawTownStall(
|
|
|
|
this._drawTownStall(
|
|
|
|
gfx,
|
|
|
|
gfx,
|
|
|
|
tx + Math.cos(stallAngle) * stallDist,
|
|
|
|
tx + Math.cos(stallAngle) * stallDist,
|
|
|
|
@ -967,8 +1035,9 @@ export class GameRenderer {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Draw towns visible in the current viewport.
|
|
|
|
* Draw towns visible in the current viewport.
|
|
|
|
* Each town renders a ground plane, a large cluster of buildings with detail,
|
|
|
|
* Each town renders a ground plane, a paved central plaza, a fountain or well at the
|
|
|
|
* market stalls, fences, a name label, and a dashed border.
|
|
|
|
* plaza center (procedural fallback) or from server buildings, one civic hall offset
|
|
|
|
|
|
|
|
* from center, NPC homes and stalls, a name label, and a dashed border.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
drawTowns(towns: TownData[], camera: Camera, screenWidth: number, screenHeight: number): void {
|
|
|
|
drawTowns(towns: TownData[], camera: Camera, screenWidth: number, screenHeight: number): void {
|
|
|
|
const gfx = this._townGfx;
|
|
|
|
const gfx = this._townGfx;
|
|
|
|
@ -1029,14 +1098,27 @@ export class GameRenderer {
|
|
|
|
gfx.circle(tx, ty, borderRadius * 0.6);
|
|
|
|
gfx.circle(tx, ty, borderRadius * 0.6);
|
|
|
|
gfx.fill({ color: 0xdaa520, alpha: 0.04 });
|
|
|
|
gfx.fill({ color: 0xdaa520, alpha: 0.04 });
|
|
|
|
|
|
|
|
|
|
|
|
// --- Buildings: server-driven if available, fallback procedural ---
|
|
|
|
// --- Central plaza (paving); well/fountain + civic sit on or beside it ---
|
|
|
|
|
|
|
|
this._drawTownPlaza(gfx, tx, ty, groundW, groundH);
|
|
|
|
|
|
|
|
|
|
|
|
const townSeed = typeof town.id === 'number' ? town.id : 0;
|
|
|
|
const townSeed = typeof town.id === 'number' ? town.id : 0;
|
|
|
|
const spread = 100 * s;
|
|
|
|
const spread = 100 * s;
|
|
|
|
|
|
|
|
const civicWx = town.centerX + 0.18 * town.radius;
|
|
|
|
|
|
|
|
const civicWy = town.centerY - 0.36 * town.radius;
|
|
|
|
|
|
|
|
const civicScreen = worldToScreen(civicWx, civicWy);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Buildings: server-driven if available, fallback procedural ---
|
|
|
|
if (town.buildings && town.buildings.length > 0) {
|
|
|
|
if (town.buildings && town.buildings.length > 0) {
|
|
|
|
this._drawServerBuildings(gfx, town.buildings, tx, ty, s);
|
|
|
|
this._drawServerBuildings(gfx, town.buildings, tx, ty, s);
|
|
|
|
|
|
|
|
this._drawCivicBuilding(gfx, civicScreen.x, civicScreen.y, s);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this._drawProceduralBuildings(gfx, tx, ty, s, spread, town.size, townSeed);
|
|
|
|
this._drawProceduralBuildings(gfx, tx, ty, s, spread, town.size, townSeed);
|
|
|
|
|
|
|
|
if ((townSeed & 1) === 0) {
|
|
|
|
|
|
|
|
this._drawTownFountain(gfx, tx, ty, s);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
this._drawTownWell(gfx, tx, ty, s);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this._drawCivicBuilding(gfx, civicScreen.x, civicScreen.y, s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Town name label (larger font, positioned higher) ---
|
|
|
|
// --- Town name label (larger font, positioned higher) ---
|
|
|
|
|