mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 10:21:27 +00:00
Map generator -verbose and -performance flags (#2721)
Resolves #2718 ## Description: Adds go-style error log levels, with an additional ALL log level. - WARN/ERROR - Only success output - INFO - Existing output - DEBUG - New output - ALL - New output (includes the logs from when removal/performance is enabled) In addition - Add `-verbose` (`-v`), `-log-level`, `-log-removal`, and `-log-performance` flags to map generator - No changes to default behavior of `go run .` without the new flags - excludes test maps from performance warnings (test maps already skip the removal steps) - updates readme with the different flags and how they impact the logger Default run (matches existing) `go run . >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365745/output.txt) Default run w/ `-verbose` (log level DEBUG) `go run . -v >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365812/output.txt) Default run w/ `-log-performance` `go run . -log-performance >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365971/output.txt) Run of just africa w/ all new logging enabled `go run . -maps=africa -log-level=all >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365724/output.txt) ## 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 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -2,20 +2,25 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/chai2010/webp"
|
||||
)
|
||||
|
||||
// The smallest a body of land or lake can be, all smaller are removed
|
||||
const (
|
||||
// The smallest a body of land or lake can be, all smaller are removed
|
||||
minIslandSize = 30
|
||||
minLakeSize = 200
|
||||
// the recommended max area pixel size for input images
|
||||
minRecommendedPixelSize = 2000000
|
||||
maxRecommendedPixelSize = 3000000
|
||||
// the recommended max number of land tiles in the output bin at full size
|
||||
maxRecommendedLandTileCount = 3000000
|
||||
)
|
||||
|
||||
// Holds raw RGBA image data for the thumbnail
|
||||
@@ -94,7 +99,8 @@ type GeneratorArgs struct {
|
||||
//
|
||||
// Misc Notes
|
||||
// - It normalizes map width/height to multiples of 4 for the mini map downscaling.
|
||||
func GenerateMap(args GeneratorArgs) (MapResult, error) {
|
||||
func GenerateMap(ctx context.Context, args GeneratorArgs) (MapResult, error) {
|
||||
logger := LoggerFromContext(ctx)
|
||||
img, err := png.Decode(bytes.NewReader(args.ImageBuffer))
|
||||
if err != nil {
|
||||
return MapResult{}, fmt.Errorf("failed to decode PNG: %w", err)
|
||||
@@ -107,7 +113,12 @@ func GenerateMap(args GeneratorArgs) (MapResult, error) {
|
||||
width = width - (width % 4)
|
||||
height = height - (height % 4)
|
||||
|
||||
log.Printf("Processing Map: %s, dimensions: %dx%d", args.Name, width, height)
|
||||
logger.Info(fmt.Sprintf("Processing Map: %s, dimensions: %dx%d", args.Name, width, height))
|
||||
|
||||
area := width * height
|
||||
if area < minRecommendedPixelSize || area > maxRecommendedPixelSize {
|
||||
logger.Debug(fmt.Sprintf("Map area %d pixels is outside recommended range (%d - %d)", area, minRecommendedPixelSize, maxRecommendedPixelSize), PerformanceLogTag)
|
||||
}
|
||||
|
||||
// Initialize terrain grid
|
||||
terrain := make([][]Terrain, width)
|
||||
@@ -137,16 +148,16 @@ func GenerateMap(args GeneratorArgs) (MapResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
removeSmallIslands(terrain, args.RemoveSmall)
|
||||
processWater(terrain, args.RemoveSmall)
|
||||
removeSmallIslands(ctx, terrain, args.RemoveSmall)
|
||||
processWater(ctx, terrain, args.RemoveSmall)
|
||||
|
||||
terrain4x := createMiniMap(terrain)
|
||||
processWater(terrain4x, false)
|
||||
processWater(ctx, terrain4x, false)
|
||||
|
||||
terrain16x := createMiniMap(terrain4x)
|
||||
processWater(terrain16x, false)
|
||||
processWater(ctx, terrain16x, false)
|
||||
|
||||
thumb := createMapThumbnail(terrain4x, 0.5)
|
||||
thumb := createMapThumbnail(ctx, terrain4x, 0.5)
|
||||
webp, err := convertToWebP(ThumbData{
|
||||
Data: thumb.Pix,
|
||||
Width: thumb.Bounds().Dx(),
|
||||
@@ -156,9 +167,20 @@ func GenerateMap(args GeneratorArgs) (MapResult, error) {
|
||||
return MapResult{}, fmt.Errorf("failed to save thumbnail: %w", err)
|
||||
}
|
||||
|
||||
mapData, mapNumLandTiles := packTerrain(terrain)
|
||||
mapData4x, numLandTiles4x := packTerrain(terrain4x)
|
||||
mapData16x, numLandTiles16x := packTerrain(terrain16x)
|
||||
mapData, mapNumLandTiles := packTerrain(ctx, terrain)
|
||||
mapData4x, numLandTiles4x := packTerrain(ctx, terrain4x)
|
||||
mapData16x, numLandTiles16x := packTerrain(ctx, terrain16x)
|
||||
|
||||
logger.Debug(fmt.Sprintf("Land Tile Count (1x): %d", mapNumLandTiles))
|
||||
logger.Debug(fmt.Sprintf("Land Tile Count (4x): %d", numLandTiles4x))
|
||||
logger.Debug(fmt.Sprintf("Land Tile Count (16x): %d", numLandTiles16x))
|
||||
|
||||
if mapNumLandTiles == 0 {
|
||||
return MapResult{}, fmt.Errorf("Map has 0 land tiles")
|
||||
}
|
||||
if mapNumLandTiles > maxRecommendedLandTileCount {
|
||||
logger.Debug(fmt.Sprintf("Map has more land tiles (%d) than recommended maximum (%d)", mapNumLandTiles, maxRecommendedLandTileCount), PerformanceLogTag)
|
||||
}
|
||||
|
||||
return MapResult{
|
||||
Map: MapInfo{
|
||||
@@ -242,8 +264,9 @@ func createMiniMap(tm [][]Terrain) [][]Terrain {
|
||||
// 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 {
|
||||
log.Println("Identifying shorelines")
|
||||
func processShore(ctx context.Context, terrain [][]Terrain) []Coord {
|
||||
logger := LoggerFromContext(ctx)
|
||||
logger.Info("Identifying shorelines")
|
||||
var shorelineWaters []Coord
|
||||
width := len(terrain)
|
||||
height := len(terrain[0])
|
||||
@@ -280,8 +303,9 @@ func processShore(terrain [][]Terrain) []Coord {
|
||||
// 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) {
|
||||
log.Println("Setting Water tiles magnitude = Manhattan distance from nearest land")
|
||||
func processDistToLand(ctx context.Context, shorelineWaters []Coord, terrain [][]Terrain) {
|
||||
logger := LoggerFromContext(ctx)
|
||||
logger.Info("Setting Water tiles magnitude = Manhattan distance from nearest land")
|
||||
|
||||
width := len(terrain)
|
||||
height := len(terrain[0])
|
||||
@@ -362,8 +386,9 @@ func getNeighborCoords(x, y int, terrain [][]Terrain) []Coord {
|
||||
// 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) {
|
||||
log.Println("Processing water bodies")
|
||||
func processWater(ctx context.Context, terrain [][]Terrain, removeSmall bool) {
|
||||
logger := LoggerFromContext(ctx)
|
||||
logger.Info("Processing water bodies")
|
||||
visited := make(map[string]bool)
|
||||
|
||||
type waterBody struct {
|
||||
@@ -408,13 +433,14 @@ func processWater(terrain [][]Terrain, removeSmall bool) {
|
||||
for _, coord := range largestWaterBody.coords {
|
||||
terrain[coord.X][coord.Y].Ocean = true
|
||||
}
|
||||
log.Printf("Identified ocean with %d water tiles", largestWaterBody.size)
|
||||
logger.Info(fmt.Sprintf("Identified ocean with %d water tiles", largestWaterBody.size))
|
||||
|
||||
if removeSmall {
|
||||
// Remove small water bodies
|
||||
log.Println("Searching for small water bodies for removal")
|
||||
logger.Info("Searching for small water bodies for removal")
|
||||
for w := 1; w < len(waterBodies); w++ {
|
||||
if waterBodies[w].size < minLakeSize {
|
||||
logger.Debug(fmt.Sprintf("Removing small lake at %d,%d (size %d)", waterBodies[w].coords[0].X, waterBodies[w].coords[0].Y, waterBodies[w].size), RemovalLogTag)
|
||||
smallLakes++
|
||||
for _, coord := range waterBodies[w].coords {
|
||||
terrain[coord.X][coord.Y].Type = Land
|
||||
@@ -422,15 +448,14 @@ func processWater(terrain [][]Terrain, removeSmall bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("Identified and removed %d bodies of water smaller than %d tiles",
|
||||
smallLakes, minLakeSize)
|
||||
logger.Info(fmt.Sprintf("Identified and removed %d bodies of water smaller than %d tiles", smallLakes, minLakeSize))
|
||||
}
|
||||
|
||||
// Process shorelines and distances
|
||||
shorelineWaters := processShore(terrain)
|
||||
processDistToLand(shorelineWaters, terrain)
|
||||
shorelineWaters := processShore(ctx, terrain)
|
||||
processDistToLand(ctx, shorelineWaters, terrain)
|
||||
} else {
|
||||
log.Println("No water bodies found in the map")
|
||||
logger.Info("No water bodies found in the map")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +490,8 @@ func getArea(x, y int, terrain [][]Terrain, visited map[string]bool) []Coord {
|
||||
|
||||
// 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(ctx context.Context, terrain [][]Terrain, removeSmall bool) {
|
||||
logger := LoggerFromContext(ctx)
|
||||
if !removeSmall {
|
||||
return
|
||||
}
|
||||
@@ -501,6 +527,7 @@ func removeSmallIslands(terrain [][]Terrain, removeSmall bool) {
|
||||
|
||||
for _, body := range landBodies {
|
||||
if body.size < minIslandSize {
|
||||
logger.Debug(fmt.Sprintf("Removing small island at %d,%d (size %d)", body.coords[0].X, body.coords[0].Y, body.size), RemovalLogTag)
|
||||
smallIslands++
|
||||
for _, coord := range body.coords {
|
||||
terrain[coord.X][coord.Y].Type = Water
|
||||
@@ -509,8 +536,7 @@ func removeSmallIslands(terrain [][]Terrain, removeSmall bool) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Identified and removed %d islands smaller than %d tiles",
|
||||
smallIslands, minIslandSize)
|
||||
logger.Info(fmt.Sprintf("Identified and removed %d islands smaller than %d tiles", smallIslands, minIslandSize))
|
||||
}
|
||||
|
||||
// packTerrain serializes the terrain grid into a byte slice.
|
||||
@@ -521,7 +547,7 @@ func removeSmallIslands(terrain [][]Terrain, removeSmall bool) {
|
||||
// - 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(ctx context.Context, terrain [][]Terrain) (data []byte, numLandTiles int) {
|
||||
width := len(terrain)
|
||||
height := len(terrain[0])
|
||||
packedData := make([]byte, width*height)
|
||||
@@ -553,15 +579,16 @@ func packTerrain(terrain [][]Terrain) (data []byte, numLandTiles int) {
|
||||
}
|
||||
}
|
||||
|
||||
logBinaryAsBits(packedData, 8)
|
||||
logBinaryAsBits(ctx, packedData, 8)
|
||||
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 {
|
||||
log.Println("Creating thumbnail")
|
||||
func createMapThumbnail(ctx context.Context, terrain [][]Terrain, quality float64) *image.RGBA {
|
||||
logger := LoggerFromContext(ctx)
|
||||
logger.Info("Creating thumbnail")
|
||||
|
||||
srcWidth := len(terrain)
|
||||
srcHeight := len(terrain[0])
|
||||
@@ -664,7 +691,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(ctx context.Context, data []byte, length int) {
|
||||
logger := LoggerFromContext(ctx)
|
||||
if length > len(data) {
|
||||
length = len(data)
|
||||
}
|
||||
@@ -673,7 +701,7 @@ func logBinaryAsBits(data []byte, length int) {
|
||||
for i := 0; i < length; i++ {
|
||||
bits += fmt.Sprintf("%08b ", data[i])
|
||||
}
|
||||
log.Printf("Binary data (bits): %s", bits)
|
||||
logger.Info(fmt.Sprintf("Binary data (bits): %s", bits))
|
||||
}
|
||||
|
||||
// createCombinedBinary combines the info JSON, map data, and mini-map data into a single binary buffer.
|
||||
|
||||
Reference in New Issue
Block a user