Rename Emitter power and rework shield regeneration
authorTheSaminator <TheSaminator@users.noreply.github.com>
Wed, 9 Feb 2022 18:10:12 +0000 (13:10 -0500)
committerTheSaminator <TheSaminator@users.noreply.github.com>
Wed, 9 Feb 2022 18:10:12 +0000 (13:10 -0500)
plan/mechanics.md [deleted file]
src/commonMain/kotlin/starshipfights/game/game_ability.kt
src/commonMain/kotlin/starshipfights/game/game_state.kt
src/commonMain/kotlin/starshipfights/game/ship_instances.kt
src/jsMain/resources/images/subsystem-battery.svg [new file with mode: 0644]
src/jsMain/resources/images/subsystem-emitter.svg [deleted file]
src/jvmMain/kotlin/starshipfights/info/views_ships.kt

diff --git a/plan/mechanics.md b/plan/mechanics.md
deleted file mode 100644 (file)
index e5f95b8..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-# 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.
index 05de1699db512c76449a247cda8bc3ac75ecb266..add7a057f7f6aa435a3983ed8dc8ef9e71febde0 100644 (file)
@@ -137,12 +137,14 @@ sealed class PlayerAbilityType {
                        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)
                        
index 4c7acf312fac62fef50cc917e74f4c47f31f7435..ae2a0d019254294e816b5876dd72472d63aa32e9 100644 (file)
@@ -32,19 +32,14 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide
        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
@@ -58,6 +53,8 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide
                                
                                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 })
@@ -70,6 +67,7 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide
                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
@@ -115,6 +113,8 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide
                                        }
                                }
                        }.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)
@@ -126,8 +126,13 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide
                                        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(),
index ade5f96f47d771f6346e8d6cbedeb5ac6f54bf91..bd3ee1f00ba28333ff09366751540af97a751569 100644 (file)
@@ -59,7 +59,7 @@ data class ShipPosition(
 }
 
 enum class ShipSubsystem {
-       WEAPONS, SHIELDS, ENGINES, EMITTER;
+       WEAPONS, SHIELDS, ENGINES, BATTERY;
        
        val displayName: String
                get() = name.lowercase().replaceFirstChar { it.uppercase() }
@@ -69,7 +69,7 @@ enum class ShipSubsystem {
                        WEAPONS -> "#FF6633"
                        SHIELDS -> "#6699FF"
                        ENGINES -> "#FFCC33"
-                       EMITTER -> "#33FF66"
+                       BATTERY -> "#33FF66"
                }
        
        val imageUrl: String
@@ -86,13 +86,13 @@ data class ShipPowerMode(
        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 }
@@ -101,11 +101,11 @@ data class ShipPowerMode(
                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]) }
 }
diff --git a/src/jsMain/resources/images/subsystem-battery.svg b/src/jsMain/resources/images/subsystem-battery.svg
new file mode 100644 (file)
index 0000000..fefe69b
--- /dev/null
@@ -0,0 +1,16 @@
+<?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>
diff --git a/src/jsMain/resources/images/subsystem-emitter.svg b/src/jsMain/resources/images/subsystem-emitter.svg
deleted file mode 100644 (file)
index fefe69b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?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>
index 6d36955b2e4cbe9762c605734a2debe1245ac4a9..b776589b272e93d36eda4033f699b0e28c703795 100644 (file)
@@ -30,7 +30,7 @@ suspend fun ApplicationCall.shipsPage(): HTML.() -> Unit = page("Game Manual", s
                }
                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 {