mirror of
https://github.com/SirBlobby/Hoya26.git
synced 2026-02-04 03:34:34 -05:00
Landscape updates
This commit is contained in:
@@ -4,46 +4,141 @@ export function drawSmoggyBuildings(dc: DrawContext): void {
|
||||
const { ctx, width, height, state } = dc;
|
||||
const parallax = state.mouseX * 0.35;
|
||||
|
||||
const drawBuilding = (x: number, bWidth: number, bHeight: number) => {
|
||||
const baseY = height * 0.8;
|
||||
// Helper to draw varied building shapes (Geometry matched to Clean City)
|
||||
const drawBuildingShape = (
|
||||
bx: number, by: number, bw: number, bh: number,
|
||||
color: string, windowColor: string, type: number,
|
||||
stableSeedX: number
|
||||
) => {
|
||||
ctx.fillStyle = color;
|
||||
// Base
|
||||
ctx.fillRect(bx, by - bh, bw, bh + height * 0.5);
|
||||
|
||||
ctx.fillStyle = '#37474F';
|
||||
ctx.fillRect(x + parallax, baseY - bHeight, bWidth, bHeight);
|
||||
// Roof variations (Matched to Clean City)
|
||||
if (type === 1) { // Slanted
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bx, by - bh);
|
||||
ctx.lineTo(bx + bw, by - bh);
|
||||
ctx.lineTo(bx + bw, by - bh - 20);
|
||||
ctx.fill();
|
||||
} else if (type === 2) { // Spire (Matched Clean City)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bx + bw/2 - 5, by - bh);
|
||||
ctx.lineTo(bx + bw/2, by - bh - 40);
|
||||
ctx.lineTo(bx + bw/2 + 5, by - bh);
|
||||
ctx.fill();
|
||||
// Flag/Light (Red for pollution warning?)
|
||||
ctx.fillStyle = '#B71C1C';
|
||||
ctx.fillRect(bx + bw/2 - 1, by - bh - 40, 2, 40);
|
||||
ctx.fillStyle = color;
|
||||
} else if (type === 3) { // Tri-peak (Matched Clean City)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bx, by - bh);
|
||||
ctx.lineTo(bx + bw * 0.25, by - bh - 15);
|
||||
ctx.lineTo(bx + bw * 0.5, by - bh);
|
||||
ctx.lineTo(bx + bw * 0.75, by - bh - 15);
|
||||
ctx.lineTo(bx + bw, by - bh);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
|
||||
ctx.fillRect(x + parallax, baseY - bHeight * 0.8, bWidth * 0.5, bHeight * 0.3);
|
||||
ctx.fillRect(x + parallax + bWidth * 0.6, baseY - bHeight * 0.5, bWidth * 0.3, bHeight * 0.2);
|
||||
|
||||
const windowCols = Math.floor(bWidth / 18);
|
||||
const windowRows = Math.floor(bHeight / 25);
|
||||
|
||||
for (let row = 0; row < windowRows; row++) {
|
||||
for (let col = 0; col < windowCols; col++) {
|
||||
// Deterministic "randomness" based on position to stop flashing
|
||||
const seed = x + col * 13 + row * 71;
|
||||
const isBroken = Math.abs(Math.sin(seed)) > 0.85;
|
||||
const isLit = Math.cos(seed) > 0.1;
|
||||
|
||||
if (!isBroken) {
|
||||
ctx.fillStyle = isLit ? 'rgba(255, 180, 100, 0.5)' : 'rgba(50, 50, 50, 0.8)';
|
||||
ctx.fillRect(
|
||||
x + parallax + col * 18 + 5,
|
||||
baseY - bHeight + row * 25 + 8,
|
||||
10, 12
|
||||
);
|
||||
}
|
||||
}
|
||||
// Windows (Matched Clean City: 8x12)
|
||||
const cols = Math.floor(bw / 12);
|
||||
const rows = Math.floor(bh / 18);
|
||||
const padding = 4;
|
||||
|
||||
ctx.fillStyle = windowColor;
|
||||
for(let r=0; r<rows; r++) {
|
||||
for(let c=0; c<cols; c++) {
|
||||
// Deterministic pattern matching clean city
|
||||
const seed = stableSeedX + c * 23 + r * 17;
|
||||
if (Math.abs(Math.sin(seed)) > 0.8) continue; // Some gaps
|
||||
|
||||
// Flicker logic
|
||||
if (Math.abs(Math.cos(seed)) > 0.92) {
|
||||
ctx.fillStyle = 'rgba(255, 200, 100, 0.4)'; // Yellowish light
|
||||
ctx.fillRect(bx + c*12 + padding, by - bh + r*18 + padding, 8, 12);
|
||||
ctx.fillStyle = windowColor;
|
||||
} else {
|
||||
ctx.fillRect(bx + c*12 + padding, by - bh + r*18 + padding, 8, 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
drawBuilding(width * 0.02, 60, height * 0.35);
|
||||
drawBuilding(width * 0.1, 75, height * 0.48);
|
||||
drawBuilding(width * 0.22, 55, height * 0.38);
|
||||
drawBuilding(width * 0.32, 90, height * 0.55);
|
||||
drawBuilding(width * 0.48, 65, height * 0.42);
|
||||
drawBuilding(width * 0.58, 80, height * 0.52);
|
||||
drawBuilding(width * 0.72, 55, height * 0.36);
|
||||
drawBuilding(width * 0.82, 70, height * 0.45);
|
||||
// -- Background Layer -- (Darker, slower)
|
||||
const bgParallax = state.mouseX * 0.15;
|
||||
const bgBaseY = height * 0.78;
|
||||
let currentBgX = -100;
|
||||
while(currentBgX < width + 100) {
|
||||
const seed = currentBgX * 0.1;
|
||||
const bW = 60 + (Math.abs(Math.sin(seed)) * 40);
|
||||
const bH = height * 0.25 + (Math.abs(Math.cos(seed)) * height * 0.25);
|
||||
const gap = 10 + Math.abs(Math.sin(seed * 2)) * 20;
|
||||
|
||||
// Exact same type distribution as Clean City BG
|
||||
const type = Math.floor(Math.abs(Math.sin(seed * 5)) * 4);
|
||||
|
||||
drawBuildingShape(
|
||||
currentBgX + bgParallax, bgBaseY, bW, bH,
|
||||
'#1f292e', 'rgba(255, 255, 255, 0.03)',
|
||||
type, currentBgX
|
||||
);
|
||||
currentBgX += bW + gap;
|
||||
}
|
||||
|
||||
// -- Middle Layer --
|
||||
const midParallax = state.mouseX * 0.25;
|
||||
const midBaseY = height * 0.8;
|
||||
let currentMidX = -50;
|
||||
while(currentMidX < width + 50) {
|
||||
const seed = currentMidX * 0.17;
|
||||
const bW = 70 + (Math.abs(Math.sin(seed)) * 50);
|
||||
const bH = height * 0.2 + (Math.abs(Math.cos(seed)) * height * 0.2);
|
||||
const gap = 30 + Math.abs(Math.sin(seed * 4)) * 40;
|
||||
|
||||
// Exact same type distribution as Clean City Mid
|
||||
const type = Math.floor(Math.abs(Math.cos(seed * 7)) * 4);
|
||||
|
||||
drawBuildingShape(
|
||||
currentMidX + midParallax, midBaseY, bW, bH,
|
||||
'#2d3b42', 'rgba(200, 200, 200, 0.08)',
|
||||
type, currentMidX
|
||||
);
|
||||
currentMidX += bW + gap;
|
||||
}
|
||||
|
||||
// -- Foreground Layer --
|
||||
const fgBaseY = height * 0.82;
|
||||
let currentFgX = 20;
|
||||
while(currentFgX < width - 20) {
|
||||
const seed = currentFgX * 0.33;
|
||||
const bW = 90 + (Math.abs(Math.sin(seed)) * 60);
|
||||
const bH = height * 0.15 + (Math.abs(Math.cos(seed)) * height * 0.2);
|
||||
const gap = 80 + Math.abs(Math.sin(seed * 1.5)) * 80;
|
||||
|
||||
// Exact same type distribution as Clean City FG
|
||||
const type = Math.floor(Math.abs(Math.sin(seed * 9)) * 4);
|
||||
|
||||
ctx.save();
|
||||
drawBuildingShape(
|
||||
currentFgX + parallax, fgBaseY, bW, bH,
|
||||
'#37474F', 'rgba(50, 50, 50, 0.8)',
|
||||
type, currentFgX
|
||||
);
|
||||
|
||||
// Extra FG details (AC units) - Rare
|
||||
if (Math.abs(Math.sin(seed)) > 0.7) {
|
||||
const bx = currentFgX + parallax;
|
||||
const by = fgBaseY - bH;
|
||||
if (type === 0) { // Only on flat roofs
|
||||
ctx.fillStyle = '#263238';
|
||||
ctx.fillRect(bx + 10, by - 12, 15, 12);
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
currentFgX += bW + gap;
|
||||
}
|
||||
}
|
||||
|
||||
export function drawSmokestacks(dc: DrawContext): void {
|
||||
@@ -55,7 +150,8 @@ export function drawSmokestacks(dc: DrawContext): void {
|
||||
const baseY = height * 0.8;
|
||||
|
||||
ctx.fillStyle = '#263238';
|
||||
ctx.fillRect(x + parallax - 12, baseY - stackHeight, 24, stackHeight);
|
||||
// Extend smokestack down as well
|
||||
ctx.fillRect(x + parallax - 12, baseY - stackHeight, 24, stackHeight + height * 0.2);
|
||||
|
||||
ctx.fillStyle = '#B71C1C';
|
||||
ctx.fillRect(x + parallax - 12, baseY - stackHeight, 24, 15);
|
||||
@@ -106,6 +202,7 @@ export function drawSmog(dc: DrawContext): void {
|
||||
|
||||
export function drawTrafficJam(dc: DrawContext): void {
|
||||
const { ctx, width, height } = dc;
|
||||
const time = Date.now() * 0.0005; // Adjust speed
|
||||
|
||||
ctx.fillStyle = '#2E2E2E';
|
||||
ctx.fillRect(0, height * 0.82, width, height * 0.08);
|
||||
@@ -119,7 +216,7 @@ export function drawTrafficJam(dc: DrawContext): void {
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
const drawCar = (x: number, y: number, color: string) => {
|
||||
const drawCar = (x: number, y: number, color: string, facingRight: boolean) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, 35, 14, 2);
|
||||
@@ -133,23 +230,63 @@ export function drawTrafficJam(dc: DrawContext): void {
|
||||
ctx.arc(x + 8, y + 14, 4, 0, Math.PI * 2);
|
||||
ctx.arc(x + 27, y + 14, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Lights
|
||||
if (facingRight) {
|
||||
// Headlights (right)
|
||||
ctx.fillStyle = 'rgba(255, 255, 200, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + 35, y + 5, 2, 0, Math.PI * 2); // Front right
|
||||
ctx.arc(x + 35, y + 10, 2, 0, Math.PI * 2); // Front left (perspective approx)
|
||||
ctx.fill();
|
||||
|
||||
// Taillights (left) - Red
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y + 5, 2, 0, Math.PI * 2);
|
||||
ctx.arc(x, y + 10, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
} else {
|
||||
// Headlights (left)
|
||||
ctx.fillStyle = 'rgba(255, 255, 200, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y + 5, 2, 0, Math.PI * 2);
|
||||
ctx.arc(x, y + 10, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Taillights (right) - Red
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + 35, y + 5, 2, 0, Math.PI * 2);
|
||||
ctx.arc(x + 35, y + 10, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
const carColors = ['#616161', '#424242', '#757575', '#546E7A', '#455A64'];
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const lane = i % 2;
|
||||
const x = i * 55 + 20;
|
||||
const y = height * 0.83 + lane * 35;
|
||||
drawCar(x, y, carColors[i % carColors.length]);
|
||||
}
|
||||
|
||||
// Top lane (moving left)
|
||||
const lane0Y = height * 0.82;
|
||||
const spacing = 70;
|
||||
const numCars = Math.ceil(width / spacing) + 2;
|
||||
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.4)';
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const x = i * 55 + 48;
|
||||
const y = height * 0.835 + (i % 2) * 35 + 7;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 3, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
for (let i = 0; i < numCars; i++) {
|
||||
// Lane 0: Move Left
|
||||
const offset0 = (time * 60 + i * spacing) % (width + 100);
|
||||
const x0 = (width + 50) - offset0 - 50; // Move from right to left
|
||||
|
||||
drawCar(x0, lane0Y, carColors[i % carColors.length], false);
|
||||
}
|
||||
|
||||
// Bottom lane (moving right)
|
||||
const lane1Y = height * 0.82 + 25;
|
||||
for (let i = 0; i < numCars; i++) {
|
||||
// Lane 1: Move Right
|
||||
const offset1 = (time * 60 + i * spacing) % (width + 100);
|
||||
const x1 = offset1 - 50;
|
||||
|
||||
drawCar(x1, lane1Y, carColors[(i + 3) % carColors.length], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +296,10 @@ export function drawDebris(dc: DrawContext): void {
|
||||
ctx.fillStyle = '#5D4037';
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const x = Math.random() * width;
|
||||
const y = height * 0.81 + Math.random() * 8;
|
||||
const size = 3 + Math.random() * 5;
|
||||
const seed = i * 13.7;
|
||||
const x = (Math.abs(Math.sin(seed * 4.1)) * width);
|
||||
const y = height * 0.81 + Math.abs(Math.cos(seed * 2.9)) * 8;
|
||||
const size = 3 + Math.abs(Math.sin(seed * 7.3)) * 5;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.rect(x, y, size, size * 0.7);
|
||||
@@ -169,10 +307,136 @@ export function drawDebris(dc: DrawContext): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function drawCityGround(dc: DrawContext): void {
|
||||
const { ctx, width, height } = dc;
|
||||
|
||||
// Draw Sidewalk at the bottom
|
||||
const pavementY = height * 0.90;
|
||||
|
||||
// Curb (side surface)
|
||||
ctx.fillStyle = '#546E7A';
|
||||
ctx.fillRect(0, pavementY, width, height * 0.02);
|
||||
|
||||
// Sidewalk pavement (top surface) - Plain
|
||||
ctx.fillStyle = '#37474F';
|
||||
ctx.fillRect(0, pavementY + height * 0.02, width, height * 0.08);
|
||||
|
||||
// Detailed Trash (retained for polluted theme, but kept sidewalk otherwise plain)
|
||||
const drawTrash = (tx: number, ty: number, type: 'can' | 'paper' | 'bottle') => {
|
||||
if (type === 'can') {
|
||||
ctx.fillStyle = '#B71C1C'; // Red can
|
||||
ctx.fillRect(tx, ty, 6, 8);
|
||||
ctx.fillStyle = '#E57373'; // Label
|
||||
ctx.fillRect(tx + 1, ty + 2, 4, 4);
|
||||
} else if (type === 'paper') {
|
||||
ctx.fillStyle = '#ECEFF1';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tx, ty);
|
||||
ctx.lineTo(tx + 8, ty + 2);
|
||||
ctx.lineTo(tx + 6, ty + 6);
|
||||
ctx.lineTo(tx - 2, ty + 4);
|
||||
ctx.fill();
|
||||
} else { // bottle
|
||||
ctx.fillStyle = 'rgba(76, 175, 80, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.rect(tx, ty, 4, 10);
|
||||
ctx.rect(tx + 1, ty - 3, 2, 3);
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const seed = i * 997;
|
||||
const tx = (seed * 31) % width;
|
||||
const ty = pavementY + height * 0.02 + (seed % (height * 0.06));
|
||||
const types: ('can' | 'paper' | 'bottle')[] = ['can', 'paper', 'bottle'];
|
||||
drawTrash(tx, ty, types[i % 3]);
|
||||
}
|
||||
}
|
||||
|
||||
export function drawStreetLamps(dc: DrawContext): void {
|
||||
const { ctx, width, height, state } = dc;
|
||||
const parallax = state.mouseX * 0.8;
|
||||
|
||||
const drawLamp = (x: number) => {
|
||||
const lampBaseX = x + parallax;
|
||||
// Anchor firmly on the sidewalk surface (sidewalk starts at 0.90 + 0.02)
|
||||
const pavementY = height * 0.94;
|
||||
|
||||
// Pole
|
||||
ctx.fillStyle = '#263238';
|
||||
ctx.fillRect(lampBaseX - 3, pavementY - 150, 6, 150);
|
||||
|
||||
// Lamp Head
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lampBaseX, pavementY - 150);
|
||||
ctx.lineTo(lampBaseX + 25, pavementY - 150); // Arm
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#263238';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lampBaseX + 20, pavementY - 155);
|
||||
ctx.lineTo(lampBaseX + 45, pavementY - 145);
|
||||
ctx.lineTo(lampBaseX + 20, pavementY - 140);
|
||||
ctx.fill();
|
||||
|
||||
// Light Glow
|
||||
const gradient = ctx.createRadialGradient(
|
||||
lampBaseX + 35, pavementY - 142, 2,
|
||||
lampBaseX + 35, pavementY - 142, 40
|
||||
);
|
||||
gradient.addColorStop(0, 'rgba(255, 235, 59, 0.9)');
|
||||
gradient.addColorStop(0.4, 'rgba(255, 193, 7, 0.3)');
|
||||
gradient.addColorStop(1, 'rgba(255, 193, 7, 0)');
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(lampBaseX + 35, pavementY - 142, 40, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
// Draw a few lamps spaced out
|
||||
drawLamp(width * 0.15);
|
||||
drawLamp(width * 0.5);
|
||||
drawLamp(width * 0.85);
|
||||
}
|
||||
|
||||
export function drawPollutedClouds(dc: DrawContext): void {
|
||||
const { ctx, width, height, state } = dc;
|
||||
|
||||
// Darker, heavier clouds for pollution
|
||||
const drawCloud = (x: number, y: number, scale: number) => {
|
||||
ctx.fillStyle = 'rgba(90, 90, 90, 0.85)';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x, y, 60 * scale, 35 * scale, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x + 50 * scale, y + 10 * scale, 50 * scale, 30 * scale, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x - 40 * scale, y + 5 * scale, 45 * scale, 25 * scale, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x + 20 * scale, y - 15 * scale, 40 * scale, 25 * scale, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
const cloudOffsetX = state.mouseX * 0.5 - state.scrollY * 0.03;
|
||||
const cloudOffsetY = state.scrollY * 0.1;
|
||||
|
||||
// Lower, more oppressive positioning
|
||||
drawCloud(width * 0.2 + cloudOffsetX, height * 0.18 + cloudOffsetY, 1.3);
|
||||
drawCloud(width * 0.6 + cloudOffsetX * 0.7, height * 0.22 + cloudOffsetY * 0.8, 1.1);
|
||||
drawCloud(width * 0.9 + cloudOffsetX * 0.5, height * 0.15 + cloudOffsetY * 0.6, 1.2);
|
||||
}
|
||||
|
||||
export function drawPollutedCityScene(dc: DrawContext): void {
|
||||
drawPollutedClouds(dc);
|
||||
drawSmog(dc);
|
||||
drawSmoggyBuildings(dc);
|
||||
drawSmokestacks(dc);
|
||||
drawDebris(dc);
|
||||
// Draw traffic BEFORE ground so it looks like it's behind the sidewalk "paved" area
|
||||
drawTrafficJam(dc);
|
||||
drawCityGround(dc);
|
||||
drawStreetLamps(dc); // Foreground, on top of sidewalk
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user