Ships are destroyed when they are destroyed
authorTheSaminator <TheSaminator@users.noreply.github.com>
Mon, 6 Jun 2022 23:33:35 +0000 (19:33 -0400)
committerTheSaminator <TheSaminator@users.noreply.github.com>
Mon, 6 Jun 2022 23:33:35 +0000 (19:33 -0400)
src/commonMain/kotlin/starshipfights/game/ai/ai_behaviors.kt
src/commonMain/kotlin/starshipfights/game/ai/ai_util_nav.kt
src/commonMain/kotlin/starshipfights/game/ship_instances.kt
src/jvmMain/kotlin/starshipfights/auth/providers.kt
src/jvmMain/kotlin/starshipfights/data/admiralty/admirals.kt
src/jvmMain/kotlin/starshipfights/game/server_game.kt
src/jvmMain/kotlin/starshipfights/info/views_user.kt

index f1d4a0405350718d2cf1da93a1853f8e0983ce71..a2f392aabac3723301eb23eac66e0aa3dbd7d951 100644 (file)
@@ -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)
 }
index dd1c8d1561e09e3a4a0d8491d58f5d1fc61ea588..55fb9ee5eb1cb1f8843d01bccbfed6981c6e4690 100644 (file)
@@ -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)
+       )
+}
index 894412767719c51487c4f869a845bfe356896d94..c1a41bd459e2853d79f9eac4570ebdafbbba056e 100644 (file)
@@ -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<ShipInstance>
                get() = ship.id.reinterpret()
index 9dc64c8074d8025d60a6284be810a5e8a31c9135..f776aaf9c0fd01094611727ebe7f90a7b81a1e53 100644 (file)
@@ -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
                                        )
                                        
index 2577a2bad595c08f7d87848d44c2fb8045b9a077..88f11809340b63f51bd2c020c748999f582fc107 100644 (file)
@@ -81,6 +81,24 @@ data class ShipInDrydock(
        })
 }
 
+@Serializable
+data class ShipMemorial(
+       @SerialName("_id")
+       override val id: Id<ShipMemorial> = Id(),
+       val name: String,
+       val shipType: ShipType,
+       val destroyedAt: @Contextual Instant,
+       val owningAdmiral: Id<Admiral>,
+       val destroyedIn: Id<BattleRecord>,
+) : DataDocument<ShipMemorial> {
+       val fullName: String
+               get() = "${shipType.faction.shipPrefix}$name"
+       
+       companion object Table : DocumentTable<ShipMemorial> by DocumentTable.create({
+               index(ShipMemorial::owningAdmiral)
+       })
+}
+
 suspend fun getAllInGameAdmirals(user: User) = Admiral.filter(Admiral::owningUser eq user.id).map { admiral ->
        InGameAdmiral(
                admiral.id.reinterpret(),
index 74a8f81abbf95d4bf2fb6b07b80e9a61590c0890..9d651c77cb918104c62e2b8ea88a9fed6f3b1f15 100644 (file)
@@ -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<ShipInDrydock>() }.toSet()
-       val escapedShips = shipWrecks.filterValues { it.isEscape }.keys.map { it.reinterpret<ShipInDrydock>() }.toSet()
-       val damagedShips = ships.filterValues { it.hullAmount < it.durability.maxHullPoints }.keys.map { it.reinterpret<ShipInDrydock>() }.toSet()
-       val intactShips = ships.keys.map { it.reinterpret<ShipInDrydock>() }.toSet() - damagedShips
-       
        val hostAdmiralId = gameState.hostInfo.id.reinterpret<Admiral>()
        val guestAdmiralId = gameState.guestInfo.id.reinterpret<Admiral>()
        
-       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<ShipInDrydock>() }.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<ShipInDrydock>() }.toSet()
+       val damagedShips = ships.filterValues { it.hullAmount < it.durability.maxHullPoints }.keys.map { it.reinterpret<ShipInDrydock>() }.toSet()
+       val intactShips = ships.keys.map { it.reinterpret<ShipInDrydock>() }.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))
index c5bbdffcd9eb534175c24062d793feab44b5b06d..1f6c565b3170f8aee09c792218cf76149ddb0b29 100644 (file)
@@ -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<Admiral>(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" }