all my stuff

This commit is contained in:
michaelabilliot
2025-05-02 00:37:13 +03:00
parent e849cbd091
commit 59fa65fa41
44 changed files with 729 additions and 871 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 938 KiB

After

Width:  |  Height:  |  Size: 282 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 MiB

After

Width:  |  Height:  |  Size: 278 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 KiB

After

Width:  |  Height:  |  Size: 209 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 KiB

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 MiB

After

Width:  |  Height:  |  Size: 469 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 161 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 MiB

After

Width:  |  Height:  |  Size: 256 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+274 -274
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 369 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+1 -1
View File
@@ -292,7 +292,7 @@ export class TerritoryLayer implements Layer {
this.game.x(tile),
this.game.y(tile),
this.theme.territoryColor(owner),
150,
200,
);
}
}
+44 -3
View File
@@ -450,16 +450,57 @@ export class DefaultConfig implements Config {
mag = 85;
speed = 16.5;
break;
case TerrainType.Highland:
case TerrainType.Forest:
mag = 95;
speed = 18;
break; // Forests slightly harder
case TerrainType.Desert:
mag = 75;
speed = 20;
break; // Deserts easier to defend, slower to cross
case TerrainType.DesertTransition:
mag = 80;
speed = 17;
break;
case TerrainType.ArcticForest:
mag = 90;
speed = 22;
break; // Renamed Arctic
case TerrainType.Beach:
mag = 70;
speed = 15;
break; // Easy to attack/cross
case TerrainType.MidMountain:
mag = 100;
speed = 20;
break;
case TerrainType.Mountain:
case TerrainType.HighMountain:
mag = 120;
speed = 25;
break;
case TerrainType.Jungle:
mag = 95;
speed = 20;
break; // Difficult terrain
case TerrainType.JunglePlains:
mag = 85;
speed = 18;
break; // Easier than pure jungle
case TerrainType.ArcticPlains:
mag = 85;
speed = 18;
break; // Similar to plains but maybe slightly slower
case TerrainType.SnowyHighMountain:
mag = 120;
speed = 25;
break; // Same as High Mountain
case TerrainType.Lake:
case TerrainType.Ocean:
throw new Error(
`Cannot calculate attack logic for water type: ${type}`,
);
default:
throw new Error(`terrain type ${type} not supported`);
assertNever(type); // Ensure all types are handled
}
if (defender.isPlayer()) {
for (const dp of gm.nearbyUnits(
+36 -31
View File
@@ -23,8 +23,6 @@ export const pastelTheme = new (class implements Theme {
private rand = new PseudoRandom(123);
private background = colord({ r: 60, g: 60, b: 60 });
private land = colord({ r: 194, g: 193, b: 148 });
private shore = colord({ r: 204, g: 203, b: 158 });
private falloutColors = [
colord({ r: 120, g: 255, b: 71 }), // Original color
colord({ r: 130, g: 255, b: 85 }), // Slightly lighter
@@ -32,8 +30,8 @@ export const pastelTheme = new (class implements Theme {
colord({ r: 125, g: 255, b: 75 }), // Warmer tint
colord({ r: 115, g: 250, b: 68 }), // Cooler tint
];
private water = colord({ r: 70, g: 132, b: 180 });
private shorelineWater = colord({ r: 100, g: 143, b: 255 });
private water = colord({ r: 70, g: 122, b: 207 });
private shorelineWater = colord({ r: 84, g: 139, b: 226 });
private _selfColor = colord({ r: 0, g: 255, b: 0 });
private _allyColor = colord({ r: 255, g: 255, b: 0 });
@@ -115,41 +113,48 @@ export const pastelTheme = new (class implements Theme {
}
terrainColor(gm: GameMap, tile: TileRef): Colord {
const mag = gm.magnitude(tile);
if (gm.isShore(tile)) {
return this.shore;
}
switch (gm.terrainType(tile)) {
const type = gm.terrainType(tile);
const mag = gm.magnitude(tile); // Magnitude for water is distance/2
switch (type) {
case TerrainType.Ocean:
case TerrainType.Lake:
const w = this.water.rgba;
if (gm.isShoreline(tile) && gm.isWater(tile)) {
return this.shorelineWater;
}
const baseWater = this.water.rgba;
const adjustment = 11 - Math.min(mag, 10); // Water magnitude influence
return colord({
r: Math.max(w.r - 10 + (11 - Math.min(mag, 10)), 0),
g: Math.max(w.g - 10 + (11 - Math.min(mag, 10)), 0),
b: Math.max(w.b - 10 + (11 - Math.min(mag, 10)), 0),
r: Math.max(baseWater.r - 10 + adjustment, 0),
g: Math.max(baseWater.g - 10 + adjustment, 0),
b: Math.max(baseWater.b - 10 + adjustment, 0),
});
case TerrainType.Plains:
return colord({
r: 190,
g: 220 - 2 * mag,
b: 138,
});
case TerrainType.Highland:
return colord({
r: 200 + 2 * mag,
g: 183 + 2 * mag,
b: 138 + 2 * mag,
});
case TerrainType.Mountain:
return colord({
r: 230 + mag / 2,
g: 230 + mag / 2,
b: 230 + mag / 2,
});
return colord("#7CB25C");
case TerrainType.Forest:
return colord("#628144");
case TerrainType.Desert:
return colord("#e5d789");
case TerrainType.DesertTransition:
return colord("#e0b641");
case TerrainType.ArcticForest:
return colord("#98C0E0");
case TerrainType.Beach:
return colord("#dec470");
case TerrainType.MidMountain:
return colord("#5C615E");
case TerrainType.HighMountain:
return colord("#43484a");
case TerrainType.Jungle:
return colord("#22752d");
case TerrainType.JunglePlains:
return colord("#47a35d");
case TerrainType.ArcticPlains:
return colord("#adcfed");
case TerrainType.SnowyHighMountain:
return colord("#e7f5ff");
default:
return this.background; // Fallback
}
}
+36 -33
View File
@@ -23,8 +23,6 @@ export const pastelThemeDark = new (class implements Theme {
private rand = new PseudoRandom(123);
private background = colord({ r: 0, g: 0, b: 0 });
private land = colord({ r: 194, g: 193, b: 148 });
private shore = colord({ r: 134, g: 133, b: 88 });
private falloutColors = [
colord({ r: 120, g: 255, b: 71 }), // Original color
colord({ r: 130, g: 255, b: 85 }), // Slightly lighter
@@ -115,43 +113,48 @@ export const pastelThemeDark = new (class implements Theme {
}
terrainColor(gm: GameMap, tile: TileRef): Colord {
const mag = gm.magnitude(tile);
if (gm.isShore(tile)) {
return this.shore;
}
switch (gm.terrainType(tile)) {
const type = gm.terrainType(tile);
const mag = gm.magnitude(tile); // Magnitude for water is distance/2
switch (type) {
case TerrainType.Ocean:
case TerrainType.Lake:
const w = this.water.rgba;
if (gm.isShoreline(tile) && gm.isWater(tile)) {
return this.shorelineWater;
}
if (gm.magnitude(tile) < 10) {
return colord({
r: Math.max(w.r + 9 - mag, 0),
g: Math.max(w.g + 9 - mag, 0),
b: Math.max(w.b + 9 - mag, 0),
});
}
return this.water;
const baseWater = this.water.rgba;
const adjustment = 9 - Math.min(mag, 10); // Water magnitude influence
return colord({
r: Math.max(baseWater.r + adjustment, 0),
g: Math.max(baseWater.g + adjustment, 0),
b: Math.max(baseWater.b + adjustment, 0),
});
case TerrainType.Plains:
return colord({
r: 140,
g: 170 - 2 * mag,
b: 88,
});
case TerrainType.Highland:
return colord({
r: 150 + 2 * mag,
g: 133 + 2 * mag,
b: 88 + 2 * mag,
});
case TerrainType.Mountain:
return colord({
r: 180 + mag / 2,
g: 180 + mag / 2,
b: 180 + mag / 2,
});
return colord("#64823c"); // Darker #7cb25c
case TerrainType.Forest:
return colord("#3d551e"); // Darker #61853e
case TerrainType.Desert:
return colord("#b8a971"); // Darker #e8db91
case TerrainType.DesertTransition:
return colord("#b58a35"); // Darker #e5ba45
case TerrainType.ArcticForest:
return colord("#78a0b0"); // Darker #98c0e0
case TerrainType.Beach:
return colord("#b09859"); // Darker #e0c879
case TerrainType.MidMountain:
return colord("#3c413e"); // Darker #5c615e
case TerrainType.HighMountain:
return colord("#23282a"); // Darker #43484a
case TerrainType.Jungle:
return colord("#12451d"); // Darker #22752d
case TerrainType.JunglePlains:
return colord("#27733d"); // Darker #47a35d
case TerrainType.ArcticPlains:
return colord("#8dafcd"); // Darker #adcfed
case TerrainType.SnowyHighMountain:
return colord("#c7d5df"); // Darker #e7f5ff (more gray)
default:
return this.background; // Fallback
}
}
+27 -6
View File
@@ -274,15 +274,36 @@ export class AttackExecution implements Execution {
.neighbors(neighbor)
.filter((t) => this.mg.owner(t) == this._owner).length;
let mag = 0;
switch (this.mg.terrainType(tile)) {
const terrainType = this.mg.terrainType(tile);
switch (terrainType) {
case TerrainType.Plains:
mag = 1;
case TerrainType.DesertTransition:
case TerrainType.ArcticPlains:
case TerrainType.JunglePlains:
mag = 1.0;
break;
case TerrainType.Highland:
mag = 1.5;
case TerrainType.Beach:
mag = 0.8;
break; // Easier to take
case TerrainType.Forest:
case TerrainType.ArcticForest:
mag = 1.2;
break;
case TerrainType.Mountain:
mag = 2;
case TerrainType.Desert:
mag = 1.3;
break; // Slightly harder than plains due to conditions
case TerrainType.Jungle:
mag = 1.8;
break; // Harder
case TerrainType.MidMountain:
mag = 2.0;
break;
case TerrainType.HighMountain:
case TerrainType.SnowyHighMountain:
mag = 2.5;
break;
default:
mag = 1.0;
break;
}
this.toConquer.enqueue(
+4 -1
View File
@@ -563,8 +563,11 @@ export class FakeHumanExecution implements Execution {
}
const tile = this.mg.ref(x, y);
if (this.mg.isLand(tile) && !this.mg.hasOwner(tile)) {
const terrainType = this.mg.terrainType(tile);
if (
this.mg.terrainType(tile) == TerrainType.Mountain &&
(terrainType === TerrainType.MidMountain ||
terrainType === TerrainType.HighMountain ||
terrainType === TerrainType.SnowyHighMountain) &&
this.random.chance(2)
) {
continue;
+15 -6
View File
@@ -88,7 +88,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.Oceania,
],
regional: [
GameMapType.BlackSea,
// GameMapType.BlackSea, - the heightmap is reversed cant make it into terrain for now i have to remake it from scratch
GameMapType.Britannia,
GameMapType.GatewayToTheAtlantic,
GameMapType.BetweenTwoSeas,
@@ -193,11 +193,20 @@ export class Cell {
}
export enum TerrainType {
Plains,
Highland,
Mountain,
Lake,
Ocean,
Ocean = 0,
Lake = 1,
Plains = 2,
Forest = 3,
Desert = 4,
DesertTransition = 5,
ArcticForest = 6, // Renamed from Arctic
Beach = 7, // Replaced LowMountain
MidMountain = 8,
HighMountain = 9,
Jungle = 10,
JunglePlains = 11,
ArcticPlains = 12, // New
SnowyHighMountain = 13, // New
}
export enum PlayerType {
+72 -11
View File
@@ -62,8 +62,7 @@ export class GameMapImpl implements GameMap {
private static readonly IS_LAND_BIT = 7;
private static readonly SHORELINE_BIT = 6;
private static readonly OCEAN_BIT = 5;
private static readonly MAGNITUDE_OFFSET = 4; // Uses bits 3-7 (5 bits)
private static readonly MAGNITUDE_MASK = 0x1f; // 11111 in binary
private static readonly TERRAIN_TYPE_MASK = 0x1f; // Use bits 0-4 for TerrainType enum or water magnitude
// State bits (Uint16Array)
private static readonly PLAYER_ID_OFFSET = 0; // Uses bits 0-11 (12 bits)
@@ -145,10 +144,34 @@ export class GameMapImpl implements GameMap {
}
magnitude(ref: TileRef): number {
return this.terrain[ref] & GameMapImpl.MAGNITUDE_MASK;
if (this.isLand(ref)) {
const type = this.terrainType(ref);
switch (type) {
case TerrainType.Plains:
case TerrainType.Desert:
case TerrainType.ArcticPlains:
case TerrainType.Beach:
case TerrainType.JunglePlains:
return 0;
case TerrainType.Forest:
case TerrainType.DesertTransition:
case TerrainType.ArcticForest:
case TerrainType.Jungle:
return 5;
case TerrainType.MidMountain:
return 20;
case TerrainType.HighMountain:
case TerrainType.SnowyHighMountain: // Same magnitude
return 30;
default:
return 0;
}
} else {
const storedMag = this.terrain[ref] & GameMapImpl.TERRAIN_TYPE_MASK;
return storedMag * 2;
}
}
// State getters and setters (mutable)
ownerID(ref: TileRef): number {
return this.state[ref] & GameMapImpl.PLAYER_ID_MASK;
}
@@ -224,17 +247,55 @@ export class GameMapImpl implements GameMap {
}
cost(ref: TileRef): number {
return this.magnitude(ref) < 10 ? 2 : 1;
const type = this.terrainType(ref);
switch (type) {
case TerrainType.Plains:
return 2;
case TerrainType.Forest:
return 3;
case TerrainType.Desert:
return 3;
case TerrainType.DesertTransition:
return 2;
case TerrainType.ArcticForest:
return 4; // Renamed from Arctic
case TerrainType.Beach:
return 1; // Easy to cross
case TerrainType.MidMountain:
return 6;
case TerrainType.HighMountain:
return 8;
case TerrainType.Jungle:
return 5; // Difficult
case TerrainType.JunglePlains:
return 3; // Easier Jungle
case TerrainType.ArcticPlains:
return 3; // Like regular plains but colder?
case TerrainType.SnowyHighMountain:
return 8; // Same as HighMountain
case TerrainType.Lake:
return 1;
case TerrainType.Ocean:
return 1;
default:
return 1;
}
}
terrainType(ref: TileRef): TerrainType {
if (this.isLand(ref)) {
const magnitude = this.magnitude(ref);
if (magnitude < 10) return TerrainType.Plains;
if (magnitude < 20) return TerrainType.Highland;
return TerrainType.Mountain;
const packedByte = this.terrain[ref];
if (!this.isLand(ref)) {
return this.isOcean(ref) ? TerrainType.Ocean : TerrainType.Lake;
}
return this.isOcean(ref) ? TerrainType.Ocean : TerrainType.Lake;
const typeValue = packedByte & GameMapImpl.TERRAIN_TYPE_MASK;
if (
typeValue >= TerrainType.Plains &&
typeValue <= TerrainType.SnowyHighMountain
) {
return typeValue as TerrainType;
}
console.warn(`Unexpected terrain type value ${typeValue} at ref ${ref}`);
return TerrainType.Plains;
}
neighbors(ref: TileRef): TileRef[] {
+205 -87
View File
@@ -3,6 +3,7 @@ import { Bitmap, decodePNGFromStream } from "pureimage";
//import fs from "fs/promises";
//import { createReadStream } from "fs";
import { Readable } from "stream";
import { TerrainType } from "../core/game/Game";
const min_island_size = 30;
const min_lake_size = 200;
@@ -12,7 +13,7 @@ interface Coord {
y: number;
}
enum TerrainType {
enum InternalTerrainType {
Land,
Water,
}
@@ -21,7 +22,41 @@ class Terrain {
public shoreline: boolean = false;
public magnitude: number = 0;
public ocean: boolean = false;
constructor(public type: TerrainType) {}
constructor(
public type: InternalTerrainType,
public specificType: TerrainType,
) {}
}
const TARGET_COLORS: {
[key in TerrainType]?: { r: number; g: number; b: number };
} = {
[TerrainType.Plains]: { r: 124, g: 178, b: 92 }, // #7cb25c - green (plains)
[TerrainType.Forest]: { r: 97, g: 133, b: 62 }, // #61853e - dark green (forest)
[TerrainType.Desert]: { r: 232, g: 219, b: 145 }, // #e8db91 - yellow (desert)
[TerrainType.DesertTransition]: { r: 229, g: 186, b: 69 }, // #e5ba45 - dark yellow (transition)
[TerrainType.ArcticForest]: { r: 152, g: 192, b: 224 }, // #98c0e0 - arctic blue (snowy arctic forest - OLD Arctic)
[TerrainType.Beach]: { r: 224, g: 200, b: 121 }, // #e0c879 - beach
[TerrainType.MidMountain]: { r: 92, g: 97, b: 94 }, // #5c615e - gray (middle mountain)
[TerrainType.HighMountain]: { r: 67, g: 72, b: 74 }, // #43484a - dark gray (mountain peaks)
[TerrainType.Jungle]: { r: 34, g: 117, b: 45 }, // #22752d - jungle
[TerrainType.JunglePlains]: { r: 71, g: 163, b: 93 }, // #47a35d - jungle plains
[TerrainType.ArcticPlains]: { r: 173, g: 207, b: 237 }, // #adcfed - arctic plains
[TerrainType.SnowyHighMountain]: { r: 231, g: 245, b: 255 }, // #e7f5ff - snowy high mountains
};
const OCEAN_COLOR = { r: 0, g: 0, b: 106 }; // #00006a
const COLOR_TOLERANCE = 15; // Allow slight variations in color matching
function colorDistanceSquared(
c1: { r: number; g: number; b: number },
c2: { r: number; g: number; b: number },
): number {
const dr = c1.r - c2.r;
const dg = c1.g - c2.g;
const db = c1.b - c2.b;
return dr * dr + dg * dg + db * db;
}
export async function generateMap(
@@ -42,20 +77,73 @@ export async function generateMap(
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
const color = img.getPixelRGBA(x, y);
const alpha = color & 0xff;
const blue = (color >> 8) & 0xff;
const colorValue = img.getPixelRGBA(x, y);
const r = (colorValue >> 24) & 0xff;
const g = (colorValue >> 16) & 0xff;
const b = (colorValue >> 8) & 0xff;
const a = colorValue & 0xff;
const pixelColor = { r, g, b };
if (alpha < 20 || blue == 106) {
// transparent
terrain[x][y] = new Terrain(TerrainType.Water);
if (
a < 20 ||
colorDistanceSquared(pixelColor, OCEAN_COLOR) <=
COLOR_TOLERANCE * COLOR_TOLERANCE
) {
terrain[x][y] = new Terrain(
InternalTerrainType.Water,
TerrainType.Ocean,
);
} else {
terrain[x][y] = new Terrain(TerrainType.Land);
terrain[x][y].magnitude = 0;
let matchedType: TerrainType | null = null;
let minDistance = Infinity;
// 140 -> 200 = 60
const mag = Math.min(200, Math.max(140, blue)) - 140;
terrain[x][y].magnitude = mag / 2;
for (const typeStr in TARGET_COLORS) {
const type = parseInt(typeStr) as TerrainType;
const targetColor = TARGET_COLORS[type];
const distance = colorDistanceSquared(pixelColor, targetColor);
if (distance < minDistance) {
minDistance = distance;
matchedType = type;
}
}
if (minDistance <= COLOR_TOLERANCE * COLOR_TOLERANCE) {
terrain[x][y] = new Terrain(InternalTerrainType.Land, matchedType!);
switch (matchedType) {
case TerrainType.Plains:
case TerrainType.Desert:
case TerrainType.Beach:
case TerrainType.ArcticPlains:
case TerrainType.JunglePlains:
terrain[x][y].magnitude = 0;
break;
case TerrainType.Forest:
case TerrainType.DesertTransition:
case TerrainType.ArcticForest:
case TerrainType.Jungle:
terrain[x][y].magnitude = 5;
break;
case TerrainType.MidMountain:
terrain[x][y].magnitude = 20;
break;
case TerrainType.HighMountain:
case TerrainType.SnowyHighMountain:
terrain[x][y].magnitude = 30;
break;
default:
terrain[x][y].magnitude = 0;
}
} else {
console.warn(
`Color ${r},${g},${b} at ${x},${y} did not match any target, defaulting to Plains.`,
);
terrain[x][y] = new Terrain(
InternalTerrainType.Land,
TerrainType.Plains,
);
terrain[x][y].magnitude = 0;
}
}
}
}
@@ -74,23 +162,63 @@ export async function generateMap(
}
async function createMiniMap(tm: Terrain[][]): Promise<Terrain[][]> {
// Create 2D array properly with correct dimensions
const miniMap: Terrain[][] = Array(Math.floor(tm.length / 2))
const miniMapWidth = Math.floor(tm.length / 2);
const miniMapHeight = Math.floor(tm[0].length / 2);
const miniMap: Terrain[][] = Array(miniMapWidth)
.fill(null)
.map(() => Array(Math.floor(tm[0].length / 2)).fill(null));
.map(() => Array(miniMapHeight).fill(null));
for (let x = 0; x < tm.length; x++) {
for (let y = 0; y < tm[0].length; y++) {
const miniX = Math.floor(x / 2);
const miniY = Math.floor(y / 2);
for (let miniX = 0; miniX < miniMapWidth; miniX++) {
for (let miniY = 0; miniY < miniMapHeight; miniY++) {
const startX = miniX * 2;
const startY = miniY * 2;
let isWater = false;
let isOcean = false;
let isShoreline = false;
let totalMagnitude = 0;
let landCount = 0;
let waterCount = 0;
let lastLandType: TerrainType = TerrainType.Plains;
if (
miniMap[miniX][miniY] == null ||
miniMap[miniX][miniY].type != TerrainType.Water
) {
// We shrink 4 tiles into 1 tile. If any of the 4 large tiles
// has water, then the mini tile is considered water.
miniMap[miniX][miniY] = tm[x][y];
for (let dx = 0; dx < 2; dx++) {
for (let dy = 0; dy < 2; dy++) {
const x = startX + dx;
const y = startY + dy;
if (x >= tm.length || y >= tm[0].length) continue;
const tile = tm[x][y];
if (tile.type === InternalTerrainType.Water) {
isWater = true;
waterCount++;
if (tile.ocean) isOcean = true;
if (tile.shoreline) isShoreline = true;
totalMagnitude += tile.magnitude;
} else {
landCount++;
totalMagnitude += tile.magnitude;
lastLandType = tile.specificType;
if (tile.shoreline) isShoreline = true;
}
}
}
if (isWater) {
const avgMagnitude = waterCount > 0 ? totalMagnitude / waterCount : 0;
const waterType = isOcean ? TerrainType.Ocean : TerrainType.Lake;
miniMap[miniX][miniY] = new Terrain(
InternalTerrainType.Water,
waterType,
);
miniMap[miniX][miniY].ocean = isOcean;
miniMap[miniX][miniY].magnitude = Math.round(avgMagnitude);
miniMap[miniX][miniY].shoreline = isShoreline;
} else {
const avgMagnitude = landCount > 0 ? totalMagnitude / landCount : 0;
miniMap[miniX][miniY] = new Terrain(
InternalTerrainType.Land,
lastLandType,
);
miniMap[miniX][miniY].magnitude = Math.round(avgMagnitude);
miniMap[miniX][miniY].shoreline = isShoreline;
}
}
}
@@ -103,13 +231,14 @@ function processShore(map: Terrain[][]): Coord[] {
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
const tile = map[x][y];
tile.shoreline = false;
const ns = neighbors(x, y, map);
if (tile.type == TerrainType.Land) {
if (ns.filter((t) => t.type == TerrainType.Water).length > 0) {
if (tile.type == InternalTerrainType.Land) {
if (ns.some((t) => t.type == InternalTerrainType.Water)) {
tile.shoreline = true;
}
} else {
if (ns.filter((t) => t.type == TerrainType.Land).length > 0) {
if (ns.some((t) => t.type == InternalTerrainType.Land)) {
tile.shoreline = true;
shorelineWaters.push({ x, y });
}
@@ -158,7 +287,7 @@ function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) {
nx < width &&
ny < height &&
!visited[nx][ny] &&
map[nx][ny].type === TerrainType.Water
map[nx][ny].type === InternalTerrainType.Water
) {
visited[nx][ny] = true;
map[nx][ny].magnitude = dist + 1;
@@ -182,10 +311,9 @@ function processWater(map: Terrain[][], removeSmall: boolean) {
const visited = new Set<string>();
const waterBodies: { coords: Coord[]; size: number }[] = [];
// Find all distinct water bodies
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
if (map[x][y].type === TerrainType.Water) {
if (map[x][y].type === InternalTerrainType.Water) {
const key = `${x},${y}`;
if (visited.has(key)) continue;
@@ -218,7 +346,8 @@ function processWater(map: Terrain[][], removeSmall: boolean) {
if (waterBodies[w].size < min_lake_size) {
smallLakes++;
for (const coord of waterBodies[w].coords) {
map[coord.x][coord.y].type = TerrainType.Land;
map[coord.x][coord.y].type = InternalTerrainType.Land;
// map[coord.x][coord.y].specificType = TerrainType.Plains; // Default fill
map[coord.x][coord.y].magnitude = 0;
}
}
@@ -256,7 +385,8 @@ function packTerrain(map: Terrain[][]): Uint8Array {
throw new Error(`terrain null at ${x}:${y}`);
}
if (tile.type === TerrainType.Land) {
const terrainTypeValue = tile.specificType;
if (tile.type === InternalTerrainType.Land) {
packedByte |= 0b10000000;
}
if (tile.shoreline) {
@@ -265,10 +395,11 @@ function packTerrain(map: Terrain[][]): Uint8Array {
if (tile.ocean) {
packedByte |= 0b00100000;
}
if (tile.type == TerrainType.Land) {
packedByte |= Math.min(Math.ceil(tile.magnitude), 31);
} else {
packedByte |= Math.min(Math.ceil(tile.magnitude / 2), 31);
packedByte |= terrainTypeValue & 0x1f;
if (tile.type === InternalTerrainType.Water) {
packedByte |= Math.min(Math.ceil(tile.magnitude / 2), 31) & 0x1f;
}
packedData[4 + y * width + x] = packedByte;
@@ -284,7 +415,7 @@ function getArea(
map: Terrain[][],
visited: Set<string>,
): Coord[] {
const targetType: TerrainType = map[x][y].type;
const targetType: InternalTerrainType = map[x][y].type;
const area: Coord[] = [];
const queue: Coord[] = [{ x, y }];
@@ -315,7 +446,7 @@ function removeSmallIslands(map: Terrain[][], removeSmall: boolean) {
// Find all distinct land bodies
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
if (map[x][y].type === TerrainType.Land) {
if (map[x][y].type === InternalTerrainType.Land) {
const key = `${x},${y}`;
if (visited.has(key)) continue;
@@ -334,7 +465,8 @@ function removeSmallIslands(map: Terrain[][], removeSmall: boolean) {
if (landBodies[b].size < min_island_size) {
smallIslands++;
for (const coord of landBodies[b].coords) {
map[coord.x][coord.y].type = TerrainType.Water;
map[coord.x][coord.y].type = InternalTerrainType.Water;
// map[coord.x][coord.y].specificType = TerrainType.Ocean;
map[coord.x][coord.y].magnitude = 0;
}
}
@@ -402,50 +534,36 @@ function getThumbnailColor(t: Terrain): {
b: number;
a: number;
} {
if (t.type === TerrainType.Water) {
// Shoreline water
if (t.shoreline) return { r: 100, g: 143, b: 255, a: 0 };
// All other water: adjust based on magnitude
const waterAdjRGB: number = 11 - Math.min(t.magnitude / 2, 10) - 10;
return {
r: Math.max(70 + waterAdjRGB, 0),
g: Math.max(132 + waterAdjRGB, 0),
b: Math.max(180 + waterAdjRGB, 0),
a: 0,
};
}
//shoreline land
if (t.shoreline) {
return { r: 204, g: 203, b: 158, a: 255 };
}
let adjRGB: number;
switch (true) {
//plains
case t.magnitude < 10:
adjRGB = 220 - 2 * t.magnitude;
return {
r: 190,
g: adjRGB,
b: 138,
a: 255,
};
//highlands
case t.magnitude < 20:
adjRGB = 2 * t.magnitude;
return {
r: 200 + adjRGB,
g: 183 + adjRGB,
b: 138 + adjRGB,
a: 255,
};
//mountains
case t.magnitude >= 20:
adjRGB = Math.floor(230 + t.magnitude / 2);
return {
r: adjRGB,
g: adjRGB,
b: adjRGB,
a: 255,
};
switch (t.specificType) {
case TerrainType.Ocean:
return { r: 0, g: 0, b: 106, a: 0 };
case TerrainType.Lake:
return { r: 70, g: 132, b: 180, a: 0 };
case TerrainType.Plains:
return { r: 124, g: 178, b: 92, a: 255 };
case TerrainType.Forest:
return { r: 97, g: 133, b: 62, a: 255 };
case TerrainType.Desert:
return { r: 232, g: 219, b: 145, a: 255 };
case TerrainType.DesertTransition:
return { r: 229, g: 186, b: 69, a: 255 };
case TerrainType.ArcticForest:
return { r: 152, g: 192, b: 224, a: 255 };
case TerrainType.Beach:
return { r: 224, g: 200, b: 121, a: 255 };
case TerrainType.MidMountain:
return { r: 92, g: 97, b: 94, a: 255 };
case TerrainType.HighMountain:
return { r: 67, g: 72, b: 74, a: 255 };
case TerrainType.Jungle:
return { r: 34, g: 117, b: 45, a: 255 };
case TerrainType.JunglePlains:
return { r: 71, g: 163, b: 93, a: 255 };
case TerrainType.ArcticPlains:
return { r: 173, g: 207, b: 237, a: 255 };
case TerrainType.SnowyHighMountain:
return { r: 231, g: 245, b: 255, a: 255 };
default:
return { r: 128, g: 128, b: 128, a: 255 };
}
}
-13
View File
@@ -4,27 +4,14 @@ import sharp from "sharp";
import { generateMap } from "./TerrainMapGenerator.js";
const maps = [
"Africa",
"Asia",
"WorldMap",
"BlackSea",
"Europe",
"EuropeClassic",
"Mars",
"Mena",
"Oceania",
"NorthAmerica",
"SouthAmerica",
"Britannia",
"GatewayToTheAtlantic",
"Australia",
"Pangaea",
"Iceland",
"BetweenTwoSeas",
"Japan",
"KnownWorld",
"FaroeIslands",
"DeglaciatedAntarctica",
];
const removeSmall = true;