From: TheSaminator Date: Wed, 8 Jun 2022 18:33:38 +0000 (-0400) Subject: Add new subplot and refactor ship-tier code X-Git-Url: https://gitweb.starshipfights.net/?a=commitdiff_plain;h=98cc602193a803c96b995ae808dac82300d13d30;p=starship-fights Add new subplot and refactor ship-tier code --- diff --git a/src/commonMain/kotlin/net/starshipfights/game/admiralty.kt b/src/commonMain/kotlin/net/starshipfights/game/admiralty.kt index e6506b6..f6203d3 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/admiralty.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/admiralty.kt @@ -10,6 +10,15 @@ enum class AdmiralRank { HIGH_ADMIRAL, LORD_ADMIRAL; + val maxShipTier: ShipTier + get() = when (this) { + REAR_ADMIRAL -> ShipTier.CRUISER + VICE_ADMIRAL -> ShipTier.BATTLECRUISER + ADMIRAL -> ShipTier.BATTLESHIP + HIGH_ADMIRAL -> ShipTier.BATTLESHIP + LORD_ADMIRAL -> ShipTier.TITAN + } + val maxShipWeightClass: ShipWeightClass get() = when (this) { REAR_ADMIRAL -> ShipWeightClass.CRUISER @@ -20,7 +29,7 @@ enum class AdmiralRank { } val maxBattleSize: BattleSize - get() = BattleSize.values().last { it.maxWeightClass.tier <= maxShipWeightClass.tier } + get() = BattleSize.values().last { it.minRank <= this } val minAcumen: Int get() = when (this) { diff --git a/src/commonMain/kotlin/net/starshipfights/game/ai/ai_behaviors.kt b/src/commonMain/kotlin/net/starshipfights/game/ai/ai_behaviors.kt index 803ae3a..2982e2d 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/ai/ai_behaviors.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/ai/ai_behaviors.kt @@ -43,7 +43,7 @@ suspend fun AIPlayer.behave(instincts: Instincts, mySide: GlobalSide) { is ChatEntry.ShipIdentified -> { val identifiedShip = state.ships[msg.ship] ?: continue if (identifiedShip.owner != mySide) - brain[shipAttackPriority forShip identifiedShip.id] += identifiedShip.ship.shipType.weightClass.tier.toDouble().pow(instincts[combatTargetShipWeight]) + brain[shipAttackPriority forShip identifiedShip.id] += (identifiedShip.ship.shipType.weightClass.tier.ordinal + 1.5).pow(instincts[combatTargetShipWeight]) } is ChatEntry.ShipEscaped -> { // handle escaping ship @@ -70,7 +70,7 @@ suspend fun AIPlayer.behave(instincts: Instincts, mySide: GlobalSide) { is ChatEntry.ShipDestroyed -> { val targetedShip = state.ships[msg.ship] ?: continue if (targetedShip.owner == mySide && msg.destroyedBy is ShipAttacker.EnemyShip) - brain[shipAttackPriority forShip msg.destroyedBy.id] += instincts[combatAvengeShipwrecks] * targetedShip.ship.shipType.weightClass.tier.toDouble().pow(instincts[combatAvengeShipWeight]) + brain[shipAttackPriority forShip msg.destroyedBy.id] += instincts[combatAvengeShipwrecks] * (targetedShip.ship.shipType.weightClass.tier.ordinal + 1.5).pow(instincts[combatAvengeShipWeight]) } } } @@ -292,11 +292,11 @@ suspend fun AIPlayer.behave(instincts: Instincts, mySide: GlobalSide) { fun deploy(gameState: GameState, mySide: GlobalSide, instincts: Instincts): Map, Position> { val size = gameState.battleInfo.size val totalPoints = size.numPoints - val maxWC = size.maxWeightClass + val maxTier = size.maxTier val myStart = gameState.start.playerStart(mySide) - val deployable = myStart.deployableFleet.values.filter { it.shipType.weightClass.tier <= maxWC.tier }.toMutableSet() + val deployable = myStart.deployableFleet.values.filter { it.shipType.weightClass.tier <= maxTier }.toMutableSet() val deployed = mutableSetOf() while (true) { diff --git a/src/commonMain/kotlin/net/starshipfights/game/ai/ai_optimization.kt b/src/commonMain/kotlin/net/starshipfights/game/ai/ai_optimization.kt index 8b755b2..37b0717 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/ai/ai_optimization.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/ai/ai_optimization.kt @@ -157,11 +157,6 @@ suspend fun performTestSession(gameState: GameState, hostInstincts: Instincts, g } } -val BattleSize.minRank: AdmiralRank - get() = AdmiralRank.values().first { - it.maxShipWeightClass.tier >= maxWeightClass.tier - } - fun generateFleet(faction: Faction, rank: AdmiralRank, side: GlobalSide): Map, Ship> = ShipWeightClass.values() .flatMap { swc -> val shipTypes = ShipType.values().filter { st -> @@ -171,7 +166,7 @@ fun generateFleet(faction: Faction, rank: AdmiralRank, side: GlobalSide): Map + (0 until ((rank.maxShipTier.ordinal - swc.tier.ordinal + 1) * 2).coerceAtLeast(0)).map { i -> shipTypes[i % shipTypes.size] } } diff --git a/src/commonMain/kotlin/net/starshipfights/game/game_subplots.kt b/src/commonMain/kotlin/net/starshipfights/game/game_subplots.kt index e47ef82..ec6ac22 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/game_subplots.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/game_subplots.kt @@ -105,6 +105,7 @@ sealed class Subplot { val destroyedShipPointCount = enemyWrecks.filter { !it.isEscape }.sumOf { it.ship.pointCost } val success = when { + gameState.phase == GamePhase.Deploy -> null destroyedShipPointCount * 2 >= totalEnemyShipPointCount -> true escapedShipPointCount * 2 >= totalEnemyShipPointCount -> false else -> null @@ -173,6 +174,48 @@ sealed class Subplot { else outcome } + @Serializable + class PlausibleDeniability 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.PLAUSIBLE_DENIABILITY + + override val displayName: String + get() = "Plausible Deniability" + + override fun displayObjective(gameState: GameState): GameObjective? { + val shipName = gameState.getShipInfoOrNull(againstShip ?: return null)?.fullName ?: return null + return GameObjective("Ensure that the $shipName is destroyed", outcome.toSuccess) + } + + override fun onAfterDeployShips(gameState: GameState): GameState { + if (gameState.ships[againstShip] != null) return gameState + + val myShips = gameState.ships.values.filter { it.owner == forPlayer } + val lowestShipTier = myShips.minOf { it.ship.shipType.weightClass } + val shipsNotOfLowestTier = myShips.filter { it.ship.shipType.weightClass != lowestShipTier }.ifEmpty { myShips } + + val arkancideShip = shipsNotOfLowestTier.random().id + return gameState.modifySubplotData(PlausibleDeniability(forPlayer, arkancideShip, SubplotOutcome.UNDECIDED)) + } + + override fun onGameStateChanged(gameState: GameState): GameState { + if (outcome != SubplotOutcome.UNDECIDED) return gameState + + val assassinateShipWreck = gameState.destroyedShips[againstShip ?: return gameState] ?: return gameState + return if (assassinateShipWreck.isEscape) + gameState.modifySubplotData(PlausibleDeniability(forPlayer, againstShip, SubplotOutcome.LOST)) + else + gameState.modifySubplotData(PlausibleDeniability(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) @@ -237,6 +280,7 @@ enum class SubplotType(val factory: (GlobalSide) -> Subplot) { EXTENDED_DUTY(Subplot::ExtendedDuty), NO_QUARTER(Subplot::NoQuarter), VENDETTA(Subplot::Vendetta), + PLAUSIBLE_DENIABILITY(Subplot::PlausibleDeniability), RECOVER_INFORMANT(Subplot::RecoverInformant), } diff --git a/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt b/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt index a3fda7f..90b9f2d 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/matchmaking.kt @@ -3,27 +3,27 @@ package net.starshipfights.game import kotlinx.serialization.Serializable import net.starshipfights.data.Id -enum class BattleSize(val numPoints: Int, val maxWeightClass: ShipWeightClass, val displayName: String) { - SKIRMISH(600, ShipWeightClass.CRUISER, "Skirmish"), - RAID(800, ShipWeightClass.CRUISER, "Raid"), - FIREFIGHT(1000, ShipWeightClass.BATTLECRUISER, "Firefight"), - BATTLE(1300, ShipWeightClass.BATTLECRUISER, "Battle"), - GRAND_CLASH(1600, ShipWeightClass.BATTLESHIP, "Grand Clash"), - APOCALYPSE(2000, ShipWeightClass.BATTLESHIP, "Apocalypse"), - LEGENDARY_STRUGGLE(2400, ShipWeightClass.COLOSSUS, "Legendary Struggle"), - CRUCIBLE_OF_HISTORY(3000, ShipWeightClass.COLOSSUS, "Crucible of History"); +enum class BattleSize(val numPoints: Int, val maxTier: ShipTier, val minRank: AdmiralRank, val displayName: String) { + SKIRMISH(600, ShipTier.CRUISER, AdmiralRank.REAR_ADMIRAL, "Skirmish"), + RAID(800, ShipTier.CRUISER, AdmiralRank.REAR_ADMIRAL, "Raid"), + FIREFIGHT(1000, ShipTier.BATTLECRUISER, AdmiralRank.REAR_ADMIRAL, "Firefight"), + BATTLE(1300, ShipTier.BATTLECRUISER, AdmiralRank.REAR_ADMIRAL, "Battle"), + GRAND_CLASH(1600, ShipTier.BATTLESHIP, AdmiralRank.ADMIRAL, "Grand Clash"), + APOCALYPSE(2000, ShipTier.BATTLESHIP, AdmiralRank.ADMIRAL, "Apocalypse"), + LEGENDARY_STRUGGLE(2400, ShipTier.TITAN, AdmiralRank.ADMIRAL, "Legendary Struggle"), + CRUCIBLE_OF_HISTORY(3000, ShipTier.TITAN, AdmiralRank.ADMIRAL, "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.FIREFIGHT -> (0..1).random() + BattleSize.BATTLE -> 1 BattleSize.GRAND_CLASH -> 1 BattleSize.APOCALYPSE -> 1 - BattleSize.LEGENDARY_STRUGGLE -> 1 - BattleSize.CRUCIBLE_OF_HISTORY -> (1..2).random() + BattleSize.LEGENDARY_STRUGGLE -> (1..2).random() + BattleSize.CRUCIBLE_OF_HISTORY -> 2 } enum class BattleBackground(val displayName: String, val color: String) { diff --git a/src/commonMain/kotlin/net/starshipfights/game/ship_types.kt b/src/commonMain/kotlin/net/starshipfights/game/ship_types.kt index a88f433..379a8c3 100644 --- a/src/commonMain/kotlin/net/starshipfights/game/ship_types.kt +++ b/src/commonMain/kotlin/net/starshipfights/game/ship_types.kt @@ -1,40 +1,47 @@ package net.starshipfights.game +enum class ShipTier { + ESCORT, LIGHT_CRUISER, CRUISER, BATTLECRUISER, BATTLESHIP, TITAN; + + val displayName: String + get() = name.lowercase().split('_').joinToString(separator = " ") { word -> word.replaceFirstChar { c -> c.uppercase() } } +} + enum class ShipWeightClass( val meshIndex: Int, - val tier: Int + val tier: ShipTier ) { // General - ESCORT(1, 0), - DESTROYER(2, 1), - CRUISER(3, 2), - BATTLECRUISER(4, 3), - BATTLESHIP(5, 4), + ESCORT(1, ShipTier.ESCORT), + DESTROYER(2, ShipTier.LIGHT_CRUISER), + CRUISER(3, ShipTier.CRUISER), + BATTLECRUISER(4, ShipTier.BATTLECRUISER), + BATTLESHIP(5, ShipTier.BATTLESHIP), // NdRC-specific - BATTLE_BARGE(5, 3), + BATTLE_BARGE(5, ShipTier.BATTLECRUISER), // Masra Draetsen-specific - GRAND_CRUISER(4, 3), - COLOSSUS(5, 5), + GRAND_CRUISER(4, ShipTier.BATTLECRUISER), + COLOSSUS(5, ShipTier.TITAN), // Felinae Felices-specific - FF_ESCORT(1, 1), - FF_DESTROYER(2, 2), - FF_CRUISER(3, 3), - FF_BATTLECRUISER(4, 4), - FF_BATTLESHIP(5, 5), + FF_ESCORT(1, ShipTier.LIGHT_CRUISER), + FF_DESTROYER(2, ShipTier.CRUISER), + FF_CRUISER(3, ShipTier.BATTLECRUISER), + FF_BATTLECRUISER(4, ShipTier.BATTLESHIP), + FF_BATTLESHIP(5, ShipTier.TITAN), // Isarnareykk-specific - AUXILIARY_SHIP(1, 0), - LIGHT_CRUISER(2, 1), - MEDIUM_CRUISER(3, 2), - HEAVY_CRUISER(4, 4), + AUXILIARY_SHIP(1, ShipTier.ESCORT), + LIGHT_CRUISER(2, ShipTier.LIGHT_CRUISER), + MEDIUM_CRUISER(3, ShipTier.CRUISER), + HEAVY_CRUISER(4, ShipTier.BATTLECRUISER), // Vestigium-specific - FRIGATE(1, 0), - LINE_SHIP(3, 2), - DREADNOUGHT(5, 4), + FRIGATE(1, ShipTier.ESCORT), + LINE_SHIP(3, ShipTier.CRUISER), + DREADNOUGHT(5, ShipTier.BATTLESHIP), ; val displayName: String diff --git a/src/jvmMain/kotlin/net/starshipfights/data/admiralty/admirals.kt b/src/jvmMain/kotlin/net/starshipfights/data/admiralty/admirals.kt index 7c13054..ae3a248 100644 --- a/src/jvmMain/kotlin/net/starshipfights/data/admiralty/admirals.kt +++ b/src/jvmMain/kotlin/net/starshipfights/data/admiralty/admirals.kt @@ -47,7 +47,7 @@ fun genAI(faction: Faction, forBattleSize: BattleSize) = Admiral( isFemale = true, faction = faction, acumen = AdmiralRank.values().first { - it.maxShipWeightClass.tier >= forBattleSize.maxWeightClass.tier + it.maxBattleSize >= forBattleSize }.minAcumen + 500, money = 0 ) @@ -141,7 +141,7 @@ fun generateFleet(admiral: Admiral): List = ShipWeightClass.value if (shipTypes.isEmpty()) emptyList() else - (0 until ((admiral.rank.maxShipWeightClass.tier - swc.tier + 1) * 2).coerceAtLeast(0)).map { i -> + (0 until ((admiral.rank.maxShipTier.ordinal - swc.tier.ordinal + 1) * 2).coerceAtLeast(0)).map { i -> shipTypes[i % shipTypes.size] } } diff --git a/src/jvmMain/kotlin/net/starshipfights/data/admiralty/ship_prices.kt b/src/jvmMain/kotlin/net/starshipfights/data/admiralty/ship_prices.kt index 5acf4fd..a0502d5 100644 --- a/src/jvmMain/kotlin/net/starshipfights/data/admiralty/ship_prices.kt +++ b/src/jvmMain/kotlin/net/starshipfights/data/admiralty/ship_prices.kt @@ -18,7 +18,7 @@ fun ShipType.buyPriceChecked(admiral: Admiral, ownedShips: List): } fun ShipType.buyPrice(admiral: Admiral, ownedShips: List): Int? { - if (weightClass.tier > admiral.rank.maxShipWeightClass.tier) return null + if (weightClass.tier > admiral.rank.maxShipTier) return null if (weightClass.isUnique && ownedShips.any { it.shipType.weightClass == weightClass }) return null return when { admiral.faction == faction -> buyPrice 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 950e732..a6d5733 100644 --- a/src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt +++ b/src/jvmMain/kotlin/net/starshipfights/game/game_start_jvm.kt @@ -23,7 +23,7 @@ suspend fun generateGameStart(hostInfo: InGameAdmiral, guestInfo: InGameAdmiral, PI / 2, PickBoundary.Rectangle(hostDeployCenter, deployWidth2, deployLength2), PI / 2, - getAdmiralsShips(hostInfo.id.reinterpret()).filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxWeightClass.tier } + getAdmiralsShips(hostInfo.id.reinterpret()).filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxTier } ), PlayerStart( @@ -31,7 +31,7 @@ suspend fun generateGameStart(hostInfo: InGameAdmiral, guestInfo: InGameAdmiral, -PI / 2, PickBoundary.Rectangle(guestDeployCenter, deployWidth2, deployLength2), -PI / 2, - getAdmiralsShips(guestInfo.id.reinterpret()).filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxWeightClass.tier } + getAdmiralsShips(guestInfo.id.reinterpret()).filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxTier } ), ) } @@ -58,7 +58,7 @@ suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction PickBoundary.Rectangle(hostDeployCenter, deployWidth2, deployLength2), PI / 2, getAdmiralsShips(playerInfo.id.reinterpret()) - .filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxWeightClass.tier } + .filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxTier } ), PlayerStart( @@ -68,7 +68,7 @@ suspend fun generateTrainingInitialState(playerInfo: InGameAdmiral, enemyFaction -PI / 2, generateFleet(aiAdmiral) .associate { it.shipData.id to it.shipData } - .filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxWeightClass.tier } + .filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxTier } ) ), hostInfo = playerInfo, diff --git a/src/jvmMain/kotlin/net/starshipfights/info/views_gdpr.kt b/src/jvmMain/kotlin/net/starshipfights/info/views_gdpr.kt index 20e59a1..4c016b1 100644 --- a/src/jvmMain/kotlin/net/starshipfights/info/views_gdpr.kt +++ b/src/jvmMain/kotlin/net/starshipfights/info/views_gdpr.kt @@ -137,7 +137,7 @@ suspend fun ApplicationCall.privateInfo(): String { appendLine("Admiral serves the ${admiral.faction.navyName}") appendLine("Admiral's experience is ${admiral.acumen} acumen") appendLine("Admiral's monetary wealth is ${admiral.money} ${admiral.faction.currencyName}") - appendLine("Admiral can command ships as big as a ${admiral.rank.maxShipWeightClass.displayName}") + appendLine("Admiral can command ships as big as ${admiral.rank.maxShipTier.displayName} size") val ships = admiralShips[admiral.id]?.first.orEmpty() appendLine("Admiral has ${ships.size} ships:") for (ship in ships) { diff --git a/src/jvmMain/kotlin/net/starshipfights/info/views_ships.kt b/src/jvmMain/kotlin/net/starshipfights/info/views_ships.kt index 648dda0..362b5c6 100644 --- a/src/jvmMain/kotlin/net/starshipfights/info/views_ships.kt +++ b/src/jvmMain/kotlin/net/starshipfights/info/views_ships.kt @@ -37,7 +37,7 @@ suspend fun ApplicationCall.shipsPage(): HTML.() -> Unit = page("Strategema Naut faction.blurbDesc(consumer) - for ((weightClass, weightedShipTypes) in factionShipTypes.groupBy { it.weightClass }.toSortedMap(Comparator.comparingInt(ShipWeightClass::tier))) { + for ((weightClass, weightedShipTypes) in factionShipTypes.groupBy { it.weightClass }.toSortedMap(Comparator.comparing(ShipWeightClass::tier))) { h3 { +weightClass.displayName } ul { for (shipType in weightedShipTypes) {