Files
Aaron Tidwell ae884cb902 Add New York City Map (#2542)
## Description:

Adds New York City Map.

## 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

Screenshots

<img width="1700" height="1081" alt="location-select"
src="https://github.com/user-attachments/assets/06d229c1-f804-4766-bd58-35923c6696bc"
/>
<img width="1705" height="1079" alt="map-bots"
src="https://github.com/user-attachments/assets/16a677d1-da6b-4aca-9ecf-8b0fbb161019"
/>
<img width="1706" height="1082" alt="map-select"
src="https://github.com/user-attachments/assets/8adfd26b-f506-4c72-be05-a1fa638311ab"
/>
<img width="745" height="929" alt="nation-placement"
src="https://github.com/user-attachments/assets/6b5ffd0e-0b2e-4189-b118-bd04c8ac4240"
/>


Misc

Dimensions: 1500 x 1900 px
Total Area: 2,850,000 px²
New Flag Assets: None


[Inspired by this Discord
Thread](https://discord.com/channels/1284581928254701718/1440439003160641667/1440439003160641667)

Some of the water features are adjusted for playability. NYC doesn't
have a ton of elevation differences, so marshland is replicated w/
highland noise. This is roughly placed to match
[Pre-WWI](https://www.geographicus.com/P/AntiqueMap/newyorkcity-usgs-1915),
but allows maintaining the modern silhouette of the area. This 1901 map
is also the main inspiration for composition and projection. For the
Nations, British and Dutch Colonies along with Native Peoples make this
a bit of an amalgamation of the 17th - 20th centuries

Other elevation differences come from [Topographic
Map](https://en-gb.topographic-map.com/map-6sm14/New-York/?center=40.63067%2C-73.89158&zoom=11)

[Tribal Nation Names and
Info](https://en.wikipedia.org/wiki/History_of_Long_Island)
[Additional Tribal
Names/Info](https://libguides.pratt.edu/c.php?g=1088684&p=9380209)

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-12-10 12:30:19 -08:00

237 lines
6.1 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
)
var mapsFlag string
var maps = []struct {
Name string
IsTest bool
}{
{Name: "africa"},
{Name: "asia"},
{Name: "australia"},
{Name: "achiran"},
{Name: "baikal"},
{Name: "baikalnukewars"},
{Name: "betweentwoseas"},
{Name: "blacksea"},
{Name: "britannia"},
{Name: "deglaciatedantarctica"},
{Name: "eastasia"},
{Name: "europe"},
{Name: "europeclassic"},
{Name: "falklandislands"},
{Name: "faroeislands"},
{Name: "fourislands"},
{Name: "gatewaytotheatlantic"},
{Name: "giantworldmap"},
{Name: "gulfofstlawrence"},
{Name: "halkidiki"},
{Name: "iceland"},
{Name: "italia"},
{Name: "japan"},
{Name: "lisbon"},
{Name: "mars"},
{Name: "mena"},
{Name: "montreal"},
{Name: "newyorkcity"},
{Name: "northamerica"},
{Name: "oceania"},
{Name: "pangaea"},
{Name: "pluto"},
{Name: "southamerica"},
{Name: "straitofgibraltar"},
{Name: "world"},
{Name: "big_plains", IsTest: true},
{Name: "half_land_half_ocean", IsTest: true},
{Name: "ocean_and_land", IsTest: true},
{Name: "plains", IsTest: true},
{Name: "giantworldmap", IsTest: true},
}
func outputMapDir(isTest bool) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get working directory: %w", err)
}
if isTest {
return filepath.Join(cwd, "..", "tests", "testdata", "maps"), nil
}
return filepath.Join(cwd, "..", "resources", "maps"), nil
}
func inputMapDir(isTest bool) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get working directory: %w", err)
}
if isTest {
return filepath.Join(cwd, "assets", "test_maps"), nil
} else {
return filepath.Join(cwd, "assets", "maps"), nil
}
}
func processMap(name string, isTest bool) error {
outputMapBaseDir, err := outputMapDir(isTest)
if err != nil {
return fmt.Errorf("failed to get map directory: %w", err)
}
inputMapDir, err := inputMapDir(isTest)
if err != nil {
return fmt.Errorf("failed to get input map directory: %w", err)
}
inputPath := filepath.Join(inputMapDir, name, "image.png")
imageBuffer, err := os.ReadFile(inputPath)
if err != nil {
return fmt.Errorf("failed to read map file %s: %w", inputPath, err)
}
// Read the info.json file
manifestPath := filepath.Join(inputMapDir, name, "info.json")
manifestBuffer, err := os.ReadFile(manifestPath)
if err != nil {
return fmt.Errorf("failed to read info file %s: %w", manifestPath, err)
}
// Parse the info buffer as dynamic JSON
var manifest map[string]interface{}
if err := json.Unmarshal(manifestBuffer, &manifest); err != nil {
return fmt.Errorf("failed to parse info.json for %s: %w", name, err)
}
// Generate maps
result, err := GenerateMap(GeneratorArgs{
ImageBuffer: imageBuffer,
RemoveSmall: !isTest, // Don't remove small islands for test maps
Name: name,
})
if err != nil {
return fmt.Errorf("failed to generate map for %s: %w", name, err)
}
manifest["map"] = map[string]interface{}{
"width": result.Map.Width,
"height": result.Map.Height,
"num_land_tiles": result.Map.NumLandTiles,
}
manifest["map4x"] = map[string]interface{}{
"width": result.Map4x.Width,
"height": result.Map4x.Height,
"num_land_tiles": result.Map4x.NumLandTiles,
}
manifest["map16x"] = map[string]interface{}{
"width": result.Map16x.Width,
"height": result.Map16x.Height,
"num_land_tiles": result.Map16x.NumLandTiles,
}
mapDir := filepath.Join(outputMapBaseDir, name)
if err := os.MkdirAll(mapDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory for %s: %w", name, err)
}
if err := os.WriteFile(filepath.Join(mapDir, "map.bin"), result.Map.Data, 0644); err != nil {
return fmt.Errorf("failed to write combined binary for %s: %w", name, err)
}
if err := os.WriteFile(filepath.Join(mapDir, "map4x.bin"), result.Map4x.Data, 0644); err != nil {
return fmt.Errorf("failed to write combined binary for %s: %w", name, err)
}
if err := os.WriteFile(filepath.Join(mapDir, "map16x.bin"), result.Map16x.Data, 0644); err != nil {
return fmt.Errorf("failed to write combined binary for %s: %w", name, err)
}
if err := os.WriteFile(filepath.Join(mapDir, "thumbnail.webp"), result.Thumbnail, 0644); err != nil {
return fmt.Errorf("failed to write thumbnail for %s: %w", name, err)
}
// Serialize the updated manifest to JSON
updatedManifest, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return fmt.Errorf("failed to serialize manifest for %s: %w", name, err)
}
if err := os.WriteFile(filepath.Join(mapDir, "manifest.json"), updatedManifest, 0644); err != nil {
return fmt.Errorf("failed to write manifest for %s: %w", name, err)
}
return nil
}
func parseMapsFlag() (map[string]bool, error) {
if mapsFlag == "" {
return nil, nil
}
validNames := make(map[string]bool, len(maps))
for _, m := range maps {
validNames[m.Name] = true
}
selected := make(map[string]bool)
for _, name := range strings.Split(mapsFlag, ",") {
if !validNames[name] {
return nil, fmt.Errorf("map %q is not defined", name)
}
selected[name] = true
}
return selected, nil
}
func loadTerrainMaps() error {
selectedMaps, err := parseMapsFlag()
if err != nil {
return err
}
var wg sync.WaitGroup
errChan := make(chan error, len(maps))
// Process maps concurrently
for _, mapItem := range maps {
if selectedMaps != nil && !selectedMaps[mapItem.Name] {
continue
}
wg.Add(1)
mapItem := mapItem
go func() {
defer wg.Done()
if err := processMap(mapItem.Name, mapItem.IsTest); err != nil {
errChan <- err
}
}()
}
// Wait for all goroutines to complete
wg.Wait()
close(errChan)
// Check for errors
for err := range errChan {
if err != nil {
return err
}
}
return nil
}
func main() {
flag.StringVar(&mapsFlag, "maps", "", "optional comma-separated list of maps to process. ex: --maps=world,eastasia,big_plains")
flag.Parse()
if err := loadTerrainMaps(); err != nil {
log.Fatalf("Error generating terrain maps: %v", err)
}
fmt.Println("Terrain maps generated successfully")
}