+++ /dev/null
-# Starship Fights
-
-## Phases of Battle
-
-Each turn consists of several phases. Each player takes their turn simultaneously.
-
-### Power Phase
-
-Distribute power among the ship's subsystems:
-
-* Weapons - Used to attack other ships
-* Shields - Used to defend against attacks
-* Impulse - Used to move around in space
-* Emitter - Used for special abilities
-
-Power starts off as being evenly split between all four subsystems. The ship's Grid Efficiency
-is how many Power Output (PO) points can be transferred between subsystems in any given Power Phase. Once the
-phase ends, the power distribution stays like it is until it is modified again, i.e. it never automatically
-resets back to the default evenly-shared distribution.
-
-Different ship tiers have different total amounts of power, as well as a certain Grid Efficiency:
-
-1. Escort: 8 PO, 1 GE
-2. *Frigate* (Vestigium only): 12 PO, 1 GE
-3. Destroyer: 12 PO, 2 GE
-4. Cruiser: 16 PO, 3 GE
-5. *Line Ship* (Vestigium only): 20 PO, 3 GE
-6. Battlecruiser: 16 PO, 4 GE
-7. *Heavy Cruiser* (Isarnareykk only): 24 PO, 3 GE
-8. Battleship: 24 PO, 4 GE
-9. *Dreadnought* (Vestigium only): 28 PO, 5 GE
-10. *Colossus* (Masra Draetsen only): 36 PO, 7 GE
-
-The effects that different power levels have is as follows:
-
-* Each unit of Weapon Power is consumed when a ship fires a weapon or charges a Lance battery
- * Weapon power is replenished completely after the End phase ends
-* Each unit of Shield Power is consumed when a ship's shields block an attack
- * Shield power is replenished after the End phase ends
- * Each Blast Marker the ship is touching has a 50% chance of reducing the replenished shield power by 1
-* Engine power modifies the ship's maximum acceleration
- * The normal Engine Power `e_0` is 25% of the total PO
- * The current Engine Power `e_1` is however many points the ship has put into its Engines
- * The movement speed factor is `η = sqrt(e_1 / e_0)`
- * The maximum distance moved during the Movement phase is `a_max = a_max_0 * η` where `a_max_0` is the default max movement
- * The maximum angle turned during the Movement phase is `α_max = α_max_0 * η`
-* Emitter power modifies the power of the ship's Emitter abilities
- * Similar factor: `μ = sqrt(m_1 / m_0)`
- * The reason why the `sqrt` function is used is to represent the diminishing returns of putting more power into a subsystem
-
-### Movement Phase
-
-Ships change their velocity during this phase. Velocity as a property of ships is persistent,
-being modified by the ship's acceleration which is decided during this phase.
-
-The way ships move is as follows, where ship's maximum acceleration is `a_max`, current position is `x_1`, previous position is `x_0`, and current ship facing is `θ`:
-
-1. Ship rotates a certain amount (up to maximum rotation) chosen by the player away from `θ`, this new facing angle is put into `φ`.
-2. A line is drawn starting at `x_1 + (x_1 - x_0)`, this point is put into `x_2`.
-3. The line traverses the vector `a_max * (î cos ((θ + φ) / 2) + ĵ sin ((θ + φ) / /2))` away from `x_2`
-4. The player chooses a point along this line for the ship to travel to, this point is put into `x_new`
-5. `x_1 -> x_0`, then `x_new -> x_1` and `φ -> θ`
-
-### Action Phase
-
-Ships call attacks against other ships during this phase,
-as well as defending themselves or each other from those attacks.
-
-### End Phase
-
-Attacks, defenses, and results of actions are resolved.
-
-Blast markers are created; each successfully-hit attack creates a blast marker within
-a certain radius of the targeted ship.
val shipInstance = gameState.ships[ship] ?: return GameEvent.InvalidAction("That ship does not exist")
if (!shipInstance.validatePowerMode(data.powerMode)) return GameEvent.InvalidAction("Invalid power distribution")
+ val prevShieldDamage = shipInstance.powerMode.shields - shipInstance.shieldAmount
+
val newShipInstance = shipInstance.copy(
powerMode = data.powerMode,
isDoneCurrentPhase = true,
weaponAmount = data.powerMode.weapons,
- shieldAmount = data.powerMode.shields,
+ shieldAmount = (data.powerMode.shields - prevShieldDamage).coerceAtLeast(0),
)
val newShips = gameState.ships + mapOf(ship to newShipInstance)
val newChatEntries = mutableListOf<ChatEntry>()
when (phase) {
- is GamePhase.Power -> {
- newShips = newShips.mapValues { (_, ship) ->
- ship.copy(
- weaponAmount = ship.powerMode.weapons,
- shieldAmount = ship.powerMode.shields,
- )
- }
- }
is GamePhase.Move -> {
+ // Auto-move drifting ships
newShips = newShips.mapValues { (_, ship) ->
if (ship.isDoneCurrentPhase) ship
else ship.copy(position = ship.position.drift)
}
+
+ // Ships that move off the battlefield are considered to disengage
newShips = newShips.mapNotNull fleeingShips@{ (id, ship) ->
val r = ship.position.currentLocation.vector
val mx = start.battlefieldWidth / 2
id to ship
}.toMap()
+
+ // Identify enemy ships
newShips = newShips.mapValues { (_, ship) ->
if (ship.isIdentified) ship
else if (newShips.values.any { it.owner != ship.owner && (it.position.currentLocation - ship.position.currentLocation).length <= SHIP_SENSOR_RANGE })
is GamePhase.Attack -> {
val strikeWingDamage = mutableMapOf<ShipHangarWing, Double>()
+ // Apply damage to ships from strike craft
newShips = newShips.mapNotNull strikeBombard@{ (id, ship) ->
if (ship.bomberWings.isEmpty())
return@strikeBombard id to ship
}
}
}.toMap()
+
+ // Apply damage to strike craft wings
newShips = newShips.mapValues { (shipId, ship) ->
val newArmaments = ship.armaments.weaponInstances.mapValues { (weaponId, weapon) ->
if (weapon is ShipWeaponInstance.Hangar)
armaments = ShipInstanceArmaments(newArmaments)
)
}
+
+ // Recall strike craft and regenerate weapon and shield powers
newShips = newShips.mapValues { (_, ship) ->
ship.copy(
+ weaponAmount = ship.powerMode.weapons,
+ shieldAmount = (ship.shieldAmount..ship.powerMode.shields).random(),
+
fighterWings = emptyList(),
bomberWings = emptyList(),
usedArmaments = emptySet(),
}
enum class ShipSubsystem {
- WEAPONS, SHIELDS, ENGINES, EMITTER;
+ WEAPONS, SHIELDS, ENGINES, BATTERY;
val displayName: String
get() = name.lowercase().replaceFirstChar { it.uppercase() }
WEAPONS -> "#FF6633"
SHIELDS -> "#6699FF"
ENGINES -> "#FFCC33"
- EMITTER -> "#33FF66"
+ BATTERY -> "#33FF66"
}
val imageUrl: String
val weapons: Int,
val shields: Int,
val engines: Int,
- val emitter: Int,
+ val battery: Int,
) {
operator fun plus(delta: Map<ShipSubsystem, Int>) = copy(
weapons = weapons + (delta[ShipSubsystem.WEAPONS] ?: 0),
shields = shields + (delta[ShipSubsystem.SHIELDS] ?: 0),
engines = engines + (delta[ShipSubsystem.ENGINES] ?: 0),
- emitter = emitter + (delta[ShipSubsystem.EMITTER] ?: 0),
+ battery = battery + (delta[ShipSubsystem.BATTERY] ?: 0),
)
operator fun minus(delta: Map<ShipSubsystem, Int>) = this + delta.mapValues { (_, d) -> -d }
ShipSubsystem.WEAPONS -> weapons
ShipSubsystem.SHIELDS -> shields
ShipSubsystem.ENGINES -> engines
- ShipSubsystem.EMITTER -> emitter
+ ShipSubsystem.BATTERY -> battery
}
val total: Int
- get() = weapons + shields + engines + emitter
+ get() = weapons + shields + engines + battery
infix fun distanceTo(other: ShipPowerMode) = ShipSubsystem.values().sumOf { subsystem -> abs(this[subsystem] - other[subsystem]) }
}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ viewBox="0 0 64 64"
+ height="32px"
+ width="32px">
+ <g
+ transform="translate(-83.660604,-139.51202)"
+ id="layer1">
+ <path
+ id="path1169"
+ d="m 115.72068,139.51211 a 31.999999,31.999999 0 0 0 -20.629226,7.48636 l 2.57142,3.0639 a 28,28 0 0 1 27.574536,-4.86172 l 1.36788,-3.75895 a 31.999999,31.999999 0 0 0 -10.88461,-1.92959 z m -0.015,7.99951 a 23.999999,23.999999 0 0 0 -15.47194,5.61516 l 2.57142,3.0639 a 19.999999,19.999999 0 0 1 19.69596,-3.47266 l 1.36787,-3.75894 a 23.999999,23.999999 0 0 0 -8.16332,-1.44746 z m -0.015,8.00003 a 15.999999,15.999999 0 0 0 -10.31461,3.74344 l 2.57142,3.06441 a 12,12 0 0 1 11.81736,-2.08411 l 1.36839,-3.75843 a 15.999999,15.999999 0 0 0 -5.44256,-0.96531 z m 16.4698,0 -3.99976,5.3e-4 v 8.00002 h -2.50011 v 2.99982 h -22.0002 v -4.99918 h -9.999906 l -9.9999,9.99887 9.9999,9.99991 h 9.999906 v -5.00021 h 22.0002 v 3.00033 h 2.50011 v 7.99951 l 3.99976,5.3e-4 8.00003,-8.00003 h 2.5001 v -7.00009 h 5.00021 v -1.99988 h -5.00021 v -6.99957 h -2.5001 z m 11.50007,9.001 v 4.99969 h 2.99982 v -4.99969 z m 0,8.99841 v 4.9997 h 2.99982 v -4.9997 z m -35.71358,7.19336 -2.5709,3.0639 a 16.000074,16.000074 0 0 0 15.75717,2.77864 l -1.36839,-3.75894 a 12.000056,12.000056 0 0 1 -11.81788,-2.0836 z m -5.14234,6.12831 -2.57091,3.06441 a 24.000112,24.000112 0 0 0 23.63525,4.16719 l -1.36787,-3.75843 a 20.000093,20.000093 0 0 1 -19.69647,-3.47317 z m -5.142316,6.12831 -2.57091,3.06441 a 32.000153,32.000153 0 0 0 31.513836,5.55677 l -1.36788,-3.75895 a 28.000129,28.000129 0 0 1 -27.575046,-4.86223 z"
+ style="fill:#33ff66;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17500019;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"/>
+ </g>
+</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns="http://www.w3.org/2000/svg"
- version="1.1"
- viewBox="0 0 64 64"
- height="32px"
- width="32px">
- <g
- transform="translate(-83.660604,-139.51202)"
- id="layer1">
- <path
- id="path1169"
- d="m 115.72068,139.51211 a 31.999999,31.999999 0 0 0 -20.629226,7.48636 l 2.57142,3.0639 a 28,28 0 0 1 27.574536,-4.86172 l 1.36788,-3.75895 a 31.999999,31.999999 0 0 0 -10.88461,-1.92959 z m -0.015,7.99951 a 23.999999,23.999999 0 0 0 -15.47194,5.61516 l 2.57142,3.0639 a 19.999999,19.999999 0 0 1 19.69596,-3.47266 l 1.36787,-3.75894 a 23.999999,23.999999 0 0 0 -8.16332,-1.44746 z m -0.015,8.00003 a 15.999999,15.999999 0 0 0 -10.31461,3.74344 l 2.57142,3.06441 a 12,12 0 0 1 11.81736,-2.08411 l 1.36839,-3.75843 a 15.999999,15.999999 0 0 0 -5.44256,-0.96531 z m 16.4698,0 -3.99976,5.3e-4 v 8.00002 h -2.50011 v 2.99982 h -22.0002 v -4.99918 h -9.999906 l -9.9999,9.99887 9.9999,9.99991 h 9.999906 v -5.00021 h 22.0002 v 3.00033 h 2.50011 v 7.99951 l 3.99976,5.3e-4 8.00003,-8.00003 h 2.5001 v -7.00009 h 5.00021 v -1.99988 h -5.00021 v -6.99957 h -2.5001 z m 11.50007,9.001 v 4.99969 h 2.99982 v -4.99969 z m 0,8.99841 v 4.9997 h 2.99982 v -4.9997 z m -35.71358,7.19336 -2.5709,3.0639 a 16.000074,16.000074 0 0 0 15.75717,2.77864 l -1.36839,-3.75894 a 12.000056,12.000056 0 0 1 -11.81788,-2.0836 z m -5.14234,6.12831 -2.57091,3.06441 a 24.000112,24.000112 0 0 0 23.63525,4.16719 l -1.36787,-3.75843 a 20.000093,20.000093 0 0 1 -19.69647,-3.47317 z m -5.142316,6.12831 -2.57091,3.06441 a 32.000153,32.000153 0 0 0 31.513836,5.55677 l -1.36788,-3.75895 a 28.000129,28.000129 0 0 1 -27.575046,-4.86223 z"
- style="fill:#33ff66;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17500019;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"/>
- </g>
-</svg>
}
h3 { +"Subsystem Powering" }
p {
- +"Ships have two particular attributes that are closely related: Reactor Power and Energy Flow. Reactor Power is how much power the ship's generators generate, and starts off as being split evenly between the ship's four subsystems: Weapons, Shields, Engines, and Emitters. Weapons Power is expended when firing Cannons or charging Lances; Shields Power is expended whenever the ship's shields are impacted by enemy fire; Engines Power modifies the speed and turn rate of the ship; finally, Emitter Power modifies the ship's special techno-science abilities. The ship's Energy Flow statistic determines how many transfers can be made between subsystems during the Power Distribution phase of a turn."
+ +"Ships have two particular attributes that are closely related: Reactor Power and Energy Flow. Reactor Power is how much power the ship's generators generate, and starts off as being split evenly between the ship's four subsystems: Weapons, Shields, Engines, and Battery. Weapons Power is expended when firing Cannons or charging Lances; Shields Power is expended whenever the ship's shields are impacted by enemy fire; Engines Power modifies the speed and turn rate of the ship; finally, Battery Power modifies the ship's special techno-science abilities. The ship's Energy Flow statistic determines how many transfers can be made between subsystems during the Power Distribution phase of a turn."
}
h3 { +"Turn Structure" }
p {