From 3a7637cd47267ee7bf1ce0379fac4af899f45d90 Mon Sep 17 00:00:00 2001 From: TheSaminator Date: Sat, 28 May 2022 16:17:21 -0400 Subject: [PATCH] Some refactoring --- .../starshipfights/game/game_ability.kt | 4 +- .../starshipfights/game/game_initiative.kt | 36 +++++--- .../kotlin/starshipfights/game/pick_bounds.kt | 84 +++++++++++++++++-- .../starshipfights/game/ship_instances.kt | 4 +- .../starshipfights/game/ship_weapons.kt | 22 +++-- .../starshipfights/game/pick_bounds_js.kt | 39 ++++----- .../starshipfights/data/data_routines.kt | 8 +- .../kotlin/starshipfights/server_utils.kt | 5 -- 8 files changed, 145 insertions(+), 57 deletions(-) diff --git a/src/commonMain/kotlin/starshipfights/game/game_ability.kt b/src/commonMain/kotlin/starshipfights/game/game_ability.kt index 815cb27..4e7f560 100644 --- a/src/commonMain/kotlin/starshipfights/game/game_ability.kt +++ b/src/commonMain/kotlin/starshipfights/game/game_ability.kt @@ -319,11 +319,9 @@ sealed class PlayerAbilityType { val positionPickReq = PickRequest( PickType.Location(gameState.ships.keys - ship, PickHelper.Circle(SHIP_BASE_SIZE), shipInstance.position.location), - PickBoundary.Ellipse( + PickBoundary.Circle( shipInstance.position.location, movement.inertialessDriveRange, - movement.inertialessDriveRange, - 0.0 ) ) val positionPickRes = (pick(positionPickReq) as? PickResponse.Location) ?: return null diff --git a/src/commonMain/kotlin/starshipfights/game/game_initiative.kt b/src/commonMain/kotlin/starshipfights/game/game_initiative.kt index 7d8e4d4..bbf7539 100644 --- a/src/commonMain/kotlin/starshipfights/game/game_initiative.kt +++ b/src/commonMain/kotlin/starshipfights/game/game_initiative.kt @@ -35,6 +35,30 @@ fun GameState.calculateMovePhaseInitiative(): InitiativePair = InitiativePair( } ) +fun GameState.isValidTarget(ship: ShipInstance, weapon: ShipWeaponInstance, pickRequest: PickRequest, target: ShipInstance): Boolean { + val targetPos = target.position.location + + return when (val weaponSpec = weapon.weapon) { + is AreaWeapon -> + target.owner != ship.owner && (targetPos - pickRequest.boundary.closestPointTo(targetPos)).length <= weaponSpec.areaRadius + else -> + target.owner in (pickRequest.type as PickType.Ship).allowSides && isValidPick(pickRequest, PickResponse.Ship(target.id)) + } +} + +inline fun GameState.aggregateValidTargets(ship: ShipInstance, weapon: ShipWeaponInstance, aggregate: Iterable.((ShipInstance) -> Boolean) -> T): T { + val pickRequest = ship.getWeaponPickRequest(weapon.weapon) + return ships.values.aggregate { target -> isValidTarget(ship, weapon, pickRequest, target) } +} + +fun GameState.hasValidTargets(ship: ShipInstance, weapon: ShipWeaponInstance): Boolean { + return aggregateValidTargets(ship, weapon) { any(it) } +} + +fun GameState.getValidTargets(ship: ShipInstance, weapon: ShipWeaponInstance): List { + return aggregateValidTargets(ship, weapon) { filter(it) } +} + fun GameState.calculateAttackPhaseInitiative(): InitiativePair = InitiativePair( ships .values @@ -44,17 +68,7 @@ fun GameState.calculateAttackPhaseInitiative(): InitiativePair = InitiativePair( .filter { !it.isDoneCurrentPhase } .sumOf { ship -> val allWeapons = ship.armaments.weaponInstances - .filterValues { weaponInstance -> - when (val weapon = weaponInstance.weapon) { - is AreaWeapon -> true - else -> { - val pickRequest = ship.getWeaponPickRequest(weapon) - ships.values.any { target -> - target.owner in (pickRequest.type as PickType.Ship).allowSides && target.position.location in pickRequest.boundary - } - } - } - } + .filterValues { weapon -> hasValidTargets(ship, weapon) } val usableWeapons = allWeapons - ship.usedArmaments val allWeaponShots = allWeapons.values.sumOf { it.weapon.numShots } diff --git a/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt b/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt index fe6354a..b5fdc7f 100644 --- a/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt +++ b/src/commonMain/kotlin/starshipfights/game/pick_bounds.kt @@ -5,13 +5,27 @@ import starshipfights.data.Id import kotlin.math.PI import kotlin.math.abs +fun FiringArc.getStartAngle(shipFacing: Double) = (when (this) { + FiringArc.BOW -> Vec2(1.0, -1.0) + FiringArc.ABEAM_PORT -> Vec2(-1.0, -1.0) + FiringArc.ABEAM_STARBOARD -> Vec2(1.0, 1.0) + FiringArc.STERN -> Vec2(-1.0, 1.0) +} rotatedBy shipFacing).angle + +fun FiringArc.getEndAngle(shipFacing: Double) = (when (this) { + FiringArc.BOW -> Vec2(1.0, 1.0) + FiringArc.ABEAM_PORT -> Vec2(1.0, -1.0) + FiringArc.ABEAM_STARBOARD -> Vec2(-1.0, 1.0) + FiringArc.STERN -> Vec2(-1.0, -1.0) +} rotatedBy shipFacing).angle + fun GameState.isValidPick(request: PickRequest, response: PickResponse): Boolean { if (request.type is PickType.Ship != response is PickResponse.Ship) return false when (response) { is PickResponse.Location -> { - request.type as PickType.Location + if (request.type !is PickType.Location) return false if (response.position !in request.boundary) return false if (ships.values.any { @@ -21,7 +35,7 @@ fun GameState.isValidPick(request: PickRequest, response: PickResponse): Boolean return true } is PickResponse.Ship -> { - request.type as PickType.Ship + if (request.type !is PickType.Ship) return false if (response.id !in ships) return false @@ -98,15 +112,12 @@ sealed class PickBoundary { } @Serializable - data class Ellipse( + data class Circle( val center: Position, - val widthRadius: Double, - val lengthRadius: Double, - val rotationAngle: Double, + val radius: Double, ) : PickBoundary() { override fun contains(point: Position): Boolean { - val distance = (point - center).vector rotatedBy rotationAngle scaleUneven Vec2(1 / widthRadius, 1 / lengthRadius) - return distance.magnitude <= 1.0 + return (point - center).length < radius } } @@ -155,3 +166,60 @@ sealed class PickHelper { @Serializable data class Circle(val radius: Double) : PickHelper() } + +fun PickBoundary.closestPointTo(position: Position): Position = when (this) { + is PickBoundary.AlongLine -> position.clampOnLineSegment(pointA, pointB) + is PickBoundary.Angle -> { + val distance = position - center + val midNormal = normalDistance(midAngle) + + if ((distance angleBetween midNormal) <= maxAngle) + position + else { + ((midNormal rotatedBy (midNormal angleTo distance).coerceIn(-maxAngle, maxAngle)) * distance.length) + center + } + } + is PickBoundary.Circle -> { + val distance = position - center + if (distance.length <= radius) + position + else + (distance.normal * radius) + center + } + is PickBoundary.Rectangle -> { + Distance((position - center).vector.let { (x, y) -> + Vec2(x.coerceIn(-width2, width2), y.coerceIn(-length2, length2)) + }) + center + } + is PickBoundary.WeaponsFire -> { + val distance = position - center + + val thetaHat = normalDistance(facing) + + val deltaTheta = thetaHat angleTo distance + val firingArc: FiringArc = when { + abs(deltaTheta) < PI / 4 -> FiringArc.BOW + abs(deltaTheta) > PI * 3 / 4 -> FiringArc.STERN + deltaTheta < 0 -> FiringArc.ABEAM_PORT + else -> FiringArc.ABEAM_STARBOARD + } + + if (firingArc in firingArcs) { + if (distance.length in minDistance..maxDistance) + position + else + (distance.normal * (if (distance.length < minDistance) minDistance else maxDistance)) + center + } else + firingArcs.flatMap { + val startNormal = normalDistance(it.getStartAngle(facing)) + val endNormal = normalDistance(it.getEndAngle(facing)) + + listOf( + (startNormal * minDistance) + center, + (endNormal * minDistance) + center, + (startNormal * maxDistance) + center, + (endNormal * maxDistance) + center, + ) + }.minByOrNull { (it - position).length } ?: position + } +} diff --git a/src/commonMain/kotlin/starshipfights/game/ship_instances.kt b/src/commonMain/kotlin/starshipfights/game/ship_instances.kt index 678dbff..9c2351e 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_instances.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_instances.kt @@ -231,7 +231,9 @@ val ShipInstance.durability: ShipDurability is FelinaeShipDurability -> d.copy( maxHullPoints = d.maxHullPoints - recoalescenceMaxHullDamage, ) - else -> d + is StandardShipDurability -> d.copy( + turretDefense = if (canUseTurrets) d.turretDefense else 0.0 + ) } val ShipInstance.firepower: ShipFirepower diff --git a/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt b/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt index df0cccd..2330109 100644 --- a/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt +++ b/src/commonMain/kotlin/starshipfights/game/ship_weapons.kt @@ -471,23 +471,29 @@ fun ShipInstance.afterTargeted(by: ShipInstance, weaponId: Id) = whe } } -fun ShipInstance.afterBombed(otherShips: Map, ShipInstance>, strikeWingDamage: MutableMap): ImpactResult { - if (bomberWings.isEmpty()) - return ImpactResult.Damaged(this, ImpactDamage.OtherEffect) +fun ShipInstance.calculateBombing(otherShips: Map, ShipInstance>, extraBombers: Double = 0.0, extraFighters: Double = 0.0): Double? { + if (bomberWings.isEmpty() && extraBombers < EPSILON) + return null val totalFighterHealth = fighterWings.sumOf { (carrierId, wingId) -> (otherShips[carrierId]?.armaments?.weaponInstances?.get(wingId) as? ShipWeaponInstance.Hangar)?.wingHealth ?: 0.0 - } + (if (canUseTurrets) durability.turretDefense else 0.0) + } + durability.turretDefense + extraFighters val totalBomberHealth = bomberWings.sumOf { (carrierId, wingId) -> (otherShips[carrierId]?.armaments?.weaponInstances?.get(wingId) as? ShipWeaponInstance.Hangar)?.wingHealth ?: 0.0 - } + } + extraBombers if (totalBomberHealth < EPSILON) - return ImpactResult.Damaged(this, ImpactDamage.OtherEffect) + return null + + return totalBomberHealth - totalFighterHealth +} + +fun ShipInstance.afterBombed(otherShips: Map, ShipInstance>, strikeWingDamage: MutableMap): ImpactResult { + val calculatedBombing = calculateBombing(otherShips) ?: return ImpactResult.Damaged(this, ImpactDamage.OtherEffect) - val maxBomberWingOutput = smoothNegative(totalBomberHealth - totalFighterHealth) - val maxFighterWingOutput = smoothNegative(totalFighterHealth - totalBomberHealth) + val maxBomberWingOutput = smoothNegative(calculatedBombing) + val maxFighterWingOutput = smoothNegative(-calculatedBombing) for (it in fighterWings) strikeWingDamage[it] = Random.nextDouble() * maxBomberWingOutput diff --git a/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt b/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt index 1582e6e..619dfc0 100644 --- a/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt +++ b/src/jsMain/kotlin/starshipfights/game/pick_bounds_js.kt @@ -336,20 +336,6 @@ suspend fun PickRequest.pick(context: PickContext): PickResponse? = pickMutex.wi } } -private fun FiringArc.getStartAngle(shipFacing: Double) = (when (this) { - FiringArc.BOW -> Vec2(1.0, -1.0) - FiringArc.ABEAM_PORT -> Vec2(-1.0, -1.0) - FiringArc.ABEAM_STARBOARD -> Vec2(1.0, 1.0) - FiringArc.STERN -> Vec2(-1.0, 1.0) -} rotatedBy shipFacing).angle - -private fun FiringArc.getEndAngle(shipFacing: Double) = (when (this) { - FiringArc.BOW -> Vec2(1.0, 1.0) - FiringArc.ABEAM_PORT -> Vec2(1.0, -1.0) - FiringArc.ABEAM_STARBOARD -> Vec2(-1.0, 1.0) - FiringArc.STERN -> Vec2(-1.0, -1.0) -} rotatedBy shipFacing).angle - private fun PickBoundary.render(): List { return when (this) { is PickBoundary.Angle -> { @@ -380,17 +366,17 @@ private fun PickBoundary.render(): List { .lineTo(RenderScaling.toWorldLength(center.vector.x + width2), RenderScaling.toWorldLength(center.vector.y - length2)) .unsafeCast() ) - is PickBoundary.Ellipse -> listOf( + is PickBoundary.Circle -> listOf( Shape() .ellipse( RenderScaling.toWorldLength(center.vector.x), RenderScaling.toWorldLength(center.vector.y), - RenderScaling.toWorldLength(widthRadius), - RenderScaling.toWorldLength(lengthRadius), + RenderScaling.toWorldLength(radius), + RenderScaling.toWorldLength(radius), 0, 2 * PI, false, - rotationAngle + 0 ) .unsafeCast() ) @@ -407,6 +393,21 @@ private fun PickBoundary.render(): List { .absarc(RenderScaling.toWorldLength(position.x), RenderScaling.toWorldLength(position.y), RenderScaling.toWorldLength(minDistance), startTheta, endTheta, false) .absarc(RenderScaling.toWorldLength(position.x), RenderScaling.toWorldLength(position.y), RenderScaling.toWorldLength(maxDistance), endTheta, startTheta, true) .unsafeCast() - } + } + (if (canSelfSelect) + listOf( + Shape() + .ellipse( + RenderScaling.toWorldLength(center.vector.x), + RenderScaling.toWorldLength(center.vector.y), + RenderScaling.toWorldLength(SHIP_BASE_SIZE), + RenderScaling.toWorldLength(SHIP_BASE_SIZE), + 0, + 2 * PI, + false, + 0 + ) + .unsafeCast() + ) + else emptyList()) } } diff --git a/src/jvmMain/kotlin/starshipfights/data/data_routines.kt b/src/jvmMain/kotlin/starshipfights/data/data_routines.kt index 5b862e7..9b4cca1 100644 --- a/src/jvmMain/kotlin/starshipfights/data/data_routines.kt +++ b/src/jvmMain/kotlin/starshipfights/data/data_routines.kt @@ -2,6 +2,8 @@ package starshipfights.data import kotlinx.coroutines.* import org.litote.kmongo.inc +import org.slf4j.Logger +import org.slf4j.LoggerFactory import starshipfights.data.admiralty.Admiral import starshipfights.data.admiralty.BattleRecord import starshipfights.data.admiralty.ShipInDrydock @@ -9,15 +11,16 @@ import starshipfights.data.admiralty.eq import starshipfights.data.auth.User import starshipfights.data.auth.UserSession import starshipfights.game.AdmiralRank -import starshipfights.sfLogger import java.time.Instant import java.time.ZoneId object DataRoutines { + private val logger: Logger = LoggerFactory.getLogger(javaClass) + private val scope: CoroutineScope = CoroutineScope( SupervisorJob() + CoroutineExceptionHandler { ctx, ex -> val coroutine = ctx[CoroutineName]?.name?.let { "coroutine $it" } ?: "unnamed coroutine" - sfLogger.error("Caught unhandled exception in $coroutine", ex) + logger.error("Caught unhandled exception in $coroutine", ex) } ) @@ -37,6 +40,7 @@ object DataRoutines { val currTime = Instant.now().atZone(ZoneId.systemDefault()) if (currTime.dayOfWeek != prevTime.dayOfWeek) launch { + logger.info("Paying admirals now") for (rank in AdmiralRank.values()) launch { Admiral.update( diff --git a/src/jvmMain/kotlin/starshipfights/server_utils.kt b/src/jvmMain/kotlin/starshipfights/server_utils.kt index 6fb201f..87574c2 100644 --- a/src/jvmMain/kotlin/starshipfights/server_utils.kt +++ b/src/jvmMain/kotlin/starshipfights/server_utils.kt @@ -1,8 +1,5 @@ package starshipfights -import org.slf4j.Logger -import org.slf4j.LoggerFactory - open class ForbiddenException : IllegalArgumentException() fun forbid(): Nothing = throw ForbiddenException() @@ -18,5 +15,3 @@ fun redirect(url: String, permanent: Boolean = false): Nothing = throw HttpRedir class RateLimitException : RuntimeException() fun rateLimit(): Nothing = throw RateLimitException() - -val sfLogger: Logger = LoggerFactory.getLogger("StarshipFights") -- 2.25.1