mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 19:50:18 +00:00
feat: add impassable terrain for non-rectangular maps
This commit is contained in:
@@ -86,6 +86,16 @@ If you are doing work in image editing software or using automated tools, `./map
|
||||
- `Pixel` -> `Terrain Type & Magnitude` mapping in `GenerateMap`
|
||||
- `Terrain Type` -> `Thumbnail Color` mapping in `getThumbnailColor`
|
||||
|
||||
### Impassable Terrain
|
||||
|
||||
Pure black pixels (`#000000` / `rgb(0, 0, 0)` with alpha ≥ 20) are encoded as **impassable terrain**. This is a solid, static void that:
|
||||
|
||||
- Cannot be owned, attacked, or nuked.
|
||||
- Nuke trajectories cannot pass over it (just as they cannot leave the map border).
|
||||
- Renders as the map background colour, making the map appear non-rectangular.
|
||||
|
||||
Use impassable terrain to carve out non-rectangular map shapes or to create barriers that divide regions without water.
|
||||
|
||||
In-Game, terrain is rendered using themes. The color of a tile is determined dynamically based on
|
||||
its **Terrain Type** and **Magnitude**. Theme Files:
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ type TerrainType uint8
|
||||
const (
|
||||
Land TerrainType = iota
|
||||
Water
|
||||
Impassable
|
||||
)
|
||||
|
||||
// Terrain represents the properties of a single map tile.
|
||||
@@ -90,15 +91,20 @@ type GeneratorArgs struct {
|
||||
// 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. |
|
||||
// | 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. |
|
||||
// | **#000 (black)** | Impassable | 31 (fixed) | Solid void; cannot be owned/attacked/nuked. |
|
||||
// | **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. |
|
||||
//
|
||||
// Impassable terrain is encoded in the binary format as isLand=1 + magnitude=31.
|
||||
// It renders as the map background colour (making the map appear non-rectangular)
|
||||
// and cannot be owned, attacked, or nuked. Nuke trajectories cannot cross it.
|
||||
//
|
||||
// Misc Notes
|
||||
// - It normalizes map width/height to multiples of 4 for the mini map downscaling.
|
||||
@@ -132,14 +138,19 @@ func GenerateMap(ctx context.Context, args GeneratorArgs) (MapResult, error) {
|
||||
// Process each pixel
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
_, _, b, a := img.At(x, y).RGBA()
|
||||
r, g, b, a := img.At(x, y).RGBA()
|
||||
// Convert from 16-bit to 8-bit values
|
||||
alpha := uint8(a >> 8)
|
||||
red := uint8(r >> 8)
|
||||
green := uint8(g >> 8)
|
||||
blue := uint8(b >> 8)
|
||||
alpha := uint8(a >> 8)
|
||||
|
||||
if alpha < 20 || blue == 106 {
|
||||
// Transparent or specific blue value = water
|
||||
terrain[x][y] = Terrain{Type: Water}
|
||||
} else if red == 0 && green == 0 && blue == 0 {
|
||||
// Pure black (#000) = impassable terrain
|
||||
terrain[x][y] = Terrain{Type: Impassable}
|
||||
} else {
|
||||
// Land
|
||||
terrain[x][y] = Terrain{Type: Land}
|
||||
@@ -239,8 +250,9 @@ func convertToWebP(thumb ThumbData) ([]byte, error) {
|
||||
|
||||
// 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.
|
||||
// Priority: Impassable > Water > Land. If any of the 4 source tiles is
|
||||
// Impassable, the result is Impassable; else if any is Water, the result is
|
||||
// Water; otherwise the last Land tile wins.
|
||||
func createMiniMap(tm [][]Terrain) [][]Terrain {
|
||||
width := len(tm)
|
||||
height := len(tm[0])
|
||||
@@ -258,12 +270,24 @@ func createMiniMap(tm [][]Terrain) [][]Terrain {
|
||||
miniX := x / 2
|
||||
miniY := y / 2
|
||||
|
||||
if miniX < miniWidth && miniY < miniHeight {
|
||||
// If any of the 4 tiles has water, mini tile is water
|
||||
if miniMap[miniX][miniY].Type != Water {
|
||||
miniMap[miniX][miniY] = tm[x][y]
|
||||
}
|
||||
if miniX >= miniWidth || miniY >= miniHeight {
|
||||
continue
|
||||
}
|
||||
src := tm[x][y]
|
||||
dst := &miniMap[miniX][miniY]
|
||||
// Impassable wins over everything; once set, keep it.
|
||||
if dst.Type == Impassable {
|
||||
continue
|
||||
}
|
||||
if src.Type == Impassable {
|
||||
*dst = src
|
||||
continue
|
||||
}
|
||||
// Water wins over land; once set to water, keep it.
|
||||
if dst.Type == Water {
|
||||
continue
|
||||
}
|
||||
*dst = src
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +320,7 @@ func processShore(ctx context.Context, terrain [][]Terrain) []Coord {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if tile.Type == Water {
|
||||
// Water tile adjacent to land is shoreline
|
||||
for _, c := range buf[:n] {
|
||||
if terrain[c.X][c.Y].Type == Land {
|
||||
@@ -306,6 +330,7 @@ func processShore(ctx context.Context, terrain [][]Terrain) []Coord {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Impassable tiles: never shoreline (renders as background, no outline)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,6 +592,9 @@ func removeSmallIslands(ctx context.Context, terrain [][]Terrain, minSize int, r
|
||||
// - Bit 5: Ocean
|
||||
// - Bits 0-4: Magnitude (0-31). For Water, this is (Distance / 2).
|
||||
//
|
||||
// Impassable tiles are encoded as 0b10011111 (isLand=1, magnitude=31) and are
|
||||
// NOT counted in numLandTiles (they cannot be owned/attacked/nuked).
|
||||
//
|
||||
// Returns the packed data and the count of land tiles.
|
||||
func packTerrain(ctx context.Context, terrain [][]Terrain) (data []byte, numLandTiles int) {
|
||||
width := len(terrain)
|
||||
@@ -577,6 +605,14 @@ func packTerrain(ctx context.Context, terrain [][]Terrain) (data []byte, numLand
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
tile := terrain[x][y]
|
||||
|
||||
if tile.Type == Impassable {
|
||||
// Impassable: isLand=1, magnitude=31, no shoreline, no ocean.
|
||||
// Not counted as a land tile (can't be owned/attacked/nuked).
|
||||
packedData[y*width+x] = 0b10011111
|
||||
continue
|
||||
}
|
||||
|
||||
var packedByte byte = 0
|
||||
|
||||
if tile.Type == Land {
|
||||
@@ -652,6 +688,9 @@ type RGBA struct {
|
||||
// color schemes.
|
||||
//
|
||||
// For thumbnail purposes, the terrain type -> color mapping:
|
||||
// - Impassable: (Transparent) — renders as the map background in-game, so
|
||||
// the thumbnail matches by being transparent (the map picker background
|
||||
// shows through).
|
||||
// - Water Shoreline: (Transparent)
|
||||
// - Deep Water: (Transparent)
|
||||
// - Land Shoreline: `rgb(204, 203, 158)`
|
||||
@@ -659,6 +698,9 @@ type RGBA struct {
|
||||
// - 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 {
|
||||
if t.Type == Impassable {
|
||||
return RGBA{R: 0, G: 0, B: 0, A: 0}
|
||||
}
|
||||
if t.Type == Water {
|
||||
// Shoreline water
|
||||
if t.Shoreline {
|
||||
|
||||
Reference in New Issue
Block a user