From dc6264785f44de1a6394a3409246af7c00505fec Mon Sep 17 00:00:00 2001 From: TheSaminator Date: Fri, 4 Mar 2022 15:29:28 -0500 Subject: [PATCH] Add defense turrets to ships and rework strike-craft damage --- .../kotlin/starshipfights/data/data.kt | 2 +- .../starshipfights/game/game_ability.kt | 55 +++++++++++++++++-- .../kotlin/starshipfights/game/game_state.kt | 13 ++--- .../kotlin/starshipfights/game/pick_bounds.kt | 7 ++- .../kotlin/starshipfights/game/ship.kt | 32 +++++++---- .../starshipfights/game/ship_instances.kt | 4 +- .../kotlin/starshipfights/game/ship_types.kt | 30 ++++++---- .../starshipfights/game/ship_weapons.kt | 14 ++++- .../game/ship_weapons_formats.kt | 5 +- .../starshipfights/game/ship_weapons_list.kt | 26 ++++----- .../kotlin/starshipfights/game/util.kt | 3 + .../kotlin/starshipfights/game/client.kt | 28 +++++++--- .../kotlin/starshipfights/game/client_game.kt | 3 + .../starshipfights/game/client_matchmaking.kt | 4 ++ .../kotlin/starshipfights/game/game_ui.kt | 13 +++++ .../starshipfights/game/pick_bounds_js.kt | 2 +- .../kotlin/starshipfights/game/views_game.kt | 4 +- .../kotlin/starshipfights/info/views_main.kt | 8 +-- .../kotlin/starshipfights/info/views_ships.kt | 36 ++++++------ .../resources/static/images/external-link.svg | 9 +-- 20 files changed, 197 insertions(+), 101 deletions(-) diff --git a/src/commonMain/kotlin/starshipfights/data/data.kt b/src/commonMain/kotlin/starshipfights/data/data.kt index 867e77a..7f67836 100644 --- a/src/commonMain/kotlin/starshipfights/data/data.kt +++ b/src/commonMain/kotlin/starshipfights/data/data.kt @@ -10,7 +10,7 @@ import kotlin.jvm.JvmInline @JvmInline @Serializable(with = IdSerializer::class) -value class Id(val id: String) { +value class Id<@Suppress("unused") T>(val id: String) { override fun toString() = id fun reinterpret() = Id(id) diff --git a/src/commonMain/kotlin/starshipfights/game/game_ability.kt b/src/commonMain/kotlin/starshipfights/game/game_ability.kt index add7a05..4f499ba 100644 --- a/src/commonMain/kotlin/starshipfights/game/game_ability.kt +++ b/src/commonMain/kotlin/starshipfights/game/game_ability.kt @@ -289,6 +289,40 @@ sealed class PlayerAbilityType { return gameState.useWeaponPickResponse(shipInstance, weapon, pickResponse) } } + + @Serializable + data class RecallStrikeCraft(override val ship: Id, override val weapon: Id) : 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 @@ -313,6 +347,9 @@ sealed class PlayerAbilityData { @Serializable data class UseWeapon(val target: PickResponse) : PlayerAbilityData() + + @Serializable + object RecallStrikeCraft : PlayerAbilityData() } fun GameState.getPossibleAbilities(forPlayer: GlobalSide): List = if (ready == forPlayer) @@ -362,10 +399,10 @@ else when (phase) { 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 } } @@ -375,16 +412,26 @@ else when (phase) { 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 } } diff --git a/src/commonMain/kotlin/starshipfights/game/game_state.kt b/src/commonMain/kotlin/starshipfights/game/game_state.kt index 757ed96..154abeb 100644 --- a/src/commonMain/kotlin/starshipfights/game/game_state.kt +++ b/src/commonMain/kotlin/starshipfights/game/game_state.kt @@ -3,7 +3,6 @@ package starshipfights.game import kotlinx.serialization.Serializable import starshipfights.data.Id import kotlin.math.abs -import kotlin.math.exp import kotlin.random.Random @Serializable @@ -74,20 +73,20 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide 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 @@ -124,8 +123,8 @@ fun GameState.afterPlayerReady(playerSide: GlobalSide) = if (ready == playerSide weaponAmount = ship.powerMode.weapons, shieldAmount = (ship.shieldAmount..ship.powerMode.shields).random(), - fighterWings = emptyList(), - bomberWings = emptyList(), + fighterWings = emptySet(), + bomberWings = emptySet(), usedArmaments = emptySet(), ) } diff --git a/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt b/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt index 1faa61a..4181350 100644 --- a/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt +++ b/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt @@ -116,9 +116,14 @@ sealed class PickBoundary { val facing: Double, val minDistance: Double, val maxDistance: Double, - val firingArcs: Set + val firingArcs: Set, + + 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 diff --git a/src/commonMain/kotlin/starshipfights/game/ship.kt b/src/commonMain/kotlin/starshipfights/game/ship.kt index 274c33b..c411d92 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship.kt @@ -50,6 +50,9 @@ val ShipWeightClass.reactor: ShipReactor 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) @@ -74,6 +77,9 @@ val ShipWeightClass.movement: ShipMovement 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) @@ -84,22 +90,26 @@ val ShipWeightClass.movement: ShipMovement @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) } diff --git a/src/commonMain/kotlin/starshipfights/game/ship_instances.kt b/src/commonMain/kotlin/starshipfights/game/ship_instances.kt index 28df75f..a337090 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_instances.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_instances.kt @@ -24,8 +24,8 @@ data class ShipInstance( val armaments: ShipInstanceArmaments = ship.armaments.instantiate(), val usedArmaments: Set> = emptySet(), - val fighterWings: List = emptyList(), - val bomberWings: List = emptyList(), + val fighterWings: Set = emptySet(), + val bomberWings: Set = emptySet(), ) { val id: Id get() = ship.id.reinterpret() diff --git a/src/commonMain/kotlin/starshipfights/game/ship_types.kt b/src/commonMain/kotlin/starshipfights/game/ship_types.kt index 74bbf99..2b4d7dd 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_types.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_types.kt @@ -16,6 +16,9 @@ enum class ShipWeightClass( COLOSSUS(5, 5), // Isarnareykk-specific + AUXILIARY_SHIP(1, 0), + LIGHT_CRUISER(2, 1), + MEDIUM_CRUISER(3, 2), HEAVY_CRUISER(4, 4), // Vestigium-specific @@ -38,6 +41,9 @@ enum class ShipWeightClass( GRAND_CRUISER -> 300 COLOSSUS -> 500 + AUXILIARY_SHIP -> 50 + LIGHT_CRUISER -> 100 + MEDIUM_CRUISER -> 200 HEAVY_CRUISER -> 400 FRIGATE -> 150 @@ -100,18 +106,18 @@ enum class ShipType( 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), diff --git a/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt b/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt index ea6cceb..a45c14d 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt @@ -21,6 +21,7 @@ enum class FiringArc { companion object { val FIRE_360: Set = setOf(BOW, ABEAM_PORT, ABEAM_STARBOARD, STERN) val FIRE_BROADSIDE: Set = setOf(ABEAM_PORT, ABEAM_STARBOARD) + val FIRE_FORE_270: Set = setOf(BOW, ABEAM_PORT, ABEAM_STARBOARD) } } @@ -321,9 +322,9 @@ fun ShipInstance.afterTargeted(by: ShipInstance, weaponId: Id) = whe 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 -> { @@ -390,7 +391,14 @@ fun getWeaponPickRequest(weapon: ShipWeapon, position: ShipPosition, side: Globa 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 + ) ) } } diff --git a/src/commonMain/kotlin/starshipfights/game/ship_weapons_formats.kt b/src/commonMain/kotlin/starshipfights/game/ship_weapons_formats.kt index c6cbbb6..6141a2d 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_weapons_formats.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_weapons_formats.kt @@ -69,7 +69,6 @@ fun mechyrdiaShipWeapons( fun diadochiShipWeapons( torpedoes: Int, - foreLances: Int, hasRevelationGun: Boolean, cannonSections: Int, @@ -84,8 +83,6 @@ fun diadochiShipWeapons( 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) @@ -107,7 +104,7 @@ fun diadochiShipWeapons( } 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) diff --git a/src/commonMain/kotlin/starshipfights/game/ship_weapons_list.kt b/src/commonMain/kotlin/starshipfights/game/ship_weapons_list.kt index 1904022..21caa7f 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_weapons_list.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_weapons_list.kt @@ -24,19 +24,19 @@ val ShipType.armaments: ShipArmaments 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) diff --git a/src/commonMain/kotlin/starshipfights/game/util.kt b/src/commonMain/kotlin/starshipfights/game/util.kt index b43515f..3de91fa 100644 --- a/src/commonMain/kotlin/starshipfights/game/util.kt +++ b/src/commonMain/kotlin/starshipfights/game/util.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.delay 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 { @@ -38,3 +39,5 @@ else if (multiplier == 1) this else this + (this * (multiplier - 1)) fun Double.toPercent() = "${(this * 100).roundToInt()}%" + +fun smoothNegative(x: Double) = if (x < 0) exp(x) else x + 1 diff --git a/src/jsMain/kotlin/starshipfights/game/client.kt b/src/jsMain/kotlin/starshipfights/game/client.kt index fbd8782..70736d7 100644 --- a/src/jsMain/kotlin/starshipfights/game/client.kt +++ b/src/jsMain/kotlin/starshipfights/game/client.kt @@ -3,20 +3,28 @@ package starshipfights.game 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().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") } @@ -31,16 +39,18 @@ val httpClient = HttpClient(Js) { } 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) @@ -48,3 +58,5 @@ fun main() { } } } + +var interruptExit: Boolean = false diff --git a/src/jsMain/kotlin/starshipfights/game/client_game.kt b/src/jsMain/kotlin/starshipfights/game/client_game.kt index 4629b11..0fa1f7c 100644 --- a/src/jsMain/kotlin/starshipfights/game/client_game.kt +++ b/src/jsMain/kotlin/starshipfights/game/client_game.kt @@ -181,6 +181,8 @@ private fun CoroutineScope.uiResponder(actions: SendChannel, error } suspend fun gameMain(side: GlobalSide, token: String, state: GameState) { + interruptExit = true + initializePicking() mySide = side @@ -201,6 +203,7 @@ suspend fun gameMain(side: GlobalSide, token: String, state: GameState) { val finalMessage = connectionJob.await() renderingJob.cancel() + interruptExit = false Popup.GameOver(finalMessage, gameState.value).display() } } diff --git a/src/jsMain/kotlin/starshipfights/game/client_matchmaking.kt b/src/jsMain/kotlin/starshipfights/game/client_matchmaking.kt index a376af0..cc06a63 100644 --- a/src/jsMain/kotlin/starshipfights/game/client_matchmaking.kt +++ b/src/jsMain/kotlin/starshipfights/game/client_matchmaking.kt @@ -45,6 +45,8 @@ suspend fun setupBackground() { } 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 { @@ -116,6 +118,8 @@ private suspend fun usePlayerLogin(admirals: List) { } suspend fun matchmakingMain(admirals: List) { + interruptExit = true + coroutineScope { launch { setupBackground() } launch { usePlayerLogin(admirals) } diff --git a/src/jsMain/kotlin/starshipfights/game/game_ui.kt b/src/jsMain/kotlin/starshipfights/game/game_ui.kt index 6dffa1c..3264aad 100644 --- a/src/jsMain/kotlin/starshipfights/game/game_ui.kt +++ b/src/jsMain/kotlin/starshipfights/game/game_ui.kt @@ -699,6 +699,7 @@ object GameUI { 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" @@ -748,6 +749,18 @@ object GameUI { } } } + is PlayerAbilityType.RecallStrikeCraft -> { + a(href = "#") { + +"Recall " + if (firingArcsDesc != null) + +"$firingArcsDesc " + +weaponDesc + onClickFunction = { e -> + e.preventDefault() + responder.useAbility(ability) + } + } + } } } } diff --git a/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt b/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt index 5f02b60..5c46ffa 100644 --- a/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt +++ b/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt @@ -358,7 +358,7 @@ private fun PickBoundary.render(): List { .unsafeCast() ) } - 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)) diff --git a/src/jvmMain/kotlin/starshipfights/game/views_game.kt b/src/jvmMain/kotlin/starshipfights/game/views_game.kt index 32c2bd4..4d22466 100644 --- a/src/jvmMain/kotlin/starshipfights/game/views_game.kt +++ b/src/jvmMain/kotlin/starshipfights/game/views_game.kt @@ -45,10 +45,10 @@ fun ClientMode.view(): HTML.() -> Unit = { } script { + attributes["id"] = "sf-client-mode" + type = "application/json" unsafe { - +"window.sfClientMode = " +jsonSerializer.encodeToString(ClientMode.serializer(), this@view) - +";" } } diff --git a/src/jvmMain/kotlin/starshipfights/info/views_main.kt b/src/jvmMain/kotlin/starshipfights/info/views_main.kt index e50e483..e1acb2a 100644 --- a/src/jvmMain/kotlin/starshipfights/info/views_main.kt +++ b/src/jvmMain/kotlin/starshipfights/info/views_main.kt @@ -34,9 +34,7 @@ suspend fun ApplicationCall.aboutPage(): HTML.() -> Unit { "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." } @@ -52,9 +50,7 @@ suspend fun ApplicationCall.aboutPage(): HTML.() -> Unit { ) ) { 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" } diff --git a/src/jvmMain/kotlin/starshipfights/info/views_ships.kt b/src/jvmMain/kotlin/starshipfights/info/views_ships.kt index 636cc9f..050ed72 100644 --- a/src/jvmMain/kotlin/starshipfights/info/views_ships.kt +++ b/src/jvmMain/kotlin/starshipfights/info/views_ships.kt @@ -83,42 +83,38 @@ suspend fun ApplicationCall.shipPage(shipType: ShipType): HTML.() -> Unit = page 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() diff --git a/src/jvmMain/resources/static/images/external-link.svg b/src/jvmMain/resources/static/images/external-link.svg index 715b8ee..e56fae7 100644 --- a/src/jvmMain/resources/static/images/external-link.svg +++ b/src/jvmMain/resources/static/images/external-link.svg @@ -8,16 +8,13 @@ + d="M7.002 3.01h-5v8h8v-5h-1v4h-6v-6h4z"/> + d="M5.002 1.01h7v7l-2-2-3 2v-1l3-2.25 1 1V2.01h-3.75l1 1-2.25 3h-1l2-3z"/> + 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"/> -- 2.25.1