}
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)
}
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)
+ )
+}
val ship: Ship,
val owner: GlobalSide,
val isEscape: Boolean = false,
+ val wreckedAt: Moment = Moment.now
) {
val id: Id<ShipInstance>
get() = ship.id.reinterpret()
val newShip = ShipInDrydock(
name = newShipName,
shipType = shipType,
- readyAt = Instant.now().plus(6, ChronoUnit.HOURS),
+ readyAt = Instant.now().plus(2, ChronoUnit.HOURS),
owningAdmiral = admiralId
)
})
}
+@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(),
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
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,
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))
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 {
}
}
}
+
+ 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" }