Allow players to select enemy faction flavor in singleplayer battles
authorTheSaminator <TheSaminator@users.noreply.github.com>
Thu, 16 Jun 2022 16:19:03 +0000 (12:19 -0400)
committerTheSaminator <TheSaminator@users.noreply.github.com>
Thu, 16 Jun 2022 16:19:03 +0000 (12:19 -0400)
src/commonMain/kotlin/net/starshipfights/game/game_initiative.kt
src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt
src/commonMain/kotlin/net/starshipfights/game/ship_faction_flavors.kt
src/jsMain/kotlin/net/starshipfights/game/client_matchmaking.kt
src/jsMain/kotlin/net/starshipfights/game/popup.kt
src/jsMain/kotlin/net/starshipfights/game/popup_util.kt
src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt
src/jvmMain/kotlin/net/starshipfights/game/views_training.kt

index 185632fb8df98dd932b6f01efa5ce5b04d5cf673..25c2ba3f2e0acb4d164160835b518069f24da073 100644 (file)
@@ -81,8 +81,8 @@ fun GameState.calculateAttackPhaseInitiative(): InitiativePair = InitiativePair(
                                                .filterValues { weapon -> hasValidTargets(ship, weapon) }
                                        val usableWeapons = allWeapons - ship.usedArmaments
                                        
-                                       val allWeaponShots = allWeapons.values.sumOf { it.weapon.numShots } + ship.troopsAmount
-                                       val usableWeaponShots = usableWeapons.values.sumOf { it.weapon.numShots } + (if (ship.canSendBoardingParty) ship.troopsAmount else 0)
+                                       val allWeaponShots = allWeapons.values.sumOf { it.weapon.numShots * it.weapon.firingArcs.size } + ship.troopsAmount
+                                       val usableWeaponShots = usableWeapons.values.sumOf { it.weapon.numShots * it.weapon.firingArcs.size } + (if (ship.canSendBoardingParty) ship.troopsAmount else 0)
                                        
                                        ship.ship.pointCost * (usableWeaponShots.toDouble() / allWeaponShots)
                                }
index ca3a8e6368fdf9e10488c0323c648d35b2cb2a78..527ef5298a4707df782632d2ec88f6b4fd8adb30 100644 (file)
@@ -50,12 +50,36 @@ data class PlayerLogin(
        val login: LoginMode,
 )
 
+@Serializable
+sealed class TrainingOpponent {
+       abstract val faction: Faction?
+       abstract val flavor: FactionFlavor?
+       
+       @Serializable
+       object RandomFaction : TrainingOpponent() {
+               override val faction: Faction?
+                       get() = null
+               
+               override val flavor: FactionFlavor?
+                       get() = null
+       }
+       
+       @Serializable
+       data class FactionWithRandomFlavor(override val faction: Faction) : TrainingOpponent() {
+               override val flavor: FactionFlavor?
+                       get() = null
+       }
+       
+       @Serializable
+       data class FactionAndFlavor(override val faction: Faction, override val flavor: FactionFlavor) : TrainingOpponent()
+}
+
 @Serializable
 sealed class LoginMode {
        abstract val globalSide: GlobalSide?
        
        @Serializable
-       data class Train(val battleInfo: BattleInfo, val enemyFaction: Faction?) : LoginMode() {
+       data class Train(val battleInfo: BattleInfo, val enemyFaction: TrainingOpponent) : LoginMode() {
                override val globalSide: GlobalSide?
                        get() = null
        }
index 7daeee6ef2510e0fd87f9d732554694ea61fc35f..0441c9155ad38750959fa8fc1997a8db78d0ca9f 100644 (file)
@@ -38,33 +38,33 @@ val Faction.trimColor: IntColor?
                Faction.VESTIGIUM -> IntColor(108, 96, 153)
        }
 
-enum class FactionFlavor(val nativeName: String?, val translatedName: String, val colorReplacement: IntColor) {
-       MECHYRDIA("Štelflót Ciarstuos Mehurdiasi", "Imperial Star Fleet of Mechyrdia", IntColor(255, 204, 51)),
-       TYLA("Helasram Laevashtam Moashtas Tulasras", "Stellar Navy of the Tylan Republic", IntColor(51, 102, 204)),
-       OLYMPIA("Classis Nautica Rei Publicae Olympicae", "Naval Fleet of the Olympia Commonwealth", IntColor(204, 51, 51)),
-       TEXANDRIA("Texandrische Sternenmarine der Volkswehr", "Texandrian Star Navy of the Public Defense", IntColor(255, 221, 119)),
+enum class FactionFlavor(val displayName: String, val colorReplacement: IntColor) {
+       MECHYRDIA("Štelflót Ciarstuos Mehurdiasi", IntColor(255, 204, 51)),
+       TYLA("Helasram Kasashtam Moashtas Tulasras", IntColor(51, 102, 204)),
+       OLYMPIA("Classis Nautica Rei Publicae Olympicae", IntColor(204, 51, 51)),
+       TEXANDRIA("Texandrische Sternenmarine der Volkswehr", IntColor(255, 221, 119)),
        
-       NDRC("Sterrenvloot der NdRC", "NdRC Stellar Fleet", IntColor(255, 153, 51)),
-       CCC("Collegium Comitatum Caeleste", "Celestial Caravan Company", IntColor(255, 204, 51)),
-       MJOLNIR_ENERGY("Mjolniri Energia", "Mjölnir Energy", IntColor(34, 68, 136)),
+       NDRC("Sterrenvloot der NdRC", IntColor(255, 153, 51)),
+       CCC("Collegium Comitatum Caeleste", IntColor(238, 187, 34)),
+       MJOLNIR_ENERGY("Mjölnir Energy", IntColor(34, 68, 136)),
        
-       MASRA_DRAETSEN(null, "Diadochus Masra Draetsen", IntColor(34, 85, 170)),
-       AEDON_CULTISTS(null, "Aedon Cultists", IntColor(136, 68, 204)),
-       FERTHLON_EXILES(null, "Ferthlon Exiles", IntColor(51, 204, 68)),
+       MASRA_DRAETSEN("Diadochus Masra Draetsen", IntColor(34, 85, 170)),
+       AEDON_CULTISTS("Aedonolatrous Cultists", IntColor(136, 68, 204)),
+       FERTHLON_EXILES("Ferthlon Internation Exiles", IntColor(51, 204, 68)),
        
-       RES_NOSTRA(null, "Res Nostra", IntColor(153, 17, 85)),
-       CORSAIRS(null, "Corsairs' Commune", IntColor(34, 34, 34)),
-       FELINAE_FELICES(null, "Felinae Felices", IntColor(255, 119, 187)),
+       RES_NOSTRA("Res Nostra", IntColor(153, 17, 85)),
+       CORSAIRS("Corsairs' Commune", IntColor(34, 34, 34)),
+       FELINAE_FELICES("Felinae Felices", IntColor(255, 119, 187)),
        
-       ISARNAREYKK(null, "Isarnareyksk Federation", IntColor(255, 255, 255)),
-       SWARTAREYKK(null, "Swartareyksk Totalitariat", IntColor(255, 170, 170)),
-       THEUDAREYKK(null, "Theudareyksk Kingdom", IntColor(153, 204, 255)),
-       STAHLAREYKK(null, "Stahlareyksk Binding", IntColor(204, 153, 102)),
-       LYUDAREYKK(null, "Lyudareyksk Baurginassus", IntColor(153, 204, 153)),
-       NEUIA_FULKREYKK(null, "Neuia Fulkreykk Rebellion", IntColor(153, 153, 153)),
+       ISARNAREYKK("Isarnareyksk Federation", IntColor(255, 255, 255)),
+       SWARTAREYKK("Swartareyksk Totalitariat", IntColor(255, 170, 170)),
+       THEUDAREYKK("Theudareyksk Kingdom", IntColor(153, 204, 255)),
+       STAHLAREYKK("Stahlareyksk Binding", IntColor(204, 153, 102)),
+       LYUDAREYKK("Lyudareyksk Baurginassus", IntColor(153, 204, 153)),
+       NEUIA_FULKREYKK("Neuia Fulkreykk Rebellion", IntColor(153, 153, 153)),
        
-       CORVUS_CLUSTER_VESTIGIUM(null, "Vestigium Sect in the Corvus Cluster", IntColor(108, 96, 153)),
-       COLEMAN_SF_BASE_VESTIGIUM(null, "Vestigium Sect at Coleman Space Force Base", IntColor(153, 102, 102)),
+       CORVUS_CLUSTER_VESTIGIUM("Vestigium Sect in the Corvus Cluster", IntColor(108, 96, 153)),
+       COLEMAN_SF_BASE_VESTIGIUM("Vestigium Sect at Coleman Space Force Base", IntColor(153, 102, 102)),
        ;
        
        companion object {
@@ -78,9 +78,9 @@ enum class FactionFlavor(val nativeName: String?, val translatedName: String, va
                }
                
                fun optionsForAiEnemy(computerFaction: Faction): Set<FactionFlavor> = when (computerFaction) {
-                       Faction.MECHYRDIA -> setOf(MECHYRDIA, TYLA, OLYMPIA, TEXANDRIA, NDRC, CORSAIRS, RES_NOSTRA)
-                       Faction.NDRC -> setOf(NDRC, CCC, MJOLNIR_ENERGY, RES_NOSTRA, CORSAIRS)
-                       Faction.MASRA_DRAETSEN -> setOf(MASRA_DRAETSEN, AEDON_CULTISTS, FERTHLON_EXILES)
+                       Faction.MECHYRDIA -> setOf(MECHYRDIA, TYLA, OLYMPIA, TEXANDRIA, NDRC, RES_NOSTRA, CORSAIRS, FERTHLON_EXILES)
+                       Faction.NDRC -> setOf(NDRC, CCC, MJOLNIR_ENERGY, RES_NOSTRA, CORSAIRS, FERTHLON_EXILES)
+                       Faction.MASRA_DRAETSEN -> setOf(MASRA_DRAETSEN, AEDON_CULTISTS, RES_NOSTRA, CORSAIRS, FERTHLON_EXILES)
                        Faction.FELINAE_FELICES -> setOf(FELINAE_FELICES, RES_NOSTRA, CORSAIRS)
                        Faction.ISARNAREYKK -> setOf(ISARNAREYKK, SWARTAREYKK, THEUDAREYKK, STAHLAREYKK, LYUDAREYKK, NEUIA_FULKREYKK)
                        Faction.VESTIGIUM -> setOf(CORVUS_CLUSTER_VESTIGIUM, COLEMAN_SF_BASE_VESTIGIUM)
index 5c6341ebb03ac5f506f2c2b3898607057e708f61..cb51de38abc8e7c1356ea9c2f48b7297ea5e53d1 100644 (file)
@@ -45,7 +45,7 @@ suspend fun setupBackground() {
        }
 }
 
-private suspend fun enterTraining(admiral: Id<InGameAdmiral>, battleInfo: BattleInfo, faction: Faction?): Nothing {
+private suspend fun enterTraining(admiral: Id<InGameAdmiral>, battleInfo: BattleInfo, opponent: TrainingOpponent): Nothing {
        interruptExit = false
        
        document.body!!.append.form(action = "/train", method = FormMethod.post, encType = FormEncType.applicationXWwwFormUrlEncoded) {
@@ -64,7 +64,11 @@ private suspend fun enterTraining(admiral: Id<InGameAdmiral>, battleInfo: Battle
                }
                hiddenInput {
                        name = "enemy-faction"
-                       value = faction?.toUrlSlug() ?: "-random"
+                       value = opponent.faction?.toUrlSlug() ?: "-random"
+               }
+               hiddenInput {
+                       name = "enemy-flavor"
+                       value = opponent.flavor?.toUrlSlug() ?: "-random"
                }
        }.submit()
        awaitCancellation()
index 931508a5dfe44d675d8f9297d688368e3afed436..be251e0b9dac8ca3efbedd4853ea55f1a419fbc2 100644 (file)
@@ -265,6 +265,43 @@ sealed class Popup<out T> {
                }
        }
        
+       class ChooseEnemyFactionFlavorScreen(val forFaction: Faction) : Popup<AIFactionFlavorChoice?>() {
+               override fun TagConsumer<*>.render(context: CoroutineContext, callback: (AIFactionFlavorChoice?) -> Unit) {
+                       p {
+                               style = "text-align:center"
+                               
+                               +"Select a color scheme for the enemy faction"
+                       }
+                       
+                       div(classes = "button-set col") {
+                               button {
+                                       +"Random"
+                                       onClickFunction = { e ->
+                                               e.preventDefault()
+                                               callback(AIFactionFlavorChoice.Random)
+                                       }
+                               }
+                               for (flavor in FactionFlavor.optionsForAiEnemy(forFaction)) {
+                                       button {
+                                               +flavor.displayName
+                                               
+                                               onClickFunction = { e ->
+                                                       e.preventDefault()
+                                                       callback(AIFactionFlavorChoice.Chosen(flavor))
+                                               }
+                                       }
+                               }
+                               button {
+                                       +"Cancel"
+                                       onClickFunction = { e ->
+                                               e.preventDefault()
+                                               callback(null)
+                                       }
+                               }
+                       }
+               }
+       }
+       
        class GuestRequestScreen(private val hostInfo: InGameAdmiral, private val guestInfo: InGameAdmiral) : Popup<Boolean?>() {
                override fun TagConsumer<*>.render(context: CoroutineContext, callback: (Boolean?) -> Unit) {
                        p {
index 548254d1d11310d55240bdc4b1261bd66c3cac6d..25308427bf556c58549934e7e3b2a36f956b2caa 100644 (file)
@@ -12,6 +12,12 @@ sealed class AIFactionChoice {
        data class Chosen(val faction: Faction) : AIFactionChoice()
 }
 
+sealed class AIFactionFlavorChoice {
+       object Random : AIFactionFlavorChoice()
+       
+       data class Chosen(val flavor: FactionFlavor) : AIFactionFlavorChoice()
+}
+
 private suspend fun Popup.Companion.getPlayerInfo(admirals: List<InGameAdmiral>): InGameAdmiral {
        return Popup.ChooseAdmiralScreen(admirals).display()
 }
@@ -22,10 +28,25 @@ private suspend fun Popup.Companion.getBattleInfo(admiral: InGameAdmiral): Battl
        return BattleInfo(battleSize, battleBackground)
 }
 
+private suspend fun Popup.Companion.getTrainingOpponent(): TrainingOpponent? {
+       val faction = Popup.ChooseEnemyFactionScreen.display() ?: return null
+       return when (faction) {
+               is AIFactionChoice.Chosen -> {
+                       val flavor = Popup.ChooseEnemyFactionFlavorScreen(faction.faction).display() ?: return getTrainingOpponent()
+                       when (flavor) {
+                               is AIFactionFlavorChoice.Chosen -> TrainingOpponent.FactionAndFlavor(faction.faction, flavor.flavor)
+                               AIFactionFlavorChoice.Random -> TrainingOpponent.FactionWithRandomFlavor(faction.faction)
+                       }
+               }
+               AIFactionChoice.Random -> TrainingOpponent.RandomFaction
+       }
+}
+
 private suspend fun Popup.Companion.getTrainingInfo(admiral: InGameAdmiral): LoginMode? {
        val battleInfo = getBattleInfo(admiral) ?: return getLoginMode(admiral)
-       val faction = Popup.ChooseEnemyFactionScreen.display() ?: return getLoginMode(admiral)
-       return LoginMode.Train(battleInfo, (faction as? AIFactionChoice.Chosen)?.faction)
+       val opponent = getTrainingOpponent() ?: return getTrainingInfo(admiral)
+       
+       return LoginMode.Train(battleInfo, opponent)
 }
 
 private suspend fun Popup.Companion.getLoginMode(admiral: InGameAdmiral): LoginMode? {
index 89c84b48a217c468388e29ca3e7c80de5162bc5b..7ec31ec89cc30871c66e78887ebce10959d209d3 100644 (file)
@@ -36,7 +36,7 @@ suspend fun generateGameStart(hostInfo: InGameAdmiral, guestInfo: InGameAdmiral,
        )
 }
 
-suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction: Faction, battleInfo: BattleInfo): GameState {
+suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction: Faction, enemyFlavor: FactionFlavor, battleInfo: BattleInfo): GameState {
        val battleWidth = (25..35).random() * 500.0
        val battleLength = (15..45).random() * 500.0
        
@@ -47,7 +47,6 @@ suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction
        val guestDeployCenter = Position(Vec2(0.0, (battleLength / 2) - deployLength2))
        
        val aiAdmiral = genAI(enemyFaction, battleInfo.size)
-       val aiFlavor = FactionFlavor.optionsForAiEnemy(enemyFaction).random()
        
        return GameState(
                start = GameStart(
@@ -67,7 +66,7 @@ suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction
                                -PI / 2,
                                PickBoundary.Rectangle(guestDeployCenter, deployWidth2, deployLength2),
                                -PI / 2,
-                               generateFleet(aiAdmiral, aiFlavor)
+                               generateFleet(aiAdmiral, enemyFlavor)
                                        .associate { it.shipData.id to it.shipData }
                                        .filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxTier }
                        )
index eeda0c8a08883516477667070c9e85fad527c08f..111def80c9d1cb1273f3ddf8d69a15855b3efaf5 100644 (file)
@@ -22,8 +22,9 @@ suspend fun ApplicationCall.getTrainingClientMode(): ClientMode {
        val battleInfo = BattleInfo(battleSize, battleBg)
        
        val enemyFaction = Faction.values().singleOrNull { it.toUrlSlug() == parameters["enemy-faction"] } ?: Faction.values().random()
+       val enemyFlavor = FactionFlavor.values().singleOrNull { it.toUrlSlug() == parameters["enemy-flavor"] } ?: FactionFlavor.optionsForAiEnemy(enemyFaction).random()
        
-       val initialState = generateTrainingInitialState(admiralData, enemyFaction, battleInfo)
+       val initialState = generateTrainingInitialState(admiralData, enemyFaction, enemyFlavor, battleInfo)
        
        return ClientMode.InTrainingGame(initialState)
 }