From: TheSaminator Date: Tue, 7 Jun 2022 23:18:14 +0000 (-0400) Subject: Add subplots X-Git-Url: https://gitweb.starshipfights.net/?a=commitdiff_plain;h=0f6cd5085e10ddd8161a8376f31910993a24bfd9;p=starship-fights Add subplots --- diff --git a/src/commonMain/kotlin/starshipfights/data/admiralty/admiral_names.kt b/src/commonMain/kotlin/starshipfights/data/admiralty/admiral_names.kt new file mode 100644 index 0000000..520829d --- /dev/null +++ b/src/commonMain/kotlin/starshipfights/data/admiralty/admiral_names.kt @@ -0,0 +1,873 @@ +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> = 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> = 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> = 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) + } +} diff --git a/src/commonMain/kotlin/starshipfights/data/admiralty/ship_names.kt b/src/commonMain/kotlin/starshipfights/data/admiralty/ship_names.kt new file mode 100644 index 0000000..d09ce98 --- /dev/null +++ b/src/commonMain/kotlin/starshipfights/data/admiralty/ship_names.kt @@ -0,0 +1,452 @@ +package starshipfights.data.admiralty + +import starshipfights.game.Faction +import starshipfights.game.ShipWeightClass +import kotlin.random.Random + +fun newShipName(faction: Faction, shipWeightClass: ShipWeightClass, existingNames: MutableSet) = 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() +} diff --git a/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt b/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt index b2f0368..3b01eca 100644 --- a/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt +++ b/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt @@ -241,7 +241,8 @@ fun generateOptimizationInitialState(hostFaction: Faction, guestFaction: Faction faction = guestFaction, rank = rank ), - battleInfo = battleInfo + battleInfo = battleInfo, + subplots = emptySet(), ) } diff --git a/src/commonMain/kotlin/starshipfights/game/game_packet.kt b/src/commonMain/kotlin/starshipfights/game/game_packet.kt index 9334a2a..5231351 100644 --- a/src/commonMain/kotlin/starshipfights/game/game_packet.kt +++ b/src/commonMain/kotlin/starshipfights/game/game_packet.kt @@ -31,7 +31,12 @@ sealed class GameEvent { 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 = emptyMap() + ) : GameEvent() } fun GameState.after(player: GlobalSide, packet: PlayerAction): GameEvent = when (packet) { @@ -56,12 +61,21 @@ fun GameState.after(player: GlobalSide, packet: PlayerAction): GameEvent = when 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 } diff --git a/src/commonMain/kotlin/starshipfights/game/game_state.kt b/src/commonMain/kotlin/starshipfights/game/game_state.kt index 7558530..231362b 100644 --- a/src/commonMain/kotlin/starshipfights/game/game_state.kt +++ b/src/commonMain/kotlin/starshipfights/game/game_state.kt @@ -11,6 +11,8 @@ data class GameState( val guestInfo: InGameAdmiral, val battleInfo: BattleInfo, + val subplots: Set, + val phase: GamePhase = GamePhase.Deploy, val doneWithPhase: GlobalSide? = null, val calculatedInitiative: GlobalSide? = null, @@ -21,7 +23,10 @@ data class GameState( val chatBox: List = emptyList(), ) { fun getShipInfo(id: Id) = destroyedShips[id]?.ship ?: ships.getValue(id).ship + fun getShipInfoOrNull(id: Id) = destroyedShips[id]?.ship ?: ships[id]?.ship + fun getShipOwner(id: Id) = destroyedShips[id]?.owner ?: ships.getValue(id).owner + fun getShipOwnerOrNull(id: Id) = destroyedShips[id]?.owner ?: ships[id]?.owner } val GameState.currentInitiative: GlobalSide? @@ -47,6 +52,17 @@ private fun GameState.afterPhase(): GameState { 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() } @@ -162,12 +178,24 @@ fun GameState.checkVictory(): GameEvent.GameEnd? { 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 } diff --git a/src/commonMain/kotlin/starshipfights/game/game_subplots.kt b/src/commonMain/kotlin/starshipfights/game/game_subplots.kt new file mode 100644 index 0000000..8b41938 --- /dev/null +++ b/src/commonMain/kotlin/starshipfights/game/game_subplots.kt @@ -0,0 +1,296 @@ +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 = 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?, private val outcome: SubplotOutcome) : Subplot() { + constructor(forPlayer: GlobalSide) : this(forPlayer, null, SubplotOutcome.UNDECIDED) + constructor(forPlayer: GlobalSide, againstShip: Id) : 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?, private val outcome: SubplotOutcome, private val mostRecentChatMessages: Moment?) : Subplot() { + constructor(forPlayer: GlobalSide) : this(forPlayer, null, SubplotOutcome.UNDECIDED, null) + constructor(forPlayer: GlobalSide, onBoardShip: Id) : 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 = + (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): 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" + } +} diff --git a/src/commonMain/kotlin/starshipfights/game/matchmaking.kt b/src/commonMain/kotlin/starshipfights/game/matchmaking.kt index 88ea5b8..bbfe00a 100644 --- a/src/commonMain/kotlin/starshipfights/game/matchmaking.kt +++ b/src/commonMain/kotlin/starshipfights/game/matchmaking.kt @@ -14,6 +14,18 @@ enum class BattleSize(val numPoints: Int, val maxWeightClass: ShipWeightClass, v 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"), diff --git a/src/commonMain/kotlin/starshipfights/game/ship_modifiers.kt b/src/commonMain/kotlin/starshipfights/game/ship_modifiers.kt new file mode 100644 index 0000000..6d6cbd1 --- /dev/null +++ b/src/commonMain/kotlin/starshipfights/game/ship_modifiers.kt @@ -0,0 +1,3 @@ +package starshipfights.game + + diff --git a/src/commonMain/kotlin/starshipfights/game/util.kt b/src/commonMain/kotlin/starshipfights/game/util.kt index 07807e9..6618d98 100644 --- a/src/commonMain/kotlin/starshipfights/game/util.kt +++ b/src/commonMain/kotlin/starshipfights/game/util.kt @@ -1,6 +1,12 @@ 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 @@ -15,6 +21,21 @@ val jsonSerializer = Json { useAlternativeNames = false } +class MapAsListSerializer(keySerializer: KSerializer, valueSerializer: KSerializer) : KSerializer> { + private val inner = ListSerializer(PairSerializer(keySerializer, valueSerializer)) + + override val descriptor: SerialDescriptor + get() = inner.descriptor + + override fun serialize(encoder: Encoder, value: Map) { + inner.serialize(encoder, value.toList()) + } + + override fun deserialize(decoder: Decoder): Map { + return inner.deserialize(decoder).toMap() + } +} + const val EPSILON = 0.00_001 fun > T.toUrlSlug() = name.replace('_', '-').lowercase() diff --git a/src/jsMain/kotlin/starshipfights/game/client_game.kt b/src/jsMain/kotlin/starshipfights/game/client_game.kt index 6448316..2a43721 100644 --- a/src/jsMain/kotlin/starshipfights/game/client_game.kt +++ b/src/jsMain/kotlin/starshipfights/game/client_game.kt @@ -114,8 +114,8 @@ suspend fun GameRenderInteraction.execute(scope: CoroutineScope) { } } -private suspend fun GameNetworkInteraction.execute(token: String): Pair { - val gameEnd = CompletableDeferred>() +private suspend fun GameNetworkInteraction.execute(token: String): GameEvent.GameEnd { + val gameEnd = CompletableDeferred() try { httpClient.webSocket("$rootPathWs/game/$token") { @@ -124,7 +124,7 @@ private suspend fun GameNetworkInteraction.execute(token: String): Pair { - 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() } @@ -195,10 +195,10 @@ suspend fun gameMain(side: GlobalSide, token: String, state: GameState) { 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() } } diff --git a/src/jsMain/kotlin/starshipfights/game/client_training.kt b/src/jsMain/kotlin/starshipfights/game/client_training.kt index a4bacf8..b6f178c 100644 --- a/src/jsMain/kotlin/starshipfights/game/client_training.kt +++ b/src/jsMain/kotlin/starshipfights/game/client_training.kt @@ -51,7 +51,7 @@ class GameSession(gameState: GameState) { } } -private suspend fun GameNetworkInteraction.execute(): Pair { +private suspend fun GameNetworkInteraction.execute(): GameEvent.GameEnd { val gameSession = GameSession(gameState.value) val aiSide = mySide.other @@ -113,7 +113,7 @@ private suspend fun GameNetworkInteraction.execute(): Pair { aiHandlingJob.cancel() playerHandlingJob.cancel() - gameEnd.winner?.relativeTo(mySide) to gameEnd.message + gameEnd } } @@ -135,10 +135,10 @@ suspend fun trainingMain(state: GameState) { 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() } } diff --git a/src/jsMain/kotlin/starshipfights/game/game_ui.kt b/src/jsMain/kotlin/starshipfights/game/game_ui.kt index 8b1be0a..d18225e 100644 --- a/src/jsMain/kotlin/starshipfights/game/game_ui.kt +++ b/src/jsMain/kotlin/starshipfights/game/game_ui.kt @@ -31,6 +31,8 @@ object GameUI { 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 @@ -79,6 +81,10 @@ object GameUI { id = "top-right-bar" } + div { + id = "objectives" + } + p { id = "error-messages" } @@ -114,6 +120,8 @@ object GameUI { topMiddleInfo = document.getElementById("top-middle-info").unsafeCast() topRightBar = document.getElementById("top-right-bar").unsafeCast() + objectives = document.getElementById("objectives").unsafeCast() + errorMessages = document.getElementById("error-messages").unsafeCast() helpMessages = document.getElementById("help-messages").unsafeCast() @@ -394,6 +402,20 @@ object GameUI { } }.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() diff --git a/src/jsMain/kotlin/starshipfights/game/popup.kt b/src/jsMain/kotlin/starshipfights/game/popup.kt index 70b58ea..9ed0a7c 100644 --- a/src/jsMain/kotlin/starshipfights/game/popup.kt +++ b/src/jsMain/kotlin/starshipfights/game/popup.kt @@ -497,17 +497,13 @@ sealed class Popup { } } - class GameOver(private val winner: LocalSide?, private val outcome: String, private val finalState: GameState) : Popup() { + class GameOver(private val winner: GlobalSide?, private val outcome: String, private val subplotStatuses: Map, private val finalState: GameState) : Popup() { 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 { diff --git a/src/jsMain/resources/style.css b/src/jsMain/resources/style.css index 8ffb9ea..4bde652 100644 --- a/src/jsMain/resources/style.css +++ b/src/jsMain/resources/style.css @@ -219,15 +219,6 @@ hr + hr { 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; @@ -375,3 +366,38 @@ button:disabled > img { 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; +} diff --git a/src/jvmMain/kotlin/starshipfights/data/admiralty/admiral_names.kt b/src/jvmMain/kotlin/starshipfights/data/admiralty/admiral_names.kt deleted file mode 100644 index af93345..0000000 --- a/src/jvmMain/kotlin/starshipfights/data/admiralty/admiral_names.kt +++ /dev/null @@ -1,861 +0,0 @@ -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> = 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> = 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> = 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) - } -} diff --git a/src/jvmMain/kotlin/starshipfights/data/admiralty/ship_names.kt b/src/jvmMain/kotlin/starshipfights/data/admiralty/ship_names.kt deleted file mode 100644 index d09ce98..0000000 --- a/src/jvmMain/kotlin/starshipfights/data/admiralty/ship_names.kt +++ /dev/null @@ -1,452 +0,0 @@ -package starshipfights.data.admiralty - -import starshipfights.game.Faction -import starshipfights.game.ShipWeightClass -import kotlin.random.Random - -fun newShipName(faction: Faction, shipWeightClass: ShipWeightClass, existingNames: MutableSet) = 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() -} diff --git a/src/jvmMain/kotlin/starshipfights/game/game_start_jvm.kt b/src/jvmMain/kotlin/starshipfights/game/game_start_jvm.kt index ee61855..40bcce5 100644 --- a/src/jvmMain/kotlin/starshipfights/game/game_start_jvm.kt +++ b/src/jvmMain/kotlin/starshipfights/game/game_start_jvm.kt @@ -83,6 +83,7 @@ suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction faction = aiAdmiral.faction, rank = aiAdmiral.rank ), - battleInfo = battleInfo + battleInfo = battleInfo, + subplots = generateSubplots(battleInfo.size, GlobalSide.HOST) ) } diff --git a/src/jvmMain/kotlin/starshipfights/game/server_game.kt b/src/jvmMain/kotlin/starshipfights/game/server_game.kt index ff96b3f..a63d02b 100644 --- a/src/jvmMain/kotlin/starshipfights/game/server_game.kt +++ b/src/jvmMain/kotlin/starshipfights/game/server_game.kt @@ -32,7 +32,8 @@ object GameManager { 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)