Map Generator Go Code Documentation (#2656)

Resolves #2602

## Description:
tldr: `npm run docs:map-generator`

Adds documentation to the `map-generator` go code.

This has no functional changes, other than the renaming of the package.
I used the github url, though this can be set to anything as long as it
contains a `.` so that the docs parse it correctly. Go doc best
practices seem a little verbose and terse, but attempted to comply

Future Facing (to get these docs viewable without running locally):
- Wait until the -http issue is sorted, then these are easy to
statically host alongside builds
- Could use the legacy `godoc`
- Could do formatting after outputting the `txt` output


## Change List:

- Add documentation to all types/fns in map-generator go code
- Ensure this outputs correctly with `go doc`
- Add `docs:map-generator` command to package.json. This runs `go doc`
in `map-generator` w/ appropriate flags to generate full documentation.
(see notes in readme)
- rename `map-generator` module to work around pkgsite assuming all
packages without a . are stdlib (this makes `-http` work at all)
- Add new sections to README and update existing sections
- Add additional references to locations in the primary code base where
things can be found
- Update documentation in the ts theme files to add output color
mappings - this ensures that everything needed to trace the input file
-> in game rendered asset is fully documented.


## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

tidwell
This commit is contained in:
Aaron Tidwell
2025-12-21 17:00:54 -05:00
committed by GitHub
parent 183b0ae4cf
commit 9ab1319126
8 changed files with 217 additions and 26 deletions
+68 -14
View File
@@ -1,19 +1,25 @@
# MapGenerator # MapGenerator
This is a tool to generate map files for OpenFront. This is a go-based tool to generate map files for OpenFront.
The map generator reads PNG files and converts pixels into terrain based primarily on the **Blue** channel.
Because only blue values are used, grayscale and other formats are fully supported. Many maps in `assets/maps/<mapname>` are grayscale.
Additional Guides, Tutorials, Scripts, Resources, and Third Party Unofficial Applications can be found on
the [Official Openfront Wiki](https://openfront.wiki/Map_Making)
## Installation ## Installation
1. Install go https://go.dev/doc/install 1. Install go <https://go.dev/doc/install>
2. Install dependencies: `go mod download` 2. Install dependencies: `go mod download`
3. Run the generator: `go run .` 3. Run the generator: `go run .`
## Creating a new map ## Creating a new map
1. Create a new folder in `assets/maps/<map_name>` 1. Create a new folder in `assets/maps/<map_name>`
2. Create image.png 2. Create `assets/maps/<map_name>/image.png`
3. Create info.json with name and countries 3. Create `assets/maps/<map_name>/info.json` with name and countries
4. Add the map name in main.go 4. Add the map name in `main.go` The `<name>` in `{Name: "<name>"},` should match the `<map-name>` folder at `assets/maps/<map_name>`
5. Run the generator: `go run .` 5. Run the generator: `go run .`
6. Find the output folder at `../resources/maps/<map_name>` 6. Find the output folder at `../resources/maps/<map_name>`
@@ -29,16 +35,27 @@ To process a subset of maps, pass a comma-separated list:
## Create image.png ## Create image.png
The map-generator will process your input file at `assets/maps/<map_name>/image.png` to generate the map
thumbnail and binary files. To create this `png` input file, you can crop the world map:
1. [Download world map (warning very large file)](https://drive.google.com/file/d/1W2oMPj1L5zWRyPhh8LfmnY3_kve-FBR2/view?usp=sharing) 1. [Download world map (warning very large file)](https://drive.google.com/file/d/1W2oMPj1L5zWRyPhh8LfmnY3_kve-FBR2/view?usp=sharing)
2. Crop the file (recommend Gimp) 2. Crop the file (recommend Gimp)
- We recommend roughly 2 million pixels for performance reasons If you are doing work in image editing software or using automated tools, `./map_generator.go` contains documentation for:
- Do not go over 4 million pixels.
- `Pixel` -> `Terrain Type & Magnitude` mapping in `GenerateMap`
- `Terrain Type` -> `Thumbnail Color` mapping in `getThumbnailColor`
In-Game, terrain is rendered using themes. The color of a tile is determined dynamically based on
its **Terrain Type** and **Magnitude**. Theme Files:
- `../src/core/configuration/PastelTheme.ts` (Light)
- `../src/core/configuration/PastelThemeDark.ts` (Dark).
## Create info.json ## Create info.json
- Look at existing info.json for structure The map-generator will process your input file at `assets/maps/<map_name>/info.json` to determine the
- [Use country codes found here](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) position of Nations, their starting coordinates, and any flags.
Example: Example:
@@ -55,18 +72,43 @@ Example:
} }
``` ```
`coordinates` is x/y position of the nation spawn on the map. Origin is at top left, with x extending right and y extending down
`name` is a `CamelCaseName` of your map. It is used to enable the map in-game.
`flag` is the code for a country
- The full list of supported codes can be seen in `../src/client/data/countries.json` - all ISO_3166 codes are supported, with several additions.
- For quick reference, [Use country codes found here](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes)
## Update CREDITS.md
Add License & Attribution information to `../CREDITS.md`. If you are unsure if
a map's license can be used, open an issue or ask in Discord before beginning work.
## Adding Flags
Flags can be added to `../resources/flags/<iso_code>.svg`
The country will need to be added to `../src/client/data/countries.json`
## To Enable In-Game ## To Enable In-Game
- Add a translation for the map name to `resources/lang/en.json` Using the `name` from your json:
- Add the MapDescription `src/client/components/Maps.ts`
- Add the numPlayersConfig `src/core/configuration/DefaultConfig.ts` - Add to the MapDescription `../src/client/components/Maps.ts`
- Add the GameMapType `src/core/game/Game.ts` - Add to the numPlayersConfig `../src/core/configuration/DefaultConfig.ts`
- To add to the map playlist, modify `src/server/MapPlaylist.ts` - Add to the mapCategories `../src/core/game/Game.ts`
- Add to the map playlist `../src/server/MapPlaylist.ts`
- Add to the `map` translation object in `../resources/lang/en.json`
## Notes ## Notes
- Maps should be between 2 - 3 million pixels square (area)
- Islands smaller than 30 tiles (pixels) are automatically removed by the script. - Islands smaller than 30 tiles (pixels) are automatically removed by the script.
- Bodies of water smaller than 200 tiles (pixels) are also removed. - Bodies of water smaller than 200 tiles (pixels) are also removed.
- The map generator normalizes dimensions to multiples of 4. Any pixels beyond `Width - (Width % 4)` or `Height - (Height % 4)` are cropped.
## 🛠️ Development Tools ## 🛠️ Development Tools
@@ -75,3 +117,15 @@ Example:
```bash ```bash
go fmt . go fmt .
``` ```
- **Output Map Generator Documentation**:
```bash
go doc -cmd -u -all
```
The map-generator is a cli tool, to get any visibility, we pass `-cmd`. It also
does not expose any API, so we use `-u` and `-all` to show all documentation for
unexposed values.
_Known Bug_ Using `-http` does not respect the other flags and only renders the README
+1 -2
View File
@@ -1,5 +1,4 @@
module map-generator module github.com/openfrontio/OpenFrontIO/map-generator
go 1.24.4 go 1.24.4
require github.com/chai2010/webp v1.4.0 require github.com/chai2010/webp v1.4.0
+17
View File
@@ -11,8 +11,13 @@ import (
"sync" "sync"
) )
// mapsFlag holds the comma-separated list of map names passed via the --maps command-line argument.
var mapsFlag string var mapsFlag string
// maps defines the registry of available maps to be processed.
// Each entry contains the folder name and a flag indicating if it's a test map.
//
// New maps need to be added here in order to allow the map-generator to process them.
var maps = []struct { var maps = []struct {
Name string Name string
IsTest bool IsTest bool
@@ -61,6 +66,8 @@ var maps = []struct {
{Name: "giantworldmap", IsTest: true}, {Name: "giantworldmap", IsTest: true},
} }
// outputMapDir returns the absolute path to the directory where generated map files should be written.
// It distinguishes between test and production output locations.
func outputMapDir(isTest bool) (string, error) { func outputMapDir(isTest bool) (string, error) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@@ -72,6 +79,8 @@ func outputMapDir(isTest bool) (string, error) {
return filepath.Join(cwd, "..", "resources", "maps"), nil return filepath.Join(cwd, "..", "resources", "maps"), nil
} }
// inputMapDir returns the absolute path to the directory containing source map assets.
// It distinguishes between test and production asset locations.
func inputMapDir(isTest bool) (string, error) { func inputMapDir(isTest bool) (string, error) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@@ -84,6 +93,8 @@ func inputMapDir(isTest bool) (string, error) {
} }
} }
// processMap handles the end-to-end generation for a single map.
// It reads the source image and JSON, generates the terrain data, and writes the binary outputs and updated manifest.
func processMap(name string, isTest bool) error { func processMap(name string, isTest bool) error {
outputMapBaseDir, err := outputMapDir(isTest) outputMapBaseDir, err := outputMapDir(isTest)
if err != nil { if err != nil {
@@ -169,6 +180,8 @@ func processMap(name string, isTest bool) error {
return nil return nil
} }
// parseMapsFlag validates and parses the --maps command-line argument.
// It returns a set of selected map names or nil if no flag was provided (implying all maps).
func parseMapsFlag() (map[string]bool, error) { func parseMapsFlag() (map[string]bool, error) {
if mapsFlag == "" { if mapsFlag == "" {
return nil, nil return nil, nil
@@ -189,6 +202,8 @@ func parseMapsFlag() (map[string]bool, error) {
return selected, nil return selected, nil
} }
// loadTerrainMaps manages the concurrent generation of all selected maps.
// It spins up goroutines for each map and aggregates any errors.
func loadTerrainMaps() error { func loadTerrainMaps() error {
selectedMaps, err := parseMapsFlag() selectedMaps, err := parseMapsFlag()
if err != nil { if err != nil {
@@ -226,6 +241,8 @@ func loadTerrainMaps() error {
return nil return nil
} }
// main is the entry point for the map generator tool.
// It parses flags and triggers the map generation process.
func main() { func main() {
flag.StringVar(&mapsFlag, "maps", "", "optional comma-separated list of maps to process. ex: --maps=world,eastasia,big_plains") flag.StringVar(&mapsFlag, "maps", "", "optional comma-separated list of maps to process. ex: --maps=world,eastasia,big_plains")
flag.Parse() flag.Parse()
+111 -10
View File
@@ -12,28 +12,35 @@ import (
"github.com/chai2010/webp" "github.com/chai2010/webp"
) )
// The smallest a body of land or lake can be, all smaller are removed
const ( const (
minIslandSize = 30 minIslandSize = 30
minLakeSize = 200 minLakeSize = 200
) )
// Holds raw RGBA image data for the thumbnail
type ThumbData struct { type ThumbData struct {
Data []byte Data []byte
Width int Width int
Height int Height int
} }
// XY coord, origin top left, x extending right, y extends down
type Coord struct { type Coord struct {
X, Y int X, Y int
} }
// TerrainType represents the classification of a map tile (e.g., Land or Water).
type TerrainType int type TerrainType int
// Enumeration of possible TerrainType values.
const ( const (
Land TerrainType = iota Land TerrainType = iota
Water Water
) )
// Terrain represents the properties of a single map tile.
// Magnitude represents elevation for Land (0-30) or distance to land for Water.
type Terrain struct { type Terrain struct {
Type TerrainType Type TerrainType
Shoreline bool Shoreline bool
@@ -41,6 +48,7 @@ type Terrain struct {
Ocean bool Ocean bool
} }
// MapResult is the output format from the GenerateMap workflow
type MapResult struct { type MapResult struct {
Thumbnail []byte Thumbnail []byte
Map MapInfo Map MapInfo
@@ -48,19 +56,44 @@ type MapResult struct {
Map16x MapInfo Map16x MapInfo
} }
// MapInfo contains the serialized map data and metadata for a specific scale.
type MapInfo struct { type MapInfo struct {
Data []byte Data []byte // packed map data
Width int Width int
Height int Height int
NumLandTiles int NumLandTiles int
} }
// GeneratorArgs defines the input parameters for the map generation process.
type GeneratorArgs struct { type GeneratorArgs struct {
Name string Name string
ImageBuffer []byte ImageBuffer []byte
RemoveSmall bool RemoveSmall bool
} }
// GenerateMap is the main map-generator workflow.
// - Maps each pixel to a Terrain type based on its blue value
// - Removes small islands and lakes
// - Creates a WebP thumbnail
// - Packs the map data into binary format for full scale, 1/4 tile count (half dimensions), and 1/16 tile count (quarter dimensions)
//
// Red/green pixel values have no impact, only blue values are used
// For Land tiles, "Magnitude" is determined by `(Blue - 140) / 2“.
// For Water tiles, "Magnitude" is calculated during generation as the distance to the nearest land.
//
// Pixel -> Terrain & Magnitude mapping
// | Input Condition | Terrain Type | Magnitude | Notes |
// | :----------------- | :-------------- | :----------------- | :------------------------------- |
// | **Alpha < 20** | Water | Distance to Land\* | Transparent pixels become water. |
// | **Blue = 106** | Water | Distance to Land\* | Specific key color for water. |
// | **Blue < 140** | Land (Plains) | 0 | Clamped to minimum magnitude. |
// | **Blue 140 - 158** | Land (Plains) | 0 - 9 | |
// | **Blue 159 - 178** | Land (Highland) | 10 - 19 | |
// | **Blue 179 - 200** | Land (Mountain) | 20 - 30 | |
// | **Blue > 200** | Land (Mountain) | 30 | Clamped to maximum magnitude. |
//
// Misc Notes
// - It normalizes map width/height to multiples of 4 for the mini map downscaling.
func GenerateMap(args GeneratorArgs) (MapResult, error) { func GenerateMap(args GeneratorArgs) (MapResult, error) {
img, err := png.Decode(bytes.NewReader(args.ImageBuffer)) img, err := png.Decode(bytes.NewReader(args.ImageBuffer))
if err != nil { if err != nil {
@@ -150,6 +183,7 @@ func GenerateMap(args GeneratorArgs) (MapResult, error) {
}, nil }, nil
} }
// convertToWebP encodes raw RGBA thumbnail data into WebP format.
func convertToWebP(thumb ThumbData) ([]byte, error) { func convertToWebP(thumb ThumbData) ([]byte, error) {
// Create RGBA image from raw data // Create RGBA image from raw data
img := image.NewRGBA(image.Rect(0, 0, thumb.Width, thumb.Height)) img := image.NewRGBA(image.Rect(0, 0, thumb.Width, thumb.Height))
@@ -171,6 +205,10 @@ func convertToWebP(thumb ThumbData) ([]byte, error) {
return webpData, nil return webpData, nil
} }
// createMiniMap downscales the terrain grid by half.
// It maps 2x2 blocks of input tiles to a single output tile.
// The logic prioritizes Water: if any of the 4 source tiles is Water,
// the resulting mini-map tile becomes Water.
func createMiniMap(tm [][]Terrain) [][]Terrain { func createMiniMap(tm [][]Terrain) [][]Terrain {
width := len(tm) width := len(tm)
height := len(tm[0]) height := len(tm[0])
@@ -200,6 +238,10 @@ func createMiniMap(tm [][]Terrain) [][]Terrain {
return miniMap return miniMap
} }
// processShore identifies shoreline tiles by checking adjacency.
// It marks Land tiles as shoreline if they neighbor Water, and Water tiles as
// shoreline if they neighbor Land.
// Returns a list of coordinates for all shoreline Water tiles found.
func processShore(terrain [][]Terrain) []Coord { func processShore(terrain [][]Terrain) []Coord {
log.Println("Identifying shorelines") log.Println("Identifying shorelines")
var shorelineWaters []Coord var shorelineWaters []Coord
@@ -235,6 +277,9 @@ func processShore(terrain [][]Terrain) []Coord {
return shorelineWaters return shorelineWaters
} }
// processDistToLand calculates the distance of water tiles from the nearest land.
// It uses a Breadth-First Search (BFS) starting from the shoreline water tiles.
// The distance is stored in the Magnitude field of the Water tiles.
func processDistToLand(shorelineWaters []Coord, terrain [][]Terrain) { func processDistToLand(shorelineWaters []Coord, terrain [][]Terrain) {
log.Println("Setting Water tiles magnitude = Manhattan distance from nearest land") log.Println("Setting Water tiles magnitude = Manhattan distance from nearest land")
@@ -280,6 +325,7 @@ func processDistToLand(shorelineWaters []Coord, terrain [][]Terrain) {
} }
} }
// getNeighbors returns a list of Terrain tiles adjacent to the specified coordinates.
func getNeighbors(x, y int, terrain [][]Terrain) []Terrain { func getNeighbors(x, y int, terrain [][]Terrain) []Terrain {
coords := getNeighborCoords(x, y, terrain) coords := getNeighborCoords(x, y, terrain)
neighbors := make([]Terrain, len(coords)) neighbors := make([]Terrain, len(coords))
@@ -289,6 +335,8 @@ func getNeighbors(x, y int, terrain [][]Terrain) []Terrain {
return neighbors return neighbors
} }
// getNeighborCoords returns a list of valid adjacent coordinates (up, down, left, right).
// It ensures that the returned coordinates are within the bounds of the terrain grid.
func getNeighborCoords(x, y int, terrain [][]Terrain) []Coord { func getNeighborCoords(x, y int, terrain [][]Terrain) []Coord {
width := len(terrain) width := len(terrain)
height := len(terrain[0]) height := len(terrain[0])
@@ -310,6 +358,10 @@ func getNeighborCoords(x, y int, terrain [][]Terrain) []Coord {
return coords return coords
} }
// processWater identifies and processes bodies of water in the terrain.
// It finds all connected water bodies and marks the largest one as Ocean.
// If removeSmall is true, lakes smaller than minLakeSize are converted to Land.
// Finally, it triggers shoreline identification and distance-to-land calculations.
func processWater(terrain [][]Terrain, removeSmall bool) { func processWater(terrain [][]Terrain, removeSmall bool) {
log.Println("Processing water bodies") log.Println("Processing water bodies")
visited := make(map[string]bool) visited := make(map[string]bool)
@@ -382,6 +434,9 @@ func processWater(terrain [][]Terrain, removeSmall bool) {
} }
} }
// getArea performs a Breadth-First Search (BFS) to find a contiguous area of tiles
// sharing the same TerrainType as the passed x,y coordinates.
// The visited map is updated to prevent reprocessing tiles.
func getArea(x, y int, terrain [][]Terrain, visited map[string]bool) []Coord { func getArea(x, y int, terrain [][]Terrain, visited map[string]bool) []Coord {
targetType := terrain[x][y].Type targetType := terrain[x][y].Type
var area []Coord var area []Coord
@@ -408,6 +463,8 @@ func getArea(x, y int, terrain [][]Terrain, visited map[string]bool) []Coord {
return area return area
} }
// removeSmallIslands identifies and removes small land masses from the terrain.
// If removeSmall is true, any removed bodies are converted to Water.
func removeSmallIslands(terrain [][]Terrain, removeSmall bool) { func removeSmallIslands(terrain [][]Terrain, removeSmall bool) {
if !removeSmall { if !removeSmall {
return return
@@ -456,6 +513,14 @@ func removeSmallIslands(terrain [][]Terrain, removeSmall bool) {
smallIslands, minIslandSize) smallIslands, minIslandSize)
} }
// packTerrain serializes the terrain grid into a byte slice.
// Each byte represents a single tile with bit flags:
// - Bit 7: Land (1) / Water (0)
// - Bit 6: Shoreline
// - Bit 5: Ocean
// - Bits 0-4: Magnitude (0-31). For Water, this is (Distance / 2).
//
// Returns the packed data and the count of land tiles.
func packTerrain(terrain [][]Terrain) (data []byte, numLandTiles int) { func packTerrain(terrain [][]Terrain) (data []byte, numLandTiles int) {
width := len(terrain) width := len(terrain)
height := len(terrain[0]) height := len(terrain[0])
@@ -492,6 +557,9 @@ func packTerrain(terrain [][]Terrain) (data []byte, numLandTiles int) {
return packedData, numLandTiles return packedData, numLandTiles
} }
// createMapThumbnail generates an RGBA image representation of the terrain.
// It scales the map dimensions based on the provided quality factor.
// Each pixel's color is determined by the terrain type and magnitude via getThumbnailColor.
func createMapThumbnail(terrain [][]Terrain, quality float64) *image.RGBA { func createMapThumbnail(terrain [][]Terrain, quality float64) *image.RGBA {
log.Println("Creating thumbnail") log.Println("Creating thumbnail")
@@ -520,10 +588,28 @@ func createMapThumbnail(terrain [][]Terrain, quality float64) *image.RGBA {
return img return img
} }
// RGBA represents a color with Red, Green, Blue, and Alpha channels.
// It is used locally for thumbnail generation.
type RGBA struct { type RGBA struct {
R, G, B, A uint8 R, G, B, A uint8
} }
// getThumbnailColor determines the RGBA color for a specific terrain tile for
// the map preview thumbnail.
//
// It handles color generation for Water (shoreline vs deep water) and Land
// (shoreline, plains, highlands, mountains) based on the tile's magnitude.
//
// The thumbnail renders its own set of colors separate from the in-game light/dark
// color schemes.
//
// For thumbnail purposes, the terrain type -> color mapping:
// - Water Shoreline: (Transparent)
// - Deep Water: (Transparent)
// - Land Shoreline: `rgb(204, 203, 158)`
// - Plains (Mag < 10): `rgb(190, 220, 138)` - `rgb(190, 202, 138)`
// - Highlands (Mag 10-19): `rgb(220, 203, 158)` - `rgb(238, 221, 176)`
// - Mountains (Mag >= 20): `rgb(240, 240, 240)` - `rgb(245, 245, 245)`
func getThumbnailColor(t Terrain) RGBA { func getThumbnailColor(t Terrain) RGBA {
if t.Type == Water { if t.Type == Water {
// Shoreline water // Shoreline water
@@ -576,6 +662,8 @@ func getThumbnailColor(t Terrain) RGBA {
} }
} }
// logBinaryAsBits logs the binary representation of the first 'length' bytes of data.
// It is a helper function for debugging packed terrain data.
func logBinaryAsBits(data []byte, length int) { func logBinaryAsBits(data []byte, length int) {
if length > len(data) { if length > len(data) {
length = len(data) length = len(data)
@@ -588,21 +676,23 @@ func logBinaryAsBits(data []byte, length int) {
log.Printf("Binary data (bits): %s", bits) log.Printf("Binary data (bits): %s", bits)
} }
// createCombinedBinary combines the info JSON, map data, and mini-map data into a single binary buffer.
//
// Note: This function is currently unused by the main workflow, which writes separate files instead.
// It creates a header with the following structure:
// - Bytes 0-3: Version (1)
// - Bytes 4-7: Info section offset
// - Bytes 8-11: Info section size
// - Bytes 12-15: Map section offset
// - Bytes 16-19: Map section size
// - Bytes 20-23: MiniMap section offset
// - Bytes 24-27: MiniMap section size
func createCombinedBinary(infoBuffer []byte, mapData []byte, miniMapData []byte) []byte { func createCombinedBinary(infoBuffer []byte, mapData []byte, miniMapData []byte) []byte {
// Calculate section sizes // Calculate section sizes
infoSize := len(infoBuffer) infoSize := len(infoBuffer)
mapSize := len(mapData) mapSize := len(mapData)
miniMapSize := len(miniMapData) miniMapSize := len(miniMapData)
// Header structure:
// Bytes 0-3: Version (1)
// Bytes 4-7: Info section offset (always 28)
// Bytes 8-11: Info section size
// Bytes 12-15: Map section offset
// Bytes 16-19: Map section size
// Bytes 20-23: MiniMap section offset
// Bytes 24-27: MiniMap section size
headerSize := 28 headerSize := 28
infoOffset := headerSize infoOffset := headerSize
mapOffset := infoOffset + infoSize mapOffset := infoOffset + infoSize
@@ -634,6 +724,9 @@ func createCombinedBinary(infoBuffer []byte, mapData []byte, miniMapData []byte)
return combined return combined
} }
// writeUint32 writes a 32-bit unsigned integer to the byte slice at the specified offset.
// It uses Little Endian byte order.
// Note: This function is currently unused.
func writeUint32(data []byte, offset int, value uint32) { func writeUint32(data []byte, offset int, value uint32) {
data[offset] = byte(value & 0xff) data[offset] = byte(value & 0xff)
data[offset+1] = byte((value >> 8) & 0xff) data[offset+1] = byte((value >> 8) & 0xff)
@@ -641,10 +734,16 @@ func writeUint32(data []byte, offset int, value uint32) {
data[offset+3] = byte((value >> 24) & 0xff) data[offset+3] = byte((value >> 24) & 0xff)
} }
// readUint32 reads a 32-bit unsigned integer from the byte slice at the specified offset.
// It assumes Little Endian byte order.
// Note: This function is currently unused.
func readUint32(data []byte, offset int) uint32 { func readUint32(data []byte, offset int) uint32 {
return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24 return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24
} }
// decodeCombinedBinary parses a combined binary buffer into its constituent parts.
// It validates the header and extracts the Info JSON, Map data, and MiniMap data sections.
// Note: This function is currently unused.
func decodeCombinedBinary(data []byte) (*CombinedBinaryHeader, []byte, []byte, []byte, error) { func decodeCombinedBinary(data []byte) (*CombinedBinaryHeader, []byte, []byte, []byte, error) {
if len(data) < 28 { if len(data) < 28 {
return nil, nil, nil, nil, fmt.Errorf("data too short for header") return nil, nil, nil, nil, fmt.Errorf("data too short for header")
@@ -675,6 +774,8 @@ func decodeCombinedBinary(data []byte) (*CombinedBinaryHeader, []byte, []byte, [
return header, infoData, mapData, miniMapData, nil return header, infoData, mapData, miniMapData, nil
} }
// CombinedBinaryHeader represents the metadata header of the combined map file format.
// Note: This struct is currently unused.
type CombinedBinaryHeader struct { type CombinedBinaryHeader struct {
Version uint32 Version uint32
InfoOffset uint32 InfoOffset uint32
+1
View File
@@ -9,6 +9,7 @@
"dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"", "dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"dev:staging": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.dev concurrently \"npm run start:client\" \"npm run start:server-dev\"", "dev:staging": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"dev:prod": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.io concurrently \"npm run start:client\" \"npm run start:server-dev\"", "dev:prod": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.io concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"docs:map-generator": "cd map-generator && go doc -cmd -u -all",
"tunnel": "npm run build-prod && npm run start:server", "tunnel": "npm run build-prod && npm run start:server",
"test": "jest", "test": "jest",
"perf": "npx tsx tests/perf/*.ts", "perf": "npx tsx tests/perf/*.ts",
+8
View File
@@ -138,6 +138,14 @@ export class PastelTheme implements Theme {
return player.type() === PlayerType.Human ? "#000000" : "#4D4D4D"; return player.type() === PlayerType.Human ? "#000000" : "#4D4D4D";
} }
// | Terrain Type | Magnitude | Base Color Logic | Visual Description |
// | :---------------- | :-------- | :---------------------------------------------- | :------------------------------------------------------------------- |
// | **Shore (Land)** | N/A | Fixed: `rgb(204, 203, 158)` | Sandy beige. Overrides other land types if adjacent to water. |
// | **Plains** | 0 - 9 | `rgb(190, 220, 138)` - `rgb(190, 202, 138)` | Light green. Gets slightly darker/less green as magnitude increases. |
// | **Highland** | 10 - 19 | `rgb(220, 203, 158)` - `rgb(238, 221, 176)` | Tan/Beige. Gets lighter as magnitude increases. |
// | **Mountain** | 20 - 30 | `rgb(240, 240, 240)` - `rgb(245, 245, 245)` | Grayscale (White/Grey). Represents snow caps or rocky peaks. |
// | **Water (Shore)** | 0 | Fixed: `rgb(100, 143, 255)` | Light blue near land. |
// | **Water (Deep)** | 1 - 10+ | `rgb(70, 132, 180)` - `rgb(61, 123, 171)` | Darker blue, adjusted slightly by distance to land. |
terrainColor(gm: GameMap, tile: TileRef): Colord { terrainColor(gm: GameMap, tile: TileRef): Colord {
const mag = gm.magnitude(tile); const mag = gm.magnitude(tile);
if (gm.isShore(tile)) { if (gm.isShore(tile)) {
@@ -9,6 +9,15 @@ export class PastelThemeDark extends PastelTheme {
private darkWater = colord("rgb(14,11,30)"); private darkWater = colord("rgb(14,11,30)");
private darkShorelineWater = colord("rgb(50,50,50)"); private darkShorelineWater = colord("rgb(50,50,50)");
// | Terrain Type | Magnitude | Base Color Logic | Visual Description |
// | :---------------- | :-------- | :---------------------------------------------- | :-------------------- |
// | **Shore (Land)** | N/A | Fixed: `rgb(134, 133, 88)` | Dark olive. |
// | **Plains** | 0 - 9 | `rgb(140, 170, 88)` - `rgb(140, 152, 88)` | Muted green. |
// | **Highland** | 10 - 19 | `rgb(170, 153, 108)` - `rgb(188, 171, 126)` | Dark earth tone. |
// | **Mountain** | 20 - 30 | `rgb(190, 190, 190)` - `rgb(195, 195, 195)` | Dark gray. |
// | **Water (Shore)** | 0 | Fixed: `rgb(50, 50, 50)` | Dark gray/black. |
// | **Water (Deep)** | 1 - 10+ | `rgb(22, 19, 38)` - `rgb(14, 11, 30)` | Very dark blue/black. |
terrainColor(gm: GameMap, tile: TileRef): Colord { terrainColor(gm: GameMap, tile: TileRef): Colord {
const mag = gm.magnitude(tile); const mag = gm.magnitude(tile);
if (gm.isShore(tile)) { if (gm.isShore(tile)) {
+2
View File
@@ -249,6 +249,8 @@ export class GameMapImpl implements GameMap {
return this.magnitude(ref) < 10 ? 2 : 1; return this.magnitude(ref) < 10 ? 2 : 1;
} }
// if updating these magnitude values, also update
// `../../../map-generator/map_generator.go` `getThumbnailColor`
terrainType(ref: TileRef): TerrainType { terrainType(ref: TileRef): TerrainType {
if (this.isLand(ref)) { if (this.isLand(ref)) {
const magnitude = this.magnitude(ref); const magnitude = this.magnitude(ref);