From 2e005aaf73e2c9bc4712f9a335539b52ec250ad9 Mon Sep 17 00:00:00 2001 From: TheSaminator Date: Mon, 6 Jun 2022 19:33:35 -0400 Subject: [PATCH] Ships are destroyed when they are destroyed --- .../starshipfights/game/ai/ai_behaviors.kt | 17 +------ .../starshipfights/game/ai/ai_util_nav.kt | 18 +++++++ .../starshipfights/game/ship_instances.kt | 1 + .../kotlin/starshipfights/auth/providers.kt | 2 +- .../starshipfights/data/admiralty/admirals.kt | 18 +++++++ .../kotlin/starshipfights/game/server_game.kt | 40 +++++++++++----- .../kotlin/starshipfights/info/views_user.kt | 48 +++++++++++++++++-- 7 files changed, 114 insertions(+), 30 deletions(-) diff --git a/src/commonMain/kotlin/starshipfights/game/ai/ai_behaviors.kt b/src/commonMain/kotlin/starshipfights/game/ai/ai_behaviors.kt index f1d4a04..a2f392a 100644 --- a/src/commonMain/kotlin/starshipfights/game/ai/ai_behaviors.kt +++ b/src/commonMain/kotlin/starshipfights/game/ai/ai_behaviors.kt @@ -411,25 +411,12 @@ fun engage(gameState: GameState, ship: ShipInstance): PlayerAction.UseAbility { } fun pursue(gameState: GameState, ship: ShipInstance): PlayerAction.UseAbility { - val myLocation = ship.position.location val targetLocation = gameState.ships.values.filter { it.owner != ship.owner }.map { it.position.location }.minByOrNull { loc -> - (loc - myLocation).length + (loc - ship.position.location).length } ?: return PlayerAction.UseAbility( PlayerAbilityType.MoveShip(ship.id), PlayerAbilityData.MoveShip(ship.position) ) - val angleTo = normalDistance(ship.position.facing) angleTo (targetLocation - myLocation) - val maxTurn = ship.movement.turnAngle * 0.99 - val turnNormal = normalDistance(ship.position.facing) rotatedBy angleTo.coerceIn(-maxTurn..maxTurn) - - val move = (ship.movement.moveSpeed * if (turnNormal angleBetween (targetLocation - myLocation) < EPSILON) 0.99 else 0.51) * turnNormal - val newLoc = ship.position.location + move - - val position = ShipPosition(newLoc, move.angle) - - return PlayerAction.UseAbility( - PlayerAbilityType.MoveShip(ship.id), - PlayerAbilityData.MoveShip(position) - ) + return ship.navigateTo(targetLocation) } diff --git a/src/commonMain/kotlin/starshipfights/game/ai/ai_util_nav.kt b/src/commonMain/kotlin/starshipfights/game/ai/ai_util_nav.kt index dd1c8d1..55fb9ee 100644 --- a/src/commonMain/kotlin/starshipfights/game/ai/ai_util_nav.kt +++ b/src/commonMain/kotlin/starshipfights/game/ai/ai_util_nav.kt @@ -100,3 +100,21 @@ private fun calculateShipDamageChanceFromBombing(calculatedBombing: Double): Dou return smoothNegative(maxBomberWingOutput - maxFighterWingOutput) } + +fun ShipInstance.navigateTo(targetLocation: Position): PlayerAction.UseAbility { + val myLocation = position.location + + val angleTo = normalDistance(position.facing) angleTo (targetLocation - myLocation) + val maxTurn = movement.turnAngle * 0.99 + val turnNormal = normalDistance(position.facing) rotatedBy angleTo.coerceIn(-maxTurn..maxTurn) + + val move = (movement.moveSpeed * if (turnNormal angleBetween (targetLocation - myLocation) < EPSILON) 0.99 else 0.51) * turnNormal + val newLoc = position.location + move + + val position = ShipPosition(newLoc, move.angle) + + return PlayerAction.UseAbility( + PlayerAbilityType.MoveShip(id), + PlayerAbilityData.MoveShip(position) + ) +} diff --git a/src/commonMain/kotlin/starshipfights/game/ship_instances.kt b/src/commonMain/kotlin/starshipfights/game/ship_instances.kt index 8944127..c1a41bd 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_instances.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_instances.kt @@ -115,6 +115,7 @@ data class ShipWreck( val ship: Ship, val owner: GlobalSide, val isEscape: Boolean = false, + val wreckedAt: Moment = Moment.now ) { val id: Id get() = ship.id.reinterpret() diff --git a/src/jvmMain/kotlin/starshipfights/auth/providers.kt b/src/jvmMain/kotlin/starshipfights/auth/providers.kt index 9dc64c8..f776aaf 100644 --- a/src/jvmMain/kotlin/starshipfights/auth/providers.kt +++ b/src/jvmMain/kotlin/starshipfights/auth/providers.kt @@ -274,7 +274,7 @@ interface AuthProvider { val newShip = ShipInDrydock( name = newShipName, shipType = shipType, - readyAt = Instant.now().plus(6, ChronoUnit.HOURS), + readyAt = Instant.now().plus(2, ChronoUnit.HOURS), owningAdmiral = admiralId ) diff --git a/src/jvmMain/kotlin/starshipfights/data/admiralty/admirals.kt b/src/jvmMain/kotlin/starshipfights/data/admiralty/admirals.kt index 2577a2b..88f1180 100644 --- a/src/jvmMain/kotlin/starshipfights/data/admiralty/admirals.kt +++ b/src/jvmMain/kotlin/starshipfights/data/admiralty/admirals.kt @@ -81,6 +81,24 @@ data class ShipInDrydock( }) } +@Serializable +data class ShipMemorial( + @SerialName("_id") + override val id: Id = Id(), + val name: String, + val shipType: ShipType, + val destroyedAt: @Contextual Instant, + val owningAdmiral: Id, + val destroyedIn: Id, +) : DataDocument { + val fullName: String + get() = "${shipType.faction.shipPrefix}$name" + + companion object Table : DocumentTable by DocumentTable.create({ + index(ShipMemorial::owningAdmiral) + }) +} + suspend fun getAllInGameAdmirals(user: User) = Admiral.filter(Admiral::owningUser eq user.id).map { admiral -> InGameAdmiral( admiral.id.reinterpret(), diff --git a/src/jvmMain/kotlin/starshipfights/game/server_game.kt b/src/jvmMain/kotlin/starshipfights/game/server_game.kt index 74a8f81..9d651c7 100644 --- a/src/jvmMain/kotlin/starshipfights/game/server_game.kt +++ b/src/jvmMain/kotlin/starshipfights/game/server_game.kt @@ -16,6 +16,7 @@ import starshipfights.data.Id import starshipfights.data.admiralty.Admiral import starshipfights.data.admiralty.BattleRecord import starshipfights.data.admiralty.ShipInDrydock +import starshipfights.data.admiralty.ShipMemorial import starshipfights.data.auth.User import starshipfights.data.createToken import java.time.Instant @@ -190,25 +191,16 @@ suspend fun DefaultWebSocketServerSession.gameEndpoint(user: User, token: String private const val SHIP_POINTS_PER_ACUMEN = 5 private suspend fun onGameEnd(gameState: GameState, gameEnd: GameEvent.GameEnd, startedAt: Instant, endedAt: Instant) { - val destroyedShipReadyAt = endedAt.plus(12, ChronoUnit.HOURS) - val damagedShipReadyAt = endedAt.plus(9, ChronoUnit.HOURS) + val damagedShipReadyAt = endedAt.plus(6, ChronoUnit.HOURS) val intactShipReadyAt = endedAt.plus(3, ChronoUnit.HOURS) val escapedShipReadyAt = endedAt.plus(3, ChronoUnit.HOURS) val shipWrecks = gameState.destroyedShips val ships = gameState.ships - val destroyedShips = shipWrecks.filterValues { !it.isEscape }.keys.map { it.reinterpret() }.toSet() - val escapedShips = shipWrecks.filterValues { it.isEscape }.keys.map { it.reinterpret() }.toSet() - val damagedShips = ships.filterValues { it.hullAmount < it.durability.maxHullPoints }.keys.map { it.reinterpret() }.toSet() - val intactShips = ships.keys.map { it.reinterpret() }.toSet() - damagedShips - val hostAdmiralId = gameState.hostInfo.id.reinterpret() val guestAdmiralId = gameState.guestInfo.id.reinterpret() - val hostAcumenGain = shipWrecks.values.filter { it.owner == GlobalSide.GUEST && !it.isEscape }.sumOf { it.ship.pointCost / SHIP_POINTS_PER_ACUMEN } - val guestAcumenGain = shipWrecks.values.filter { it.owner == GlobalSide.HOST && !it.isEscape }.sumOf { it.ship.pointCost / SHIP_POINTS_PER_ACUMEN } - val battleRecord = BattleRecord( battleInfo = gameState.battleInfo, @@ -225,9 +217,35 @@ private suspend fun onGameEnd(gameState: GameState, gameEnd: GameEvent.GameEnd, winMessage = gameEnd.message ) + val destructions = shipWrecks.filterValues { !it.isEscape } + val destroyedShips = destructions.keys.map { it.reinterpret() }.toSet() + val rememberedShips = destructions.values.map { wreck -> + ShipMemorial( + id = Id("RIP_${wreck.id.id}"), + name = wreck.ship.name, + shipType = wreck.ship.shipType, + destroyedAt = wreck.wreckedAt.instant, + owningAdmiral = when (wreck.owner) { + GlobalSide.HOST -> hostAdmiralId + GlobalSide.GUEST -> guestAdmiralId + }, + destroyedIn = battleRecord.id + ) + } + + val escapedShips = shipWrecks.filterValues { it.isEscape }.keys.map { it.reinterpret() }.toSet() + val damagedShips = ships.filterValues { it.hullAmount < it.durability.maxHullPoints }.keys.map { it.reinterpret() }.toSet() + val intactShips = ships.keys.map { it.reinterpret() }.toSet() - damagedShips + + val hostAcumenGain = shipWrecks.values.filter { it.owner == GlobalSide.GUEST && !it.isEscape }.sumOf { it.ship.pointCost / SHIP_POINTS_PER_ACUMEN } + val guestAcumenGain = shipWrecks.values.filter { it.owner == GlobalSide.HOST && !it.isEscape }.sumOf { it.ship.pointCost / SHIP_POINTS_PER_ACUMEN } + coroutineScope { launch { - ShipInDrydock.update(ShipInDrydock::id `in` destroyedShips, setValue(ShipInDrydock::readyAt, destroyedShipReadyAt)) + ShipMemorial.put(rememberedShips) + } + launch { + ShipInDrydock.remove(ShipInDrydock::id `in` destroyedShips) } launch { ShipInDrydock.update(ShipInDrydock::id `in` damagedShips, setValue(ShipInDrydock::readyAt, damagedShipReadyAt)) diff --git a/src/jvmMain/kotlin/starshipfights/info/views_user.kt b/src/jvmMain/kotlin/starshipfights/info/views_user.kt index c5bbdff..1f6c565 100644 --- a/src/jvmMain/kotlin/starshipfights/info/views_user.kt +++ b/src/jvmMain/kotlin/starshipfights/info/views_user.kt @@ -449,12 +449,13 @@ suspend fun ApplicationCall.createAdmiralPage(): HTML.() -> Unit { suspend fun ApplicationCall.admiralPage(): HTML.() -> Unit { val currentUser = getUserSession()?.user val admiralId = parameters["id"]?.let { Id(it) }!! - val (admiral, ships, records) = coroutineScope { - val admiral = async { Admiral.get(admiralId)!! } + val admiral = Admiral.get(admiralId)!! + val (ships, graveyard, records) = coroutineScope { val ships = async { ShipInDrydock.filter(ShipInDrydock::owningAdmiral eq admiralId).toList() } + val graveyard = async { ShipMemorial.filter(ShipMemorial::owningAdmiral eq admiralId).toList() } val records = async { BattleRecord.filter(or(BattleRecord::hostAdmiral eq admiralId, BattleRecord::guestAdmiral eq admiralId)).toList() } - Triple(admiral.await(), ships.await(), records.await()) + Triple(ships.await(), graveyard.await(), records.await()) } val recordRoles = records.mapNotNull { @@ -539,6 +540,47 @@ suspend fun ApplicationCall.admiralPage(): HTML.() -> Unit { } } } + + p { + +"The following ships were lost under " + +(if (admiral.isFemale) "her" else "his") + +" command:" + } + table { + tr { + th { +"Ship Name" } + th { +"Ship Class" } + th { +"Destruction" } + } + + for (ship in graveyard.sortedBy { it.name }.sortedBy { it.shipType.weightClass.tier }) { + tr { + td { +ship.fullName } + td { + a(href = "/info/${ship.shipType.toUrlSlug()}") { + +ship.shipType.fullDisplayName + } + } + td { + +"Destroyed by " + val opponent = recordOpponents[ship.destroyedIn] + if (opponent == null) + i { +"(Deleted Admiral)" } + else + a(href = "/admiral/${opponent.id}") { + +opponent.fullName + } + br + br + +"Destroyed at " + span(classes = "moment") { + style = "display:none" + +ship.destroyedAt.toEpochMilli().toString() + } + } + } + } + } } section { h2 { +"Valor" } -- 2.25.1