get() = name.lowercase().replaceFirstChar { it.uppercase() }
}
-enum class ClusterLaneDensity(val chanceToRemove: Double, val numToAdd: Int) {
- SPARSE(0.72, 1), MEDIUM(0.36, 2), DENSE(0.12, 3);
+enum class ClusterLaneDensity(val chanceToRemove: Double) {
+ SPARSE(0.72), MEDIUM(0.36), DENSE(0.12);
val displayName: String
get() = name.lowercase().replaceFirstChar { it.uppercase() }
import kotlinx.serialization.Serializable
import net.starshipfights.data.Id
-import net.starshipfights.game.*
-
-val FactionFlavor.getMapColor: IntColor
- get() = when (this) {
- FactionFlavor.MECHYRDIA -> IntColor(255, 204, 51)
- FactionFlavor.TYLA -> IntColor(255, 204, 51)
- FactionFlavor.OLYMPIA -> IntColor(255, 204, 51)
- FactionFlavor.TEXANDRIA -> IntColor(255, 204, 51)
-
- FactionFlavor.NDRC -> IntColor(255, 204, 51)
- FactionFlavor.CCC -> IntColor(255, 204, 51)
- FactionFlavor.MJOLNIR_ENERGY -> IntColor(255, 204, 51)
-
- FactionFlavor.MASRA_DRAETSEN -> IntColor(204, 34, 34)
- FactionFlavor.AEDON_CULTISTS -> IntColor(204, 34, 34)
- FactionFlavor.FERTHLON_EXILES -> IntColor(204, 34, 34)
-
- FactionFlavor.RES_NOSTRA -> IntColor(204, 102, 153)
- FactionFlavor.CORSAIRS -> IntColor(204, 102, 153)
- FactionFlavor.FELINAE_FELICES -> IntColor(204, 102, 153)
-
- FactionFlavor.ISARNAREYKK -> IntColor(34, 221, 34)
- FactionFlavor.SWARTAREYKK -> IntColor(34, 221, 34)
- FactionFlavor.THEUDAREYKK -> IntColor(255, 204, 51) // Mechyrdia
- FactionFlavor.STAHLAREYKK -> IntColor(255, 204, 51) // Also Mechyrdia
- FactionFlavor.LYUDAREYKK -> IntColor(34, 221, 34)
- FactionFlavor.NEUIA_FULKREYKK -> IntColor(34, 221, 34)
-
- FactionFlavor.CORVUS_CLUSTER_VESTIGIUM -> IntColor(108, 96, 153)
- FactionFlavor.COLEMAN_SF_BASE_VESTIGIUM -> IntColor(108, 96, 153)
- }
+import net.starshipfights.game.Faction
+import net.starshipfights.game.FactionFlavor
+import net.starshipfights.game.Ship
val Faction.allegiences: Set<FactionFlavor>
get() = FactionFlavor.values().filter { this in it.loyalties }.toSet()
FactionFlavor.COLEMAN_SF_BASE_VESTIGIUM -> listOf(Faction.VESTIGIUM)
}
-val FactionFlavor.mapCounterShipClass: ShipType
- get() = when (this) {
- FactionFlavor.MECHYRDIA -> ShipType.VENSCA
- FactionFlavor.TYLA -> ShipType.VENSCA
- FactionFlavor.OLYMPIA -> ShipType.VENSCA
- FactionFlavor.TEXANDRIA -> ShipType.VENSCA
-
- FactionFlavor.NDRC -> ShipType.VOORHOEDE
- FactionFlavor.CCC -> ShipType.VOORHOEDE
- FactionFlavor.MJOLNIR_ENERGY -> ShipType.VOORHOEDE
-
- FactionFlavor.MASRA_DRAETSEN -> ShipType.MORGOTH
- FactionFlavor.AEDON_CULTISTS -> ShipType.MORGOTH
- FactionFlavor.FERTHLON_EXILES -> ShipType.MORGOTH
-
- FactionFlavor.RES_NOSTRA -> ShipType.BOBCAT
- FactionFlavor.CORSAIRS -> ShipType.BOBCAT
- FactionFlavor.FELINAE_FELICES -> ShipType.BOBCAT
-
- FactionFlavor.ISARNAREYKK -> ShipType.TEFRAN
- FactionFlavor.SWARTAREYKK -> ShipType.TEFRAN
- FactionFlavor.THEUDAREYKK -> ShipType.TEFRAN
- FactionFlavor.STAHLAREYKK -> ShipType.TEFRAN
- FactionFlavor.LYUDAREYKK -> ShipType.TEFRAN
- FactionFlavor.NEUIA_FULKREYKK -> ShipType.TEFRAN
-
- FactionFlavor.CORVUS_CLUSTER_VESTIGIUM -> ShipType.LEXINGTON
- FactionFlavor.COLEMAN_SF_BASE_VESTIGIUM -> ShipType.LEXINGTON
- }
-
@Serializable
data class FleetPresence(
val name: String,
}
@Serializable
-class StarClusterView(
+data class StarClusterView(
val background: StarClusterBackground,
val systems: Map<Id<StarSystem>, StarSystem>,
val lanes: Set<WarpLane>
}
}
+val IntColor.shadow: IntColor
+ get() = let { (r, g, b) ->
+ IntColor(
+ r * 2 / 3,
+ g * 2 / 3,
+ b * 2 / 3,
+ )
+ }
+
val IntColor.highlight: IntColor
get() = let { (r, g, b) ->
IntColor(
import net.starshipfights.data.Id
import net.starshipfights.game.*
-suspend fun campaignMain(playingAs: Id<InGameAdmiral>, otherAdmirals: Map<Id<InGameAdmiral>, CampaignAdmiral>, clusterToken: Id<StarClusterView>, clusterView: StarClusterView) {
+var mySide: CampaignAdmiral? = null
+
+suspend fun campaignMain(playingAs: Id<InGameAdmiral>, admirals: Map<Id<InGameAdmiral>, CampaignAdmiral>, clusterToken: Id<StarClusterView>, clusterView: StarClusterView) {
Popup.LoadingScreen("Loading resources...") {
CampaignResources.load()
}.display()
+ mySide = admirals[playingAs]
+
val updateLoop = Popup.LoadingScreen<FlowCollector<Double>>("Rendering cluster...") {
delay(500L)
renderer.render(scene, camera)
- CampaignUI.initCampaignUI(uiResponder(clusterView))
+ CampaignUI.initCampaignUI(uiResponder(clusterView, scene))
addSelectionHandler(clusterView, camera, scene)
cameraControls.update(dt)
renderer.render(scene, camera)
CampaignUI.renderCampaignUI(cameraControls)
+ CampaignUI.fitLabels()
scene.traverse { obj3d ->
obj3d.celestialObjectRenderImmediate?.let { ptr ->
object None : Selection()
data class System(val id: Id<StarSystem>) : Selection()
data class CelestialObject(val pointer: CelestialObjectPointer) : Selection()
+ data class FleetPresence(val pointer: FleetPresencePointer) : Selection()
}
private val selectionMutable = MutableStateFlow<Selection>(Selection.None)
y = 1 - (me.clientY.toDouble() / window.innerHeight) * 2
}, camera)
+ val fleetCounters = scene.children
+ .single { it.isStarCluster }.children
+ .single { it.isStarClusterFleets }
+
+ val fleetPresence = raycaster.intersectObject(fleetCounters, recursive = true).firstOrNull()?.`object`?.fleetPresenceRender
+ if (fleetPresence != null) {
+ selectionMutable.value = Selection.FleetPresence(fleetPresence)
+ return
+ }
+
val position = raycaster.intersectXZPlane()
if (position == null) {
selectionMutable.value = Selection.None
interface CampaignUIResponder {
fun getStarCluster(): StarClusterView
+ fun getRenderScene(): Scene
}
object CampaignUI {
private val selectedObjectIndicators = mutableMapOf<CelestialObjectPointer, CSS3DSprite>()
private val visibleSelectedObjectIndicators = mutableSetOf<CelestialObjectPointer>()
+ private val selectedFleetIndicators = mutableMapOf<FleetPresencePointer, CSS3DSprite>()
+ private val visibleSelectedFleetIndicators = mutableSetOf<FleetPresencePointer>()
+
private lateinit var errorMessages: HTMLParagraphElement
private lateinit var helpMessages: HTMLParagraphElement
systemsOverlayRenderer.setSize(window.innerWidth, window.innerHeight)
})
+ val fleetRenders = responder.getRenderScene()
+ .children.single { it.isStarCluster }
+ .children.single { it.isStarClusterFleets }
+
for ((systemId, system) in responder.getStarCluster().systems) {
val systemWithId = StarSystemWithId(systemId, system)
systemsOverlayScene.add(CSS3DSprite(document.create.div {
element.style.asDynamic().pointerEvents = "none"
position.copy(CampaignScaling.toWorldPosition(system.position))
- position.y = 50
+ position.y = 64
})
selectedSystemIndicators[systemId] = CSS3DObject(document.create.img(src = "/static/game/images/crosshair-round.svg")).apply {
visible = false
}.also { systemsOverlayScene.add(it) }
}
+
+ val fleetRender = fleetRenders
+ .children.single { it.isStarSystemFleets(systemId) }
+
+ for (fleetId in system.fleets.keys) {
+ val fleetPtr = FleetPresencePointer(systemId, fleetId)
+ val currFleetRender = fleetRender
+ .children.single { it.fleetPresenceRender == fleetPtr }
+
+ selectedFleetIndicators[fleetPtr] = CSS3DSprite(document.create.img(src = "/static/game/images/crosshair.svg")).apply {
+ scale.setScalar(0.004)
+
+ element.style.asDynamic().pointerEvents = "none"
+
+ position.copy(currFleetRender.position)
+
+ visible = false
+ }.also { systemsOverlayScene.add(it) }
+ }
}
-
+ }
+
+ private var labelsFit = false
+ fun fitLabels() {
+ if (labelsFit) return
textFit(document.getElementsByClassName("system-label"))
+ labelsFit = true
}
fun renderCampaignUI(controls: CampaignCameraControls) {
}
visibleSelectedObjectIndicators.clear()
+ for (ptr in visibleSelectedFleetIndicators) {
+ selectedFleetIndicators[ptr]?.visible = false
+ }
+ visibleSelectedFleetIndicators.clear()
+
topRightBar.clear()
topRightBar.append {
when (selection) {
p {
style = "text-align:center"
+(system.holder?.loyalties?.first()?.getDefiniteShortName()?.let { "Controlled by $it" } ?: "Wilderness")
+ br
+ system.holder?.let {
+ img(alt = it.displayName, src = it.flagUrl) {
+ style = "width:4em;height:2.5em"
+ }
+ }
}
selectedSystemIndicators[selection.id]?.visible = true
selectedObjectIndicators[selection.pointer]?.visible = true
visibleSelectedObjectIndicators += selection.pointer
}
+ is Selection.FleetPresence -> {
+ val fleet = selection.pointer.resolve(starCluster) ?: return@append selectionRender(clearSelection())
+
+ p {
+ style = "text-align:center"
+ strong(classes = "heading") {
+ +fleet.name
+ }
+ }
+ p {
+ style = "text-align:center"
+ +"${fleet.ships.size} ships"
+ }
+ p {
+ style = "text-align:center"
+ +"Operated by ${fleet.owner.loyalties.first().getDefiniteShortName()}"
+ br
+ img(alt = fleet.owner.displayName, src = fleet.owner.flagUrl) {
+ style = "width:4em;height:2.5em"
+ }
+ }
+
+ selectedFleetIndicators[selection.pointer]?.visible = true
+ visibleSelectedFleetIndicators += selection.pointer
+ }
}
}
}
attributes["data-ship-id"] = system.id.toString()
p(classes = "system-label") {
- val color = system.starSystem.holder?.getMapColor?.toString() ?: "#fff"
+ val color = system.starSystem.holder?.mapColor?.toString() ?: "#fff"
style = "color:$color;margin:0;white-space:nowrap;width:640px;height:125px"
+system.starSystem.name
}
style = "text-align:center;color:#fff;margin:0;white-space:nowrap;width:640px;height:125px"
system.starSystem.holder?.let { flavor ->
img(src = flavor.flagUrl, alt = flavor.displayName) {
- style = "width:200px;height:125px"
+ style = "width:640px;height:400px;transform-origin:top center;transform:scale(0.32)"
+ }
+ }
+ }
+ }
+
+ private fun redrawSystemLabel(system: StarSystemWithId) {
+ val div = document.getElementById("system-overlay-${system.id}").unsafeCast<HTMLDivElement>()
+
+ div.clear()
+ div.append {
+ p(classes = "system-label") {
+ val color = system.starSystem.holder?.mapColor?.toString() ?: "#fff"
+ style = "color:$color;margin:0;white-space:nowrap;width:640px;height:125px"
+ +system.starSystem.name
+ }
+
+ p(classes = "system-faction") {
+ style = "text-align:center;color:#fff;margin:0;white-space:nowrap;width:640px;height:125px"
+ system.starSystem.holder?.let { flavor ->
+ img(src = flavor.flagUrl, alt = flavor.displayName) {
+ style = "width:640px;height:400px;transform-origin:top center;transform:scale(0.32)"
+ }
}
}
}
package net.starshipfights.campaign
-private class CampaignUIResponderImpl(val clusterView: StarClusterView) : CampaignUIResponder {
+import externals.threejs.Scene
+
+private class CampaignUIResponderImpl(val clusterView: StarClusterView, val scene: Scene) : CampaignUIResponder {
override fun getStarCluster() = clusterView
+ override fun getRenderScene() = scene
}
-fun uiResponder(clusterView: StarClusterView): CampaignUIResponder = CampaignUIResponderImpl(clusterView)
+fun uiResponder(clusterView: StarClusterView, scene: Scene): CampaignUIResponder = CampaignUIResponderImpl(clusterView, scene)
package net.starshipfights.campaign
+import externals.threejs.Object3D
import externals.threejs.Vector3
import net.starshipfights.game.Faction
import net.starshipfights.game.FactionFlavor
+import net.starshipfights.game.IntColor
+import net.starshipfights.game.ShipType
import kotlin.math.PI
import kotlin.math.atan2
import kotlin.math.cos
val rotation: Double
)
+fun Object3D.applyRenderPosition(renderPosition: FleetRenderPosition) {
+ position.copy(renderPosition.worldPos)
+ CampaignScaling.toWorldRotation(renderPosition.rotation, this)
+}
+
enum class FleetSide {
FRIEND,
ENEMY;
- fun getPositions(numFleets: Int, worldRadius: Double): List<FleetRenderPosition> {
+ fun getPositions(numFleets: Int, worldRadius: Double, worldCenter: Vector3): List<FleetRenderPosition> {
val marks = (1..numFleets).map { it / (numFleets + 1.0) }
val angles = marks.map { (it + 0.5) * PI }
val cosFactor = when (this) {
- FRIEND -> 1.0
- ENEMY -> -1.0
+ FRIEND -> -1.0
+ ENEMY -> 1.0
}
return angles.map { theta ->
- val position = Vector3(cosFactor * cos(theta) * worldRadius, 0, sin(theta) * worldRadius)
+ val position = Vector3(cosFactor * cos(theta) * worldRadius, 9.6, sin(theta) * worldRadius).add(worldCenter)
- val rotation = atan2(cos(theta) * cosFactor, sin(theta))
+ val rotation = atan2(-cos(theta) * cosFactor, -sin(theta))
FleetRenderPosition(position, rotation)
}
FleetSide.FRIEND
else
FleetSide.ENEMY
+
+fun getFleetSide(fleetOwner: FactionFlavor) =
+ mySide?.admiral?.faction?.let { admiralFaction ->
+ getFleetSide(admiralFaction, fleetOwner)
+ } ?: FleetSide.ENEMY
+
+val FactionFlavor.mapColor: IntColor
+ get() = when (this) {
+ FactionFlavor.MECHYRDIA -> IntColor(255, 204, 51)
+ FactionFlavor.TYLA -> IntColor(255, 204, 51)
+ FactionFlavor.OLYMPIA -> IntColor(255, 204, 51)
+ FactionFlavor.TEXANDRIA -> IntColor(255, 204, 51)
+
+ FactionFlavor.NDRC -> IntColor(255, 204, 51)
+ FactionFlavor.CCC -> IntColor(255, 204, 51)
+ FactionFlavor.MJOLNIR_ENERGY -> IntColor(255, 204, 51)
+
+ FactionFlavor.MASRA_DRAETSEN -> IntColor(204, 34, 34)
+ FactionFlavor.AEDON_CULTISTS -> IntColor(204, 34, 34)
+ FactionFlavor.FERTHLON_EXILES -> IntColor(204, 34, 34)
+
+ FactionFlavor.RES_NOSTRA -> IntColor(204, 102, 153)
+ FactionFlavor.CORSAIRS -> IntColor(204, 102, 153)
+ FactionFlavor.FELINAE_FELICES -> IntColor(204, 102, 153)
+
+ FactionFlavor.ISARNAREYKK -> IntColor(34, 221, 34)
+ FactionFlavor.SWARTAREYKK -> IntColor(34, 221, 34)
+ FactionFlavor.THEUDAREYKK -> IntColor(255, 204, 51) // Mechyrdia
+ FactionFlavor.STAHLAREYKK -> IntColor(255, 204, 51) // Also Mechyrdia
+ FactionFlavor.LYUDAREYKK -> IntColor(34, 221, 34)
+ FactionFlavor.NEUIA_FULKREYKK -> IntColor(34, 221, 34)
+
+ FactionFlavor.CORVUS_CLUSTER_VESTIGIUM -> IntColor(108, 96, 153)
+ FactionFlavor.COLEMAN_SF_BASE_VESTIGIUM -> IntColor(108, 96, 153)
+ }
+
+val FactionFlavor.mapCounterShipClass: ShipType
+ get() = when (this) {
+ FactionFlavor.MECHYRDIA -> ShipType.VENSCA
+ FactionFlavor.TYLA -> ShipType.VENSCA
+ FactionFlavor.OLYMPIA -> ShipType.VENSCA
+ FactionFlavor.TEXANDRIA -> ShipType.VENSCA
+
+ FactionFlavor.NDRC -> ShipType.KRIJGSCHUIT
+ FactionFlavor.CCC -> ShipType.KRIJGSCHUIT
+ FactionFlavor.MJOLNIR_ENERGY -> ShipType.KRIJGSCHUIT
+
+ FactionFlavor.MASRA_DRAETSEN -> ShipType.AZATHOTH
+ FactionFlavor.AEDON_CULTISTS -> ShipType.AZATHOTH
+ FactionFlavor.FERTHLON_EXILES -> ShipType.AZATHOTH
+
+ FactionFlavor.RES_NOSTRA -> ShipType.BOBCAT
+ FactionFlavor.CORSAIRS -> ShipType.BOBCAT
+ FactionFlavor.FELINAE_FELICES -> ShipType.BOBCAT
+
+ FactionFlavor.ISARNAREYKK -> ShipType.KHORR
+ FactionFlavor.SWARTAREYKK -> ShipType.KHORR
+ FactionFlavor.THEUDAREYKK -> ShipType.KHORR
+ FactionFlavor.STAHLAREYKK -> ShipType.KHORR
+ FactionFlavor.LYUDAREYKK -> ShipType.KHORR
+ FactionFlavor.NEUIA_FULKREYKK -> ShipType.KHORR
+
+ FactionFlavor.CORVUS_CLUSTER_VESTIGIUM -> ShipType.IOWA
+ FactionFlavor.COLEMAN_SF_BASE_VESTIGIUM -> ShipType.IOWA
+ }
private set
private lateinit var starTypes: Map<StarType, RenderFactory>
- lateinit var star: CustomRenderFactory<CelestialObjectWithPointer<CelestialObject.Star>>
- private set
+ private lateinit var star: CustomRenderFactory<CelestialObjectWithPointer<CelestialObject.Star>>
private lateinit var planetTypes: Map<PlanetType, RenderFactory>
- lateinit var planet: CustomRenderFactory<CelestialObjectWithPointer<CelestialObject.Planet>>
- private set
+ private lateinit var planet: CustomRenderFactory<CelestialObjectWithPointer<CelestialObject.Planet>>
- lateinit var starSystem: CustomRenderFactory<StarSystemWithId>
- private set
+ private lateinit var fleetMeshesRaw: Map<ShipType, RenderFactory>
+ private lateinit var fleetMeshes: Map<FactionFlavor, RenderFactory>
+ private lateinit var fleetCounter: CustomRenderFactory<FleetPresenceWithPointer>
+ private lateinit var fleetCountersInSystem: CustomRenderFactory<StarSystemWithId>
+ private lateinit var fleetCountersInCluster: CustomRenderFactory<StarClusterView>
+
+ private lateinit var starSystem: CustomRenderFactory<StarSystemWithId>
private lateinit var warpLane: CustomRenderFactory<WarpLaneData>
obj
}
}
+
+ launch {
+ val shipTypes = FactionFlavor.values().map { it.mapCounterShipClass }.distinct()
+
+ fleetMeshesRaw = shipTypes.associateWith { shipType ->
+ async { loadModel(shipType.meshName) }
+ }.mapValues { (_, meshAsync) ->
+ val mesh = meshAsync.await()
+ mesh.scale.setScalar(0.25)
+ RenderFactory { mesh.clone(true) }
+ }
+
+ fleetMeshes = FactionFlavor.values().associateWith { flavor ->
+ val shipTypeMesh = fleetMeshesRaw.getValue(flavor.mapCounterShipClass).generate().unsafeCast<Mesh>()
+
+ shipTypeMesh.material = shipTypeMesh.material.unsafeCast<MeshPhongMaterial>()
+ .apply { emissive = flavor.mapColor.shadow.to3JS() }
+ .forShip(flavor.mapCounterShipClass.faction, flavor)
+
+ RenderFactory { shipTypeMesh.clone(true) }
+ }
+
+ fleetCounter = CustomRenderFactory { (ptr, fleet) ->
+ fleetMeshes.getValue(fleet.owner).generate().apply {
+ userData = FleetPresenceRender(ptr)
+ }
+ }
+
+ fleetCountersInSystem = CustomRenderFactory { (systemId, system) ->
+ val fleetList = system.fleets
+ .map { (id, fleet) -> FleetPresenceWithPointer(FleetPresencePointer(systemId, id), fleet) }
+ .sortedBy { (_, fleet) -> fleet.owner }
+
+ val friendFleets = fleetList.filter { (_, fleet) ->
+ getFleetSide(fleet.owner) == FleetSide.FRIEND
+ }
+
+ val enemyFleets = fleetList.filter { (_, fleet) ->
+ getFleetSide(fleet.owner) == FleetSide.ENEMY
+ }
+
+ val worldRadius = CampaignScaling.toWorldLength(system.radius)
+ val worldCenter = CampaignScaling.toWorldPosition(system.position)
+ val friendPositions = FleetSide.FRIEND.getPositions(friendFleets.size, worldRadius, worldCenter)
+ val enemyPositions = FleetSide.ENEMY.getPositions(enemyFleets.size, worldRadius, worldCenter)
+
+ val friendFleetRenders = (friendFleets.map { fleetCounter.generate(it) } zip friendPositions).map { (obj3d, pos) ->
+ obj3d.apply { applyRenderPosition(pos) }
+ }
+ val enemyFleetRenders = (enemyFleets.map { fleetCounter.generate(it) } zip enemyPositions).map { (obj3d, pos) ->
+ obj3d.apply { applyRenderPosition(pos) }
+ }
+
+ Group().apply {
+ for (friendFleetRender in friendFleetRenders)
+ add(friendFleetRender)
+
+ for (enemyFleetRender in enemyFleetRenders)
+ add(enemyFleetRender)
+
+ userData = systemId.id
+ }
+ }
+
+ fleetCountersInCluster = CustomRenderFactory { cluster ->
+ val systemsFleets = cluster.systems.map { (id, system) ->
+ fleetCountersInSystem.generate(StarSystemWithId(id, system))
+ }
+
+ Group().apply {
+ for (systemFleets in systemsFleets)
+ add(systemFleets)
+
+ userData = "fleet counters"
+ }
+ }
+ }
}
starSystem = CustomRenderFactory { (ssId, starSystem) ->
val torusGeom = TorusGeometry(CampaignScaling.toWorldLength(starSystem.radius), 0.25, 4, 64)
val torusMat = MeshBasicMaterial(configure {
- color = (starSystem.holder?.getMapColor ?: IntColor(255, 255, 255)).to3JS()
+ color = (starSystem.holder?.mapColor ?: IntColor(255, 255, 255)).to3JS()
})
val torus = Mesh(torusGeom, torusMat)
for (lane in cluster.lanes)
add(warpLane.generate(lane.resolve(cluster) ?: continue))
+ add(fleetCountersInCluster.generate(cluster))
+
userData = "star cluster"
}
}
}
object CampaignScaling {
- private const val CELESTIAL_BODY_SIZE_TO_3JS_UNITS_FACTOR = 0.25
+ const val CELESTIAL_BODY_SIZE_PER_3JS_UNITS = 4.0
fun toWorldRotation(facing: Double, obj: Object3D) {
obj.rotateY(facing)
}
- fun toSpaceLength(length3js: Double) = length3js / CELESTIAL_BODY_SIZE_TO_3JS_UNITS_FACTOR
- fun toWorldLength(lengthSc: Double) = lengthSc * CELESTIAL_BODY_SIZE_TO_3JS_UNITS_FACTOR
+ fun toSpaceLength(length3js: Double) = length3js * CELESTIAL_BODY_SIZE_PER_3JS_UNITS
+ fun toWorldLength(lengthSc: Double) = lengthSc / CELESTIAL_BODY_SIZE_PER_3JS_UNITS
fun toSpacePosition(v3: Vector3) = Position(
- Vec2(v3.x.toDouble(), v3.z.toDouble()) / CELESTIAL_BODY_SIZE_TO_3JS_UNITS_FACTOR
+ Vec2(v3.x.toDouble(), v3.z.toDouble()) * CELESTIAL_BODY_SIZE_PER_3JS_UNITS
)
- fun toWorldPosition(pos: Position) = (pos.vector * CELESTIAL_BODY_SIZE_TO_3JS_UNITS_FACTOR).let { (x, y) ->
+ fun toWorldPosition(pos: Position) = (pos.vector / CELESTIAL_BODY_SIZE_PER_3JS_UNITS).let { (x, y) ->
Vector3(x, 0, y)
}
- fun toWorldScale(size: Int) = sqrt(size * 32.0) * CELESTIAL_BODY_SIZE_TO_3JS_UNITS_FACTOR
+ fun toWorldScale(size: Int) = sqrt(size * 32.0) / CELESTIAL_BODY_SIZE_PER_3JS_UNITS
}
external interface CelestialObjectRender {
else
parent?.starSystemRender
+external interface FleetPresenceRender {
+ var isFleetPresence: Boolean
+ var fleetPresenceId: String
+ var starSystemId: String
+}
+
+fun FleetPresenceRender(ptr: FleetPresencePointer) = configure<FleetPresenceRender> {
+ isFleetPresence = true
+ starSystemId = ptr.starSystem.id
+ fleetPresenceId = ptr.fleetPresence.id
+}
+
+val FleetPresenceRender.pointer: FleetPresencePointer
+ get() = FleetPresencePointer(Id(starSystemId), Id(fleetPresenceId))
+
+val Object3D.fleetPresenceRender: FleetPresencePointer?
+ get() = if (userData.isFleetPresence == true)
+ userData.unsafeCast<FleetPresenceRender>().pointer
+ else
+ parent?.fleetPresenceRender
+
val StarClusterBackground.ambientColor: IntColor
get() = when (this) {
StarClusterBackground.BLUE -> IntColor(34, 51, 85)
val Object3D.isStarCluster: Boolean
get() = userData == "star cluster"
+
+val Object3D.isStarClusterFleets: Boolean
+ get() = userData == "fleet counters"
+
+fun Object3D.isStarSystemFleets(system: Id<StarSystem>) = (parent?.isStarClusterFleets == true) && userData == system.id
cameraControls.update(dt)
renderer.render(scene, camera)
GameUI.renderGameUI(cameraControls)
+ GameUI.fitLabels()
}
}
position.y = 7.5
})
}
-
+ }
+
+ private var labelsFit = false
+ fun fitLabels() {
+ if (labelsFit) return
textFit(document.getElementsByClassName("ship-label"))
+ labelsFit = true
}
private fun DIV.drawShipLabel(state: GameState, abilities: List<PlayerAbilityType>, shipId: Id<ShipInstance>, ship: ShipInstance) {
return shipShaderMaterial.clone().unsafeCast<ShaderMaterial>().also { material ->
material.uniforms["diffuse"]?.value?.unsafeCast<Color>()?.copy(color)
material.uniforms["specular"]?.value?.unsafeCast<Color>()?.copy(specular)
+ material.uniforms["emissive"]?.value?.unsafeCast<Color>()?.copy(emissive)
material.uniforms["shininess"]?.value = shininess.toDouble().coerceAtLeast(EPSILON)
map?.let { material.uniforms["map"]?.value = it }
if (detachedSystems.isEmpty())
return warpLanes
+ val lanesWithSegments = warpLanes.map { lane ->
+ val (aId, bId) = lane
+ lane to LineSegment(positions.getValue(aId), positions.getValue(bId))
+ }
+
val possibleLanes = discoveredSystems.map { a ->
val aPos = positions.getValue(a)
val (b, bPos) = detachedSystems.map { b ->
(aPos - bPos).magnitude
}!!
WarpLane(a, b) to LineSegment(aPos, bPos)
+ }.filter { (warpLane, segment) ->
+ lanesWithSegments.none { (l, s) ->
+ warpLane !in l && s in segment
+ } && positions.none { (id, pos) ->
+ id !in warpLane && segment.intersectsCircle(pos, MAX_SYSTEM_SIZE)
+ }
}.sortedBy { (_, it) -> it.length }.map { (it, _) -> it }
- return fixWarpLanes(positions, warpLanes + possibleLanes.take(settings.laneDensity.numToAdd))
+ return fixWarpLanes(positions, warpLanes + possibleLanes.take(1))
}
private fun createStarSystems() = flow {
private val UnplacedStarSystem.numHabitableWorlds: Int
get() = bodies.count { it is CelestialObject.Planet && it.type == PlanetType.TERRESTRIAL }
-private val UnplacedStarSystem.isEldritch: Boolean
- get() = bodies.any { it is CelestialObject.Star && it.type == StarType.X }
-
-private val UnplacedStarSystem.numWorlds: Int
- get() = bodies.count { it is CelestialObject.Planet }
-
@JvmInline
private value class UnnamedCelestialObject private constructor(private val celestialObject: CelestialObject) {
val position: Position
val gray = type.lightColor.let { (r, g, b) -> r + g + b } / 765.0
val area = 4 * PI * size // radius is proportional to sqrt(size)
- // screw it
- val hueTemperature = exp(hue)
-
// neutron stars look blue, but emit light as if they were gray
- val temperature = if (type == StarType.NEUTRON_STAR) 0.6441 else hueTemperature
+ val temperature = exp(if (type == StarType.NEUTRON_STAR) 0.0 else hue)
// not-so-physically-accurate variant of Stefan-Boltzmann equation
return gray * area * temperature
--- /dev/null
+package net.starshipfights.campaign
+
+import net.starshipfights.data.Id
+import net.starshipfights.game.FactionFlavor
+
+fun StarClusterView.testPostProcess(): StarClusterView {
+ val flavors = FactionFlavor.values().toList().shuffled()
+ val ownerFlavors = sequence {
+ while (true)
+ for (flavor in flavors)
+ yield(flavor)
+ }.take(systems.size).toList()
+
+ val ownedSystems = (systems.toList().shuffled() zip ownerFlavors).associate { (systemWithId, flavor) ->
+ val (systemId, system) = systemWithId
+
+ val numOfFleets = (1..3).random()
+ val fleets = (1..numOfFleets).associate { i ->
+ Id<FleetPresence>("${systemId.id}-fleet-$i") to FleetPresence(
+ "Test Fleet $i",
+ flavor,
+ emptyMap()
+ )
+ }
+
+ systemId to system.copy(holder = flavor, fleets = fleets)
+ }
+
+ return copy(systems = ownedSystems)
+}
val cluster = ClusterGenerator(
ClusterGenerationSettings(color, size, density, planets, corruption)
- ).generateCluster()
+ ).generateCluster().testPostProcess()
val clientMode = ClientMode.CampaignMap(
Id(""),