From d9976babbd9876f387e0891505ce6f4740931851 Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:46:51 +0200 Subject: [PATCH] =?UTF-8?q?Add=20tribe=20name=20themes=20system=20with=20c?= =?UTF-8?q?ustom=20tribes=20support=20=F0=9F=8F=B0=20(#4647)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- .gitignore | 1 + map-generator/assets/maps/germany/info.json | 410 +++++ map-generator/codegen.go | 59 + resources/maps/germany/manifest.json | 410 +++++ resources/tribeNameThemes.json | 1580 +++++++++++++++++++ src/client/view/GameView.ts | 7 +- src/core/Util.ts | 14 +- src/core/execution/TribeSpawner.ts | 27 +- src/core/execution/utils/TribeNames.ts | 336 ++-- src/core/game/Maps.gen.ts | 414 +++++ tests/MapConsistency.test.ts | 2 + tests/TribeNameThemes.test.ts | 134 ++ 12 files changed, 3132 insertions(+), 262 deletions(-) create mode 100644 resources/tribeNameThemes.json create mode 100644 tests/TribeNameThemes.test.ts diff --git a/.gitignore b/.gitignore index 8a3fdf385..3223dce9f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ tests/perf/output/ # Locally built Go binary (gen-maps uses `go run .`) map-generator/map-generator +map-generator/map-generator.exe diff --git a/map-generator/assets/maps/germany/info.json b/map-generator/assets/maps/germany/info.json index 5a035d4e2..4ba2fb4ce 100644 --- a/map-generator/assets/maps/germany/info.json +++ b/map-generator/assets/maps/germany/info.json @@ -4,6 +4,416 @@ "translation_key": "map.germany", "categories": ["europe", "new"], "multiplayer_frequency": 5, + "custom_tribes": [ + "Aachen, Städteregion", + "Ahrweiler", + "Aichach-Friedberg", + "Alb-Donau-Kreis", + "Altenburger Land", + "Altenkirchen (Westerwald)", + "Altmarkkreis Salzwedel", + "Altötting", + "Alzey-Worms", + "Amberg-Sulzbach", + "Ammerland", + "Anhalt-Bitterfeld", + "Ansbach", + "Aschaffenburg", + "Augsburg", + "Aurich", + "Bad Dürkheim", + "Bad Kissingen", + "Bad Kreuznach", + "Bad Tölz-Wolfratshausen", + "Bamberg", + "Barnim", + "Bautzen", + "Bayreuth", + "Berchtesgadener Land", + "Bergstraße", + "Bernkastel-Wittlich", + "Biberach", + "Birkenfeld", + "Böblingen", + "Bodenseekreis", + "Börde", + "Borken", + "Breisgau-Hochschwarzwald", + "Burgenlandkreis", + "Calw", + "Celle", + "Cham", + "Cloppenburg", + "Coburg", + "Cochem-Zell", + "Coesfeld", + "Cuxhaven", + "Dachau", + "Dahme-Spreewald", + "Darmstadt-Dieburg", + "Deggendorf", + "Diepholz", + "Dillingen an der Donau", + "Dingolfing-Landau", + "Dithmarschen", + "Donau-Ries", + "Donnersbergkreis", + "Düren", + "Ebersberg", + "Eichsfeld", + "Eichstätt", + "Eifelkreis Bitburg-Prüm", + "Elbe-Elster", + "Emmendingen", + "Emsland", + "Ennepe-Ruhr-Kreis", + "Enzkreis", + "Erding", + "Erlangen-Höchstadt", + "Erzgebirgskreis", + "Esslingen", + "Euskirchen", + "Forchheim", + "Freising", + "Freudenstadt", + "Freyung-Grafenau", + "Friesland", + "Fulda", + "Fürstenfeldbruck", + "Fürth", + "Garmisch-Partenkirchen", + "Germersheim", + "Gießen", + "Gifhorn", + "Göppingen", + "Görlitz", + "Goslar", + "Gotha", + "Göttingen", + "Grafschaft Bentheim", + "Greiz", + "Groß-Gerau", + "Günzburg", + "Gütersloh", + "Hameln-Pyrmont", + "Hannover, Region", + "Harburg", + "Harz", + "Haßberge", + "Havelland", + "Heidekreis", + "Heidenheim", + "Heilbronn", + "Heinsberg", + "Helmstedt", + "Herford", + "Hersfeld-Rotenburg", + "Herzogtum Lauenburg", + "Hildburghausen", + "Hildesheim", + "Hochsauerlandkreis", + "Hochtaunuskreis", + "Hof", + "Hohenlohekreis", + "Holzminden", + "Höxter", + "Ilm-Kreis", + "Jerichower Land", + "Kaiserslautern", + "Karlsruhe", + "Kassel", + "Kelheim", + "Kitzingen", + "Kleve", + "Konstanz", + "Kronach", + "Kulmbach", + "Kusel", + "Kyffhäuserkreis", + "Lahn-Dill-Kreis", + "Landsberg am Lech", + "Landshut", + "Leer", + "Leipzig", + "Lichtenfels", + "Limburg-Weilburg", + "Lindau (Bodensee)", + "Lippe", + "Lörrach", + "Lüchow-Dannenberg", + "Ludwigsburg", + "Ludwigslust-Parchim", + "Lüneburg", + "Main-Kinzig-Kreis", + "Main-Spessart", + "Main-Tauber-Kreis", + "Main-Taunus-Kreis", + "Mainz-Bingen", + "Mansfeld-Südharz", + "Marburg-Biedenkopf", + "Märkischer Kreis", + "Märkisch-Oderland", + "Mayen-Koblenz", + "Mecklenburgische Seenplatte", + "Meißen", + "Merzig-Wadern", + "Mettmann", + "Miesbach", + "Miltenberg", + "Minden-Lübbecke", + "Mittelsachsen", + "Mühldorf am Inn", + "München", + "Neckar-Odenwald-Kreis", + "Neu-Ulm", + "Neuburg-Schrobenhausen", + "Neumarkt in der Oberpfalz", + "Neunkirchen", + "Neustadt an der Aisch-Bad Windsheim", + "Neustadt an der Waldnaab", + "Neuwied", + "Nienburg/Weser", + "Nordfriesland", + "Nordhausen", + "Nordsachsen", + "Nordwestmecklenburg", + "Northeim", + "Nürnberger Land", + "Oberallgäu", + "Oberbergischer Kreis", + "Oberhavel", + "Oberspreewald-Lausitz", + "Odenwaldkreis", + "Oder-Spree", + "Offenbach", + "Oldenburg", + "Olpe", + "Ortenaukreis", + "Osnabrück", + "Ostalbkreis", + "Ostallgäu", + "Osterholz", + "Ostholstein", + "Ostprignitz-Ruppin", + "Paderborn", + "Passau", + "Peine", + "Pfaffenhofen an der Ilm", + "Pinneberg", + "Plön", + "Potsdam-Mittelmark", + "Prignitz", + "Rastatt", + "Ravensburg", + "Recklinghausen", + "Regen", + "Regensburg", + "Rems-Murr-Kreis", + "Rendsburg-Eckernförde", + "Reutlingen", + "Rhein-Erft-Kreis", + "Rheingau-Taunus-Kreis", + "Rhein-Hunsrück-Kreis", + "Rheinisch-Bergischer Kreis", + "Rhein-Kreis Neuss", + "Rhein-Lahn-Kreis", + "Rhein-Neckar-Kreis", + "Rhein-Pfalz-Kreis", + "Rhein-Sieg-Kreis", + "Rhön-Grabfeld", + "Rosenheim", + "Rostock", + "Rotenburg (Wümme)", + "Roth", + "Rottal-Inn", + "Rottweil", + "Saale-Holzland-Kreis", + "Saalekreis", + "Saale-Orla-Kreis", + "Saalfeld-Rudolstadt", + "Saarbrücken, Regionalverband", + "Saarlouis", + "Saarpfalz-Kreis", + "Sächsische Schweiz-Osterzgebirge", + "Salzlandkreis", + "Schaumburg", + "Schleswig-Flensburg", + "Schmalkalden-Meiningen", + "Schwäbisch Hall", + "Schwalm-Eder-Kreis", + "Schwandorf", + "Schwarzwald-Baar-Kreis", + "Schweinfurt", + "Segeberg", + "Siegen-Wittgenstein", + "Sigmaringen", + "Soest", + "Sömmerda", + "Sonneberg", + "Spree-Neiße", + "St. Wendel", + "Stade", + "Starnberg", + "Steinburg", + "Steinfurt", + "Stendal", + "Stormarn", + "Straubing-Bogen", + "Südliche Weinstraße", + "Südwestpfalz", + "Teltow-Fläming", + "Tirschenreuth", + "Traunstein", + "Trier-Saarburg", + "Tübingen", + "Tuttlingen", + "Uckermark", + "Uelzen", + "Unna", + "Unstrut-Hainich-Kreis", + "Unterallgäu", + "Vechta", + "Verden", + "Viersen", + "Vogelsbergkreis", + "Vogtlandkreis", + "Vorpommern-Greifswald", + "Vorpommern-Rügen", + "Vulkaneifel", + "Waldeck-Frankenberg", + "Waldshut", + "Warendorf", + "Wartburgkreis", + "Weilheim-Schongau", + "Weimarer Land", + "Weißenburg-Gunzenhausen", + "Werra-Meißner-Kreis", + "Wesel", + "Wesermarsch", + "Westerwaldkreis", + "Wetteraukreis", + "Wittenberg", + "Wittmund", + "Wolfenbüttel", + "Wunsiedel im Fichtelgebirge", + "Würzburg", + "Zollernalbkreis", + "Zwickau", + "Aachen", + "Amberg", + "Baden-Baden", + "Bielefeld", + "Bochum", + "Bonn", + "Bottrop", + "Brandenburg an der Havel", + "Braunschweig", + "Bremerhaven", + "Chemnitz", + "Cottbus", + "Darmstadt", + "Delmenhorst", + "Dessau-Roßlau", + "Dortmund", + "Dresden", + "Duisburg", + "Düsseldorf", + "Emden", + "Erfurt", + "Erlangen", + "Essen", + "Flensburg", + "Frankenthal (Pfalz)", + "Frankfurt (Oder)", + "Frankfurt am Main", + "Freiburg im Breisgau", + "Gelsenkirchen", + "Gera", + "Hagen", + "Halle (Saale)", + "Hamm", + "Hanau", + "Hannover", + "Heidelberg", + "Herne", + "Ingolstadt", + "Jena", + "Kaufbeuren", + "Kempten (Allgäu)", + "Kiel", + "Koblenz", + "Köln", + "Krefeld", + "Landau in der Pfalz", + "Leverkusen", + "Lübeck", + "Ludwigshafen am Rhein", + "Magdeburg", + "Mainz", + "Mannheim", + "Memmingen", + "Mönchengladbach", + "Mülheim an der Ruhr", + "Münster", + "Neumünster", + "Neustadt an der Weinstraße", + "Nürnberg", + "Oberhausen", + "Offenbach am Main", + "Oldenburg (Oldb)", + "Pforzheim", + "Pirmasens", + "Potsdam", + "Remscheid", + "Salzgitter", + "Schwabach", + "Schwerin", + "Solingen", + "Speyer", + "Straubing", + "Stuttgart", + "Suhl", + "Trier", + "Ulm", + "Weiden in der Oberpfalz", + "Weimar", + "Wiesbaden", + "Wilhelmshaven", + "Wolfsburg", + "Worms", + "Wuppertal", + "Zweibrücken", + "Preußen", + "Schwaben", + "Franken", + "Rheinland", + "Westfalen", + "Pommern", + "Schleswig", + "Holstein", + "Anhalt", + "Lausitz", + "Elsass", + "Deutscher Orden", + "Nassau", + "Kurpfalz", + "Berg", + "Jülich", + "Lothringen", + "Harzgau", + "Wetterau", + "Ruhrgebiet", + "Allgäu", + "Schwarzwald", + "Spreewald", + "Altmühltal", + "Spessart", + "Odenwald", + "Eifel", + "Sauerland", + "Westerwald", + "Pfalz" + ], "nations": [ { "coordinates": [688, 396], diff --git a/map-generator/codegen.go b/map-generator/codegen.go index b30535081..bbaecb0cd 100644 --- a/map-generator/codegen.go +++ b/map-generator/codegen.go @@ -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") diff --git a/resources/maps/germany/manifest.json b/resources/maps/germany/manifest.json index daf9ce4ad..bb0309da1 100644 --- a/resources/maps/germany/manifest.json +++ b/resources/maps/germany/manifest.json @@ -1,5 +1,415 @@ { "categories": ["europe", "new"], + "custom_tribes": [ + "Aachen, Städteregion", + "Ahrweiler", + "Aichach-Friedberg", + "Alb-Donau-Kreis", + "Altenburger Land", + "Altenkirchen (Westerwald)", + "Altmarkkreis Salzwedel", + "Altötting", + "Alzey-Worms", + "Amberg-Sulzbach", + "Ammerland", + "Anhalt-Bitterfeld", + "Ansbach", + "Aschaffenburg", + "Augsburg", + "Aurich", + "Bad Dürkheim", + "Bad Kissingen", + "Bad Kreuznach", + "Bad Tölz-Wolfratshausen", + "Bamberg", + "Barnim", + "Bautzen", + "Bayreuth", + "Berchtesgadener Land", + "Bergstraße", + "Bernkastel-Wittlich", + "Biberach", + "Birkenfeld", + "Böblingen", + "Bodenseekreis", + "Börde", + "Borken", + "Breisgau-Hochschwarzwald", + "Burgenlandkreis", + "Calw", + "Celle", + "Cham", + "Cloppenburg", + "Coburg", + "Cochem-Zell", + "Coesfeld", + "Cuxhaven", + "Dachau", + "Dahme-Spreewald", + "Darmstadt-Dieburg", + "Deggendorf", + "Diepholz", + "Dillingen an der Donau", + "Dingolfing-Landau", + "Dithmarschen", + "Donau-Ries", + "Donnersbergkreis", + "Düren", + "Ebersberg", + "Eichsfeld", + "Eichstätt", + "Eifelkreis Bitburg-Prüm", + "Elbe-Elster", + "Emmendingen", + "Emsland", + "Ennepe-Ruhr-Kreis", + "Enzkreis", + "Erding", + "Erlangen-Höchstadt", + "Erzgebirgskreis", + "Esslingen", + "Euskirchen", + "Forchheim", + "Freising", + "Freudenstadt", + "Freyung-Grafenau", + "Friesland", + "Fulda", + "Fürstenfeldbruck", + "Fürth", + "Garmisch-Partenkirchen", + "Germersheim", + "Gießen", + "Gifhorn", + "Göppingen", + "Görlitz", + "Goslar", + "Gotha", + "Göttingen", + "Grafschaft Bentheim", + "Greiz", + "Groß-Gerau", + "Günzburg", + "Gütersloh", + "Hameln-Pyrmont", + "Hannover, Region", + "Harburg", + "Harz", + "Haßberge", + "Havelland", + "Heidekreis", + "Heidenheim", + "Heilbronn", + "Heinsberg", + "Helmstedt", + "Herford", + "Hersfeld-Rotenburg", + "Herzogtum Lauenburg", + "Hildburghausen", + "Hildesheim", + "Hochsauerlandkreis", + "Hochtaunuskreis", + "Hof", + "Hohenlohekreis", + "Holzminden", + "Höxter", + "Ilm-Kreis", + "Jerichower Land", + "Kaiserslautern", + "Karlsruhe", + "Kassel", + "Kelheim", + "Kitzingen", + "Kleve", + "Konstanz", + "Kronach", + "Kulmbach", + "Kusel", + "Kyffhäuserkreis", + "Lahn-Dill-Kreis", + "Landsberg am Lech", + "Landshut", + "Leer", + "Leipzig", + "Lichtenfels", + "Limburg-Weilburg", + "Lindau (Bodensee)", + "Lippe", + "Lörrach", + "Lüchow-Dannenberg", + "Ludwigsburg", + "Ludwigslust-Parchim", + "Lüneburg", + "Main-Kinzig-Kreis", + "Main-Spessart", + "Main-Tauber-Kreis", + "Main-Taunus-Kreis", + "Mainz-Bingen", + "Mansfeld-Südharz", + "Marburg-Biedenkopf", + "Märkischer Kreis", + "Märkisch-Oderland", + "Mayen-Koblenz", + "Mecklenburgische Seenplatte", + "Meißen", + "Merzig-Wadern", + "Mettmann", + "Miesbach", + "Miltenberg", + "Minden-Lübbecke", + "Mittelsachsen", + "Mühldorf am Inn", + "München", + "Neckar-Odenwald-Kreis", + "Neu-Ulm", + "Neuburg-Schrobenhausen", + "Neumarkt in der Oberpfalz", + "Neunkirchen", + "Neustadt an der Aisch-Bad Windsheim", + "Neustadt an der Waldnaab", + "Neuwied", + "Nienburg/Weser", + "Nordfriesland", + "Nordhausen", + "Nordsachsen", + "Nordwestmecklenburg", + "Northeim", + "Nürnberger Land", + "Oberallgäu", + "Oberbergischer Kreis", + "Oberhavel", + "Oberspreewald-Lausitz", + "Odenwaldkreis", + "Oder-Spree", + "Offenbach", + "Oldenburg", + "Olpe", + "Ortenaukreis", + "Osnabrück", + "Ostalbkreis", + "Ostallgäu", + "Osterholz", + "Ostholstein", + "Ostprignitz-Ruppin", + "Paderborn", + "Passau", + "Peine", + "Pfaffenhofen an der Ilm", + "Pinneberg", + "Plön", + "Potsdam-Mittelmark", + "Prignitz", + "Rastatt", + "Ravensburg", + "Recklinghausen", + "Regen", + "Regensburg", + "Rems-Murr-Kreis", + "Rendsburg-Eckernförde", + "Reutlingen", + "Rhein-Erft-Kreis", + "Rheingau-Taunus-Kreis", + "Rhein-Hunsrück-Kreis", + "Rheinisch-Bergischer Kreis", + "Rhein-Kreis Neuss", + "Rhein-Lahn-Kreis", + "Rhein-Neckar-Kreis", + "Rhein-Pfalz-Kreis", + "Rhein-Sieg-Kreis", + "Rhön-Grabfeld", + "Rosenheim", + "Rostock", + "Rotenburg (Wümme)", + "Roth", + "Rottal-Inn", + "Rottweil", + "Saale-Holzland-Kreis", + "Saalekreis", + "Saale-Orla-Kreis", + "Saalfeld-Rudolstadt", + "Saarbrücken, Regionalverband", + "Saarlouis", + "Saarpfalz-Kreis", + "Sächsische Schweiz-Osterzgebirge", + "Salzlandkreis", + "Schaumburg", + "Schleswig-Flensburg", + "Schmalkalden-Meiningen", + "Schwäbisch Hall", + "Schwalm-Eder-Kreis", + "Schwandorf", + "Schwarzwald-Baar-Kreis", + "Schweinfurt", + "Segeberg", + "Siegen-Wittgenstein", + "Sigmaringen", + "Soest", + "Sömmerda", + "Sonneberg", + "Spree-Neiße", + "St. Wendel", + "Stade", + "Starnberg", + "Steinburg", + "Steinfurt", + "Stendal", + "Stormarn", + "Straubing-Bogen", + "Südliche Weinstraße", + "Südwestpfalz", + "Teltow-Fläming", + "Tirschenreuth", + "Traunstein", + "Trier-Saarburg", + "Tübingen", + "Tuttlingen", + "Uckermark", + "Uelzen", + "Unna", + "Unstrut-Hainich-Kreis", + "Unterallgäu", + "Vechta", + "Verden", + "Viersen", + "Vogelsbergkreis", + "Vogtlandkreis", + "Vorpommern-Greifswald", + "Vorpommern-Rügen", + "Vulkaneifel", + "Waldeck-Frankenberg", + "Waldshut", + "Warendorf", + "Wartburgkreis", + "Weilheim-Schongau", + "Weimarer Land", + "Weißenburg-Gunzenhausen", + "Werra-Meißner-Kreis", + "Wesel", + "Wesermarsch", + "Westerwaldkreis", + "Wetteraukreis", + "Wittenberg", + "Wittmund", + "Wolfenbüttel", + "Wunsiedel im Fichtelgebirge", + "Würzburg", + "Zollernalbkreis", + "Zwickau", + "Aachen", + "Amberg", + "Baden-Baden", + "Bielefeld", + "Bochum", + "Bonn", + "Bottrop", + "Brandenburg an der Havel", + "Braunschweig", + "Bremerhaven", + "Chemnitz", + "Cottbus", + "Darmstadt", + "Delmenhorst", + "Dessau-Roßlau", + "Dortmund", + "Dresden", + "Duisburg", + "Düsseldorf", + "Emden", + "Erfurt", + "Erlangen", + "Essen", + "Flensburg", + "Frankenthal (Pfalz)", + "Frankfurt (Oder)", + "Frankfurt am Main", + "Freiburg im Breisgau", + "Gelsenkirchen", + "Gera", + "Hagen", + "Halle (Saale)", + "Hamm", + "Hanau", + "Hannover", + "Heidelberg", + "Herne", + "Ingolstadt", + "Jena", + "Kaufbeuren", + "Kempten (Allgäu)", + "Kiel", + "Koblenz", + "Köln", + "Krefeld", + "Landau in der Pfalz", + "Leverkusen", + "Lübeck", + "Ludwigshafen am Rhein", + "Magdeburg", + "Mainz", + "Mannheim", + "Memmingen", + "Mönchengladbach", + "Mülheim an der Ruhr", + "Münster", + "Neumünster", + "Neustadt an der Weinstraße", + "Nürnberg", + "Oberhausen", + "Offenbach am Main", + "Oldenburg (Oldb)", + "Pforzheim", + "Pirmasens", + "Potsdam", + "Remscheid", + "Salzgitter", + "Schwabach", + "Schwerin", + "Solingen", + "Speyer", + "Straubing", + "Stuttgart", + "Suhl", + "Trier", + "Ulm", + "Weiden in der Oberpfalz", + "Weimar", + "Wiesbaden", + "Wilhelmshaven", + "Wolfsburg", + "Worms", + "Wuppertal", + "Zweibrücken", + "Preußen", + "Schwaben", + "Franken", + "Rheinland", + "Westfalen", + "Pommern", + "Schleswig", + "Holstein", + "Anhalt", + "Lausitz", + "Elsass", + "Deutscher Orden", + "Nassau", + "Kurpfalz", + "Berg", + "Jülich", + "Lothringen", + "Harzgau", + "Wetterau", + "Ruhrgebiet", + "Allgäu", + "Schwarzwald", + "Spreewald", + "Altmühltal", + "Spessart", + "Odenwald", + "Eifel", + "Sauerland", + "Westerwald", + "Pfalz" + ], "id": "Germany", "map": { "height": 1516, diff --git a/resources/tribeNameThemes.json b/resources/tribeNameThemes.json new file mode 100644 index 000000000..234e1bd71 --- /dev/null +++ b/resources/tribeNameThemes.json @@ -0,0 +1,1580 @@ +{ + "default": { + "prefixes": [ + "Akkadian", + "Babylonian", + "Sumerian", + "Hittite", + "Phoenician", + "Canaanite", + "Minoan", + "Mycenaean", + "Etruscan", + "Scythian", + "Thracian", + "Dacian", + "Illyrian", + "Median", + "Chaldean", + "Roman", + "Greek", + "Byzantine", + "Persian", + "Parthian", + "Seleucid", + "Ptolemaic", + "Palmyrene", + "Macedonian", + "Carthaginian", + "Ming", + "Tang", + "Song", + "Yuan", + "Mauryan", + "Kushan", + "Rajput", + "Mughal", + "Satavahana", + "Vijayanagara", + "Egyptian", + "Nubian", + "Kushite", + "Aksumite", + "Ethiopian", + "Songhai", + "Malian", + "Ghanaian", + "Benin", + "Ashanti", + "Zulu", + "Tuareg", + "Berber", + "Kanem-Bornu", + "Buganda", + "Mossi", + "Swahili", + "Somali", + "Wolof", + "Umayyad", + "Abbasid", + "Ayyubid", + "Fatimid", + "Mamluk", + "Seljuk", + "Safavid", + "Ottoman", + "Almoravid", + "Almohad", + "Rashidun", + "Ziyarid", + "Frankish", + "Visigothic", + "Ostrogothic", + "Viking", + "Norman", + "Saxon", + "Anglo-Saxon", + "Celtic", + "Gaulish", + "Carolingian", + "Merovingian", + "Capetian", + "Plantagenet", + "Tudor", + "Stuart", + "Habsburg", + "Romanov", + "Lancaster", + "York", + "Bourbon", + "Napoleonic", + "British", + "French", + "Spanish", + "Portuguese", + "Dutch", + "Russian", + "German", + "Italian", + "Swedish", + "Norwegian", + "Danish", + "Polish", + "Hungarian", + "Austrian", + "Swiss", + "Czech", + "Slovak", + "Serbian", + "Croatian", + "Bosnian", + "Montenegrin", + "Bulgarian", + "Romanian", + "Apache", + "Sioux", + "Cherokee", + "Navajo", + "Iroquois", + "Inuit", + "Arawak", + "Carib", + "Taino", + "Aztec", + "Mayan", + "Incan", + "Mapuche", + "Guarani", + "Tupi", + "Yanomami", + "Zuni", + "Hopi", + "Kiowa", + "Comanche", + "Shoshone", + "Japanese", + "Ryukyu", + "Ainu", + "Cham", + "Khmer", + "Thai", + "Vietnamese", + "Burmese", + "Balinese", + "Malay", + "Filipino", + "Mongolian", + "Korean", + "Tibetan", + "Manchu", + "Uyghur", + "Hmong", + "Karen", + "Pyu", + "Hawaiian", + "Fijian", + "Tongan", + "Samoan", + "Maori", + "Micronesian", + "Hebrew", + "Armenian", + "Circassian", + "Georgian", + "Kurdish", + "Turkic", + "Kazakh", + "Uzbek", + "Kyrgyz", + "Tajik", + "Pashtun", + "Baloch", + "Afghan", + "Kenyan", + "Ugandan", + "Bhutanese", + "Latin", + "Moldovan", + "Militant", + "Spartan" + ], + "suffixes": [ + "Empire", + "Dynasty", + "Kingdom", + "Queendom", + "Sultanate", + "Confederation", + "Union", + "Republic", + "Caliphate", + "Dominion", + "Realm", + "State", + "Federation", + "Territory", + "Commonwealth", + "League", + "Duchy", + "Province", + "Protectorate", + "Colony", + "Mandate", + "Free State", + "Canton", + "Region", + "Nation", + "Assembly", + "Hierarchy", + "Archduchy", + "Grand Duchy", + "Metropolis", + "Cluster", + "Alliance", + "Tribunal", + "Council", + "Confederacy", + "Order", + "Regime", + "Syndicate", + "Guild", + "Corporation", + "Patriarchy", + "Matriarchy", + "Legion", + "Horde", + "Clan", + "Brotherhood", + "Sisterhood", + "Ascendancy", + "Supremacy", + "Tribe", + "Army", + "Dictatorship", + "Country", + "Oligarchy", + "Monkdom", + "Throng", + "Host", + "Area", + "District", + "Fief", + "Wilderness", + "Settlement", + "Parliament", + "Anarchy", + "Democracy", + "Autocracy" + ] + }, + "north_america": { + "prefixes": [ + "Apache", + "Sioux", + "Cherokee", + "Navajo", + "Iroquois", + "Inuit", + "Arawak", + "Carib", + "Taino", + "Aztec", + "Mayan", + "Zuni", + "Hopi", + "Kiowa", + "Comanche", + "Shoshone", + "Cree", + "Ojibwe", + "Seminole", + "Cheyenne", + "Pawnee", + "Mohawk", + "Huron", + "Tlingit", + "Haida", + "Nez Perce", + "Crow", + "Blackfoot", + "Pueblo", + "Mi'kmaq", + "Algonquin", + "Wampanoag", + "Potawatomi", + "Shawnee", + "Chickasaw", + "Choctaw", + "Creek", + "Canadian", + "American", + "Mexican", + "Texan", + "Californian", + "Quebecois", + "Acadian", + "Louisianan", + "Appalachian", + "Yankee", + "Frontiersman" + ], + "suffixes": [ + "Nation", + "Confederation", + "Territory", + "Federation", + "Republic", + "State", + "Province", + "Colony", + "Commonwealth", + "Dominion", + "Council", + "Assembly", + "Tribe", + "Clan", + "League", + "Alliance", + "Union", + "Band", + "Settlement", + "Frontier", + "Range", + "Heartland", + "Homeland", + "Territories" + ] + }, + "south_america": { + "prefixes": [ + "Incan", + "Mapuche", + "Guarani", + "Tupi", + "Yanomami", + "Quechua", + "Aymara", + "Chibcha", + "Muisca", + "Taíno", + "Carib", + "Wari", + "Chimu", + "Nazca", + "Moche", + "Olmec", + "Zapotec", + "Mixtec", + "Toltec", + "Brazilian", + "Argentine", + "Chilean", + "Colombian", + "Peruvian", + "Venezuelan", + "Ecuadorian", + "Bolivian", + "Paraguayan", + "Uruguayan", + "Guyanese", + "Surinamese", + "Patagonian", + "Amazonian", + "Andean", + "Gaucho", + "Carioca", + "Porteño", + "Cordobés", + "Bogotano", + "Limeño" + ], + "suffixes": [ + "Empire", + "Kingdom", + "Republic", + "Confederation", + "Federation", + "State", + "Nation", + "Territory", + "Province", + "Chiefdom", + "Dynasty", + "Alliance", + "League", + "Council", + "Tribe", + "Clan", + "Collective", + "Domain", + "Realm", + "Heartland", + "Amazonia", + "Patagonia", + "Andes", + "Pampas" + ] + }, + "europe": { + "prefixes": [ + "Roman", + "Greek", + "Byzantine", + "Frankish", + "Visigothic", + "Ostrogothic", + "Viking", + "Norman", + "Saxon", + "Anglo-Saxon", + "Celtic", + "Gaulish", + "Carolingian", + "Merovingian", + "Capetian", + "Plantagenet", + "Tudor", + "Stuart", + "Habsburg", + "Romanov", + "Lancaster", + "York", + "Bourbon", + "Napoleonic", + "British", + "French", + "Spanish", + "Portuguese", + "Dutch", + "Russian", + "German", + "Italian", + "Swedish", + "Norwegian", + "Danish", + "Polish", + "Hungarian", + "Austrian", + "Swiss", + "Czech", + "Slovak", + "Serbian", + "Croatian", + "Bosnian", + "Montenegrin", + "Bulgarian", + "Romanian", + "Finnish", + "Estonian", + "Latvian", + "Lithuanian", + "Irish", + "Scottish", + "Welsh", + "Basque", + "Galician", + "Sardinian", + "Sicilian", + "Venetian", + "Florentine", + "Genoese", + "Milanese", + "Prussian", + "Bavarian", + "Teutonic", + "Knights Templar", + "Crusader", + "Papal", + "Holy Roman" + ], + "suffixes": [ + "Kingdom", + "Empire", + "Republic", + "Duchy", + "Grand Duchy", + "Archduchy", + "County", + "Principality", + "Barony", + "Lordship", + "March", + "Shire", + "Fief", + "Realm", + "Dominion", + "Crown", + "Dynasty", + "House", + "Order", + "League", + "Confederation", + "Alliance", + "Pact", + "Union", + "Federation", + "Commonwealth", + "State", + "Nation", + "Province", + "Territory" + ] + }, + "africa": { + "prefixes": [ + "Egyptian", + "Nubian", + "Kushite", + "Aksumite", + "Ethiopian", + "Songhai", + "Malian", + "Ghanaian", + "Benin", + "Ashanti", + "Zulu", + "Tuareg", + "Berber", + "Kanem-Bornu", + "Buganda", + "Mossi", + "Swahili", + "Somali", + "Wolof", + "Yoruba", + "Igbo", + "Hausa", + "Fulani", + "Maasai", + "Kikuyu", + "Shona", + "Luba", + "Lunda", + "Kongo", + "Lozi", + "Tswana", + "Sotho", + "Xhosa", + "Rwandan", + "Burundian", + "Baganda", + "Bamileke", + "Fang", + "San", + "Khoikhoi", + "Merina", + "Sakalava", + "Betsileo", + "Matabele", + "Rozvi", + "Mutapa" + ], + "suffixes": [ + "Empire", + "Kingdom", + "Sultanate", + "Caliphate", + "Dynasty", + "Chiefdom", + "Confederation", + "Republic", + "State", + "Territory", + "Province", + "Region", + "Nation", + "Tribe", + "Clan", + "Alliance", + "League", + "Council", + "Assembly", + "Federation", + "Union", + "Dominion", + "Realm", + "Colony", + "Protectorate" + ] + }, + "asia": { + "prefixes": [ + "Ming", + "Tang", + "Song", + "Yuan", + "Mauryan", + "Kushan", + "Rajput", + "Mughal", + "Satavahana", + "Vijayanagara", + "Japanese", + "Ryukyu", + "Ainu", + "Cham", + "Khmer", + "Thai", + "Vietnamese", + "Burmese", + "Balinese", + "Malay", + "Filipino", + "Mongolian", + "Korean", + "Tibetan", + "Manchu", + "Uyghur", + "Hmong", + "Karen", + "Pyu", + "Han", + "Qin", + "Jin", + "Sui", + "Zhou", + "Shang", + "Xia", + "Choson", + "Goguryeo", + "Baekje", + "Silla", + "Goryeo", + "Joseon", + "Yamato", + "Heian", + "Tokugawa", + "Ashikaga", + "Kamakura", + "Fujiwara", + "Taira", + "Minamoto", + "Srivijayan", + "Majapahit", + "Mataram", + "Ayutthayan", + "Sukhothai", + "Lanna", + "Champa", + "Dai Viet", + "Khitan", + "Jurchen", + "Xiongnu", + "Göktürk", + "Tangut", + "Timurid", + "Gupta", + "Pala", + "Chola", + "Pandya", + "Rashtrakuta", + "Chalukya", + "Hoysala", + "Kalinga" + ], + "suffixes": [ + "Dynasty", + "Empire", + "Kingdom", + "Shogunate", + "Sultanate", + "Khanate", + "Principality", + "Republic", + "State", + "Confederation", + "Horde", + "Realm", + "Province", + "Commandery", + "Satrapy", + "Viceroyalty", + "Mandate", + "Regency", + "Clan", + "Domain", + "Fief", + "March", + "Colony", + "Protectorate", + "Tributary" + ] + }, + "oceania": { + "prefixes": [ + "Hawaiian", + "Fijian", + "Tongan", + "Samoan", + "Maori", + "Micronesian", + "Polynesian", + "Melanesian", + "Papuan", + "Aboriginal", + "Torres Strait", + "Australian", + "New Zealand", + "Tahitian", + "Marquesan", + "Rapa Nui", + "Chamorro", + "Carolinian", + "Marshallese", + "Palauan", + "Nauruan", + "Tuvaluan", + "Niuean", + "Cook Islander", + "Tokelauan", + "Wallisian", + "Futunan", + "New Caledonian", + "Vanuatuan", + "Solomon Islander", + "Bougainvillean" + ], + "suffixes": [ + "Islands", + "Nation", + "Federation", + "Confederation", + "Kingdom", + "Republic", + "Commonwealth", + "Territory", + "Council", + "Alliance", + "League", + "Union", + "Domain", + "Realm", + "Parliament", + "Assembly", + "Tribal Council", + "Clan", + "Tribe", + "Homelands", + "Archipelago", + "Atoll", + "Lagoon" + ] + }, + "space": { + "prefixes": [ + "Andromedan", + "Centaurian", + "Sirian", + "Arcturian", + "Pleiadian", + "Vegan", + "Lyran", + "Orion", + "Antarian", + "Procyon", + "Aldebaran", + "Rigelian", + "Betelgeusian", + "Denebian", + "Altairian", + "Fomalhautian", + "Capellan", + "Zeta", + "Tau", + "Epsilon", + "Keplerian", + "Gliesean", + "Trappist", + "Proximan", + "Barnardian", + "Wolfian", + "Luytenian", + "Rossian", + "Cygnian", + "Aquilan", + "Draconian", + "Pegasian", + "Hydran", + "Corvan", + "Nebulan", + "Quasar", + "Pulsar", + "Novan", + "Stellar", + "Cosmic", + "Galactic", + "Lunar", + "Solar", + "Astral", + "Voidborn", + "Starforged", + "Nebulite", + "Quantum", + "Dark Matter" + ], + "suffixes": [ + "Dominion", + "Empire", + "Federation", + "Confederacy", + "Commonwealth", + "Collective", + "Hive", + "Alliance", + "Coalition", + "Concordat", + "Protectorate", + "Mandate", + "Territory", + "Sector", + "Cluster", + "Nebula", + "Constellation", + "Armada", + "Fleet", + "Legion", + "Order", + "Syndicate", + "Corporation", + "Council", + "Tribunal", + "Ascendancy", + "Supremacy", + "Swarm", + "Horde", + "Brotherhood" + ] + }, + "fantasy": { + "prefixes": [ + "Dragon", + "Elven", + "Dwarven", + "Orcish", + "Goblin", + "Fairy", + "Shadow", + "Crystal", + "Storm", + "Iron", + "Steel", + "Golden", + "Silver", + "Emerald", + "Ruby", + "Sapphire", + "Obsidian", + "Moonlit", + "Starlit", + "Twilight", + "Dawn", + "Dusk", + "Eternal", + "Ancient", + "Arcane", + "Mystic", + "Enchanted", + "Cursed", + "Blessed", + "Sacred", + "Dark", + "Light", + "Frost", + "Flame", + "Thorn", + "Raven", + "Wolf", + "Bear", + "Eagle", + "Lion", + "Serpent", + "Phoenix", + "Griffin", + "Unicorn", + "Pegasus", + "Troll", + "Giant", + "Fey", + "Sylvan", + "Abyssal", + "Celestial", + "Infernal", + "Primordial", + "Runed", + "Warded" + ], + "suffixes": [ + "Kingdom", + "Realm", + "Dominion", + "Empire", + "Dynasty", + "Order", + "Covenant", + "Pact", + "Alliance", + "Guild", + "Circle", + "Council", + "Conclave", + "Sanctum", + "Citadel", + "Throne", + "Crown", + "Spire", + "Hollow", + "Reach", + "Bastion", + "Haven", + "Enclave", + "Brotherhood", + "Sisterhood", + "Horde", + "Legion", + "Swarm", + "Host", + "House", + "Clan", + "Tribe", + "Nation", + "Domain" + ] + }, + "war": { + "prefixes": [ + "Iron", + "Steel", + "Thunder", + "Lightning", + "Blitz", + "Vanguard", + "Phalanx", + "Spartan", + "Gladiator", + "Centurion", + "Hussar", + "Cossack", + "Samurai", + "Mongol", + "Hoplite", + "Legionary", + "Musketeer", + "Dragoon", + "Lancer", + "Commando", + "Ranger", + "Sentinel", + "Warden", + "Reaver", + "Berserker", + "Juggernaut", + "Valkyrie", + "Warlord", + "Overlord", + "General", + "Marshal", + "Captain", + "Sergeant", + "Colonel", + "Admiral", + "Commodore", + "Tactician", + "Strategist", + "Warmonger", + "Conqueror", + "Liberator", + "Insurgent", + "Partisan", + "Guerrilla", + "Militia", + "Mercenary", + "Veteran", + "Elite" + ], + "suffixes": [ + "Legion", + "Army", + "Corps", + "Division", + "Brigade", + "Battalion", + "Regiment", + "Company", + "Platoon", + "Squad", + "Phalanx", + "Bastion", + "Front", + "Theater", + "Warband", + "Host", + "Horde", + "Force", + "Command", + "Operations", + "Strike", + "Assault", + "Defense", + "Garrison", + "Outpost", + "Fortress", + "Citadel", + "Arsenal", + "Armory", + "War Council", + "High Command", + "Tribunal" + ] + }, + "western": { + "prefixes": [ + "Cowboy", + "Outlaw", + "Sheriff", + "Marshal", + "Ranger", + "Desperado", + "Gunslinger", + "Bounty Hunter", + "Frontiersman", + "Pioneer", + "Prospector", + "Rancher", + "Bandit", + "Renegade", + "Drifter", + "Wrangler", + "Vaquero", + "Buckaroo", + "Lawman", + "Deputy", + "Scout", + "Trapper", + "Miner", + "Preacher", + "Doc", + "Dusty", + "Lonesome", + "Wild", + "Iron", + "Silver", + "Gold", + "Copper", + "Dakota", + "Texas", + "Arizona", + "Montana", + "Nevada", + "Colorado", + "Wyoming", + "Oklahoma", + "Kansas", + "Nebraska", + "New Mexico", + "Deadwood", + "Tombstone" + ], + "suffixes": [ + "Territory", + "Frontier", + "Range", + "County", + "District", + "Settlement", + "Outpost", + "Trading Post", + "Ranch", + "Claim", + "Camp", + "Crossing", + "Pass", + "Gulch", + "Canyon", + "Ridge", + "Valley", + "Springs", + "Flats", + "Hollow", + "Posse", + "Gang", + "Cartel", + "Syndicate", + "Alliance", + "Confederation", + "Sheriff's Office", + "Marshal's Office", + "Wagon Train", + "Cattle Drive" + ] + }, + "under_ocean": { + "prefixes": [ + "Atlantean", + "Abyssal", + "Coral", + "Deep", + "Leviathan", + "Kraken", + "Nautilus", + "Trident", + "Pearl", + "Tidal", + "Submarine", + "Bathyal", + "Hadal", + "Pelagic", + "Neritic", + "Benthic", + "Abyssopelagic", + "Mesopelagic", + "Epipelagic", + "Siren", + "Merfolk", + "Triton", + "Poseidon", + "Neptune", + "Nereid", + "Scylla", + "Charybdis", + "Lusca", + "Jormungandr", + "Tiamat", + "Naga", + "Selkie", + "Kelpie", + "Bunyip", + "Djinn", + "Undine", + "Nixie", + "Rusalka" + ], + "suffixes": [ + "Kingdom", + "Depth", + "Abyss", + "Trench", + "Reef", + "Lagoon", + "Atoll", + "Archipelago", + "Domain", + "Realm", + "Empire", + "Dynasty", + "Confederation", + "Federation", + "Collective", + "Colony", + "Hive", + "Swarm", + "School", + "Council", + "Conclave", + "Order", + "Brotherhood", + "Sisterhood", + "Horde", + "Legion", + "Host", + "Current", + "Tide", + "Depths", + "Waters", + "Seas" + ] + }, + "tournament": { + "prefixes": [ + "Champion", + "Knight", + "Gladiator", + "Warrior", + "Paladin", + "Hero", + "Defender", + "Guardian", + "Protector", + "Avenger", + "Slayer", + "Conqueror", + "Victor", + "Legend", + "Titan", + "Colossus", + "Goliath", + "Hercules", + "Achilles", + "Odysseus", + "Leonidas", + "Alexander", + "Caesar", + "Attila", + "Genghis", + "Napoleon", + "Valor", + "Honor", + "Glory", + "Triumph", + "Dominance", + "Supremacy", + "Invincible", + "Unstoppable", + "Relentless", + "Fearless", + "Ruthless", + "Merciless", + "Undying", + "Immortal", + "Eternal", + "Ascendant", + "Apex" + ], + "suffixes": [ + "Arena", + "Colosseum", + "Ring", + "Tournament", + "Championship", + "League", + "Series", + "Circuit", + "Grand Prix", + "Classic", + "Invitational", + "Open", + "Cup", + "Trophy", + "Prize", + "Crown", + "Laurels", + "Medal", + "Award", + "Honor Guard", + "Elite", + "Champions", + "Masters", + "Grandmasters", + "Titans", + "Legends", + "Hall of Fame", + "Order of Merit", + "Circle" + ] + }, + "funny": { + "prefixes": [ + "Silly", + "Wacky", + "Zany", + "Goofy", + "Bonkers", + "Loony", + "Nutty", + "Kooky", + "Quirky", + "Batty", + "Daffy", + "Dizzy", + "Loopy", + "Wobbly", + "Funky", + "Groovy", + "Spunky", + "Peppy", + "Bouncy", + "Jolly", + "Merry", + "Cheerful", + "Giggly", + "Snicker", + "Chuckle", + "Tickle", + "Pickle", + "Noodle", + "Waffle", + "Pancake", + "Muffin", + "Cupcake", + "Donut", + "Taco", + "Burrito", + "Nacho", + "Pretzel", + "Bagel", + "Cookie", + "Biscuit", + "Crumpet", + "Scone", + "Pudding", + "Custard", + "Trifle", + "Jellybean", + "Gummy", + "Lollipop" + ], + "suffixes": [ + "Squad", + "Team", + "Club", + "Gang", + "Crew", + "Bunch", + "Pack", + "Mob", + "Horde", + "Swarm", + "Flock", + "Herd", + "School", + "Colony", + "Band", + "Troupe", + "Ensemble", + "Posse", + "Clique", + "Circle", + "Society", + "Association", + "Fellowship", + "Brotherhood", + "Sisterhood", + "Alliance", + "Coalition", + "League", + "Union", + "Federation", + "Confederation", + "Collective" + ] + }, + "scary": { + "prefixes": [ + "Dark", + "Shadow", + "Nightmare", + "Ghoul", + "Phantom", + "Specter", + "Wraith", + "Revenant", + "Banshee", + "Dread", + "Terror", + "Horror", + "Malice", + "Venom", + "Plague", + "Pestilence", + "Famine", + "Death", + "Doom", + "Ruin", + "Blight", + "Curse", + "Hex", + "Jinx", + "Spell", + "Charm", + "Hexen", + "Witch", + "Warlock", + "Necromancer", + "Lich", + "Vampire", + "Werewolf", + "Zombie", + "Skeleton", + "Corpse", + "Carrion", + "Rot", + "Decay", + "Void", + "Abyss", + "Oblivion", + "Entropy", + "Chaos", + "Madness", + "Insanity", + "Dementia", + "Psychosis" + ], + "suffixes": [ + "Horde", + "Legion", + "Swarm", + "Plague", + "Scourge", + "Blight", + "Curse", + "Nightmare", + "Terror", + "Dread", + "Fear", + "Panic", + "Ruin", + "Doom", + "Death", + "Reaping", + "Harvest", + "Culling", + "Purge", + "Extermination", + "Brood", + "Nest", + "Lair", + "Den", + "Crypt", + "Tomb", + "Catacombs", + "Coven", + "Circle", + "Order", + "Cult", + "Sect", + "Brotherhood", + "Pact" + ] + }, + "weird": { + "prefixes": [ + "Bizarre", + "Strange", + "Odd", + "Peculiar", + "Weird", + "Eerie", + "Uncanny", + "Surreal", + "Absurd", + "Grotesque", + "Macabre", + "Eldritch", + "Ethereal", + "Spectral", + "Gossamer", + "Translucent", + "Iridescent", + "Opalescent", + "Prismatic", + "Kaleidoscopic", + "Phantasmagoric", + "Ineffable", + "Inscrutable", + "Paradoxical", + "Impossible", + "Inconceivable", + "Unfathomable", + "Inexplicable", + "Anomalous", + "Aberrant", + "Deviant", + "Atypical", + "Unorthodox", + "Heterodox", + "Nonconformist", + "Maverick", + "Rogue", + "Rebel", + "Dissident", + "Heretic" + ], + "suffixes": [ + "Collective", + "Hive", + "Swarm", + "Network", + "Matrix", + "Web", + "Lattice", + "Grid", + "Array", + "Spectrum", + "Prism", + "Kaleidoscope", + "Paradox", + "Anomaly", + "Aberration", + "Enigma", + "Mystery", + "Riddle", + "Puzzle", + "Conundrum", + "Dilemma", + "Quandary", + "Oxymoron", + "Contradiction", + "Dichotomy", + "Juxtaposition", + "Irony", + "Satire", + "Absurdity", + "Nonsense" + ] + }, + "vs": { + "prefixes": [ + "Red", + "Blue", + "Green", + "Yellow", + "Orange", + "Purple", + "Pink", + "Black", + "White", + "Grey", + "Gold", + "Silver", + "Bronze", + "Copper", + "Iron", + "Steel", + "Alpha", + "Beta", + "Gamma", + "Delta", + "Epsilon", + "Zeta", + "Eta", + "Theta", + "North", + "South", + "East", + "West", + "Dawn", + "Dusk", + "Day", + "Night", + "Fire", + "Water", + "Earth", + "Air", + "Lightning", + "Ice", + "Nature", + "Shadow", + "Light", + "Dark", + "Order", + "Chaos", + "Life", + "Death", + "War", + "Peace" + ], + "suffixes": [ + "Team", + "Squad", + "Crew", + "Division", + "Brigade", + "Regiment", + "Battalion", + "Company", + "Platoon", + "Unit", + "Force", + "Command", + "Operations", + "Strike", + "Assault", + "Defense", + "Vanguard", + "Rearguard", + "Flank", + "Center", + "Wing", + "Corps", + "Legion", + "Army", + "Horde", + "Host", + "Alliance", + "Coalition", + "League", + "Union", + "Federation", + "Confederation" + ] + } +} diff --git a/src/client/view/GameView.ts b/src/client/view/GameView.ts index 71044ea82..23dd41f03 100644 --- a/src/client/view/GameView.ts +++ b/src/client/view/GameView.ts @@ -3,6 +3,7 @@ import { Cell, GameUpdates, PlayerID, + PlayerType, TerrainType, TerraNullius, Tick, @@ -376,8 +377,12 @@ export class GameView implements GameMap { pu, gu.playerNameViewData?.[pu.id], // First check human by clientID, then check nation by name. + // Only match by name for actual Nations — not Bots (tribes) whose + // random names may coincidentally match a nation name. this._cosmetics.get(pu.clientID ?? "") ?? - this._cosmetics.get(pu.name!) ?? + (pu.playerType === PlayerType.Nation + ? this._cosmetics.get(pu.name!) + : undefined) ?? {}, ); this._players.set(pu.id, player); diff --git a/src/core/Util.ts b/src/core/Util.ts index 1e69f452e..3153bfbeb 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -13,10 +13,7 @@ import { Winner, } from "./Schemas"; -import { - TRIBE_NAME_PREFIXES, - TRIBE_NAME_SUFFIXES, -} from "./execution/utils/TribeNames"; +import { resolveTribeNameData } from "./execution/utils/TribeNames"; export function manhattanDistWrapped( c1: Cell, @@ -366,13 +363,12 @@ export function createRandomName( ): string | null { let randomName: string | null = null; if (playerType === PlayerType.Human) { + const { prefixes, suffixes } = resolveTribeNameData(); const hash = simpleHash(name); - const prefixIndex = hash % TRIBE_NAME_PREFIXES.length; - const suffixIndex = - Math.floor(hash / TRIBE_NAME_PREFIXES.length) % - TRIBE_NAME_SUFFIXES.length; + const prefixIndex = hash % prefixes.length; + const suffixIndex = Math.floor(hash / prefixes.length) % suffixes.length; - randomName = `👤 ${TRIBE_NAME_PREFIXES[prefixIndex]} ${TRIBE_NAME_SUFFIXES[suffixIndex]}`; + randomName = `👤 ${prefixes[prefixIndex]} ${suffixes[suffixIndex]}`; } return randomName; } diff --git a/src/core/execution/TribeSpawner.ts b/src/core/execution/TribeSpawner.ts index 18529bccd..ec1f775b7 100644 --- a/src/core/execution/TribeSpawner.ts +++ b/src/core/execution/TribeSpawner.ts @@ -3,10 +3,12 @@ import { PseudoRandom } from "../PseudoRandom"; import { GameID } from "../Schemas"; import { simpleHash } from "../Util"; import { SpawnExecution } from "./SpawnExecution"; -import { TRIBE_NAME_PREFIXES, TRIBE_NAME_SUFFIXES } from "./utils/TribeNames"; +import { type TribeNameData, resolveTribeNameData } from "./utils/TribeNames"; export class TribeSpawner { private random: PseudoRandom; + private tribeNameData: TribeNameData; + private usedCustomTribes: Set = new Set(); constructor( private gs: Game, @@ -15,6 +17,7 @@ export class TribeSpawner { // Use a different seed than createGameRunner (which uses simpleHash(gameID)) // to avoid tribe IDs colliding with nation/human IDs from the same PRNG sequence. this.random = new PseudoRandom(simpleHash(gameID) + 2); + this.tribeNameData = resolveTribeNameData(gs.config().gameConfig().gameMap); } spawnTribes(numTribes: number): SpawnExecution[] { @@ -33,8 +36,24 @@ export class TribeSpawner { } private randomTribeName(): string { - const prefixIndex = this.random.nextInt(0, TRIBE_NAME_PREFIXES.length); - const suffixIndex = this.random.nextInt(0, TRIBE_NAME_SUFFIXES.length); - return `${TRIBE_NAME_PREFIXES[prefixIndex]} ${TRIBE_NAME_SUFFIXES[suffixIndex]}`; + const { customTribes, prefixes, suffixes } = this.tribeNameData; + + // Use custom tribes first (random selection, no duplicates until exhausted). + if (customTribes !== undefined) { + const available = customTribes.filter( + (name) => !this.usedCustomTribes.has(name), + ); + if (available.length > 0) { + const index = this.random.nextInt(0, available.length); + const chosen = available[index]; + this.usedCustomTribes.add(chosen); + return chosen; + } + } + + // Fall back to theme-based prefix + suffix names. + const prefixIndex = this.random.nextInt(0, prefixes.length); + const suffixIndex = this.random.nextInt(0, suffixes.length); + return `${prefixes[prefixIndex]} ${suffixes[suffixIndex]}`; } } diff --git a/src/core/execution/utils/TribeNames.ts b/src/core/execution/utils/TribeNames.ts index 8f0786dbf..198506db2 100644 --- a/src/core/execution/utils/TribeNames.ts +++ b/src/core/execution/utils/TribeNames.ts @@ -1,248 +1,88 @@ -export const TRIBE_NAME_PREFIXES = [ - "Akkadian", - "Babylonian", - "Sumerian", - "Hittite", - "Phoenician", - "Canaanite", - "Minoan", - "Mycenaean", - "Etruscan", - "Scythian", - "Thracian", - "Dacian", - "Illyrian", - "Median", - "Chaldean", - "Roman", - "Greek", - "Byzantine", - "Persian", - "Parthian", - "Seleucid", - "Ptolemaic", - "Palmyrene", - "Macedonian", - "Carthaginian", - "Ming", - "Tang", - "Song", - "Yuan", - "Mauryan", - "Kushan", - "Rajput", - "Mughal", - "Satavahana", - "Vijayanagara", - "Egyptian", - "Nubian", - "Kushite", - "Aksumite", - "Ethiopian", - "Songhai", - "Malian", - "Ghanaian", - "Benin", - "Ashanti", - "Zulu", - "Tuareg", - "Berber", - "Kanem-Bornu", - "Buganda", - "Mossi", - "Swahili", - "Somali", - "Wolof", - "Umayyad", - "Abbasid", - "Ayyubid", - "Fatimid", - "Mamluk", - "Seljuk", - "Safavid", - "Ottoman", - "Almoravid", - "Almohad", - "Rashidun", - "Ziyarid", - "Frankish", - "Visigothic", - "Ostrogothic", - "Viking", - "Norman", - "Saxon", - "Anglo-Saxon", - "Celtic", - "Gaulish", - "Carolingian", - "Merovingian", - "Capetian", - "Plantagenet", - "Tudor", - "Stuart", - "Habsburg", - "Romanov", - "Lancaster", - "York", - "Bourbon", - "Napoleonic", - "British", - "French", - "Spanish", - "Portuguese", - "Dutch", - "Russian", - "German", - "Italian", - "Swedish", - "Norwegian", - "Danish", - "Polish", - "Hungarian", - "Austrian", - "Swiss", - "Czech", - "Slovak", - "Serbian", - "Croatian", - "Bosnian", - "Montenegrin", - "Bulgarian", - "Romanian", - "Apache", - "Sioux", - "Cherokee", - "Navajo", - "Iroquois", - "Inuit", - "Arawak", - "Carib", - "Taino", - "Aztec", - "Mayan", - "Incan", - "Mapuche", - "Guarani", - "Tupi", - "Yanomami", - "Zuni", - "Hopi", - "Kiowa", - "Comanche", - "Shoshone", - "Japanese", - "Ryukyu", - "Ainu", - "Cham", - "Khmer", - "Thai", - "Vietnamese", - "Burmese", - "Balinese", - "Malay", - "Filipino", - "Mongolian", - "Korean", - "Tibetan", - "Manchu", - "Uyghur", - "Hmong", - "Karen", - "Pyu", - "Hawaiian", - "Fijian", - "Tongan", - "Samoan", - "Maori", - "Micronesian", - "Hebrew", - "Armenian", - "Circassian", - "Georgian", - "Kurdish", - "Turkic", - "Kazakh", - "Uzbek", - "Kyrgyz", - "Tajik", - "Uighur", - "Pashtun", - "Baloch", - "Afghan", - "Kenyan", - "Ugandan", - "Bhutanese", - "Latin", - "Moldovan", - "Militant", - "Spartan", -]; -export const TRIBE_NAME_SUFFIXES = [ - "Empire", - "Dynasty", - "Kingdom", - "Queendom", - "Sultanate", - "Confederation", - "Union", - "Republic", - "Caliphate", - "Dominion", - "Realm", - "State", - "Federation", - "Territory", - "Commonwealth", - "League", - "Duchy", - "Province", - "Protectorate", - "Colony", - "Mandate", - "Free State", - "Canton", - "Region", - "Nation", - "Assembly", - "Hierarchy", - "Archduchy", - "Grand Duchy", - "Metropolis", - "Cluster", - "Alliance", - "Tribunal", - "Council", - "Confederacy", - "Order", - "Regime", - "Syndicate", - "Guild", - "Corporation", - "Patriarchy", - "Matriarchy", - "Legion", - "Horde", - "Clan", - "Brotherhood", - "Sisterhood", - "Ascendancy", - "Supremacy", - "Tribe", - "Republics", - "Army", - "Dictatorship", - "Country", - "Oligarchy", - "Monkdom", - "Throng", - "Host", - "Area", - "District", - "Fief", - "Wilderness", - "Settlement", - "Parliament", - "Anarchy", - "Democracy", - "Autocracy", -]; +import tribeNameThemesData from "resources/tribeNameThemes.json"; +import { GameMapType, type MapInfo, maps } from "../../game/Maps.gen"; + +export interface TribeNameData { + prefixes: string[]; + suffixes: string[]; + customTribes?: string[]; +} + +interface TribeNameTheme { + prefixes: string[]; + suffixes: string[]; +} + +const tribeNameThemes: Record = tribeNameThemesData; + +/** Look up MapInfo by GameMapType. */ +function getMapInfo(mapType: GameMapType): MapInfo | undefined { + return maps.find((m) => m.type === mapType); +} + +/** + * Resolve tribe name data for a given map type. + * + * Priority: + * 1. Custom tribes from the map's info.json (used as-is, no prefix/suffix) + * 2. Theme-based names (prefix + suffix) from the referenced theme + * 3. Default theme if no theme is specified or the referenced theme is missing + */ +export function resolveTribeNameData(mapType?: GameMapType): TribeNameData { + if (mapType !== undefined) { + const mapInfo = getMapInfo(mapType); + if (mapInfo !== undefined) { + const themeNames = + mapInfo.themes !== undefined && mapInfo.themes.length > 0 + ? mapInfo.themes + : ["default"]; + + const mergedPrefixes: string[] = []; + const mergedSuffixes: string[] = []; + + for (const themeName of themeNames) { + const theme = tribeNameThemes[themeName]; + if (theme === undefined) { + console.warn( + `[TribeNames] Map "${mapType}" references unknown tribe name theme "${themeName}". Skipping.`, + ); + continue; + } + mergedPrefixes.push(...theme.prefixes); + mergedSuffixes.push(...theme.suffixes); + } + + // If all themes were unknown, fall back to default. + if (mergedPrefixes.length === 0 || mergedSuffixes.length === 0) { + const defaultTheme = tribeNameThemes["default"]; + if (defaultTheme === undefined) { + throw new Error( + "[TribeNames] Default theme is missing from tribeNameThemes.json", + ); + } + mergedPrefixes.push(...defaultTheme.prefixes); + mergedSuffixes.push(...defaultTheme.suffixes); + } + + return { + prefixes: mergedPrefixes, + suffixes: mergedSuffixes, + customTribes: + mapInfo.customTribes !== undefined && mapInfo.customTribes.length > 0 + ? mapInfo.customTribes + : undefined, + }; + } + } + + // No map type or map not found — use default theme. + const defaultTheme = tribeNameThemes["default"]; + if (defaultTheme === undefined) { + throw new Error( + "[TribeNames] Default theme is missing from tribeNameThemes.json", + ); + } + return { + prefixes: defaultTheme.prefixes, + suffixes: defaultTheme.suffixes, + }; +} diff --git a/src/core/game/Maps.gen.ts b/src/core/game/Maps.gen.ts index fe4c760c3..8ef4509be 100644 --- a/src/core/game/Maps.gen.ts +++ b/src/core/game/Maps.gen.ts @@ -167,6 +167,10 @@ export interface MapInfo { featuredRank?: number; /** Preferred team count in team/special games (see MapPlaylist). */ specialTeamCount?: number; + /** Tribe name theme(s) (keys in tribeNameThemes.json). */ + themes?: string[]; + /** Custom tribe names with priority over theme-generated names. */ + customTribes?: string[]; } export const maps: readonly MapInfo[] = [ @@ -469,6 +473,416 @@ export const maps: readonly MapInfo[] = [ translationKey: "map.germany", categories: ["europe", "new"], multiplayerFrequency: 5, + customTribes: [ + "Aachen, Städteregion", + "Ahrweiler", + "Aichach-Friedberg", + "Alb-Donau-Kreis", + "Altenburger Land", + "Altenkirchen (Westerwald)", + "Altmarkkreis Salzwedel", + "Altötting", + "Alzey-Worms", + "Amberg-Sulzbach", + "Ammerland", + "Anhalt-Bitterfeld", + "Ansbach", + "Aschaffenburg", + "Augsburg", + "Aurich", + "Bad Dürkheim", + "Bad Kissingen", + "Bad Kreuznach", + "Bad Tölz-Wolfratshausen", + "Bamberg", + "Barnim", + "Bautzen", + "Bayreuth", + "Berchtesgadener Land", + "Bergstraße", + "Bernkastel-Wittlich", + "Biberach", + "Birkenfeld", + "Böblingen", + "Bodenseekreis", + "Börde", + "Borken", + "Breisgau-Hochschwarzwald", + "Burgenlandkreis", + "Calw", + "Celle", + "Cham", + "Cloppenburg", + "Coburg", + "Cochem-Zell", + "Coesfeld", + "Cuxhaven", + "Dachau", + "Dahme-Spreewald", + "Darmstadt-Dieburg", + "Deggendorf", + "Diepholz", + "Dillingen an der Donau", + "Dingolfing-Landau", + "Dithmarschen", + "Donau-Ries", + "Donnersbergkreis", + "Düren", + "Ebersberg", + "Eichsfeld", + "Eichstätt", + "Eifelkreis Bitburg-Prüm", + "Elbe-Elster", + "Emmendingen", + "Emsland", + "Ennepe-Ruhr-Kreis", + "Enzkreis", + "Erding", + "Erlangen-Höchstadt", + "Erzgebirgskreis", + "Esslingen", + "Euskirchen", + "Forchheim", + "Freising", + "Freudenstadt", + "Freyung-Grafenau", + "Friesland", + "Fulda", + "Fürstenfeldbruck", + "Fürth", + "Garmisch-Partenkirchen", + "Germersheim", + "Gießen", + "Gifhorn", + "Göppingen", + "Görlitz", + "Goslar", + "Gotha", + "Göttingen", + "Grafschaft Bentheim", + "Greiz", + "Groß-Gerau", + "Günzburg", + "Gütersloh", + "Hameln-Pyrmont", + "Hannover, Region", + "Harburg", + "Harz", + "Haßberge", + "Havelland", + "Heidekreis", + "Heidenheim", + "Heilbronn", + "Heinsberg", + "Helmstedt", + "Herford", + "Hersfeld-Rotenburg", + "Herzogtum Lauenburg", + "Hildburghausen", + "Hildesheim", + "Hochsauerlandkreis", + "Hochtaunuskreis", + "Hof", + "Hohenlohekreis", + "Holzminden", + "Höxter", + "Ilm-Kreis", + "Jerichower Land", + "Kaiserslautern", + "Karlsruhe", + "Kassel", + "Kelheim", + "Kitzingen", + "Kleve", + "Konstanz", + "Kronach", + "Kulmbach", + "Kusel", + "Kyffhäuserkreis", + "Lahn-Dill-Kreis", + "Landsberg am Lech", + "Landshut", + "Leer", + "Leipzig", + "Lichtenfels", + "Limburg-Weilburg", + "Lindau (Bodensee)", + "Lippe", + "Lörrach", + "Lüchow-Dannenberg", + "Ludwigsburg", + "Ludwigslust-Parchim", + "Lüneburg", + "Main-Kinzig-Kreis", + "Main-Spessart", + "Main-Tauber-Kreis", + "Main-Taunus-Kreis", + "Mainz-Bingen", + "Mansfeld-Südharz", + "Marburg-Biedenkopf", + "Märkischer Kreis", + "Märkisch-Oderland", + "Mayen-Koblenz", + "Mecklenburgische Seenplatte", + "Meißen", + "Merzig-Wadern", + "Mettmann", + "Miesbach", + "Miltenberg", + "Minden-Lübbecke", + "Mittelsachsen", + "Mühldorf am Inn", + "München", + "Neckar-Odenwald-Kreis", + "Neu-Ulm", + "Neuburg-Schrobenhausen", + "Neumarkt in der Oberpfalz", + "Neunkirchen", + "Neustadt an der Aisch-Bad Windsheim", + "Neustadt an der Waldnaab", + "Neuwied", + "Nienburg/Weser", + "Nordfriesland", + "Nordhausen", + "Nordsachsen", + "Nordwestmecklenburg", + "Northeim", + "Nürnberger Land", + "Oberallgäu", + "Oberbergischer Kreis", + "Oberhavel", + "Oberspreewald-Lausitz", + "Odenwaldkreis", + "Oder-Spree", + "Offenbach", + "Oldenburg", + "Olpe", + "Ortenaukreis", + "Osnabrück", + "Ostalbkreis", + "Ostallgäu", + "Osterholz", + "Ostholstein", + "Ostprignitz-Ruppin", + "Paderborn", + "Passau", + "Peine", + "Pfaffenhofen an der Ilm", + "Pinneberg", + "Plön", + "Potsdam-Mittelmark", + "Prignitz", + "Rastatt", + "Ravensburg", + "Recklinghausen", + "Regen", + "Regensburg", + "Rems-Murr-Kreis", + "Rendsburg-Eckernförde", + "Reutlingen", + "Rhein-Erft-Kreis", + "Rheingau-Taunus-Kreis", + "Rhein-Hunsrück-Kreis", + "Rheinisch-Bergischer Kreis", + "Rhein-Kreis Neuss", + "Rhein-Lahn-Kreis", + "Rhein-Neckar-Kreis", + "Rhein-Pfalz-Kreis", + "Rhein-Sieg-Kreis", + "Rhön-Grabfeld", + "Rosenheim", + "Rostock", + "Rotenburg (Wümme)", + "Roth", + "Rottal-Inn", + "Rottweil", + "Saale-Holzland-Kreis", + "Saalekreis", + "Saale-Orla-Kreis", + "Saalfeld-Rudolstadt", + "Saarbrücken, Regionalverband", + "Saarlouis", + "Saarpfalz-Kreis", + "Sächsische Schweiz-Osterzgebirge", + "Salzlandkreis", + "Schaumburg", + "Schleswig-Flensburg", + "Schmalkalden-Meiningen", + "Schwäbisch Hall", + "Schwalm-Eder-Kreis", + "Schwandorf", + "Schwarzwald-Baar-Kreis", + "Schweinfurt", + "Segeberg", + "Siegen-Wittgenstein", + "Sigmaringen", + "Soest", + "Sömmerda", + "Sonneberg", + "Spree-Neiße", + "St. Wendel", + "Stade", + "Starnberg", + "Steinburg", + "Steinfurt", + "Stendal", + "Stormarn", + "Straubing-Bogen", + "Südliche Weinstraße", + "Südwestpfalz", + "Teltow-Fläming", + "Tirschenreuth", + "Traunstein", + "Trier-Saarburg", + "Tübingen", + "Tuttlingen", + "Uckermark", + "Uelzen", + "Unna", + "Unstrut-Hainich-Kreis", + "Unterallgäu", + "Vechta", + "Verden", + "Viersen", + "Vogelsbergkreis", + "Vogtlandkreis", + "Vorpommern-Greifswald", + "Vorpommern-Rügen", + "Vulkaneifel", + "Waldeck-Frankenberg", + "Waldshut", + "Warendorf", + "Wartburgkreis", + "Weilheim-Schongau", + "Weimarer Land", + "Weißenburg-Gunzenhausen", + "Werra-Meißner-Kreis", + "Wesel", + "Wesermarsch", + "Westerwaldkreis", + "Wetteraukreis", + "Wittenberg", + "Wittmund", + "Wolfenbüttel", + "Wunsiedel im Fichtelgebirge", + "Würzburg", + "Zollernalbkreis", + "Zwickau", + "Aachen", + "Amberg", + "Baden-Baden", + "Bielefeld", + "Bochum", + "Bonn", + "Bottrop", + "Brandenburg an der Havel", + "Braunschweig", + "Bremerhaven", + "Chemnitz", + "Cottbus", + "Darmstadt", + "Delmenhorst", + "Dessau-Roßlau", + "Dortmund", + "Dresden", + "Duisburg", + "Düsseldorf", + "Emden", + "Erfurt", + "Erlangen", + "Essen", + "Flensburg", + "Frankenthal (Pfalz)", + "Frankfurt (Oder)", + "Frankfurt am Main", + "Freiburg im Breisgau", + "Gelsenkirchen", + "Gera", + "Hagen", + "Halle (Saale)", + "Hamm", + "Hanau", + "Hannover", + "Heidelberg", + "Herne", + "Ingolstadt", + "Jena", + "Kaufbeuren", + "Kempten (Allgäu)", + "Kiel", + "Koblenz", + "Köln", + "Krefeld", + "Landau in der Pfalz", + "Leverkusen", + "Lübeck", + "Ludwigshafen am Rhein", + "Magdeburg", + "Mainz", + "Mannheim", + "Memmingen", + "Mönchengladbach", + "Mülheim an der Ruhr", + "Münster", + "Neumünster", + "Neustadt an der Weinstraße", + "Nürnberg", + "Oberhausen", + "Offenbach am Main", + "Oldenburg (Oldb)", + "Pforzheim", + "Pirmasens", + "Potsdam", + "Remscheid", + "Salzgitter", + "Schwabach", + "Schwerin", + "Solingen", + "Speyer", + "Straubing", + "Stuttgart", + "Suhl", + "Trier", + "Ulm", + "Weiden in der Oberpfalz", + "Weimar", + "Wiesbaden", + "Wilhelmshaven", + "Wolfsburg", + "Worms", + "Wuppertal", + "Zweibrücken", + "Preußen", + "Schwaben", + "Franken", + "Rheinland", + "Westfalen", + "Pommern", + "Schleswig", + "Holstein", + "Anhalt", + "Lausitz", + "Elsass", + "Deutscher Orden", + "Nassau", + "Kurpfalz", + "Berg", + "Jülich", + "Lothringen", + "Harzgau", + "Wetterau", + "Ruhrgebiet", + "Allgäu", + "Schwarzwald", + "Spreewald", + "Altmühltal", + "Spessart", + "Odenwald", + "Eifel", + "Sauerland", + "Westerwald", + "Pfalz", + ], }, { id: "GiantWorldMap", diff --git a/tests/MapConsistency.test.ts b/tests/MapConsistency.test.ts index 0806e8530..b0e22cd6d 100644 --- a/tests/MapConsistency.test.ts +++ b/tests/MapConsistency.test.ts @@ -144,6 +144,8 @@ describe("Map consistency", () => { orOmitted(info.special_team_count), map.specialTeamCount, ], + ["themes", orOmitted(info.themes), map.themes], + ["custom_tribes", orOmitted(info.custom_tribes), map.customTribes], ]; for (const [field, infoValue, mapValue] of fields) { if (JSON.stringify(infoValue) !== JSON.stringify(mapValue)) { diff --git a/tests/TribeNameThemes.test.ts b/tests/TribeNameThemes.test.ts new file mode 100644 index 000000000..3c7aecd08 --- /dev/null +++ b/tests/TribeNameThemes.test.ts @@ -0,0 +1,134 @@ +import { resolveTribeNameData } from "../src/core/execution/utils/TribeNames"; +import { GameMapType, PlayerType } from "../src/core/game/Game"; +import { PseudoRandom } from "../src/core/PseudoRandom"; +import { createRandomName } from "../src/core/Util"; + +describe("resolveTribeNameData", () => { + test("returns default theme when called with no arguments", () => { + const data = resolveTribeNameData(); + expect(data.prefixes.length).toBeGreaterThan(0); + expect(data.suffixes.length).toBeGreaterThan(0); + expect(data.customTribes).toBeUndefined(); + // Spot-check that the default theme contains known prefixes. + expect(data.prefixes).toContain("Roman"); + expect(data.prefixes).toContain("Viking"); + expect(data.suffixes).toContain("Empire"); + expect(data.suffixes).toContain("Kingdom"); + }); + + test("returns default theme when called with undefined", () => { + const data = resolveTribeNameData(undefined); + expect(data.prefixes.length).toBeGreaterThan(0); + expect(data.suffixes.length).toBeGreaterThan(0); + }); + + test("returns default theme for a map with no theme or customTribes", () => { + // Most maps in Maps.gen.ts have no theme/customTribes defined. + const data = resolveTribeNameData(GameMapType.World); + expect(data.prefixes.length).toBeGreaterThan(0); + expect(data.suffixes.length).toBeGreaterThan(0); + expect(data.customTribes).toBeUndefined(); + }); + + test("returns data for every valid GameMapType without throwing", () => { + for (const mapType of Object.values(GameMapType)) { + const data = resolveTribeNameData(mapType); + expect(data.prefixes.length).toBeGreaterThan(0); + expect(data.suffixes.length).toBeGreaterThan(0); + } + }); + + test("all default theme prefixes and suffixes are non-empty strings", () => { + const data = resolveTribeNameData(); + for (const prefix of data.prefixes) { + expect(prefix.length).toBeGreaterThan(0); + } + for (const suffix of data.suffixes) { + expect(suffix.length).toBeGreaterThan(0); + } + }); + + test("prefix + suffix combinations produce valid tribe names", () => { + const data = resolveTribeNameData(); + const random = new PseudoRandom(42); + for (let i = 0; i < 50; i++) { + const prefixIndex = random.nextInt(0, data.prefixes.length); + const suffixIndex = random.nextInt(0, data.suffixes.length); + const name = `${data.prefixes[prefixIndex]} ${data.suffixes[suffixIndex]}`; + expect(name.length).toBeGreaterThan(0); + expect(name).toContain(" "); + } + }); +}); + +describe("TribeNameData consistency", () => { + test("default theme has no duplicate prefixes", () => { + const data = resolveTribeNameData(); + const unique = new Set(data.prefixes); + expect(unique.size).toBe(data.prefixes.length); + }); + + test("default theme has no duplicate suffixes", () => { + const data = resolveTribeNameData(); + const unique = new Set(data.suffixes); + expect(unique.size).toBe(data.suffixes.length); + }); +}); + +describe("tribeNameThemes.json", () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const themes = require("../resources/tribeNameThemes.json"); + + test("default theme exists", () => { + expect(themes.default).toBeDefined(); + expect(themes.default.prefixes).toBeInstanceOf(Array); + expect(themes.default.suffixes).toBeInstanceOf(Array); + }); + + test("all themes have non-empty prefixes and suffixes", () => { + for (const [, theme] of Object.entries(themes)) { + const t = theme as { prefixes: string[]; suffixes: string[] }; + expect(t.prefixes.length).toBeGreaterThan(0); + expect(t.suffixes.length).toBeGreaterThan(0); + } + }); + + test("all theme entries are non-empty strings", () => { + for (const [, theme] of Object.entries(themes)) { + const t = theme as { prefixes: string[]; suffixes: string[] }; + for (const prefix of t.prefixes) { + expect(prefix.length).toBeGreaterThan(0); + } + for (const suffix of t.suffixes) { + expect(suffix.length).toBeGreaterThan(0); + } + } + }); + + test("at least 3 themes are defined", () => { + expect(Object.keys(themes).length).toBeGreaterThanOrEqual(3); + }); +}); + +describe("createRandomName (deterministic)", () => { + test("returns null for non-human players", () => { + expect(createRandomName("test", PlayerType.Bot)).toBeNull(); + }); + + test("returns a deterministic name for human players", () => { + const name1 = createRandomName("Alice", PlayerType.Human); + const name2 = createRandomName("Alice", PlayerType.Human); + expect(name1).toBe(name2); + expect(name1).toBeTruthy(); + expect(name1).toContain("👤"); + }); + + test("different names produce different random names (usually)", () => { + const name1 = createRandomName("Alice", PlayerType.Human); + const name2 = createRandomName("Bob", PlayerType.Human); + // They could theoretically collide, but with 178*66 combinations it's unlikely. + // We just verify both are valid. + expect(name1).toBeTruthy(); + expect(name2).toBeTruthy(); + }); +});