@JvmInline
@Serializable(with = IdSerializer::class)
-value class Id<T>(val id: String) {
+value class Id<@Suppress("unused") T>(val id: String) {
override fun toString() = id
fun <U> reinterpret() = Id<U>(id)
return gameState.useWeaponPickResponse(shipInstance, weapon, pickResponse)
}
}
+
+ @Serializable
+ data class RecallStrikeCraft(override val ship: Id<ShipInstance>, override val weapon: Id<ShipWeapon>) : PlayerAbilityType(), CombatAbility {
+ override suspend fun beginOnClient(gameState: GameState, playerSide: GlobalSide, pick: suspend (PickRequest) -> PickResponse?): PlayerAbilityData? {
+ if (gameState.phase !is GamePhase.Attack) return null
+ val shipInstance = gameState.ships[ship] ?: return null
+ if (weapon !in shipInstance.usedArmaments) return null
+ val shipWeapon = shipInstance.armaments.weaponInstances[weapon] ?: return null
+ if (shipWeapon !is ShipWeaponInstance.Hangar) return null
+
+ return PlayerAbilityData.RecallStrikeCraft
+ }
+
+ override fun finishOnServer(gameState: GameState, playerSide: GlobalSide, data: PlayerAbilityData): GameEvent {
+ if (gameState.phase !is GamePhase.Attack) return GameEvent.InvalidAction("Ships can only recall strike craft during Phase III")
+ val shipInstance = gameState.ships[ship] ?: return GameEvent.InvalidAction("That ship does not exist")
+ if (weapon !in shipInstance.usedArmaments) return GameEvent.InvalidAction("Cannot recall unused strike craft")
+ val shipWeapon = shipInstance.armaments.weaponInstances[weapon] ?: return GameEvent.InvalidAction("That weapon does not exist")
+ if (shipWeapon !is ShipWeaponInstance.Hangar) return GameEvent.InvalidAction("Cannot recall non-hangar weapons")
+
+ val hangarWing = ShipHangarWing(ship, weapon)
+
+ return GameEvent.StateChange(
+ gameState.copy(
+ ships = gameState.ships.mapValues { (_, targetShip) ->
+ targetShip.copy(
+ fighterWings = targetShip.fighterWings - hangarWing,
+ bomberWings = targetShip.bomberWings - hangarWing,
+ )
+ }
+ )
+ )
+ }
+ }
}
@Serializable
@Serializable
data class UseWeapon(val target: PickResponse) : PlayerAbilityData()
+
+ @Serializable
+ object RecallStrikeCraft : PlayerAbilityData()
}
fun GameState.getPossibleAbilities(forPlayer: GlobalSide): List<PlayerAbilityType> = if (ready == forPlayer)
val chargeableLances = ships
.filterValues { it.owner == forPlayer && it.weaponAmount > 0 }
.flatMap { (id, ship) ->
- (ship.armaments.weaponInstances - ship.usedArmaments).mapNotNull { (weaponId, weapon) ->
+ ship.armaments.weaponInstances.mapNotNull { (weaponId, weapon) ->
PlayerAbilityType.ChargeLance(id, weaponId).takeIf {
when (weapon) {
- is ShipWeaponInstance.Lance -> weapon.charge != 1.0
+ is ShipWeaponInstance.Lance -> weapon.charge != 1.0 && weaponId !in ship.usedArmaments
else -> false
}
}
val usableWeapons = ships
.filterValues { it.owner == forPlayer }
.flatMap { (id, ship) ->
- (ship.armaments.weaponInstances - ship.usedArmaments).mapNotNull { (weaponId, weapon) ->
+ ship.armaments.weaponInstances.mapNotNull { (weaponId, weapon) ->
PlayerAbilityType.UseWeapon(id, weaponId).takeIf {
weaponId !in ship.usedArmaments && canWeaponBeUsed(ship, weapon)
}
}
}
+ val recallableStrikeWings = ships
+ .filterValues { it.owner == forPlayer }
+ .flatMap { (id, ship) ->
+ ship.armaments.weaponInstances.mapNotNull { (weaponId, weapon) ->
+ PlayerAbilityType.RecallStrikeCraft(id, weaponId).takeIf {
+ weaponId in ship.usedArmaments && weapon is ShipWeaponInstance.Hangar
+ }
+ }
+ }
+
val finishAttacking = listOf(PlayerAbilityType.DonePhase(GamePhase.Attack(phase.turn)))
- chargeableLances + usableWeapons + finishAttacking
+ chargeableLances + usableWeapons + recallableStrikeWings + finishAttacking
}
}
import kotlinx.serialization.Serializable
import starshipfights.data.Id
import kotlin.math.abs
-import kotlin.math.exp
import kotlin.random.Random
@Serializable
val totalFighterHealth = ship.fighterWings.sumOf { (carrierId, wingId) ->
(newShips[carrierId]?.armaments?.weaponInstances?.get(wingId) as? ShipWeaponInstance.Hangar)?.wingHealth ?: 0.0
- }
+ } + ship.ship.durability.turretDefense
val totalBomberHealth = ship.bomberWings.sumOf { (carrierId, wingId) ->
(newShips[carrierId]?.armaments?.weaponInstances?.get(wingId) as? ShipWeaponInstance.Hangar)?.wingHealth ?: 0.0
}
- val maxBomberWingOutput = exp(totalBomberHealth - totalFighterHealth)
- val maxFighterWingOutput = exp(totalFighterHealth - totalBomberHealth)
+ val maxBomberWingOutput = smoothNegative(totalBomberHealth - totalFighterHealth)
+ val maxFighterWingOutput = smoothNegative(totalFighterHealth - totalBomberHealth)
ship.fighterWings.forEach { strikeWingDamage[it] = Random.nextDouble() * maxBomberWingOutput }
ship.bomberWings.forEach { strikeWingDamage[it] = Random.nextDouble() * maxFighterWingOutput }
var hits = 0
- var chanceOfShipDamage = (maxBomberWingOutput - maxFighterWingOutput).coerceAtLeast(0.0) / 2
+ var chanceOfShipDamage = smoothNegative(maxBomberWingOutput - maxFighterWingOutput)
while (chanceOfShipDamage >= 1.0) {
hits++
chanceOfShipDamage -= 1.0
weaponAmount = ship.powerMode.weapons,
shieldAmount = (ship.shieldAmount..ship.powerMode.shields).random(),
- fighterWings = emptyList(),
- bomberWings = emptyList(),
+ fighterWings = emptySet(),
+ bomberWings = emptySet(),
usedArmaments = emptySet(),
)
}
val facing: Double,
val minDistance: Double,
val maxDistance: Double,
- val firingArcs: Set<FiringArc>
+ val firingArcs: Set<FiringArc>,
+
+ val canSelfSelect: Boolean = false
) : PickBoundary() {
override fun contains(point: Position): Boolean {
+ if (canSelfSelect && (point - center).length < EPSILON)
+ return true
+
val r = point - center
if (r.length !in minDistance..maxDistance)
return false
ShipWeightClass.GRAND_CRUISER -> ShipReactor(6, 4)
ShipWeightClass.COLOSSUS -> ShipReactor(9, 6)
+ ShipWeightClass.AUXILIARY_SHIP -> ShipReactor(2, 1)
+ ShipWeightClass.LIGHT_CRUISER -> ShipReactor(3, 1)
+ ShipWeightClass.MEDIUM_CRUISER -> ShipReactor(4, 2)
ShipWeightClass.HEAVY_CRUISER -> ShipReactor(6, 3)
ShipWeightClass.FRIGATE -> ShipReactor(4, 1)
ShipWeightClass.GRAND_CRUISER -> ShipMovement(PI / 4, 600.0)
ShipWeightClass.COLOSSUS -> ShipMovement(PI / 6, 400.0)
+ ShipWeightClass.AUXILIARY_SHIP -> ShipMovement(PI / 2, 800.0)
+ ShipWeightClass.LIGHT_CRUISER -> ShipMovement(PI / 2, 700.0)
+ ShipWeightClass.MEDIUM_CRUISER -> ShipMovement(PI / 3, 600.0)
ShipWeightClass.HEAVY_CRUISER -> ShipMovement(PI / 3, 500.0)
ShipWeightClass.FRIGATE -> ShipMovement(PI * 2 / 3, 1000.0)
@Serializable
data class ShipDurability(
val maxHullPoints: Int,
+ val turretDefense: Double,
)
val ShipWeightClass.durability: ShipDurability
get() = when (this) {
- ShipWeightClass.ESCORT -> ShipDurability(2)
- ShipWeightClass.DESTROYER -> ShipDurability(4)
- ShipWeightClass.CRUISER -> ShipDurability(6)
- ShipWeightClass.BATTLECRUISER -> ShipDurability(7)
- ShipWeightClass.BATTLESHIP -> ShipDurability(9)
+ ShipWeightClass.ESCORT -> ShipDurability(2, 0.5)
+ ShipWeightClass.DESTROYER -> ShipDurability(4, 0.5)
+ ShipWeightClass.CRUISER -> ShipDurability(6, 1.0)
+ ShipWeightClass.BATTLECRUISER -> ShipDurability(7, 1.0)
+ ShipWeightClass.BATTLESHIP -> ShipDurability(9, 2.0)
- ShipWeightClass.GRAND_CRUISER -> ShipDurability(8)
- ShipWeightClass.COLOSSUS -> ShipDurability(13)
+ ShipWeightClass.GRAND_CRUISER -> ShipDurability(8, 1.5)
+ ShipWeightClass.COLOSSUS -> ShipDurability(13, 3.0)
- ShipWeightClass.HEAVY_CRUISER -> ShipDurability(8)
+ ShipWeightClass.AUXILIARY_SHIP -> ShipDurability(2, 2.0)
+ ShipWeightClass.LIGHT_CRUISER -> ShipDurability(4, 3.0)
+ ShipWeightClass.MEDIUM_CRUISER -> ShipDurability(6, 3.5)
+ ShipWeightClass.HEAVY_CRUISER -> ShipDurability(8, 4.0)
- ShipWeightClass.FRIGATE -> ShipDurability(4)
- ShipWeightClass.LINE_SHIP -> ShipDurability(7)
- ShipWeightClass.DREADNOUGHT -> ShipDurability(10)
+ ShipWeightClass.FRIGATE -> ShipDurability(4, 1.5)
+ ShipWeightClass.LINE_SHIP -> ShipDurability(7, 2.0)
+ ShipWeightClass.DREADNOUGHT -> ShipDurability(10, 2.5)
}
val armaments: ShipInstanceArmaments = ship.armaments.instantiate(),
val usedArmaments: Set<Id<ShipWeapon>> = emptySet(),
- val fighterWings: List<ShipHangarWing> = emptyList(),
- val bomberWings: List<ShipHangarWing> = emptyList(),
+ val fighterWings: Set<ShipHangarWing> = emptySet(),
+ val bomberWings: Set<ShipHangarWing> = emptySet(),
) {
val id: Id<ShipInstance>
get() = ship.id.reinterpret()
COLOSSUS(5, 5),
// Isarnareykk-specific
+ AUXILIARY_SHIP(1, 0),
+ LIGHT_CRUISER(2, 1),
+ MEDIUM_CRUISER(3, 2),
HEAVY_CRUISER(4, 4),
// Vestigium-specific
GRAND_CRUISER -> 300
COLOSSUS -> 500
+ AUXILIARY_SHIP -> 50
+ LIGHT_CRUISER -> 100
+ MEDIUM_CRUISER -> 200
HEAVY_CRUISER -> 400
FRIGATE -> 150
AEDON(Faction.MASRA_DRAETSEN, ShipWeightClass.COLOSSUS),
// Isarnareykk
- GANNAN(Faction.ISARNAREYKK, ShipWeightClass.ESCORT),
- LODOVIK(Faction.ISARNAREYKK, ShipWeightClass.ESCORT),
-
- KARNAS(Faction.ISARNAREYKK, ShipWeightClass.DESTROYER),
- PERTONA(Faction.ISARNAREYKK, ShipWeightClass.DESTROYER),
- VOSS(Faction.ISARNAREYKK, ShipWeightClass.DESTROYER),
-
- BREKORYN(Faction.ISARNAREYKK, ShipWeightClass.CRUISER),
- FALK(Faction.ISARNAREYKK, ShipWeightClass.CRUISER),
- LORUS(Faction.ISARNAREYKK, ShipWeightClass.CRUISER),
- ORSH(Faction.ISARNAREYKK, ShipWeightClass.CRUISER),
- TEFRAN(Faction.ISARNAREYKK, ShipWeightClass.CRUISER),
+ GANNAN(Faction.ISARNAREYKK, ShipWeightClass.AUXILIARY_SHIP),
+ LODOVIK(Faction.ISARNAREYKK, ShipWeightClass.AUXILIARY_SHIP),
+
+ KARNAS(Faction.ISARNAREYKK, ShipWeightClass.LIGHT_CRUISER),
+ PERTONA(Faction.ISARNAREYKK, ShipWeightClass.LIGHT_CRUISER),
+ VOSS(Faction.ISARNAREYKK, ShipWeightClass.LIGHT_CRUISER),
+
+ BREKORYN(Faction.ISARNAREYKK, ShipWeightClass.MEDIUM_CRUISER),
+ FALK(Faction.ISARNAREYKK, ShipWeightClass.MEDIUM_CRUISER),
+ LORUS(Faction.ISARNAREYKK, ShipWeightClass.MEDIUM_CRUISER),
+ ORSH(Faction.ISARNAREYKK, ShipWeightClass.MEDIUM_CRUISER),
+ TEFRAN(Faction.ISARNAREYKK, ShipWeightClass.MEDIUM_CRUISER),
KASSCK(Faction.ISARNAREYKK, ShipWeightClass.HEAVY_CRUISER),
KHORR(Faction.ISARNAREYKK, ShipWeightClass.HEAVY_CRUISER),
companion object {
val FIRE_360: Set<FiringArc> = setOf(BOW, ABEAM_PORT, ABEAM_STARBOARD, STERN)
val FIRE_BROADSIDE: Set<FiringArc> = setOf(ABEAM_PORT, ABEAM_STARBOARD)
+ val FIRE_FORE_270: Set<FiringArc> = setOf(BOW, ABEAM_PORT, ABEAM_STARBOARD)
}
}
is ShipWeaponInstance.Hangar -> {
ImpactResult.Damaged(
if (weapon.weapon.wing == StrikeCraftWing.FIGHTERS)
- copy(fighterWings = fighterWings + listOf(ShipHangarWing(by.id, weaponId)))
+ copy(fighterWings = fighterWings + setOf(ShipHangarWing(by.id, weaponId)))
else
- copy(bomberWings = bomberWings + listOf(ShipHangarWing(by.id, weaponId)))
+ copy(bomberWings = bomberWings + setOf(ShipHangarWing(by.id, weaponId)))
)
}
is ShipWeaponInstance.Torpedo -> {
PickRequest(
PickType.Ship(targetSet),
- PickBoundary.WeaponsFire(position.currentLocation, position.facingAngle, weapon.minRange, weapon.maxRange, weapon.firingArcs)
+ PickBoundary.WeaponsFire(
+ center = position.currentLocation,
+ facing = position.facingAngle,
+ minDistance = weapon.minRange,
+ maxDistance = weapon.maxRange,
+ firingArcs = weapon.firingArcs,
+ canSelfSelect = side in targetSet
+ )
)
}
}
fun diadochiShipWeapons(
torpedoes: Int,
- foreLances: Int,
hasRevelationGun: Boolean,
cannonSections: Int,
idCounter.add(weapons, ShipWeapon.Torpedo(setOf(FiringArc.BOW), "Fore torpedo launchers"))
}
- idCounter.add(weapons, ShipWeapon.Lance(foreLances, setOf(FiringArc.BOW), "Fore lance battery"))
-
if (hasRevelationGun)
idCounter.add(weapons, ShipWeapon.RevelationGun)
}
repeat(dorsalLances) {
- idCounter.add(weapons, ShipWeapon.Lance(2, FiringArc.FIRE_BROADSIDE, "Dorsal lance turrets"))
+ idCounter.add(weapons, ShipWeapon.Lance(2, FiringArc.FIRE_FORE_270, "Dorsal lance batteries"))
}
return ShipArmaments(weapons)
ShipType.NOVA_ROMA -> mechyrdiaShipWeapons(3, false, 0, 3, 0, 1)
ShipType.TYLA -> mechyrdiaShipWeapons(3, false, 1, 0, 2, 1)
- ShipType.ERIS -> diadochiShipWeapons(2, 0, false, 1, 0, 0, 0)
- ShipType.TYPHON -> diadochiShipWeapons(0, 2, false, 1, 0, 0, 0)
- ShipType.AHRIMAN -> diadochiShipWeapons(1, 0, false, 0, 1, 0, 0)
- ShipType.APOPHIS -> diadochiShipWeapons(1, 0, false, 0, 0, 1, 1)
- ShipType.AZATHOTH -> diadochiShipWeapons(1, 0, false, 1, 0, 0, 0)
- ShipType.CHERNOBOG -> diadochiShipWeapons(2, 0, false, 0, 2, 0, 0)
- ShipType.CIPACTLI -> diadochiShipWeapons(2, 0, false, 2, 0, 0, 0)
- ShipType.LOTAN -> diadochiShipWeapons(2, 0, false, 0, 0, 2, 2)
- ShipType.MORGOTH -> diadochiShipWeapons(2, 0, false, 1, 1, 0, 0)
- ShipType.TIAMAT -> diadochiShipWeapons(2, 0, false, 1, 0, 1, 1)
- ShipType.CHARYBDIS -> diadochiShipWeapons(3, 0, false, 3, 0, 0, 0)
- ShipType.SCYLLA -> diadochiShipWeapons(3, 0, false, 1, 0, 2, 0)
- ShipType.AEDON -> diadochiShipWeapons(0, 0, true, 3, 0, 0, 0)
+ ShipType.ERIS -> diadochiShipWeapons(2, false, 1, 0, 0, 0)
+ ShipType.TYPHON -> diadochiShipWeapons(0, false, 1, 0, 0, 1)
+ ShipType.AHRIMAN -> diadochiShipWeapons(1, false, 0, 1, 0, 0)
+ ShipType.APOPHIS -> diadochiShipWeapons(1, false, 0, 0, 1, 1)
+ ShipType.AZATHOTH -> diadochiShipWeapons(1, false, 1, 0, 0, 0)
+ ShipType.CHERNOBOG -> diadochiShipWeapons(2, false, 0, 2, 0, 0)
+ ShipType.CIPACTLI -> diadochiShipWeapons(2, false, 2, 0, 0, 0)
+ ShipType.LOTAN -> diadochiShipWeapons(2, false, 0, 0, 2, 2)
+ ShipType.MORGOTH -> diadochiShipWeapons(2, false, 1, 1, 0, 0)
+ ShipType.TIAMAT -> diadochiShipWeapons(2, false, 1, 0, 1, 1)
+ ShipType.CHARYBDIS -> diadochiShipWeapons(3, false, 3, 0, 0, 0)
+ ShipType.SCYLLA -> diadochiShipWeapons(3, false, 1, 0, 2, 0)
+ ShipType.AEDON -> diadochiShipWeapons(0, true, 3, 0, 0, 0)
ShipType.GANNAN -> fulkreykkShipWeapons(0, true, 0, 0)
ShipType.LODOVIK -> fulkreykkShipWeapons(4, false, 0, 0)
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.serialization.json.Json
+import kotlin.math.exp
import kotlin.math.roundToInt
val jsonSerializer = Json {
else this + (this * (multiplier - 1))
fun Double.toPercent() = "${(this * 100).roundToInt()}%"
+
+fun smoothNegative(x: Double) = if (x < 0) exp(x) else x + 1
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.features.websocket.*
+import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.json.decodeFromDynamic
+import org.w3c.dom.HTMLScriptElement
val rootPathWs = "ws" + window.location.origin.removePrefix("http")
@OptIn(ExperimentalSerializationApi::class)
val clientMode: ClientMode = try {
- jsonSerializer.decodeFromDynamic(ClientMode.serializer(), window.asDynamic().sfClientMode)
-} catch (_: Exception) {
+ jsonSerializer.decodeFromString(
+ ClientMode.serializer(),
+ document.getElementById("sf-client-mode").unsafeCast<HTMLScriptElement>().text
+ )
+} catch (ex: Exception) {
+ ex.printStackTrace()
+ ClientMode.Error("Invalid client mode sent from server")
+} catch (dyn: dynamic) {
+ console.error(dyn)
ClientMode.Error("Invalid client mode sent from server")
}
}
fun main() {
+ window.addEventListener("beforeunload", { e ->
+ if (interruptExit) {
+ e.preventDefault()
+ e.asDynamic().returnValue = ""
+ }
+ })
+
AppScope.launch {
Popup.LoadingScreen("Loading resources...") {
RenderResources.load(clientMode !is ClientMode.InGame)
}.display()
- window.addEventListener("beforeunload", { e ->
- e.preventDefault()
- e.asDynamic().returnValue = ""
- })
-
when (clientMode) {
is ClientMode.MatchmakingMenu -> matchmakingMain(clientMode.admirals)
is ClientMode.InGame -> gameMain(clientMode.playerSide, clientMode.connectToken, clientMode.initialState)
}
}
}
+
+var interruptExit: Boolean = false
}
suspend fun gameMain(side: GlobalSide, token: String, state: GameState) {
+ interruptExit = true
+
initializePicking()
mySide = side
val finalMessage = connectionJob.await()
renderingJob.cancel()
+ interruptExit = false
Popup.GameOver(finalMessage, gameState.value).display()
}
}
}
private suspend fun enterGame(connectToken: String): Nothing {
+ interruptExit = false
+
document.body!!.append.form(action = "/play", method = FormMethod.post, encType = FormEncType.applicationXWwwFormUrlEncoded) {
style = "display:none"
hiddenInput {
}
suspend fun matchmakingMain(admirals: List<InGameAdmiral>) {
+ interruptExit = true
+
coroutineScope {
launch { setupBackground() }
launch { usePlayerLogin(admirals) }
val firingArcsDesc = when (firingArcs) {
FiringArc.FIRE_360 -> "360-Degree"
FiringArc.FIRE_BROADSIDE -> "Broadside"
+ FiringArc.FIRE_FORE_270 -> "Dorsal"
setOf(FiringArc.ABEAM_PORT) -> "Port"
setOf(FiringArc.ABEAM_STARBOARD) -> "Starboard"
setOf(FiringArc.BOW) -> "Fore"
}
}
}
+ is PlayerAbilityType.RecallStrikeCraft -> {
+ a(href = "#") {
+ +"Recall "
+ if (firingArcsDesc != null)
+ +"$firingArcsDesc "
+ +weaponDesc
+ onClickFunction = { e ->
+ e.preventDefault()
+ responder.useAbility(ability)
+ }
+ }
+ }
}
}
}
.unsafeCast<Shape>()
)
}
- is PickBoundary.AlongLine -> emptyList()
+ is PickBoundary.AlongLine -> emptyList() // Handled in a special case
is PickBoundary.Rectangle -> listOf(
Shape()
.moveTo(RenderScaling.toWorldLength(center.vector.x + width2), RenderScaling.toWorldLength(center.vector.y + length2))
}
script {
+ attributes["id"] = "sf-client-mode"
+ type = "application/json"
unsafe {
- +"window.sfClientMode = "
+jsonSerializer.encodeToString(ClientMode.serializer(), this@view)
- +";"
}
}
"About", standardNavBar(), null
) {
section {
- img(alt = "Starship Fights Logo", src = "/static/images/logo.svg") {
- style = "width:100%"
- }
+ h1 { +"In Development" }
p {
+"This is a test instance of Starship Fights."
}
)
) {
section {
- img(alt = "Starship Fights Logo", src = "/static/images/logo.svg") {
- style = "width:100%"
- }
+ h1 { +"About Starship Fights" }
p {
+"Starship Fights is designed and programmed by the person behind "
a(href = "https://nationstates.net/mechyrdia") { +"Mechyrdia" }
table {
tr {
- th {
- +"Weight Class"
- br
- +"(Point Cost)"
- }
- th {
- +"Hull Integrity"
- }
- th { +"Max Acceleration" }
- th { +"Max Rotation" }
- th {
- +"Reactor Power"
- br
- +"(Per Subsystem)"
- }
- th { +"Energy Flow" }
+ th { +"Weight Class" }
+ th { +"Hull Integrity" }
+ th { +"Defense Turrets" }
}
tr {
td {
+shipType.weightClass.displayName
br
- +"(${shipType.weightClass.basePointCost})"
+ +"(${shipType.weightClass.basePointCost} points to deploy)"
}
td {
+"${shipType.weightClass.durability.maxHullPoints} impacts"
}
td {
- +"${shipType.weightClass.movement.moveSpeed.roundToInt()} meters/turn"
+ +"${shipType.weightClass.durability.turretDefense.toPercent()} fighter-wing equivalent"
}
+ }
+ tr {
+ th { +"Max Movement" }
+ th { +"Reactor Power" }
+ th { +"Energy Flow" }
+ }
+ tr {
td {
- +"${(shipType.weightClass.movement.turnAngle * 180.0 / PI).roundToInt()} degrees/turn"
+ +"Accelerate ${shipType.weightClass.movement.moveSpeed.roundToInt()} meters/turn"
+ br
+ +"Rotate ${(shipType.weightClass.movement.turnAngle * 180.0 / PI).roundToInt()} degrees/turn"
}
td {
+shipType.weightClass.reactor.powerOutput.toString()
br
- +"(${shipType.weightClass.reactor.subsystemAmount})"
+ +"(${shipType.weightClass.reactor.subsystemAmount} per subsystem)"
}
td {
+shipType.weightClass.reactor.gridEfficiency.toString()
<path
style="fill:#2255bb;fill-opacity:1"
id="path12"
- d="M7.002 3.01h-5v8h8v-5h-1v4h-6v-6h4z"
- fill="#36b"/>
+ d="M7.002 3.01h-5v8h8v-5h-1v4h-6v-6h4z"/>
<path
style="fill:#4477dd;fill-opacity:1"
id="path10"
- d="M5.002 1.01h7v7l-2-2-3 2v-1l3-2.25 1 1V2.01h-3.75l1 1-2.25 3h-1l2-3z"
- fill="#36b"/>
+ d="M5.002 1.01h7v7l-2-2-3 2v-1l3-2.25 1 1V2.01h-3.75l1 1-2.25 3h-1l2-3z"/>
<path
style="fill:#6699ff;fill-opacity:1"
id="path14"
- d="M4.082 5.51c0-.621.621-.621.621-.621 1.864.621 3.107 1.864 3.728 3.728 0 0 0 .621-.62.621-1.245-1.864-1.866-2.485-3.73-3.728z"
- fill="#15a5ea"/>
+ d="M4.082 5.51c0-.621.621-.621.621-.621 1.864.621 3.107 1.864 3.728 3.728 0 0 0 .621-.62.621-1.245-1.864-1.866-2.485-3.73-3.728z"/>
</svg>