Add tribe name themes system with custom tribes support 🏰 (#4647)

## Description:

Adds a theme system for bot tribe names that maps can customize via
their info.json. Instead of all bots using the same global name pool,
maps can now specify one or more themed name sets and define custom
tribe names with higher priority.

Key changes:
- New `src/core/execution/utils/tribeNameThemes.json` containing 17
themed name sets (default, north_america, south_america, europe, africa,
asia, oceania, space, fantasy, war, western, under_ocean, tournament,
funny, scary, weird, vs). The default theme preserves the original
`TribeNames.ts` values. **Mappers are encouraged to change that json
file, the new themes just serve as an example and are currently not used
by any map.**
- Maps can specify `"themes": ["europe", "war"]` in info.json to merge
prefixes/suffixes from multiple themes into one name pool.
- Maps can specify `"custom_tribes": ["Holy Roman Empire", "Kalmar
Union"]` for exact tribe names that take priority over theme-generated
names. Custom tribes are used first (random selection, no duplicates
until exhausted), then theme-based prefix+suffix combos.
- `TribeNameResolver` resolves themes per-map at runtime, with fallback
to the default theme and a console warning for unknown theme names.
- Go codegen (`codegen.go`) updated to propagate `themes` and
`custom_tribes` from info.json to Maps.gen.ts.
- Germany map now has 404 custom tribes (all German Landkreise and
kreisfreie Städte) to showcase the new feature.
- Fixed a bug where tribe bots could inherit nation flags if their
random name coincidentally matched a nation name (name-based cosmetics
lookup now only applies to actual Nations, not Bots).

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

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

FloPinguin
This commit is contained in:
FloPinguin
2026-07-19 18:46:51 -07:00
committed by GitHub
parent 9c14ab04e0
commit d9976babbd
12 changed files with 3132 additions and 262 deletions
+59
View File
@@ -48,6 +48,16 @@ type mapInfo struct {
// Preferred team count in team/special games (see MapPlaylist).
// 0 (or omitted) means no preference.
SpecialTeamCount int `json:"special_team_count"`
// Theme name(s) for bot tribe names (references tribeNameThemes.json).
// Empty or omitted uses the "default" theme.
Themes []string `json:"themes"`
// Custom tribe names that take priority over theme-generated names.
// Each entry is used as-is (no prefix/suffix composition).
CustomTribes []string `json:"custom_tribes"`
// Nations defined on this map (used for validation only).
Nations []struct {
Name string `json:"name"`
} `json:"nations"`
}
// hasCategory reports whether the map lists the given category.
@@ -113,6 +123,31 @@ func loadMapInfos() ([]mapInfo, error) {
if len(info.Categories) == 0 {
return nil, fmt.Errorf("map %s: info.json \"categories\" must list at least one category", m.Name)
}
for _, ct := range info.CustomTribes {
if ct == "" {
return nil, fmt.Errorf("map %s: info.json \"custom_tribes\" contains an empty string", m.Name)
}
}
{
ctSeen := make(map[string]bool)
for _, ct := range info.CustomTribes {
if ctSeen[ct] {
return nil, fmt.Errorf("map %s: info.json \"custom_tribes\" contains duplicate %q", m.Name, ct)
}
ctSeen[ct] = true
}
}
{
nationNames := make(map[string]bool)
for _, n := range info.Nations {
nationNames[n.Name] = true
}
for _, ct := range info.CustomTribes {
if nationNames[ct] {
return nil, fmt.Errorf("map %s: info.json \"custom_tribes\" contains %q which is already a nation name", m.Name, ct)
}
}
}
seen := make(map[string]bool)
for _, category := range info.Categories {
valid := false
@@ -190,6 +225,10 @@ func generateMapsTS(infos []mapInfo) error {
b.WriteString(" featuredRank?: number;\n")
b.WriteString(" /** Preferred team count in team/special games (see MapPlaylist). */\n")
b.WriteString(" specialTeamCount?: number;\n")
b.WriteString(" /** Tribe name theme(s) (keys in tribeNameThemes.json). */\n")
b.WriteString(" themes?: string[];\n")
b.WriteString(" /** Custom tribe names with priority over theme-generated names. */\n")
b.WriteString(" customTribes?: string[];\n")
b.WriteString("}\n\n")
b.WriteString("export const maps: readonly MapInfo[] = [\n")
@@ -213,6 +252,26 @@ func generateMapsTS(infos []mapInfo) error {
if info.SpecialTeamCount > 0 {
b.WriteString(fmt.Sprintf(" specialTeamCount: %d,\n", info.SpecialTeamCount))
}
if len(info.Themes) > 0 {
b.WriteString(" themes: [")
for i, th := range info.Themes {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(fmt.Sprintf("%q", th))
}
b.WriteString("],\n")
}
if len(info.CustomTribes) > 0 {
b.WriteString(" customTribes: [")
for i, ct := range info.CustomTribes {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(fmt.Sprintf("%q", ct))
}
b.WriteString("],\n")
}
b.WriteString(" },\n")
}
b.WriteString("];\n")