From 7608b73e7eba756b9472958827a0f3649a4ca772 Mon Sep 17 00:00:00 2001 From: TheSaminator Date: Thu, 16 Jun 2022 12:19:03 -0400 Subject: [PATCH] Allow players to select enemy faction flavor in singleplayer battles --- .../starshipfights/game/game_initiative.kt | 4 +- .../net/starshipfights/game/matchmaking.kt | 26 +++++++++- .../game/ship_faction_flavors.kt | 50 +++++++++---------- .../starshipfights/game/client_matchmaking.kt | 8 ++- .../kotlin/net/starshipfights/game/popup.kt | 37 ++++++++++++++ .../net/starshipfights/game/popup_util.kt | 25 +++++++++- .../net/starshipfights/game/game_start_jvm.kt | 5 +- .../net/starshipfights/game/views_training.kt | 3 +- 8 files changed, 122 insertions(+), 36 deletions(-) diff --git a/src/commonMain/kotlin/net/starshipfights/game/game_initiative.kt b/src/commonMain/kotlin/net/starshipfights/game/game_initiative.kt index 185632f..25c2ba3 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/game_initiative.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/game_initiative.kt @@ -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) } diff --git a/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt b/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt index ca3a8e6..527ef52 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt @@ -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 } diff --git a/src/commonMain/kotlin/net/starshipfights/game/ship_faction_flavors.kt b/src/commonMain/kotlin/net/starshipfights/game/ship_faction_flavors.kt index 7daeee6..0441c91 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/ship_faction_flavors.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/ship_faction_flavors.kt @@ -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 = 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) diff --git a/src/jsMain/kotlin/net/starshipfights/game/client_matchmaking.kt b/src/jsMain/kotlin/net/starshipfights/game/client_matchmaking.kt index 5c6341e..cb51de3 100644 --- a/src/jsMain/kotlin/net/starshipfights/game/client_matchmaking.kt +++ b/src/jsMain/kotlin/net/starshipfights/game/client_matchmaking.kt @@ -45,7 +45,7 @@ suspend fun setupBackground() { } } -private suspend fun enterTraining(admiral: Id, battleInfo: BattleInfo, faction: Faction?): Nothing { +private suspend fun enterTraining(admiral: Id, 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, 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() diff --git a/src/jsMain/kotlin/net/starshipfights/game/popup.kt b/src/jsMain/kotlin/net/starshipfights/game/popup.kt index 931508a..be251e0 100644 --- a/src/jsMain/kotlin/net/starshipfights/game/popup.kt +++ b/src/jsMain/kotlin/net/starshipfights/game/popup.kt @@ -265,6 +265,43 @@ sealed class Popup { } } + class ChooseEnemyFactionFlavorScreen(val forFaction: Faction) : Popup() { + 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() { override fun TagConsumer<*>.render(context: CoroutineContext, callback: (Boolean?) -> Unit) { p { diff --git a/src/jsMain/kotlin/net/starshipfights/game/popup_util.kt b/src/jsMain/kotlin/net/starshipfights/game/popup_util.kt index 548254d..2530842 100644 --- a/src/jsMain/kotlin/net/starshipfights/game/popup_util.kt +++ b/src/jsMain/kotlin/net/starshipfights/game/popup_util.kt @@ -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 { 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? { diff --git a/src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt b/src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt index 89c84b4..7ec31ec 100644 --- a/src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt +++ b/src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt @@ -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 } ) diff --git a/src/jvmMain/kotlin/net/starshipfights/game/views_training.kt b/src/jvmMain/kotlin/net/starshipfights/game/views_training.kt index eeda0c8..111def8 100644 --- a/src/jvmMain/kotlin/net/starshipfights/game/views_training.kt +++ b/src/jvmMain/kotlin/net/starshipfights/game/views_training.kt @@ -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) } -- 2.25.1