--- /dev/null
+package starshipfights.data.admiralty
+
+import starshipfights.game.Faction
+import kotlin.random.Random
+
+enum class AdmiralNameFlavor {
+ MECHYRDIA, TYLA, CALIBOR, OLYMPIA, // Mechyrdia-aligned
+ DUTCH, // NdRC-aliged
+ NORTHERN_DIADOCHI, SOUTHERN_DIADOCHI, // Masra Draetsen-aligned
+ FULKREYKK, // Isarnareykk-aligned
+ AMERICAN, HISPANIC_AMERICAN; // Vestigium-aligned
+
+ val displayName: String
+ get() = when (this) {
+ MECHYRDIA -> "Mechyrdian"
+ TYLA -> "Tylan"
+ CALIBOR -> "Caliborese"
+ OLYMPIA -> "Olympian"
+ DUTCH -> "Dutch"
+ NORTHERN_DIADOCHI -> "Northern Diadochi"
+ SOUTHERN_DIADOCHI -> "Southern Diadochi"
+ FULKREYKK -> "Thedish"
+ AMERICAN -> "American"
+ HISPANIC_AMERICAN -> "Hispanic-American"
+ }
+
+ companion object {
+ fun forFaction(faction: Faction) = when (faction) {
+ Faction.MECHYRDIA -> setOf(MECHYRDIA, TYLA, CALIBOR, OLYMPIA, DUTCH)
+ Faction.NDRC -> setOf(DUTCH)
+ Faction.MASRA_DRAETSEN -> setOf(CALIBOR, NORTHERN_DIADOCHI, SOUTHERN_DIADOCHI)
+ Faction.FELINAE_FELICES -> setOf(OLYMPIA)
+ Faction.ISARNAREYKK -> setOf(FULKREYKK)
+ Faction.VESTIGIUM -> setOf(AMERICAN, HISPANIC_AMERICAN)
+ }
+ }
+}
+
+object AdmiralNames {
+ // PERSONAL NAME to PATRONYMIC
+ private val mechyrdianMaleNames: List<Pair<String, String>> = listOf(
+ "Marc" to "Marcówič",
+ "Anton" to "Antonówič",
+ "Bjarnarð" to "Bjarnarðówič",
+ "Carl" to "Carlówič",
+ "Þjutarix" to "Þjutarigówič",
+ "Friðurix" to "Friðurigówič",
+ "Iwan" to "Iwanówič",
+ "Wladimer" to "Wladimerówič",
+ "Giulius" to "Giuliówič",
+ "Nicólei" to "Nicóleiówič",
+ "Þjódor" to "Þjóderówič",
+ "Sigismund" to "Sigismundówič",
+ "Stefan" to "Stefanówič",
+ "Wilhelm" to "Wilhelmówič",
+ "Giórgj" to "Giórgiówič"
+ )
+
+ // PERSONAL NAME to MATRONYMIC
+ private val mechyrdianFemaleNames: List<Pair<String, String>> = listOf(
+ "Octavia" to "Octaviówca",
+ "Annica" to "Annicówca",
+ "Astrið" to "Astriðówca",
+ "Caþarin" to "Caþarinówca",
+ "Signi" to "Signówca",
+ "Erica" to "Ericówca",
+ "Fréja" to "Fréjówca",
+ "Hilda" to "Hildówca",
+ "Žanna" to "Žannówca",
+ "Xenia" to "Xeniówca",
+ "Carina" to "Carinówca",
+ "Giadwiga" to "Giadwigówca",
+ "Ženia" to "Ženiówca"
+ )
+
+ private val mechyrdianFamilyNames: List<Pair<String, String>> = listOf(
+ "Alexandrów",
+ "Antonów",
+ "Pogdanów",
+ "Hrusčjów",
+ "Caísarów",
+ "Carolów",
+ "Sócolów",
+ "Romanów",
+ "Nemeciów",
+ "Pjótrów",
+ "Brutów",
+ "Augustów",
+ "Calašniców",
+ "Anželów",
+ "Sigmarów",
+ "Dróganów",
+ "Coroljów",
+ "Wlasów"
+ ).map { it to "${it}a" }
+
+ private fun randomMechyrdianName(isFemale: Boolean) = if (isFemale)
+ mechyrdianFemaleNames.random().first + " " + mechyrdianFemaleNames.random().second + " " + mechyrdianFamilyNames.random().second
+ else
+ mechyrdianMaleNames.random().first + " " + mechyrdianMaleNames.random().second + " " + mechyrdianFamilyNames.random().first
+
+ private val tylanMaleNames = listOf(
+ "Althanar" to "Althanas",
+ "Aurans" to "Aurantes",
+ "Bochra" to "Bochranes",
+ "Chshaejar" to "Chshaejas",
+ "Hjofvachi" to "Hjovachines",
+ "Koldimar" to "Koldimas",
+ "Kor" to "Kores",
+ "Ljomas" to "Ljomates",
+ "Shajel" to "Shajel",
+ "Shokar" to "Shokas",
+ "Tolavajel" to "Tolavajel",
+ "Voskar" to "Voskas",
+ )
+
+ private val tylanFemaleNames = listOf(
+ "Althe" to "Althenes",
+ "Anaseil" to "Anaseil",
+ "Asetbur" to "Asetbus",
+ "Atautha" to "Atauthas",
+ "Aurantia" to "Aurantias",
+ "Ilasheva" to "Ilashevas",
+ "Kalora" to "Kaloras",
+ "Kotolva" to "Kotolvas",
+ "Psekna" to "Pseknas",
+ "Shenera" to "Sheneras",
+ "Reoka" to "Reokas",
+ "Velga" to "Velgas",
+ )
+
+ private val tylanFamilyNames = listOf(
+ "Kalevkar" to "Kalevka",
+ "Merku" to "Merkussa",
+ "Telet" to "Telet",
+ "Eutokar" to "Eutoka",
+ "Vsocha" to "Vsochessa",
+ "Vilar" to "Vilakauva",
+ "Nikasrar" to "Nika",
+ "Vlegamakar" to "Vlegamaka",
+ "Vtokassar" to "Vtoka",
+ "Theiar" to "Theia",
+ "Aretar" to "Areta",
+ "Derkas" to "Derkata",
+ "Vinsennas" to "Vinsenatta",
+ "Kleio" to "Kleona"
+ )
+
+ // Tylans use matronymics for both sons and daughters
+ private fun randomTylanName(isFemale: Boolean) = if (isFemale)
+ tylanFemaleNames.random().first + " " + tylanFemaleNames.random().second + "-Nahra " + tylanFamilyNames.random().second
+ else
+ tylanMaleNames.random().first + " " + tylanFemaleNames.random().second + "-Nensar " + tylanFamilyNames.random().first
+
+ private val caliboreseNames = listOf(
+ "Jathee",
+ "Muly",
+ "Simoh",
+ "Laka",
+ "Foryn",
+ "Duxio",
+ "Xirio",
+ "Surmy",
+ "Datarme",
+ "Cloren",
+ "Tared",
+ "Quiliot",
+ "Attiol",
+ "Quarree",
+ "Guil",
+ "Miro",
+ "Yryys",
+ "Zarx",
+ "Karm",
+ "Mreek",
+ "Dulyy",
+ "Quorqui",
+ "Dreminor",
+ "Samitu",
+ "Lurmak",
+ "Quashi",
+ "Barsyn",
+ "Rymyo",
+ "Soli",
+ "Ickart",
+ "Woom",
+ "Qurquy",
+ "Ymiro",
+ "Rosiliq",
+ "Xant",
+ "Xateen",
+ "Mssly",
+ "Vixie",
+ "Quelynn",
+ "Plly",
+ "Tessy",
+ "Veekah",
+ "Quett",
+ "Xezeez",
+ "Xyph",
+ "Jixi",
+ "Jeekie",
+ "Meelen",
+ "Rasah",
+ "Reteeshy",
+ "Xinchie",
+ "Zae",
+ "Ziggy",
+ "Wurikah",
+ "Loppie",
+ "Tymma",
+ "Reely",
+ "Yjutee",
+ "Len",
+ "Vixirat",
+ "Xumie",
+ "Xilly",
+ "Liwwy",
+ "Gancee",
+ "Pamah",
+ "Zeryll",
+ "Luteet",
+ "Qusseet",
+ "Alixika",
+ "Sepirah",
+ "Luttrah",
+ "Aramynn",
+ "Laxerynn",
+ "Murylyt",
+ "Quarapyt",
+ "Tormiray",
+ "Daromynn",
+ "Zuleerynn",
+ "Quarimat",
+ "Dormaquazi",
+ "Tullequazi",
+ "Aleeray",
+ "Eppiquit",
+ "Wittirynn",
+ "Semiokolipan",
+ "Sosopurr",
+ "Quamixit",
+ "Croffet",
+ "Xaalit",
+ "Xemiolyt"
+ )
+
+ private val caliboreseVowels = "aeiouy".toSet()
+ private fun randomCaliboreseName(isFemale: Boolean) = caliboreseNames.filter {
+ it.length < 8 && (isFemale == (it.last() in caliboreseVowels))
+ }.random() + " " + caliboreseNames.filter { it.length > 7 }.random()
+
+ private val latinMaleCommonPraenomina = listOf(
+ "Gaius",
+ "Lucius",
+ "Marcus",
+ )
+
+ private val latinMaleUncommonPraenomina = listOf(
+ "Publius",
+ "Quintus",
+ "Titus",
+ "Gnaeus"
+ )
+
+ private val latinMaleRarePraenomina = listOf(
+ "Aulus",
+ "Spurius",
+ "Tiberius",
+ "Servius",
+ "Hostus"
+ )
+
+ private val latinFemaleCommonPraenomina = listOf(
+ "Gaia",
+ "Lucia",
+ "Marcia",
+ )
+
+ private val latinFemaleUncommonPraenomina = listOf(
+ "Prima",
+ "Secunda",
+ "Tertia",
+ "Quarta",
+ "Quinta",
+ "Sexta",
+ "Septima",
+ "Octavia",
+ "Nona",
+ "Decima"
+ )
+
+ private val latinFemaleRarePraenomina = listOf(
+ "Caesula",
+ "Titia",
+ "Tiberia",
+ "Tanaquil"
+ )
+
+ private val latinNominaGentilica = listOf(
+ "Aelius" to "Aelia",
+ "Aternius" to "Aternia",
+ "Caecilius" to "Caecilia",
+ "Cassius" to "Cassia",
+ "Claudius" to "Claudia",
+ "Cornelius" to "Cornelia",
+ "Calpurnius" to "Calpurnia",
+ "Fabius" to "Fabia",
+ "Flavius" to "Flavia",
+ "Fulvius" to "Fulvia",
+ "Haterius" to "Hateria",
+ "Hostilius" to "Hostilia",
+ "Iulius" to "Iulia",
+ "Iunius" to "Iunia",
+ "Iuventius" to "Iuventia",
+ "Lavinius" to "Lavinia",
+ "Licinius" to "Licinia",
+ "Marius" to "Maria",
+ "Octavius" to "Octavia",
+ "Pompeius" to "Pompeia",
+ "Porcius" to "Porcia",
+ "Salvius" to "Salvia",
+ "Sempronius" to "Sempronia",
+ "Spurius" to "Spuria",
+ "Terentius" to "Terentia",
+ "Tullius" to "Tullia",
+ "Ulpius" to "Ulpia",
+ "Valerius" to "Valeria"
+ )
+
+ private val latinCognomina = listOf(
+ "Agricola" to "Agricola",
+ "Agrippa" to "Agrippina",
+ "Aquilinus" to "Aquilina",
+ "Balbus" to "Balba",
+ "Bibulus" to "Bibula",
+ "Bucco" to "Bucco",
+ "Caecus" to "Caeca",
+ "Calidus" to "Calida",
+ "Catilina" to "Catilina",
+ "Catulus" to "Catula",
+ "Crassus" to "Crassa",
+ "Crispus" to "Crispa",
+ "Drusus" to "Drusilla",
+ "Flaccus" to "Flacca",
+ "Gracchus" to "Graccha",
+ "Laevinus" to "Laevina",
+ "Lanius" to "Lania",
+ "Lepidus" to "Lepida",
+ "Lucullus" to "Luculla",
+ "Marcellus" to "Marcella",
+ "Metellus" to "Metella",
+ "Nasica" to "Nasica",
+ "Nerva" to "Nerva",
+ "Paullus" to "Paulla",
+ "Piso" to "Piso",
+ "Priscus" to "Prisca",
+ "Publicola" to "Publicola",
+ "Pulcher" to "Pulchra",
+ "Regulus" to "Regula",
+ "Rufus" to "Rufa",
+ "Scaevola" to "Scaevola",
+ "Severus" to "Severa",
+ "Structus" to "Structa",
+ "Taurus" to "Taura",
+ "Varro" to "Varro",
+ "Vitulus" to "Vitula"
+ )
+
+ private fun randomLatinPraenomen(isFemale: Boolean) = when {
+ Random.nextBoolean() -> if (isFemale) latinFemaleCommonPraenomina else latinMaleCommonPraenomina
+ Random.nextInt(3) > 0 -> if (isFemale) latinFemaleUncommonPraenomina else latinMaleUncommonPraenomina
+ else -> if (isFemale) latinFemaleRarePraenomina else latinMaleRarePraenomina
+ }.random()
+
+ private fun randomLatinName(isFemale: Boolean) = randomLatinPraenomen(isFemale) + " " + latinNominaGentilica.random().let { (m, f) -> if (isFemale) f else m } + " " + latinCognomina.random().let { (m, f) -> if (isFemale) f else m }
+
+ private val dutchMaleNames = listOf(
+ "Aalderik",
+ "Andreas",
+ "Boudewijn",
+ "Bruno",
+ "Christiaan",
+ "Cornelius",
+ "Darnath",
+ "Dirk",
+ "Eren",
+ "Erwin",
+ "Frederik",
+ "Gerlach",
+ "Helbrant",
+ "Helbrecht",
+ "Hendrik",
+ "Jakob",
+ "Jochem",
+ "Joris",
+ "Koenraad",
+ "Koorland",
+ "Leopold",
+ "Lodewijk",
+ "Maarten",
+ "Michel",
+ "Niels",
+ "Pieter",
+ "Renaat",
+ "Rogal",
+ "Ruben",
+ "Sebastiaan",
+ "Sigismund",
+ "Sjaak",
+ "Tobias",
+ "Valentijn",
+ "Wiebrand",
+ )
+
+ private val dutchFemaleNames = listOf(
+ "Adelwijn",
+ "Amberlij",
+ "Annika",
+ "Arete",
+ "Eva",
+ "Gerda",
+ "Helga",
+ "Ida",
+ "Irene",
+ "Jacqueline",
+ "Josefien",
+ "Juliana",
+ "Katharijne",
+ "Lore",
+ "Margriet",
+ "Maximilia",
+ "Meike",
+ "Nora",
+ "Rebeka",
+ "Sara",
+ "Vera",
+ "Wilhelmina",
+ )
+
+ private val dutchMerchantHouses = listOf(
+ "Venetho",
+ "Luibeck",
+ "Birka",
+ "Heiðabýr",
+ "Rostok",
+ "Guistrov",
+ "Schverin",
+ "Koeln",
+ "Bruigge",
+ "Reval",
+ "Elbing",
+ "Dorpat",
+ "Stralsund",
+ "Mijdeborg",
+ "Breslaw",
+ "Dortmund",
+ "Antwerp",
+ "Falsterbo",
+ "Zwolle",
+ "Buchtehud",
+ "Bremen",
+ "Zutphen",
+ "Kampen",
+ "Grunn",
+ "Deventer",
+ "Wismer",
+ "Luinenburg",
+
+ "Jager",
+ "Jastobaal",
+ "Varonius",
+ "Kupferberg",
+ "Dijn",
+ "Umboldt",
+ "Phalomor",
+ "Drijk",
+ "d'Wain",
+ "du Languille",
+ "Horstein",
+ "Jerulas",
+ "Kendar",
+ "Castellan",
+ "d'Aniasie",
+ "Gerrit",
+ "Hoed",
+ "lo Pan",
+ "Marchandrij",
+ "d'Aquairre",
+ "Terozzante",
+ "d'Argovon",
+ "de Monde",
+ "Paillender",
+ "Holstijn",
+ "d'Imperia",
+ "Borodin",
+ "Agranozza",
+ "d'Ortise",
+ "Ijzerhoorn",
+ "Dremel",
+ "Hinckel",
+ "Vuigens",
+ "Drazen",
+ "Marburg",
+ "Xardt",
+ "Lijze",
+ "Gerlach",
+ "Doorn",
+ "d'Arquebus",
+ "Alderic",
+ "Vogen"
+ )
+
+ private fun randomDutchName(isFemale: Boolean) = (if (isFemale) dutchFemaleNames else dutchMaleNames).random() + " van " + dutchMerchantHouses.random()
+
+ private val diadochiMaleNames = listOf(
+ "Oqatai",
+ "Amogus",
+ "Nerokhan",
+ "Choghor",
+ "Aghonei",
+ "Martaq",
+ "Qaran",
+ "Khargh",
+ "Qolkhu",
+ "Ghauran",
+ "Woriv",
+ "Vorcha",
+ "Chagatai",
+ "Neghvar",
+ "Qitinga",
+ "Jimpaq",
+ "Bivat",
+ "Durash",
+ "Elifas",
+ "Ogus",
+ "Yuli",
+ "Saret",
+ "Mher",
+ "Tyver",
+ "Ghraq",
+ "Niran",
+ "Galik"
+ )
+
+ private val diadochiFemaleNames = listOf(
+ "Lursha",
+ "Jamoqena",
+ "Lokoria",
+ "Iekuna",
+ "Shara",
+ "Etugen",
+ "Maral",
+ "Temuln",
+ "Akhensari",
+ "Khadagan",
+ "Gherelma",
+ "Shechen",
+ "Althani",
+ "Tzyrina",
+ "Daghasi",
+ "Kloya",
+ )
+
+ private val northernDiadochiEpithetParts = listOf(
+ "Skull",
+ "Blood",
+ "Death",
+ "Claw",
+ "Doom",
+ "Dread",
+ "Soul",
+ "Spirit",
+ "Hell",
+ "Dread",
+ "Bale",
+ "Fire",
+ "Fist",
+ "Bear",
+ "Pyre",
+ "Dark",
+ "Vile",
+ "Heart",
+ "Murder",
+ "Gore",
+ "Daemon",
+ "Talon",
+ )
+
+ private fun randomNorthernDiadochiName(isFemale: Boolean) = (if (isFemale) diadochiFemaleNames else diadochiMaleNames).random() + " " + northernDiadochiEpithetParts.random() + northernDiadochiEpithetParts.random().lowercase()
+
+ private val southernDiadochiClans = listOf(
+ "Arkai",
+ "Avado",
+ "Djahhim",
+ "Khankhen",
+ "Porok",
+ "Miras",
+ "Terok",
+ "Empok",
+ "Noragh",
+ "Nuunian",
+ "Soung",
+ "Akhero",
+ "Qozaq",
+ "Kherus",
+ "Axina",
+ "Ghaizas",
+ "Saxha",
+ "Meshu",
+ "Khopesh",
+ "Qitemar",
+ "Vang",
+ "Lugal",
+ "Galla",
+ "Hheka",
+ "Nesut",
+ "Koquon",
+ "Molekh"
+ )
+
+ private fun randomSouthernDiadochiClan() = when {
+ Random.nextInt(5) == 0 -> southernDiadochiClans.random() + "-" + southernDiadochiClans.random()
+ else -> southernDiadochiClans.random()
+ }
+
+ private fun randomSouthernDiadochiName(isFemale: Boolean) = (if (isFemale) diadochiFemaleNames else diadochiMaleNames).random() + (if (isFemale && Random.nextBoolean()) " ka-" else " am-") + diadochiMaleNames.random() + " " + randomSouthernDiadochiClan()
+
+ private val thedishMaleNames = listOf(
+ "Praethoris",
+ "Severus",
+ "Augast",
+ "Dagobar",
+ "Vrankenn",
+ "Kandar",
+ "Kleon",
+ "Glaius",
+ "Karul",
+ "Ylai",
+ "Toval",
+ "Ivon",
+ "Belis",
+ "Jorh",
+ "Svar",
+ "Alaric",
+ )
+
+ private val thedishFemaleNames = listOf(
+ "Serna",
+ "Veleska",
+ "Ielga",
+ "Glae",
+ "Rova",
+ "Ylia",
+ "Galera",
+ "Nerys",
+ "Veleer",
+ "Karuleyn",
+ "Amberli",
+ "Alysia",
+ "Lenera",
+ "Demeter",
+ )
+
+ private val thedishSurnames = listOf(
+ "Kassck",
+ "Orsh",
+ "Falk",
+ "Khorr",
+ "Vaskoman",
+ "Vholkazk",
+ "Brekoryn",
+ "Lorus",
+ "Karnas",
+ "Hathar",
+ "Takan",
+ "Pertona",
+ "Tefran",
+ "Arvi",
+ "Galvus",
+ "Voss",
+ "Mandanof",
+ "Ursali",
+ "Vytunn",
+ "Quesrinn",
+ )
+
+ private fun randomThedishName(isFemale: Boolean) = (if (isFemale) thedishFemaleNames else thedishMaleNames).random() + " " + thedishSurnames.random()
+
+ private val americanMaleNames = listOf(
+ "George",
+ "John",
+ "Thomas",
+ "James",
+ "Quincy",
+ "Andrew",
+ "Martin",
+ "William",
+ "Henry",
+ "James",
+ "Zachary",
+ "Millard",
+ "Franklin",
+ "Abraham",
+ "Ulysses",
+ "Rutherford",
+ "Chester",
+ "Grover",
+ "Benjamin",
+ "Theodore",
+ "Warren",
+ "Calvin",
+ "Herbert",
+ "Harry",
+ "Dwight",
+ "Lyndon",
+ "Richard",
+ "Dick",
+ "Gerald",
+ "Jimmy",
+ "Ronald",
+ "Donald"
+ )
+
+ private val americanFemaleNames = listOf(
+ "Martha",
+ "Abigail",
+ "Elizabeth",
+ "Louisa",
+ "Emily",
+ "Sarah",
+ "Anna",
+ "Jane",
+ "Julia",
+ "Margaret",
+ "Harriet",
+ "Mary",
+ "Lucy",
+ "Rose",
+ "Caroline",
+ "Ida",
+ "Helen",
+ "Grace",
+ "Jacqueline",
+ "Thelma",
+ "Eleanor",
+ "Nancy",
+ "Barbara",
+ "Laura",
+ "Melania"
+ )
+
+ private val americanFamilyNames = listOf(
+ "Knox",
+ "Pickering",
+ "McHenry",
+ "Dexter",
+ "Drawborn",
+ "Eustis",
+ "Armstrong",
+ "Monroe",
+ "Crawford",
+ "Calhoun",
+ "Barbour",
+ "Porter",
+ "Eaton",
+ "Cass",
+ "Poinsett",
+ "Bell",
+ "Forrestal",
+ "Johnson",
+ "Marshall",
+ "Lovett",
+ "Wilson",
+ "McElroy",
+ "McNamara",
+ "Clifford",
+ "Richardson",
+ "Burndt",
+ )
+
+ private fun randomAmericanName(isFemale: Boolean) = (if (isFemale) americanFemaleNames else americanMaleNames).random() + " " + americanFamilyNames.random()
+
+ private val hispanicMaleNames = listOf(
+ "Aaron",
+ "Antonio",
+ "Augusto",
+ "Eliseo",
+ "Manuel",
+ "Jose",
+ "Juan",
+ "Miguel",
+ "Rafael",
+ "Raul",
+ "Adriano",
+ "Emilio",
+ "Francisco",
+ "Ignacio",
+ "Marco",
+ "Pablo",
+ "Octavio",
+ "Victor",
+ "Vito",
+ "Valentin"
+ )
+
+ private val hispanicFemaleNames = listOf(
+ "Maria",
+ "Ana",
+ "Camila",
+ "Eva",
+ "Flora",
+ "Gloria",
+ "Julia",
+ "Marcelina",
+ "Rosalia",
+ "Victoria",
+ "Valentina",
+ "Cecilia",
+ "Francisca",
+ "Aurelia",
+ "Cristina",
+ "Magdalena",
+ "Margarita",
+ "Martina",
+ "Teresa"
+ )
+
+ private val hispanicFamilyNames = listOf(
+ "Acorda",
+ "Aguirre",
+ "Alzaga",
+ "Arriaga",
+ "Arrieta",
+ "Berroya",
+ "Barahona",
+ "Carranza",
+ "Carriaga",
+ "Elcano",
+ "Elizaga",
+ "Endaya",
+ "Franco",
+ "Garalde",
+ "Ibarra",
+ "Juarez",
+ "Lazarte",
+ "Legarda",
+ "Madariaga",
+ "Medrano",
+ "Narvaez",
+ "Olano",
+ "Ricarte",
+ "Salazar",
+ "Uriarte",
+ "Varona",
+ "Vergar",
+ )
+
+ private fun randomHispanicName(isFemale: Boolean) = (if (isFemale) hispanicFemaleNames else hispanicMaleNames).random() + " " + hispanicFamilyNames.random()
+
+ fun randomName(flavor: AdmiralNameFlavor, isFemale: Boolean) = when (flavor) {
+ AdmiralNameFlavor.MECHYRDIA -> randomMechyrdianName(isFemale)
+ AdmiralNameFlavor.TYLA -> randomTylanName(isFemale)
+ AdmiralNameFlavor.CALIBOR -> randomCaliboreseName(isFemale)
+ AdmiralNameFlavor.OLYMPIA -> randomLatinName(isFemale)
+ AdmiralNameFlavor.DUTCH -> randomDutchName(isFemale)
+ AdmiralNameFlavor.NORTHERN_DIADOCHI -> randomNorthernDiadochiName(isFemale)
+ AdmiralNameFlavor.SOUTHERN_DIADOCHI -> randomSouthernDiadochiName(isFemale)
+ AdmiralNameFlavor.FULKREYKK -> randomThedishName(isFemale)
+ AdmiralNameFlavor.AMERICAN -> randomAmericanName(isFemale)
+ AdmiralNameFlavor.HISPANIC_AMERICAN -> randomHispanicName(isFemale)
+ }
+}
--- /dev/null
+package starshipfights.data.admiralty
+
+import starshipfights.game.Faction
+import starshipfights.game.ShipWeightClass
+import kotlin.random.Random
+
+fun newShipName(faction: Faction, shipWeightClass: ShipWeightClass, existingNames: MutableSet<String>) = generateSequence {
+ nameShip(faction, shipWeightClass)
+}.take(20).dropWhile { it in existingNames }.firstOrNull()?.also { existingNames.add(it) }
+
+private val mechyrdianFrigateNames1 = listOf(
+ "Unconquerable",
+ "Indomitable",
+ "Invincible",
+ "Imperial",
+ "Regal",
+ "Royal",
+ "Imperious",
+ "Honorable",
+ "Defiant",
+ "Eternal",
+ "Infinite",
+ "Dominant",
+ "Divine",
+ "Righteous",
+ "Resplendent",
+ "Protective",
+ "Innocent",
+ "August",
+ "Loyal"
+)
+
+private val mechyrdianFrigateNames2 = listOf(
+ "Faith",
+ "Empire",
+ "Royalty",
+ "Regality",
+ "Honor",
+ "Defiance",
+ "Eternity",
+ "Dominator",
+ "Divinity",
+ "Right",
+ "Righteousness",
+ "Resplendency",
+ "Defender",
+ "Protector",
+ "Innocence",
+ "Victory",
+ "Duty",
+ "Loyalty"
+)
+
+private val mechyrdianCruiserNames1 = listOf(
+ "Defender of",
+ "Protector of",
+ "Shield of",
+ "Sword of",
+ "Champion of",
+ "Hero of",
+ "Salvation of",
+ "Savior of",
+ "Shining Light of",
+ "Righteous Flame of",
+ "Eternal Glory of",
+)
+
+private val mechyrdianCruiserNames2 = listOf(
+ "Mechyrd",
+ "Kaiserswelt",
+ "Tenno no Wakusei",
+ "Nova Roma",
+ "Mont Imperial",
+ "Tyla",
+ "Vensca",
+ "Kaltag",
+ "Languavarth Prime",
+ "Languavarth Secundum",
+ "Elcialot",
+ "Othon",
+ "Starport",
+ "Sacrilegum",
+ "New Constantinople",
+ "Fairhus",
+ "Praxagora",
+ "Karolina",
+ "Kozachnia",
+ "New New Amsterdam",
+ "Mundus Caesaris Divi",
+ "Saiwatta",
+ "Earth"
+)
+
+private val mechyrdianBattleshipNames = listOf(
+ "Kaiser Wilhelm I",
+ "Kaiser Wilhelm II",
+ "Empereur Napoléon I Bonaparte",
+ "Tsar Nikolaj II Romanov",
+ "Seliger Kaiser Karl I von Habsburg",
+ "Emperor Joshua A. Norton I",
+ "Emperor Meiji the Great",
+ "Emperor Jack G. Coleman",
+ "Emperor Trevor C. Neer",
+ "Emperor Connor F. Vance",
+ "Emperor Jean-Bédel Bokassa I",
+ "King Charles XII",
+ "King William I the Conqueror",
+ "King Alfred the Great",
+ "Gustavus Adolphus Magnus Rex",
+ "Queen Victoria",
+ "Kōnstantînos XI Dragásēs Palaiológos",
+ "Ioustinianós I ho Mégas",
+ "Kjarossa Liha Vilakauva",
+ "Kjarossa Tarkona Sovasra",
+ "Great King Kūruš",
+ "Queen Elizabeth II",
+ "Kjarossa Karelka Helasra",
+ "Imperātor Cæsar Dīvī Fīlius Augustus",
+ "Cæsar Nerva Trāiānus",
+ "King Kaleb of Axum"
+)
+
+private fun nameMechyrdianShip(weightClass: ShipWeightClass) = when (weightClass) {
+ ShipWeightClass.ESCORT -> "${mechyrdianFrigateNames1.random()} ${mechyrdianFrigateNames2.random()}"
+ ShipWeightClass.DESTROYER -> "${mechyrdianFrigateNames1.random()} ${mechyrdianFrigateNames2.random()}"
+ ShipWeightClass.CRUISER -> "${mechyrdianCruiserNames1.random()} ${mechyrdianCruiserNames2.random()}"
+ ShipWeightClass.BATTLECRUISER -> "${mechyrdianCruiserNames1.random()} ${mechyrdianCruiserNames2.random()}"
+ ShipWeightClass.BATTLESHIP -> mechyrdianBattleshipNames.random()
+ ShipWeightClass.BATTLE_BARGE -> mechyrdianBattleshipNames.random()
+ else -> error("Invalid Mechyrdian ship weight!")
+}
+
+private val masraDraetsenFrigateNames1 = listOf(
+ "Murderous",
+ "Hateful",
+ "Heinous",
+ "Pestilent",
+ "Corrupting",
+ "Homicidal",
+ "Deadly",
+ "Primordial",
+ "Painful",
+ "Agonizing",
+ "Spiteful",
+ "Odious",
+ "Miserating",
+ "Damned",
+ "Condemned",
+ "Hellish",
+ "Dark",
+ "Impious",
+ "Unfaithful",
+ "Abyssal",
+ "Furious",
+ "Vengeful",
+ "Spiritous"
+)
+
+private val masraDraetsenFrigateNames2 = listOf(
+ "Murder",
+ "Hate",
+ "Hatred",
+ "Pestilence",
+ "Corruption",
+ "Homicide",
+ "Massacre",
+ "Death",
+ "Agony",
+ "Pain",
+ "Suffering",
+ "Spite",
+ "Misery",
+ "Damnation",
+ "Hell",
+ "Darkness",
+ "Impiety",
+ "Faithlessness",
+ "Abyss",
+ "Fury",
+ "Vengeance",
+ "Spirit"
+)
+
+private val masraDraetsenCruiserNames1 = listOf(
+ "Despoiler of",
+ "Desecrator of",
+ "Desolator of",
+ "Destroyer of",
+ "Executioner of",
+ "Pillager of",
+ "Villain of",
+ "Great Devil of",
+ "Infidelity of",
+ "Incineration of",
+ "Immolation of",
+ "Crucifixion of",
+ "Unending Darkness of",
+)
+
+private val masraDraetsenCruiserNames2 = listOf(
+ // Diadochi space
+ "Eskhaton",
+ "Terminus",
+ "Tychiphage",
+ "Magaddu",
+ "Ghattusha",
+ "Three Suns",
+ "RB-5354",
+ "VT-3072",
+ "Siegsstern",
+ "Atzalstadt",
+ "Apex",
+ "Summit",
+ // Lyudareykk and Isarnareykk
+ "Vion Kann",
+ "Kasr Karul",
+ "Vladizapad",
+ // Chaebodes Star Empire
+ "Ultima Thule",
+ "Prenovez",
+ // Calibor and Vescar sectors
+ "Letum Angelorum",
+ "Pharsalus",
+ "Eutopia",
+ // Ferthlon and Olympia sectors
+ "Ferthlon Primus",
+ "Ferthlon Secundus",
+ "Nova Roma",
+ "Mont Imperial",
+)
+
+private const val masraDraetsenColossusName = "Boukephalas"
+
+private fun nameMasraDraetsenShip(weightClass: ShipWeightClass) = when (weightClass) {
+ ShipWeightClass.ESCORT -> "${masraDraetsenFrigateNames1.random()} ${masraDraetsenFrigateNames2.random()}"
+ ShipWeightClass.DESTROYER -> "${masraDraetsenFrigateNames1.random()} ${masraDraetsenFrigateNames2.random()}"
+ ShipWeightClass.CRUISER -> "${masraDraetsenCruiserNames1.random()} ${masraDraetsenCruiserNames2.random()}"
+ ShipWeightClass.GRAND_CRUISER -> "${masraDraetsenCruiserNames1.random()} ${masraDraetsenCruiserNames2.random()}"
+ ShipWeightClass.COLOSSUS -> masraDraetsenColossusName
+ else -> error("Invalid Masra Draetsen ship weight!")
+}
+
+private enum class LatinNounForm {
+ MAS_SG,
+ FEM_SG,
+ NEU_SG,
+ MAS_PL,
+ FEM_PL,
+ NEU_PL,
+}
+
+private data class LatinNoun(
+ val noun: String,
+ val form: LatinNounForm
+)
+
+private data class LatinAdjective(
+ val masculineSingular: String,
+ val feminineSingular: String,
+ val neuterSingular: String,
+ val masculinePlural: String,
+ val femininePlural: String,
+ val neuterPlural: String,
+) {
+ fun get(form: LatinNounForm) = when (form) {
+ LatinNounForm.MAS_SG -> masculineSingular
+ LatinNounForm.FEM_SG -> feminineSingular
+ LatinNounForm.NEU_SG -> neuterSingular
+ LatinNounForm.MAS_PL -> masculinePlural
+ LatinNounForm.FEM_PL -> femininePlural
+ LatinNounForm.NEU_PL -> neuterPlural
+ }
+}
+
+private infix fun LatinNoun.describedBy(adjective: LatinAdjective) = "$noun ${adjective.get(form)}"
+
+private fun felinaeFelicesEscortShipName() = "ES-" + (1000..9999).random().toString()
+
+private val felinaeFelicesLineShipNames1 = listOf(
+ LatinNoun("Aevum", LatinNounForm.NEU_SG),
+ LatinNoun("Aquila", LatinNounForm.FEM_SG),
+ LatinNoun("Argonauta", LatinNounForm.MAS_SG),
+ LatinNoun("Cattus", LatinNounForm.MAS_SG),
+ LatinNoun("Daemon", LatinNounForm.MAS_SG),
+ LatinNoun("Divitia", LatinNounForm.FEM_SG),
+ LatinNoun("Feles", LatinNounForm.FEM_SG),
+ LatinNoun("Imperium", LatinNounForm.NEU_SG),
+ LatinNoun("Ius", LatinNounForm.NEU_SG),
+ LatinNoun("Iustitia", LatinNounForm.FEM_SG),
+ LatinNoun("Leo", LatinNounForm.MAS_SG),
+ LatinNoun("Leopardus", LatinNounForm.MAS_SG),
+ LatinNoun("Lynx", LatinNounForm.FEM_SG),
+ LatinNoun("Panthera", LatinNounForm.FEM_SG),
+ LatinNoun("Salvator", LatinNounForm.MAS_SG),
+ LatinNoun("Scelus", LatinNounForm.NEU_SG),
+ LatinNoun("Tigris", LatinNounForm.MAS_SG),
+)
+
+private val felinaeFelicesLineShipNames2 = listOf(
+ LatinAdjective("Animosus", "Animosa", "Animosum", "Animosi", "Animosae", "Animosa"),
+ LatinAdjective("Ardens", "Ardens", "Ardens", "Ardentes", "Ardentes", "Ardentia"),
+ LatinAdjective("Audax", "Audax", "Audax", "Audaces", "Audaces", "Audacia"),
+ LatinAdjective("Astutus", "Astuta", "Astutum", "Astuti", "Astutae", "Astuta"),
+ LatinAdjective("Calidus", "Calida", "Calidum", "Calidi", "Calidae", "Calida"),
+ LatinAdjective("Ferox", "Ferox", "Ferox", "Feroces", "Feroces", "Ferocia"),
+ LatinAdjective("Fortis", "Fortis", "Forte", "Fortes", "Fortes", "Fortia"),
+ LatinAdjective("Fugax", "Fugax", "Fugax", "Fugaces", "Fugaces", "Fugacia"),
+ LatinAdjective("Indomitus", "Indomita", "Indomitum", "Indomiti", "Indomitae", "Indomita"),
+ LatinAdjective("Intrepidus", "Intrepida", "Intrepidum", "Intrepidi", "Intrepidae", "Intrepida"),
+ LatinAdjective("Pervicax", "Pervicax", "Pervicax", "Pervicaces", "Pervicaces", "Pervicacia"),
+ LatinAdjective("Sagax", "Sagax", "Sagax", "Sagaces", "Sagaces", "Sagacia"),
+ LatinAdjective("Superbus", "Superba", "Superbum", "Superbi", "Superbae", "Superba"),
+ LatinAdjective("Trux", "Trux", "Trux", "Truces", "Truces", "Trucia"),
+)
+
+private fun nameFelinaeFelicesShip(weightClass: ShipWeightClass) = when (weightClass) {
+ ShipWeightClass.FF_ESCORT -> felinaeFelicesEscortShipName()
+ ShipWeightClass.FF_DESTROYER -> felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random()
+ ShipWeightClass.FF_CRUISER -> felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random()
+ ShipWeightClass.FF_BATTLECRUISER -> felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random()
+ ShipWeightClass.FF_BATTLESHIP -> if (Random.nextDouble() < 0.01) "Big Floppa" else (felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random())
+ else -> error("Invalid Felinae Felices ship weight!")
+}
+
+private val isarnareykkShipNames = listOf(
+ "Professional with Standards",
+ "Online Game Cheater",
+ "Actually Made of Antimatter",
+ "Chucklehead",
+ "Guns Strapped to an Engine",
+ "Unidentified Comet",
+ "Deep Space Encounter",
+ "The Goggles Do Nothing",
+ "Sensor Error",
+ "ERROR SHIP NAME NOT FOUND",
+ "0x426F6174",
+ "Börgenkub",
+ "Instant Death",
+ "Assume The Position",
+ "Negative Space Wedgie",
+ "Tea, Earl Grey, Hot",
+ "There's Coffee In That Nebula",
+ "SPEHSS MEHREENS",
+ "Inconspicuous Asteroid",
+ "Inflatable Toy Ship",
+ "HELP TRAPPED IN SHIP FACTORY",
+ "Illegal Meme Dealer",
+ "Reverse the Polarity!",
+ "Send Your Bank Info To Win 10,000 Marks",
+ "STOP CALLING ABOUT MY STARSHIP WARRANTY",
+ "Somebody Once Told Me...",
+ "Praethoris Khorr Gaming",
+)
+
+private fun nameIsarnareykskShip() = isarnareykkShipNames.random()
+
+private val vestigiumShipNames = listOf(
+ // NAMED AFTER SPACE SHUTTLES
+ "Enterprise", // OV-101
+ "Columbia", // OV-102
+ "Discovery", // OV-103
+ "Atlantis", // OV-104
+ "Endeavor", // OV-105
+ "Conqueror", // OV-106
+ "Homeland", // OV-107
+ "Augustus", // OV-108
+ "Avenger", // OV-109
+ "Protector", // OV-110
+
+ // NAMED AFTER HISTORICAL SHIPS
+ "Yorktown",
+ "Lexington",
+ "Ranger",
+ "Hornet",
+ "Wasp",
+ "Antares",
+ "Belfast",
+ // NAMED AFTER PLACES
+ "Akron",
+ "Hudson",
+ "Cleveland",
+ "Baltimore",
+ "Bel Air",
+ "Cedar Rapids",
+ "McHenry",
+ "Rochester",
+ "Cuyahoga Valley",
+ "Catonsville",
+ "Ocean City",
+ "Philadelphia",
+ "Somerset",
+ "Pittsburgh",
+
+ "Las Vegas",
+ "Reno",
+ "Boulder City",
+ "Goodsprings",
+ "Nipton",
+ "Primm",
+ "Nellis",
+ "Fortification Hill",
+ "McCarran",
+ "Fremont",
+
+ // NAMED AFTER SPACE PROBES
+ "Voyager",
+ "Juno",
+ "Cassini",
+ "Hubble",
+ "Huygens",
+ "Pioneer",
+
+ // NAMED AFTER PEOPLE
+ // Founding Fathers
+ "George Washington",
+ "Thomas Jefferson",
+ "John Adams",
+ "Alexander Hamilton",
+ "James Madison",
+ // US Presidents
+ "Andrew Jackson",
+ "Abraham Lincoln",
+ "Theodore Roosevelt",
+ "Calvin Coolidge",
+ "Dwight Eisenhower",
+ "Richard Nixon",
+ "Ronald Reagan",
+ "Donald Trump",
+ "Ron DeSantis",
+ "Gary Martison",
+ // IS Emperors
+ "Jack Coleman",
+ "Trevor Neer",
+ "Hadrey Trevison",
+ "Dio Audrey",
+ "Connor Vance",
+ // Vestigium Leaders
+ "Thomas Blackrock",
+ "Philip Mack",
+ "Ilya Korochenko"
+)
+
+private fun nameAmericanShip() = vestigiumShipNames.random()
+
+fun nameShip(faction: Faction, weightClass: ShipWeightClass): String = when (faction) {
+ Faction.MECHYRDIA -> nameMechyrdianShip(weightClass)
+ Faction.NDRC -> nameMechyrdianShip(weightClass)
+ Faction.MASRA_DRAETSEN -> nameMasraDraetsenShip(weightClass)
+ Faction.FELINAE_FELICES -> nameFelinaeFelicesShip(weightClass)
+ Faction.ISARNAREYKK -> nameIsarnareykskShip()
+ Faction.VESTIGIUM -> nameAmericanShip()
+}
faction = guestFaction,
rank = rank
),
- battleInfo = battleInfo
+ battleInfo = battleInfo,
+ subplots = emptySet(),
)
}
data class InvalidAction(val message: String) : GameEvent()
@Serializable
- data class GameEnd(val winner: GlobalSide?, val message: String) : GameEvent()
+ data class GameEnd(
+ val winner: GlobalSide?,
+ val message: String,
+ @Serializable(with = MapAsListSerializer::class)
+ val subplotOutcomes: Map<SubplotKey, SubplotOutcome> = emptyMap()
+ ) : GameEvent()
}
fun GameState.after(player: GlobalSide, packet: PlayerAction): GameEvent = when (packet) {
val loserName = admiralInfo(player).fullName
val winnerName = admiralInfo(player.other).fullName
- GameEvent.GameEnd(player.other, "$loserName never joined the battle, yielding victory to $winnerName!")
+ GameEvent.GameEnd(player.other, "$loserName never joined the battle, yielding victory to $winnerName!", emptyMap())
}
PlayerAction.Disconnect -> {
val loserName = admiralInfo(player).fullName
val winnerName = admiralInfo(player.other).fullName
- GameEvent.GameEnd(player.other, "$loserName has disconnected from the battle, yielding victory to $winnerName!")
+ GameEvent.GameEnd(player.other, "$loserName has disconnected from the battle, yielding victory to $winnerName!", emptyMap())
}
+}.let { event ->
+ if (event is GameEvent.StateChange) {
+ val subplotKeys = event.newState.subplots.map { it.key }
+ val finalState = subplotKeys.fold(event.newState) { newState, key ->
+ val subplot = newState.subplots.single { it.key == key }
+ subplot.onGameStateChanged(newState)
+ }
+ GameEvent.StateChange(finalState)
+ } else event
}
val guestInfo: InGameAdmiral,
val battleInfo: BattleInfo,
+ val subplots: Set<Subplot>,
+
val phase: GamePhase = GamePhase.Deploy,
val doneWithPhase: GlobalSide? = null,
val calculatedInitiative: GlobalSide? = null,
val chatBox: List<ChatEntry> = emptyList(),
) {
fun getShipInfo(id: Id<ShipInstance>) = destroyedShips[id]?.ship ?: ships.getValue(id).ship
+ fun getShipInfoOrNull(id: Id<ShipInstance>) = destroyedShips[id]?.ship ?: ships[id]?.ship
+
fun getShipOwner(id: Id<ShipInstance>) = destroyedShips[id]?.owner ?: ships.getValue(id).owner
+ fun getShipOwnerOrNull(id: Id<ShipInstance>) = destroyedShips[id]?.owner ?: ships[id]?.owner
}
val GameState.currentInitiative: GlobalSide?
var newInitiative: GameState.() -> InitiativePair = { InitiativePair(emptyMap()) }
when (phase) {
+ GamePhase.Deploy -> {
+ return subplots.map { it.key }.fold(this) { newState, key ->
+ val subplot = newState.subplots.single { it.key == key }
+ subplot.onAfterDeployShips(newState)
+ }.copy(
+ phase = phase.next(),
+ ships = ships.mapValues { (_, ship) ->
+ ship.copy(isDoneCurrentPhase = false)
+ },
+ )
+ }
is GamePhase.Power -> {
// Prepare for move phase
newInitiative = { calculateMovePhaseInitiative() }
val hostDefeated = ships.none { (_, it) -> it.owner == GlobalSide.HOST }
val guestDefeated = ships.none { (_, it) -> it.owner == GlobalSide.GUEST }
+ val winner = if (hostDefeated && guestDefeated)
+ null
+ else if (hostDefeated)
+ GlobalSide.GUEST
+ else if (guestDefeated)
+ GlobalSide.HOST
+ else return null
+
+ val subplotsOutcomes = subplots.associate { subplot ->
+ subplot.key to subplot.getFinalGameResult(this, winner)
+ }
+
return if (hostDefeated && guestDefeated)
- GameEvent.GameEnd(null, "Stalemate: both sides have been completely destroyed!")
+ GameEvent.GameEnd(null, "Stalemate: both sides have been completely destroyed!", subplotsOutcomes)
else if (hostDefeated)
- GameEvent.GameEnd(GlobalSide.GUEST, victoryMessage(GlobalSide.GUEST))
+ GameEvent.GameEnd(GlobalSide.GUEST, victoryMessage(GlobalSide.GUEST), subplotsOutcomes)
else if (guestDefeated)
- GameEvent.GameEnd(GlobalSide.HOST, victoryMessage(GlobalSide.HOST))
+ GameEvent.GameEnd(GlobalSide.HOST, victoryMessage(GlobalSide.HOST), subplotsOutcomes)
else
null
}
--- /dev/null
+package starshipfights.game
+
+import kotlinx.serialization.Serializable
+import starshipfights.data.Id
+
+@Serializable
+data class GameObjective(
+ val displayText: String,
+ val succeeded: Boolean?
+)
+
+fun GameState.objectives(forPlayer: GlobalSide): List<GameObjective> = listOf(
+ GameObjective("Destroy or rout the enemy fleet", null)
+) + subplots.filter { it.forPlayer == forPlayer }.mapNotNull { it.displayObjective(this) }
+
+@Serializable
+data class SubplotKey(
+ val type: SubplotType,
+ val player: GlobalSide,
+)
+
+val Subplot.key: SubplotKey
+ get() = SubplotKey(type, forPlayer)
+
+@Serializable
+sealed class Subplot {
+ abstract val type: SubplotType
+ abstract val displayName: String
+ abstract val forPlayer: GlobalSide
+
+ override fun equals(other: Any?): Boolean {
+ return other is Subplot && other.key == key
+ }
+
+ override fun hashCode(): Int {
+ return key.hashCode()
+ }
+
+ abstract fun displayObjective(gameState: GameState): GameObjective?
+
+ abstract fun onAfterDeployShips(gameState: GameState): GameState
+ abstract fun onGameStateChanged(gameState: GameState): GameState
+ abstract fun getFinalGameResult(gameState: GameState, winner: GlobalSide?): SubplotOutcome
+
+ protected fun GameState.modifySubplotData(newSubplot: Subplot) = copy(subplots = (subplots - this@Subplot) + newSubplot)
+
+ @Serializable
+ class ExtendedDuty(override val forPlayer: GlobalSide) : Subplot() {
+ override val type: SubplotType
+ get() = SubplotType.EXTENDED_DUTY
+
+ override val displayName: String
+ get() = "Extended Duty"
+
+ override fun displayObjective(gameState: GameState) = GameObjective("Win the battle with your fleet worn out from extended duty", null)
+
+ private fun ShipInstance.preBattleDamage(): ShipInstance = when ((0..4).random()) {
+ 0 -> copy(
+ hullAmount = (2..hullAmount).random(),
+ troopsAmount = (2..troopsAmount).random(),
+ modulesStatus = ShipModulesStatus(
+ modulesStatus.statuses.mapValues { (_, status) ->
+ if (status != ShipModuleStatus.ABSENT && (1..3).random() == 1)
+ ShipModuleStatus.DESTROYED
+ else
+ status
+ }
+ )
+ )
+ 1 -> copy(
+ hullAmount = (2..hullAmount).random(),
+ troopsAmount = (2..troopsAmount).random(),
+ )
+ 2 -> copy(
+ troopsAmount = (2..troopsAmount).random(),
+ )
+ else -> this
+ }
+
+ override fun onAfterDeployShips(gameState: GameState) = gameState.copy(ships = gameState.ships.mapValues { (_, ship) ->
+ if (ship.owner == forPlayer)
+ ship.preBattleDamage()
+ else ship
+ })
+
+ override fun onGameStateChanged(gameState: GameState) = gameState
+
+ override fun getFinalGameResult(gameState: GameState, winner: GlobalSide?) = SubplotOutcome.fromBattleWinner(winner, forPlayer)
+ }
+
+ @Serializable
+ class NoQuarter(override val forPlayer: GlobalSide) : Subplot() {
+ override val type: SubplotType
+ get() = SubplotType.NO_QUARTER
+
+ override val displayName: String
+ get() = "Leave No Quarter!"
+
+ override fun displayObjective(gameState: GameState): GameObjective {
+ val enemyShips = gameState.ships.values.filter { it.owner == forPlayer.other }
+ val enemyWrecks = gameState.destroyedShips.values.filter { it.owner == forPlayer.other }
+
+ val totalEnemyShipPointCount = enemyShips.sumOf { it.ship.pointCost } + enemyWrecks.sumOf { it.ship.pointCost }
+ val escapedShipPointCount = enemyWrecks.filter { it.isEscape }.sumOf { it.ship.pointCost }
+ val destroyedShipPointCount = enemyWrecks.filter { !it.isEscape }.sumOf { it.ship.pointCost }
+
+ val success = when {
+ destroyedShipPointCount * 2 >= totalEnemyShipPointCount -> true
+ escapedShipPointCount * 2 >= totalEnemyShipPointCount -> false
+ else -> null
+ }
+
+ return GameObjective("Destroy at least half of the enemy fleet's point value - do not let them escape!", success)
+ }
+
+ override fun onAfterDeployShips(gameState: GameState) = gameState
+
+ override fun onGameStateChanged(gameState: GameState) = gameState
+
+ override fun getFinalGameResult(gameState: GameState, winner: GlobalSide?): SubplotOutcome {
+ val enemyShips = gameState.ships.values.filter { it.owner == forPlayer.other }
+ val enemyWrecks = gameState.destroyedShips.values.filter { it.owner == forPlayer.other }
+
+ val totalEnemyShipPointCount = enemyShips.sumOf { it.ship.pointCost } + enemyWrecks.sumOf { it.ship.pointCost }
+ val destroyedShipPointCount = enemyWrecks.filter { !it.isEscape }.sumOf { it.ship.pointCost }
+
+ return if (destroyedShipPointCount * 2 >= totalEnemyShipPointCount)
+ SubplotOutcome.WON
+ else
+ SubplotOutcome.LOST
+ }
+ }
+
+ @Serializable
+ class Vendetta private constructor(override val forPlayer: GlobalSide, private val againstShip: Id<ShipInstance>?, private val outcome: SubplotOutcome) : Subplot() {
+ constructor(forPlayer: GlobalSide) : this(forPlayer, null, SubplotOutcome.UNDECIDED)
+ constructor(forPlayer: GlobalSide, againstShip: Id<ShipInstance>) : this(forPlayer, againstShip, SubplotOutcome.UNDECIDED)
+
+ override val type: SubplotType
+ get() = SubplotType.VENDETTA
+
+ override val displayName: String
+ get() = "Vendetta!"
+
+ override fun displayObjective(gameState: GameState): GameObjective? {
+ val shipName = gameState.getShipInfoOrNull(againstShip ?: return null)?.fullName ?: return null
+ return GameObjective("Destroy the $shipName", outcome.toSuccess)
+ }
+
+ override fun onAfterDeployShips(gameState: GameState): GameState {
+ if (gameState.ships[againstShip] != null) return gameState
+
+ val enemyShips = gameState.ships.values.filter { it.owner == forPlayer.other }
+ val highestEnemyShipTier = enemyShips.maxOf { it.ship.shipType.weightClass }
+ val enemyShipsOfHighestTier = enemyShips.filter { it.ship.shipType.weightClass == highestEnemyShipTier }
+
+ val vendettaShip = enemyShipsOfHighestTier.random().id
+ return gameState.modifySubplotData(Vendetta(forPlayer, vendettaShip, SubplotOutcome.UNDECIDED))
+ }
+
+ override fun onGameStateChanged(gameState: GameState): GameState {
+ if (outcome != SubplotOutcome.UNDECIDED) return gameState
+
+ val vendettaShipWreck = gameState.destroyedShips[againstShip ?: return gameState] ?: return gameState
+ return if (vendettaShipWreck.isEscape)
+ gameState.modifySubplotData(Vendetta(forPlayer, againstShip, SubplotOutcome.LOST))
+ else
+ gameState.modifySubplotData(Vendetta(forPlayer, againstShip, SubplotOutcome.WON))
+ }
+
+ override fun getFinalGameResult(gameState: GameState, winner: GlobalSide?) = if (outcome == SubplotOutcome.UNDECIDED)
+ SubplotOutcome.LOST
+ else outcome
+ }
+
+ @Serializable
+ class RecoverInformant private constructor(override val forPlayer: GlobalSide, private val onBoardShip: Id<ShipInstance>?, private val outcome: SubplotOutcome, private val mostRecentChatMessages: Moment?) : Subplot() {
+ constructor(forPlayer: GlobalSide) : this(forPlayer, null, SubplotOutcome.UNDECIDED, null)
+ constructor(forPlayer: GlobalSide, onBoardShip: Id<ShipInstance>) : this(forPlayer, onBoardShip, SubplotOutcome.UNDECIDED, null)
+
+ override val type: SubplotType
+ get() = SubplotType.RECOVER_INFORMANT
+
+ override val displayName: String
+ get() = "Recover Informant"
+
+ override fun displayObjective(gameState: GameState): GameObjective? {
+ val shipName = gameState.getShipInfoOrNull(onBoardShip ?: return null)?.fullName ?: return null
+ return GameObjective("Board the $shipName and recover your informant", outcome.toSuccess)
+ }
+
+ override fun onAfterDeployShips(gameState: GameState): GameState {
+ if (gameState.ships[onBoardShip] != null) return gameState
+
+ val enemyShips = gameState.ships.values.filter { it.owner == forPlayer.other }
+ val lowestEnemyShipTier = enemyShips.minOf { it.ship.shipType.weightClass }
+ val enemyShipsNotOfLowestTier = enemyShips.filter { it.ship.shipType.weightClass != lowestEnemyShipTier }.ifEmpty { enemyShips }
+
+ val informantShip = enemyShipsNotOfLowestTier.random().id
+ return gameState.modifySubplotData(RecoverInformant(forPlayer, informantShip, SubplotOutcome.UNDECIDED, null))
+ }
+
+ private fun GameState.getNewMessages(readTime: Moment?) = if (readTime == null)
+ chatBox
+ else
+ chatBox.filter { it.sentAt > readTime }
+
+ override fun onGameStateChanged(gameState: GameState): GameState {
+ if (outcome != SubplotOutcome.UNDECIDED) return gameState
+
+ var readTime = mostRecentChatMessages
+ for (message in gameState.getNewMessages(mostRecentChatMessages)) {
+ when (message) {
+ is ChatEntry.ShipEscaped -> if (message.ship == onBoardShip)
+ return gameState.modifySubplotData(RecoverInformant(forPlayer, onBoardShip, SubplotOutcome.LOST, null))
+ is ChatEntry.ShipDestroyed -> if (message.ship == onBoardShip)
+ return gameState.modifySubplotData(RecoverInformant(forPlayer, onBoardShip, SubplotOutcome.LOST, null))
+ is ChatEntry.ShipBoarded -> if (message.ship == onBoardShip && (1..3).random() == 1)
+ return gameState.modifySubplotData(RecoverInformant(forPlayer, onBoardShip, SubplotOutcome.WON, null))
+ else -> {
+ // do nothing
+ }
+ }
+ readTime = if (readTime == null || readTime < message.sentAt) message.sentAt else readTime
+ }
+
+ return gameState.modifySubplotData(RecoverInformant(forPlayer, onBoardShip, SubplotOutcome.UNDECIDED, readTime))
+ }
+
+ override fun getFinalGameResult(gameState: GameState, winner: GlobalSide?) = if (outcome == SubplotOutcome.UNDECIDED)
+ SubplotOutcome.LOST
+ else outcome
+ }
+}
+
+enum class SubplotType(val factory: (GlobalSide) -> Subplot) {
+ EXTENDED_DUTY(Subplot::ExtendedDuty),
+ NO_QUARTER(Subplot::NoQuarter),
+ VENDETTA(Subplot::Vendetta),
+ RECOVER_INFORMANT(Subplot::RecoverInformant),
+}
+
+fun generateSubplots(battleSize: BattleSize, forPlayer: GlobalSide): Set<Subplot> =
+ (1..battleSize.numSubplotsPerPlayer).map {
+ SubplotType.values().random().factory(forPlayer)
+ }.toSet()
+
+@Serializable
+enum class SubplotOutcome {
+ UNDECIDED, WON, LOST;
+
+ val toSuccess: Boolean?
+ get() = when (this) {
+ UNDECIDED -> null
+ WON -> true
+ LOST -> false
+ }
+
+ companion object {
+ fun fromBattleWinner(winner: GlobalSide?, subplotForPlayer: GlobalSide) = when (winner) {
+ subplotForPlayer -> WON
+ subplotForPlayer.other -> LOST
+ else -> UNDECIDED
+ }
+ }
+}
+
+fun victoryTitle(player: GlobalSide, winner: GlobalSide?, subplotOutcomes: Map<SubplotKey, SubplotOutcome>): String {
+ val myOutcomes = subplotOutcomes.filterKeys { it.player == player }
+
+ return when (winner) {
+ player -> {
+ val isGlorious = myOutcomes.all { (_, outcome) -> outcome == SubplotOutcome.WON }
+ val isPyrrhic = myOutcomes.size >= 2 && myOutcomes.any { (_, outcome) -> outcome != SubplotOutcome.WON }
+
+ if (isGlorious)
+ "Glorious Victory"
+ else if (isPyrrhic)
+ "Pyrrhic Victory"
+ else
+ "Victory"
+ }
+ player.other -> {
+ val isHeroic = myOutcomes.all { (_, outcome) -> outcome == SubplotOutcome.WON }
+ val isHumiliating = myOutcomes.size >= 2 && myOutcomes.any { (_, outcome) -> outcome != SubplotOutcome.WON }
+
+ if (isHeroic)
+ "Heroic Defeat"
+ else if (isHumiliating)
+ "Humiliating Defeat"
+ else
+ "Defeat"
+ }
+ else -> "Stalemate"
+ }
+}
CRUCIBLE_OF_HISTORY(3000, ShipWeightClass.COLOSSUS, "Crucible of History");
}
+val BattleSize.numSubplotsPerPlayer: Int
+ get() = when (this) {
+ BattleSize.SKIRMISH -> 0
+ BattleSize.RAID -> 0
+ BattleSize.FIREFIGHT -> 0
+ BattleSize.BATTLE -> (0..1).random()
+ BattleSize.GRAND_CLASH -> 1
+ BattleSize.APOCALYPSE -> 1
+ BattleSize.LEGENDARY_STRUGGLE -> 1
+ BattleSize.CRUCIBLE_OF_HISTORY -> (1..2).random()
+ }
+
enum class BattleBackground(val displayName: String, val color: String) {
BLUE_BROWN("Milky Way", "#335577"),
BLUE_MAGENTA("Arcane Anomaly", "#553377"),
--- /dev/null
+package starshipfights.game
+
+
package starshipfights.game
import kotlinx.html.*
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.builtins.ListSerializer
+import kotlinx.serialization.builtins.PairSerializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlin.math.abs
import kotlin.math.exp
useAlternativeNames = false
}
+class MapAsListSerializer<K, V>(keySerializer: KSerializer<K>, valueSerializer: KSerializer<V>) : KSerializer<Map<K, V>> {
+ private val inner = ListSerializer(PairSerializer(keySerializer, valueSerializer))
+
+ override val descriptor: SerialDescriptor
+ get() = inner.descriptor
+
+ override fun serialize(encoder: Encoder, value: Map<K, V>) {
+ inner.serialize(encoder, value.toList())
+ }
+
+ override fun deserialize(decoder: Decoder): Map<K, V> {
+ return inner.deserialize(decoder).toMap()
+ }
+}
+
const val EPSILON = 0.00_001
fun <T : Enum<T>> T.toUrlSlug() = name.replace('_', '-').lowercase()
}
}
-private suspend fun GameNetworkInteraction.execute(token: String): Pair<LocalSide?, String> {
- val gameEnd = CompletableDeferred<Pair<LocalSide?, String>>()
+private suspend fun GameNetworkInteraction.execute(token: String): GameEvent.GameEnd {
+ val gameEnd = CompletableDeferred<GameEvent.GameEnd>()
try {
httpClient.webSocket("$rootPathWs/game/$token") {
}.display()
if (!opponentJoined)
- Popup.GameOver(LocalSide.GREEN, "Unfortunately, your opponent never entered the battle.", gameState.value).display()
+ Popup.GameOver(mySide, "Unfortunately, your opponent never entered the battle.", emptyMap(), gameState.value).display()
val sendActionsJob = launch {
for (action in playerActions)
errorMessages.send(event.message)
}
is GameEvent.GameEnd -> {
- gameEnd.complete(event.winner?.relativeTo(mySide) to event.message)
+ gameEnd.complete(event)
closeAndReturn { return@webSocket sendActionsJob.cancel() }
}
}
}
}
} catch (ex: WebSocketException) {
- gameEnd.complete(null to "Server closed connection abruptly")
+ gameEnd.complete(GameEvent.GameEnd(null, "Server closed connection abruptly", emptyMap()))
}
if (gameEnd.isActive)
- gameEnd.complete(null to "Connection closed")
+ gameEnd.complete(GameEvent.GameEnd(null, "Connection closed", emptyMap()))
return gameEnd.await()
}
val connectionJob = async { gameConnection.execute(token) }
val renderingJob = launch { gameRendering.execute(this@coroutineScope) }
- val (finalWinner, finalMessage) = connectionJob.await()
+ val (finalWinner, finalMessage, finalSubplots) = connectionJob.await()
renderingJob.cancel()
interruptExit = false
- Popup.GameOver(finalWinner, finalMessage, gameState.value).display()
+ Popup.GameOver(finalWinner, finalMessage, finalSubplots, gameState.value).display()
}
}
}
}
-private suspend fun GameNetworkInteraction.execute(): Pair<LocalSide?, String> {
+private suspend fun GameNetworkInteraction.execute(): GameEvent.GameEnd {
val gameSession = GameSession(gameState.value)
val aiSide = mySide.other
aiHandlingJob.cancel()
playerHandlingJob.cancel()
- gameEnd.winner?.relativeTo(mySide) to gameEnd.message
+ gameEnd
}
}
val connectionJob = async { gameConnection.execute() }
val renderingJob = launch { gameRendering.execute(this@coroutineScope) }
- val (finalWinner, finalMessage) = connectionJob.await()
+ val (finalWinner, finalMessage, finalSubplots) = connectionJob.await()
renderingJob.cancel()
interruptExit = false
- Popup.GameOver(finalWinner, finalMessage, gameState.value).display()
+ Popup.GameOver(finalWinner, finalMessage, finalSubplots, gameState.value).display()
}
}
private lateinit var topMiddleInfo: HTMLDivElement
private lateinit var topRightBar: HTMLDivElement
+ private lateinit var objectives: HTMLDivElement
+
private lateinit var errorMessages: HTMLParagraphElement
private lateinit var helpMessages: HTMLParagraphElement
id = "top-right-bar"
}
+ div {
+ id = "objectives"
+ }
+
p {
id = "error-messages"
}
topMiddleInfo = document.getElementById("top-middle-info").unsafeCast<HTMLDivElement>()
topRightBar = document.getElementById("top-right-bar").unsafeCast<HTMLDivElement>()
+ objectives = document.getElementById("objectives").unsafeCast<HTMLDivElement>()
+
errorMessages = document.getElementById("error-messages").unsafeCast<HTMLParagraphElement>()
helpMessages = document.getElementById("help-messages").unsafeCast<HTMLParagraphElement>()
}
}.lastOrNull()?.scrollIntoView()
+ objectives.clear()
+ objectives.append {
+ for (objective in state.objectives(mySide)) {
+ val classes = when (objective.succeeded) {
+ true -> "item succeeded"
+ false -> "item failed"
+ else -> "item"
+ }
+ div(classes = classes) {
+ +objective.displayText
+ }
+ }
+ }
+
val abilities = state.getPossibleAbilities(mySide)
topMiddleInfo.clear()
}
}
- class GameOver(private val winner: LocalSide?, private val outcome: String, private val finalState: GameState) : Popup<Nothing>() {
+ class GameOver(private val winner: GlobalSide?, private val outcome: String, private val subplotStatuses: Map<SubplotKey, SubplotOutcome>, private val finalState: GameState) : Popup<Nothing>() {
override fun TagConsumer<*>.render(context: CoroutineContext, callback: (Nothing) -> Unit) {
p {
style = "text-align:center"
strong(classes = "heading") {
- +when (winner) {
- LocalSide.GREEN -> "Victory"
- LocalSide.RED -> "Defeat"
- null -> "Stalemate"
- }
+ +victoryTitle(mySide, winner, subplotStatuses)
}
}
p {
display: none;
}
-#bottom-center-bar {
- position: fixed;
- bottom: 22.5vh;
- left: 50vw;
- width: 25vw;
- height: 5vh;
- transform: translate(-50%, 0);
-}
-
input[type=text] {
border: none;
border-bottom: 2px solid transparent;
color: #d22;
font-weight: bold;
}
+
+#objectives {
+ position: fixed;
+ top: 2.5vh;
+ left: 2.5vw;
+ width: 25vw;
+ height: 75vh;
+ font-size: 1.5em;
+}
+
+#objectives .item {
+ background-color: rgba(0, 0, 0, 0.6);
+ color: #cccccc;
+ padding: 1.2em 0.4em 1.2em 2em;
+}
+
+#objectives .item.failed {
+ color: #ff5555;
+}
+
+#objectives .item.failed::before {
+ content: '✘';
+ position: absolute;
+ left: 0.5em;
+}
+
+#objectives .item.succeeded {
+ color: #55ff55;
+}
+
+#objectives .item.succeeded::before {
+ content: '✔';
+ position: absolute;
+ left: 0.5em;
+}
+++ /dev/null
-package starshipfights.data.admiralty
-
-import kotlin.random.Random
-
-enum class AdmiralNameFlavor {
- MECHYRDIA, TYLA, CALIBOR, OLYMPIA, // Mechyrdia-aligned
- DUTCH, // NdRC-aliged
- NORTHERN_DIADOCHI, SOUTHERN_DIADOCHI, // Masra Draetsen-aligned
- FULKREYKK, // Isarnareykk-aligned
- AMERICAN, HISPANIC_AMERICAN; // Vestigium-aligned
-
- val displayName: String
- get() = when (this) {
- MECHYRDIA -> "Mechyrdian"
- TYLA -> "Tylan"
- CALIBOR -> "Caliborese"
- OLYMPIA -> "Olympian"
- DUTCH -> "Dutch"
- NORTHERN_DIADOCHI -> "Northern Diadochi"
- SOUTHERN_DIADOCHI -> "Southern Diadochi"
- FULKREYKK -> "Thedish"
- AMERICAN -> "American"
- HISPANIC_AMERICAN -> "Hispanic-American"
- }
-}
-
-object AdmiralNames {
- // PERSONAL NAME to PATRONYMIC
- private val mechyrdianMaleNames: List<Pair<String, String>> = listOf(
- "Marc" to "Marcówič",
- "Anton" to "Antonówič",
- "Bjarnarð" to "Bjarnarðówič",
- "Carl" to "Carlówič",
- "Þjutarix" to "Þjutarigówič",
- "Friðurix" to "Friðurigówič",
- "Iwan" to "Iwanówič",
- "Wladimer" to "Wladimerówič",
- "Giulius" to "Giuliówič",
- "Nicólei" to "Nicóleiówič",
- "Þjódor" to "Þjóderówič",
- "Sigismund" to "Sigismundówič",
- "Stefan" to "Stefanówič",
- "Wilhelm" to "Wilhelmówič",
- "Giórgj" to "Giórgiówič"
- )
-
- // PERSONAL NAME to MATRONYMIC
- private val mechyrdianFemaleNames: List<Pair<String, String>> = listOf(
- "Octavia" to "Octaviówca",
- "Annica" to "Annicówca",
- "Astrið" to "Astriðówca",
- "Caþarin" to "Caþarinówca",
- "Signi" to "Signówca",
- "Erica" to "Ericówca",
- "Fréja" to "Fréjówca",
- "Hilda" to "Hildówca",
- "Žanna" to "Žannówca",
- "Xenia" to "Xeniówca",
- "Carina" to "Carinówca",
- "Giadwiga" to "Giadwigówca",
- "Ženia" to "Ženiówca"
- )
-
- private val mechyrdianFamilyNames: List<Pair<String, String>> = listOf(
- "Alexandrów",
- "Antonów",
- "Pogdanów",
- "Hrusčjów",
- "Caísarów",
- "Carolów",
- "Sócolów",
- "Romanów",
- "Nemeciów",
- "Pjótrów",
- "Brutów",
- "Augustów",
- "Calašniców",
- "Anželów",
- "Sigmarów",
- "Dróganów",
- "Coroljów",
- "Wlasów"
- ).map { it to "${it}a" }
-
- private fun randomMechyrdianName(isFemale: Boolean) = if (isFemale)
- mechyrdianFemaleNames.random().first + " " + mechyrdianFemaleNames.random().second + " " + mechyrdianFamilyNames.random().second
- else
- mechyrdianMaleNames.random().first + " " + mechyrdianMaleNames.random().second + " " + mechyrdianFamilyNames.random().first
-
- private val tylanMaleNames = listOf(
- "Althanar" to "Althanas",
- "Aurans" to "Aurantes",
- "Bochra" to "Bochranes",
- "Chshaejar" to "Chshaejas",
- "Hjofvachi" to "Hjovachines",
- "Koldimar" to "Koldimas",
- "Kor" to "Kores",
- "Ljomas" to "Ljomates",
- "Shajel" to "Shajel",
- "Shokar" to "Shokas",
- "Tolavajel" to "Tolavajel",
- "Voskar" to "Voskas",
- )
-
- private val tylanFemaleNames = listOf(
- "Althe" to "Althenes",
- "Anaseil" to "Anaseil",
- "Asetbur" to "Asetbus",
- "Atautha" to "Atauthas",
- "Aurantia" to "Aurantias",
- "Ilasheva" to "Ilashevas",
- "Kalora" to "Kaloras",
- "Kotolva" to "Kotolvas",
- "Psekna" to "Pseknas",
- "Shenera" to "Sheneras",
- "Reoka" to "Reokas",
- "Velga" to "Velgas",
- )
-
- private val tylanFamilyNames = listOf(
- "Kalevkar" to "Kalevka",
- "Merku" to "Merkussa",
- "Telet" to "Telet",
- "Eutokar" to "Eutoka",
- "Vsocha" to "Vsochessa",
- "Vilar" to "Vilakauva",
- "Nikasrar" to "Nika",
- "Vlegamakar" to "Vlegamaka",
- "Vtokassar" to "Vtoka",
- "Theiar" to "Theia",
- "Aretar" to "Areta",
- "Derkas" to "Derkata",
- "Vinsennas" to "Vinsenatta",
- "Kleio" to "Kleona"
- )
-
- // Tylans use matronymics for both sons and daughters
- private fun randomTylanName(isFemale: Boolean) = if (isFemale)
- tylanFemaleNames.random().first + " " + tylanFemaleNames.random().second + "-Nahra " + tylanFamilyNames.random().second
- else
- tylanMaleNames.random().first + " " + tylanFemaleNames.random().second + "-Nensar " + tylanFamilyNames.random().first
-
- private val caliboreseNames = listOf(
- "Jathee",
- "Muly",
- "Simoh",
- "Laka",
- "Foryn",
- "Duxio",
- "Xirio",
- "Surmy",
- "Datarme",
- "Cloren",
- "Tared",
- "Quiliot",
- "Attiol",
- "Quarree",
- "Guil",
- "Miro",
- "Yryys",
- "Zarx",
- "Karm",
- "Mreek",
- "Dulyy",
- "Quorqui",
- "Dreminor",
- "Samitu",
- "Lurmak",
- "Quashi",
- "Barsyn",
- "Rymyo",
- "Soli",
- "Ickart",
- "Woom",
- "Qurquy",
- "Ymiro",
- "Rosiliq",
- "Xant",
- "Xateen",
- "Mssly",
- "Vixie",
- "Quelynn",
- "Plly",
- "Tessy",
- "Veekah",
- "Quett",
- "Xezeez",
- "Xyph",
- "Jixi",
- "Jeekie",
- "Meelen",
- "Rasah",
- "Reteeshy",
- "Xinchie",
- "Zae",
- "Ziggy",
- "Wurikah",
- "Loppie",
- "Tymma",
- "Reely",
- "Yjutee",
- "Len",
- "Vixirat",
- "Xumie",
- "Xilly",
- "Liwwy",
- "Gancee",
- "Pamah",
- "Zeryll",
- "Luteet",
- "Qusseet",
- "Alixika",
- "Sepirah",
- "Luttrah",
- "Aramynn",
- "Laxerynn",
- "Murylyt",
- "Quarapyt",
- "Tormiray",
- "Daromynn",
- "Zuleerynn",
- "Quarimat",
- "Dormaquazi",
- "Tullequazi",
- "Aleeray",
- "Eppiquit",
- "Wittirynn",
- "Semiokolipan",
- "Sosopurr",
- "Quamixit",
- "Croffet",
- "Xaalit",
- "Xemiolyt"
- )
-
- private val caliboreseVowels = "aeiouy".toSet()
- private fun randomCaliboreseName(isFemale: Boolean) = caliboreseNames.filter {
- it.length < 8 && (isFemale == (it.last() in caliboreseVowels))
- }.random() + " " + caliboreseNames.filter { it.length > 7 }.random()
-
- private val latinMaleCommonPraenomina = listOf(
- "Gaius",
- "Lucius",
- "Marcus",
- )
-
- private val latinMaleUncommonPraenomina = listOf(
- "Publius",
- "Quintus",
- "Titus",
- "Gnaeus"
- )
-
- private val latinMaleRarePraenomina = listOf(
- "Aulus",
- "Spurius",
- "Tiberius",
- "Servius",
- "Hostus"
- )
-
- private val latinFemaleCommonPraenomina = listOf(
- "Gaia",
- "Lucia",
- "Marcia",
- )
-
- private val latinFemaleUncommonPraenomina = listOf(
- "Prima",
- "Secunda",
- "Tertia",
- "Quarta",
- "Quinta",
- "Sexta",
- "Septima",
- "Octavia",
- "Nona",
- "Decima"
- )
-
- private val latinFemaleRarePraenomina = listOf(
- "Caesula",
- "Titia",
- "Tiberia",
- "Tanaquil"
- )
-
- private val latinNominaGentilica = listOf(
- "Aelius" to "Aelia",
- "Aternius" to "Aternia",
- "Caecilius" to "Caecilia",
- "Cassius" to "Cassia",
- "Claudius" to "Claudia",
- "Cornelius" to "Cornelia",
- "Calpurnius" to "Calpurnia",
- "Fabius" to "Fabia",
- "Flavius" to "Flavia",
- "Fulvius" to "Fulvia",
- "Haterius" to "Hateria",
- "Hostilius" to "Hostilia",
- "Iulius" to "Iulia",
- "Iunius" to "Iunia",
- "Iuventius" to "Iuventia",
- "Lavinius" to "Lavinia",
- "Licinius" to "Licinia",
- "Marius" to "Maria",
- "Octavius" to "Octavia",
- "Pompeius" to "Pompeia",
- "Porcius" to "Porcia",
- "Salvius" to "Salvia",
- "Sempronius" to "Sempronia",
- "Spurius" to "Spuria",
- "Terentius" to "Terentia",
- "Tullius" to "Tullia",
- "Ulpius" to "Ulpia",
- "Valerius" to "Valeria"
- )
-
- private val latinCognomina = listOf(
- "Agricola" to "Agricola",
- "Agrippa" to "Agrippina",
- "Aquilinus" to "Aquilina",
- "Balbus" to "Balba",
- "Bibulus" to "Bibula",
- "Bucco" to "Bucco",
- "Caecus" to "Caeca",
- "Calidus" to "Calida",
- "Catilina" to "Catilina",
- "Catulus" to "Catula",
- "Crassus" to "Crassa",
- "Crispus" to "Crispa",
- "Drusus" to "Drusilla",
- "Flaccus" to "Flacca",
- "Gracchus" to "Graccha",
- "Laevinus" to "Laevina",
- "Lanius" to "Lania",
- "Lepidus" to "Lepida",
- "Lucullus" to "Luculla",
- "Marcellus" to "Marcella",
- "Metellus" to "Metella",
- "Nasica" to "Nasica",
- "Nerva" to "Nerva",
- "Paullus" to "Paulla",
- "Piso" to "Piso",
- "Priscus" to "Prisca",
- "Publicola" to "Publicola",
- "Pulcher" to "Pulchra",
- "Regulus" to "Regula",
- "Rufus" to "Rufa",
- "Scaevola" to "Scaevola",
- "Severus" to "Severa",
- "Structus" to "Structa",
- "Taurus" to "Taura",
- "Varro" to "Varro",
- "Vitulus" to "Vitula"
- )
-
- private fun randomLatinPraenomen(isFemale: Boolean) = when {
- Random.nextBoolean() -> if (isFemale) latinFemaleCommonPraenomina else latinMaleCommonPraenomina
- Random.nextInt(3) > 0 -> if (isFemale) latinFemaleUncommonPraenomina else latinMaleUncommonPraenomina
- else -> if (isFemale) latinFemaleRarePraenomina else latinMaleRarePraenomina
- }.random()
-
- private fun randomLatinName(isFemale: Boolean) = randomLatinPraenomen(isFemale) + " " + latinNominaGentilica.random().let { (m, f) -> if (isFemale) f else m } + " " + latinCognomina.random().let { (m, f) -> if (isFemale) f else m }
-
- private val dutchMaleNames = listOf(
- "Aalderik",
- "Andreas",
- "Boudewijn",
- "Bruno",
- "Christiaan",
- "Cornelius",
- "Darnath",
- "Dirk",
- "Eren",
- "Erwin",
- "Frederik",
- "Gerlach",
- "Helbrant",
- "Helbrecht",
- "Hendrik",
- "Jakob",
- "Jochem",
- "Joris",
- "Koenraad",
- "Koorland",
- "Leopold",
- "Lodewijk",
- "Maarten",
- "Michel",
- "Niels",
- "Pieter",
- "Renaat",
- "Rogal",
- "Ruben",
- "Sebastiaan",
- "Sigismund",
- "Sjaak",
- "Tobias",
- "Valentijn",
- "Wiebrand",
- )
-
- private val dutchFemaleNames = listOf(
- "Adelwijn",
- "Amberlij",
- "Annika",
- "Arete",
- "Eva",
- "Gerda",
- "Helga",
- "Ida",
- "Irene",
- "Jacqueline",
- "Josefien",
- "Juliana",
- "Katharijne",
- "Lore",
- "Margriet",
- "Maximilia",
- "Meike",
- "Nora",
- "Rebeka",
- "Sara",
- "Vera",
- "Wilhelmina",
- )
-
- private val dutchMerchantHouses = listOf(
- "Venetho",
- "Luibeck",
- "Birka",
- "Heiðabýr",
- "Rostok",
- "Guistrov",
- "Schverin",
- "Koeln",
- "Bruigge",
- "Reval",
- "Elbing",
- "Dorpat",
- "Stralsund",
- "Mijdeborg",
- "Breslaw",
- "Dortmund",
- "Antwerp",
- "Falsterbo",
- "Zwolle",
- "Buchtehud",
- "Bremen",
- "Zutphen",
- "Kampen",
- "Grunn",
- "Deventer",
- "Wismer",
- "Luinenburg",
-
- "Jager",
- "Jastobaal",
- "Varonius",
- "Kupferberg",
- "Dijn",
- "Umboldt",
- "Phalomor",
- "Drijk",
- "d'Wain",
- "du Languille",
- "Horstein",
- "Jerulas",
- "Kendar",
- "Castellan",
- "d'Aniasie",
- "Gerrit",
- "Hoed",
- "lo Pan",
- "Marchandrij",
- "d'Aquairre",
- "Terozzante",
- "d'Argovon",
- "de Monde",
- "Paillender",
- "Holstijn",
- "d'Imperia",
- "Borodin",
- "Agranozza",
- "d'Ortise",
- "Ijzerhoorn",
- "Dremel",
- "Hinckel",
- "Vuigens",
- "Drazen",
- "Marburg",
- "Xardt",
- "Lijze",
- "Gerlach",
- "Doorn",
- "d'Arquebus",
- "Alderic",
- "Vogen"
- )
-
- private fun randomDutchName(isFemale: Boolean) = (if (isFemale) dutchFemaleNames else dutchMaleNames).random() + " van " + dutchMerchantHouses.random()
-
- private val diadochiMaleNames = listOf(
- "Oqatai",
- "Amogus",
- "Nerokhan",
- "Choghor",
- "Aghonei",
- "Martaq",
- "Qaran",
- "Khargh",
- "Qolkhu",
- "Ghauran",
- "Woriv",
- "Vorcha",
- "Chagatai",
- "Neghvar",
- "Qitinga",
- "Jimpaq",
- "Bivat",
- "Durash",
- "Elifas",
- "Ogus",
- "Yuli",
- "Saret",
- "Mher",
- "Tyver",
- "Ghraq",
- "Niran",
- "Galik"
- )
-
- private val diadochiFemaleNames = listOf(
- "Lursha",
- "Jamoqena",
- "Lokoria",
- "Iekuna",
- "Shara",
- "Etugen",
- "Maral",
- "Temuln",
- "Akhensari",
- "Khadagan",
- "Gherelma",
- "Shechen",
- "Althani",
- "Tzyrina",
- "Daghasi",
- "Kloya",
- )
-
- private val northernDiadochiEpithetParts = listOf(
- "Skull",
- "Blood",
- "Death",
- "Claw",
- "Doom",
- "Dread",
- "Soul",
- "Spirit",
- "Hell",
- "Dread",
- "Bale",
- "Fire",
- "Fist",
- "Bear",
- "Pyre",
- "Dark",
- "Vile",
- "Heart",
- "Murder",
- "Gore",
- "Daemon",
- "Talon",
- )
-
- private fun randomNorthernDiadochiName(isFemale: Boolean) = (if (isFemale) diadochiFemaleNames else diadochiMaleNames).random() + " " + northernDiadochiEpithetParts.random() + northernDiadochiEpithetParts.random().lowercase()
-
- private val southernDiadochiClans = listOf(
- "Arkai",
- "Avado",
- "Djahhim",
- "Khankhen",
- "Porok",
- "Miras",
- "Terok",
- "Empok",
- "Noragh",
- "Nuunian",
- "Soung",
- "Akhero",
- "Qozaq",
- "Kherus",
- "Axina",
- "Ghaizas",
- "Saxha",
- "Meshu",
- "Khopesh",
- "Qitemar",
- "Vang",
- "Lugal",
- "Galla",
- "Hheka",
- "Nesut",
- "Koquon",
- "Molekh"
- )
-
- private fun randomSouthernDiadochiClan() = when {
- Random.nextInt(5) == 0 -> southernDiadochiClans.random() + "-" + southernDiadochiClans.random()
- else -> southernDiadochiClans.random()
- }
-
- private fun randomSouthernDiadochiName(isFemale: Boolean) = (if (isFemale) diadochiFemaleNames else diadochiMaleNames).random() + (if (isFemale && Random.nextBoolean()) " ka-" else " am-") + diadochiMaleNames.random() + " " + randomSouthernDiadochiClan()
-
- private val thedishMaleNames = listOf(
- "Praethoris",
- "Severus",
- "Augast",
- "Dagobar",
- "Vrankenn",
- "Kandar",
- "Kleon",
- "Glaius",
- "Karul",
- "Ylai",
- "Toval",
- "Ivon",
- "Belis",
- "Jorh",
- "Svar",
- "Alaric",
- )
-
- private val thedishFemaleNames = listOf(
- "Serna",
- "Veleska",
- "Ielga",
- "Glae",
- "Rova",
- "Ylia",
- "Galera",
- "Nerys",
- "Veleer",
- "Karuleyn",
- "Amberli",
- "Alysia",
- "Lenera",
- "Demeter",
- )
-
- private val thedishSurnames = listOf(
- "Kassck",
- "Orsh",
- "Falk",
- "Khorr",
- "Vaskoman",
- "Vholkazk",
- "Brekoryn",
- "Lorus",
- "Karnas",
- "Hathar",
- "Takan",
- "Pertona",
- "Tefran",
- "Arvi",
- "Galvus",
- "Voss",
- "Mandanof",
- "Ursali",
- "Vytunn",
- "Quesrinn",
- )
-
- private fun randomThedishName(isFemale: Boolean) = (if (isFemale) thedishFemaleNames else thedishMaleNames).random() + " " + thedishSurnames.random()
-
- private val americanMaleNames = listOf(
- "George",
- "John",
- "Thomas",
- "James",
- "Quincy",
- "Andrew",
- "Martin",
- "William",
- "Henry",
- "James",
- "Zachary",
- "Millard",
- "Franklin",
- "Abraham",
- "Ulysses",
- "Rutherford",
- "Chester",
- "Grover",
- "Benjamin",
- "Theodore",
- "Warren",
- "Calvin",
- "Herbert",
- "Harry",
- "Dwight",
- "Lyndon",
- "Richard",
- "Dick",
- "Gerald",
- "Jimmy",
- "Ronald",
- "Donald"
- )
-
- private val americanFemaleNames = listOf(
- "Martha",
- "Abigail",
- "Elizabeth",
- "Louisa",
- "Emily",
- "Sarah",
- "Anna",
- "Jane",
- "Julia",
- "Margaret",
- "Harriet",
- "Mary",
- "Lucy",
- "Rose",
- "Caroline",
- "Ida",
- "Helen",
- "Grace",
- "Jacqueline",
- "Thelma",
- "Eleanor",
- "Nancy",
- "Barbara",
- "Laura",
- "Melania"
- )
-
- private val americanFamilyNames = listOf(
- "Knox",
- "Pickering",
- "McHenry",
- "Dexter",
- "Drawborn",
- "Eustis",
- "Armstrong",
- "Monroe",
- "Crawford",
- "Calhoun",
- "Barbour",
- "Porter",
- "Eaton",
- "Cass",
- "Poinsett",
- "Bell",
- "Forrestal",
- "Johnson",
- "Marshall",
- "Lovett",
- "Wilson",
- "McElroy",
- "McNamara",
- "Clifford",
- "Richardson",
- "Burndt",
- )
-
- private fun randomAmericanName(isFemale: Boolean) = (if (isFemale) americanFemaleNames else americanMaleNames).random() + " " + americanFamilyNames.random()
-
- private val hispanicMaleNames = listOf(
- "Aaron",
- "Antonio",
- "Augusto",
- "Eliseo",
- "Manuel",
- "Jose",
- "Juan",
- "Miguel",
- "Rafael",
- "Raul",
- "Adriano",
- "Emilio",
- "Francisco",
- "Ignacio",
- "Marco",
- "Pablo",
- "Octavio",
- "Victor",
- "Vito",
- "Valentin"
- )
-
- private val hispanicFemaleNames = listOf(
- "Maria",
- "Ana",
- "Camila",
- "Eva",
- "Flora",
- "Gloria",
- "Julia",
- "Marcelina",
- "Rosalia",
- "Victoria",
- "Valentina",
- "Cecilia",
- "Francisca",
- "Aurelia",
- "Cristina",
- "Magdalena",
- "Margarita",
- "Martina",
- "Teresa"
- )
-
- private val hispanicFamilyNames = listOf(
- "Acorda",
- "Aguirre",
- "Alzaga",
- "Arriaga",
- "Arrieta",
- "Berroya",
- "Barahona",
- "Carranza",
- "Carriaga",
- "Elcano",
- "Elizaga",
- "Endaya",
- "Franco",
- "Garalde",
- "Ibarra",
- "Juarez",
- "Lazarte",
- "Legarda",
- "Madariaga",
- "Medrano",
- "Narvaez",
- "Olano",
- "Ricarte",
- "Salazar",
- "Uriarte",
- "Varona",
- "Vergar",
- )
-
- private fun randomHispanicName(isFemale: Boolean) = (if (isFemale) hispanicFemaleNames else hispanicMaleNames).random() + " " + hispanicFamilyNames.random()
-
- fun randomName(flavor: AdmiralNameFlavor, isFemale: Boolean) = when (flavor) {
- AdmiralNameFlavor.MECHYRDIA -> randomMechyrdianName(isFemale)
- AdmiralNameFlavor.TYLA -> randomTylanName(isFemale)
- AdmiralNameFlavor.CALIBOR -> randomCaliboreseName(isFemale)
- AdmiralNameFlavor.OLYMPIA -> randomLatinName(isFemale)
- AdmiralNameFlavor.DUTCH -> randomDutchName(isFemale)
- AdmiralNameFlavor.NORTHERN_DIADOCHI -> randomNorthernDiadochiName(isFemale)
- AdmiralNameFlavor.SOUTHERN_DIADOCHI -> randomSouthernDiadochiName(isFemale)
- AdmiralNameFlavor.FULKREYKK -> randomThedishName(isFemale)
- AdmiralNameFlavor.AMERICAN -> randomAmericanName(isFemale)
- AdmiralNameFlavor.HISPANIC_AMERICAN -> randomHispanicName(isFemale)
- }
-}
+++ /dev/null
-package starshipfights.data.admiralty
-
-import starshipfights.game.Faction
-import starshipfights.game.ShipWeightClass
-import kotlin.random.Random
-
-fun newShipName(faction: Faction, shipWeightClass: ShipWeightClass, existingNames: MutableSet<String>) = generateSequence {
- nameShip(faction, shipWeightClass)
-}.take(20).dropWhile { it in existingNames }.firstOrNull()?.also { existingNames.add(it) }
-
-private val mechyrdianFrigateNames1 = listOf(
- "Unconquerable",
- "Indomitable",
- "Invincible",
- "Imperial",
- "Regal",
- "Royal",
- "Imperious",
- "Honorable",
- "Defiant",
- "Eternal",
- "Infinite",
- "Dominant",
- "Divine",
- "Righteous",
- "Resplendent",
- "Protective",
- "Innocent",
- "August",
- "Loyal"
-)
-
-private val mechyrdianFrigateNames2 = listOf(
- "Faith",
- "Empire",
- "Royalty",
- "Regality",
- "Honor",
- "Defiance",
- "Eternity",
- "Dominator",
- "Divinity",
- "Right",
- "Righteousness",
- "Resplendency",
- "Defender",
- "Protector",
- "Innocence",
- "Victory",
- "Duty",
- "Loyalty"
-)
-
-private val mechyrdianCruiserNames1 = listOf(
- "Defender of",
- "Protector of",
- "Shield of",
- "Sword of",
- "Champion of",
- "Hero of",
- "Salvation of",
- "Savior of",
- "Shining Light of",
- "Righteous Flame of",
- "Eternal Glory of",
-)
-
-private val mechyrdianCruiserNames2 = listOf(
- "Mechyrd",
- "Kaiserswelt",
- "Tenno no Wakusei",
- "Nova Roma",
- "Mont Imperial",
- "Tyla",
- "Vensca",
- "Kaltag",
- "Languavarth Prime",
- "Languavarth Secundum",
- "Elcialot",
- "Othon",
- "Starport",
- "Sacrilegum",
- "New Constantinople",
- "Fairhus",
- "Praxagora",
- "Karolina",
- "Kozachnia",
- "New New Amsterdam",
- "Mundus Caesaris Divi",
- "Saiwatta",
- "Earth"
-)
-
-private val mechyrdianBattleshipNames = listOf(
- "Kaiser Wilhelm I",
- "Kaiser Wilhelm II",
- "Empereur Napoléon I Bonaparte",
- "Tsar Nikolaj II Romanov",
- "Seliger Kaiser Karl I von Habsburg",
- "Emperor Joshua A. Norton I",
- "Emperor Meiji the Great",
- "Emperor Jack G. Coleman",
- "Emperor Trevor C. Neer",
- "Emperor Connor F. Vance",
- "Emperor Jean-Bédel Bokassa I",
- "King Charles XII",
- "King William I the Conqueror",
- "King Alfred the Great",
- "Gustavus Adolphus Magnus Rex",
- "Queen Victoria",
- "Kōnstantînos XI Dragásēs Palaiológos",
- "Ioustinianós I ho Mégas",
- "Kjarossa Liha Vilakauva",
- "Kjarossa Tarkona Sovasra",
- "Great King Kūruš",
- "Queen Elizabeth II",
- "Kjarossa Karelka Helasra",
- "Imperātor Cæsar Dīvī Fīlius Augustus",
- "Cæsar Nerva Trāiānus",
- "King Kaleb of Axum"
-)
-
-private fun nameMechyrdianShip(weightClass: ShipWeightClass) = when (weightClass) {
- ShipWeightClass.ESCORT -> "${mechyrdianFrigateNames1.random()} ${mechyrdianFrigateNames2.random()}"
- ShipWeightClass.DESTROYER -> "${mechyrdianFrigateNames1.random()} ${mechyrdianFrigateNames2.random()}"
- ShipWeightClass.CRUISER -> "${mechyrdianCruiserNames1.random()} ${mechyrdianCruiserNames2.random()}"
- ShipWeightClass.BATTLECRUISER -> "${mechyrdianCruiserNames1.random()} ${mechyrdianCruiserNames2.random()}"
- ShipWeightClass.BATTLESHIP -> mechyrdianBattleshipNames.random()
- ShipWeightClass.BATTLE_BARGE -> mechyrdianBattleshipNames.random()
- else -> error("Invalid Mechyrdian ship weight!")
-}
-
-private val masraDraetsenFrigateNames1 = listOf(
- "Murderous",
- "Hateful",
- "Heinous",
- "Pestilent",
- "Corrupting",
- "Homicidal",
- "Deadly",
- "Primordial",
- "Painful",
- "Agonizing",
- "Spiteful",
- "Odious",
- "Miserating",
- "Damned",
- "Condemned",
- "Hellish",
- "Dark",
- "Impious",
- "Unfaithful",
- "Abyssal",
- "Furious",
- "Vengeful",
- "Spiritous"
-)
-
-private val masraDraetsenFrigateNames2 = listOf(
- "Murder",
- "Hate",
- "Hatred",
- "Pestilence",
- "Corruption",
- "Homicide",
- "Massacre",
- "Death",
- "Agony",
- "Pain",
- "Suffering",
- "Spite",
- "Misery",
- "Damnation",
- "Hell",
- "Darkness",
- "Impiety",
- "Faithlessness",
- "Abyss",
- "Fury",
- "Vengeance",
- "Spirit"
-)
-
-private val masraDraetsenCruiserNames1 = listOf(
- "Despoiler of",
- "Desecrator of",
- "Desolator of",
- "Destroyer of",
- "Executioner of",
- "Pillager of",
- "Villain of",
- "Great Devil of",
- "Infidelity of",
- "Incineration of",
- "Immolation of",
- "Crucifixion of",
- "Unending Darkness of",
-)
-
-private val masraDraetsenCruiserNames2 = listOf(
- // Diadochi space
- "Eskhaton",
- "Terminus",
- "Tychiphage",
- "Magaddu",
- "Ghattusha",
- "Three Suns",
- "RB-5354",
- "VT-3072",
- "Siegsstern",
- "Atzalstadt",
- "Apex",
- "Summit",
- // Lyudareykk and Isarnareykk
- "Vion Kann",
- "Kasr Karul",
- "Vladizapad",
- // Chaebodes Star Empire
- "Ultima Thule",
- "Prenovez",
- // Calibor and Vescar sectors
- "Letum Angelorum",
- "Pharsalus",
- "Eutopia",
- // Ferthlon and Olympia sectors
- "Ferthlon Primus",
- "Ferthlon Secundus",
- "Nova Roma",
- "Mont Imperial",
-)
-
-private const val masraDraetsenColossusName = "Boukephalas"
-
-private fun nameMasraDraetsenShip(weightClass: ShipWeightClass) = when (weightClass) {
- ShipWeightClass.ESCORT -> "${masraDraetsenFrigateNames1.random()} ${masraDraetsenFrigateNames2.random()}"
- ShipWeightClass.DESTROYER -> "${masraDraetsenFrigateNames1.random()} ${masraDraetsenFrigateNames2.random()}"
- ShipWeightClass.CRUISER -> "${masraDraetsenCruiserNames1.random()} ${masraDraetsenCruiserNames2.random()}"
- ShipWeightClass.GRAND_CRUISER -> "${masraDraetsenCruiserNames1.random()} ${masraDraetsenCruiserNames2.random()}"
- ShipWeightClass.COLOSSUS -> masraDraetsenColossusName
- else -> error("Invalid Masra Draetsen ship weight!")
-}
-
-private enum class LatinNounForm {
- MAS_SG,
- FEM_SG,
- NEU_SG,
- MAS_PL,
- FEM_PL,
- NEU_PL,
-}
-
-private data class LatinNoun(
- val noun: String,
- val form: LatinNounForm
-)
-
-private data class LatinAdjective(
- val masculineSingular: String,
- val feminineSingular: String,
- val neuterSingular: String,
- val masculinePlural: String,
- val femininePlural: String,
- val neuterPlural: String,
-) {
- fun get(form: LatinNounForm) = when (form) {
- LatinNounForm.MAS_SG -> masculineSingular
- LatinNounForm.FEM_SG -> feminineSingular
- LatinNounForm.NEU_SG -> neuterSingular
- LatinNounForm.MAS_PL -> masculinePlural
- LatinNounForm.FEM_PL -> femininePlural
- LatinNounForm.NEU_PL -> neuterPlural
- }
-}
-
-private infix fun LatinNoun.describedBy(adjective: LatinAdjective) = "$noun ${adjective.get(form)}"
-
-private fun felinaeFelicesEscortShipName() = "ES-" + (1000..9999).random().toString()
-
-private val felinaeFelicesLineShipNames1 = listOf(
- LatinNoun("Aevum", LatinNounForm.NEU_SG),
- LatinNoun("Aquila", LatinNounForm.FEM_SG),
- LatinNoun("Argonauta", LatinNounForm.MAS_SG),
- LatinNoun("Cattus", LatinNounForm.MAS_SG),
- LatinNoun("Daemon", LatinNounForm.MAS_SG),
- LatinNoun("Divitia", LatinNounForm.FEM_SG),
- LatinNoun("Feles", LatinNounForm.FEM_SG),
- LatinNoun("Imperium", LatinNounForm.NEU_SG),
- LatinNoun("Ius", LatinNounForm.NEU_SG),
- LatinNoun("Iustitia", LatinNounForm.FEM_SG),
- LatinNoun("Leo", LatinNounForm.MAS_SG),
- LatinNoun("Leopardus", LatinNounForm.MAS_SG),
- LatinNoun("Lynx", LatinNounForm.FEM_SG),
- LatinNoun("Panthera", LatinNounForm.FEM_SG),
- LatinNoun("Salvator", LatinNounForm.MAS_SG),
- LatinNoun("Scelus", LatinNounForm.NEU_SG),
- LatinNoun("Tigris", LatinNounForm.MAS_SG),
-)
-
-private val felinaeFelicesLineShipNames2 = listOf(
- LatinAdjective("Animosus", "Animosa", "Animosum", "Animosi", "Animosae", "Animosa"),
- LatinAdjective("Ardens", "Ardens", "Ardens", "Ardentes", "Ardentes", "Ardentia"),
- LatinAdjective("Audax", "Audax", "Audax", "Audaces", "Audaces", "Audacia"),
- LatinAdjective("Astutus", "Astuta", "Astutum", "Astuti", "Astutae", "Astuta"),
- LatinAdjective("Calidus", "Calida", "Calidum", "Calidi", "Calidae", "Calida"),
- LatinAdjective("Ferox", "Ferox", "Ferox", "Feroces", "Feroces", "Ferocia"),
- LatinAdjective("Fortis", "Fortis", "Forte", "Fortes", "Fortes", "Fortia"),
- LatinAdjective("Fugax", "Fugax", "Fugax", "Fugaces", "Fugaces", "Fugacia"),
- LatinAdjective("Indomitus", "Indomita", "Indomitum", "Indomiti", "Indomitae", "Indomita"),
- LatinAdjective("Intrepidus", "Intrepida", "Intrepidum", "Intrepidi", "Intrepidae", "Intrepida"),
- LatinAdjective("Pervicax", "Pervicax", "Pervicax", "Pervicaces", "Pervicaces", "Pervicacia"),
- LatinAdjective("Sagax", "Sagax", "Sagax", "Sagaces", "Sagaces", "Sagacia"),
- LatinAdjective("Superbus", "Superba", "Superbum", "Superbi", "Superbae", "Superba"),
- LatinAdjective("Trux", "Trux", "Trux", "Truces", "Truces", "Trucia"),
-)
-
-private fun nameFelinaeFelicesShip(weightClass: ShipWeightClass) = when (weightClass) {
- ShipWeightClass.FF_ESCORT -> felinaeFelicesEscortShipName()
- ShipWeightClass.FF_DESTROYER -> felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random()
- ShipWeightClass.FF_CRUISER -> felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random()
- ShipWeightClass.FF_BATTLECRUISER -> felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random()
- ShipWeightClass.FF_BATTLESHIP -> if (Random.nextDouble() < 0.01) "Big Floppa" else (felinaeFelicesLineShipNames1.random() describedBy felinaeFelicesLineShipNames2.random())
- else -> error("Invalid Felinae Felices ship weight!")
-}
-
-private val isarnareykkShipNames = listOf(
- "Professional with Standards",
- "Online Game Cheater",
- "Actually Made of Antimatter",
- "Chucklehead",
- "Guns Strapped to an Engine",
- "Unidentified Comet",
- "Deep Space Encounter",
- "The Goggles Do Nothing",
- "Sensor Error",
- "ERROR SHIP NAME NOT FOUND",
- "0x426F6174",
- "Börgenkub",
- "Instant Death",
- "Assume The Position",
- "Negative Space Wedgie",
- "Tea, Earl Grey, Hot",
- "There's Coffee In That Nebula",
- "SPEHSS MEHREENS",
- "Inconspicuous Asteroid",
- "Inflatable Toy Ship",
- "HELP TRAPPED IN SHIP FACTORY",
- "Illegal Meme Dealer",
- "Reverse the Polarity!",
- "Send Your Bank Info To Win 10,000 Marks",
- "STOP CALLING ABOUT MY STARSHIP WARRANTY",
- "Somebody Once Told Me...",
- "Praethoris Khorr Gaming",
-)
-
-private fun nameIsarnareykskShip() = isarnareykkShipNames.random()
-
-private val vestigiumShipNames = listOf(
- // NAMED AFTER SPACE SHUTTLES
- "Enterprise", // OV-101
- "Columbia", // OV-102
- "Discovery", // OV-103
- "Atlantis", // OV-104
- "Endeavor", // OV-105
- "Conqueror", // OV-106
- "Homeland", // OV-107
- "Augustus", // OV-108
- "Avenger", // OV-109
- "Protector", // OV-110
-
- // NAMED AFTER HISTORICAL SHIPS
- "Yorktown",
- "Lexington",
- "Ranger",
- "Hornet",
- "Wasp",
- "Antares",
- "Belfast",
- // NAMED AFTER PLACES
- "Akron",
- "Hudson",
- "Cleveland",
- "Baltimore",
- "Bel Air",
- "Cedar Rapids",
- "McHenry",
- "Rochester",
- "Cuyahoga Valley",
- "Catonsville",
- "Ocean City",
- "Philadelphia",
- "Somerset",
- "Pittsburgh",
-
- "Las Vegas",
- "Reno",
- "Boulder City",
- "Goodsprings",
- "Nipton",
- "Primm",
- "Nellis",
- "Fortification Hill",
- "McCarran",
- "Fremont",
-
- // NAMED AFTER SPACE PROBES
- "Voyager",
- "Juno",
- "Cassini",
- "Hubble",
- "Huygens",
- "Pioneer",
-
- // NAMED AFTER PEOPLE
- // Founding Fathers
- "George Washington",
- "Thomas Jefferson",
- "John Adams",
- "Alexander Hamilton",
- "James Madison",
- // US Presidents
- "Andrew Jackson",
- "Abraham Lincoln",
- "Theodore Roosevelt",
- "Calvin Coolidge",
- "Dwight Eisenhower",
- "Richard Nixon",
- "Ronald Reagan",
- "Donald Trump",
- "Ron DeSantis",
- "Gary Martison",
- // IS Emperors
- "Jack Coleman",
- "Trevor Neer",
- "Hadrey Trevison",
- "Dio Audrey",
- "Connor Vance",
- // Vestigium Leaders
- "Thomas Blackrock",
- "Philip Mack",
- "Ilya Korochenko"
-)
-
-private fun nameAmericanShip() = vestigiumShipNames.random()
-
-fun nameShip(faction: Faction, weightClass: ShipWeightClass): String = when (faction) {
- Faction.MECHYRDIA -> nameMechyrdianShip(weightClass)
- Faction.NDRC -> nameMechyrdianShip(weightClass)
- Faction.MASRA_DRAETSEN -> nameMasraDraetsenShip(weightClass)
- Faction.FELINAE_FELICES -> nameFelinaeFelicesShip(weightClass)
- Faction.ISARNAREYKK -> nameIsarnareykskShip()
- Faction.VESTIGIUM -> nameAmericanShip()
-}
faction = aiAdmiral.faction,
rank = aiAdmiral.rank
),
- battleInfo = battleInfo
+ battleInfo = battleInfo,
+ subplots = generateSubplots(battleInfo.size, GlobalSide.HOST)
)
}
start = generateGameStart(hostInfo, guestInfo, battleInfo),
hostInfo = hostInfo,
guestInfo = guestInfo,
- battleInfo = battleInfo
+ battleInfo = battleInfo,
+ subplots = generateSubplots(battleInfo.size, GlobalSide.HOST) + generateSubplots(battleInfo.size, GlobalSide.GUEST)
)
val session = GameSession(gameState)