var shipCount = 0
shipTypes.map { st ->
val name = "${side}_${++shipCount}"
+ val flavor = FactionFlavor.defaultForFaction(faction)
Ship(
id = Id(name),
name = name,
shipType = st,
+ shipFlavor = flavor
)
}.associateBy { it.id }
}
val id: Id<Ship>,
val name: String,
- val shipType: ShipType
+ val shipType: ShipType,
+ val shipFlavor: FactionFlavor
) {
val fullName: String
get() = "${shipType.faction.shipPrefix}$name"
--- /dev/null
+package net.starshipfights.game
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class IntColor(val red: Int, val green: Int, val blue: Int) {
+ init {
+ require(red in 0..255) { "Invalid RGB value: red = $red" }
+ require(green in 0..255) { "Invalid RGB value: green = $green" }
+ require(blue in 0..255) { "Invalid RGB value: blue = $blue" }
+ }
+
+ override fun toString(): String {
+ val redHex = red.toString(16).padStart(2, '0')
+ val greenHex = green.toString(16).padStart(2, '0')
+ val blueHex = blue.toString(16).padStart(2, '0')
+
+ return "#$redHex$greenHex$blueHex"
+ }
+}
+
+val IntColor.highlight: IntColor
+ get() = let { (r, g, b) ->
+ IntColor(
+ 255 - (255 - r) * 2 / 3,
+ 255 - (255 - g) * 2 / 3,
+ 255 - (255 - b) * 2 / 3,
+ )
+ }
+
+val Faction.trimColor: IntColor?
+ get() = when (this) {
+ Faction.MECHYRDIA -> IntColor(255, 204, 51)
+ Faction.NDRC -> IntColor(255, 153, 51)
+ Faction.MASRA_DRAETSEN -> IntColor(34, 85, 170)
+ Faction.FELINAE_FELICES -> IntColor(255, 119, 187)
+ Faction.ISARNAREYKK -> null
+ Faction.VESTIGIUM -> IntColor(108, 96, 153)
+ }
+
+enum class FactionFlavor(val nativeName: String?, val translatedName: String, val colorReplacement: IntColor) {
+ MECHYRDIA("Štelflót Ciarstuos Mehurdiasi", "Imperial Star Fleet of Mechyrdia", IntColor(255, 204, 51)),
+ TYLA("Helasram Laevashtam Moashtas Tulasras", "Stellar Navy of the Tylan Republic", IntColor(51, 102, 204)),
+ OLYMPIA("Classis Nautica Rei Publicae Olympicae", "Naval Fleet of the Olympia Commonwealth", IntColor(204, 51, 51)),
+ TEXANDRIA("Texandrische Sternenmarine der Volkswehr", "Texandrian Star Navy of the Public Defense", IntColor(255, 221, 119)),
+
+ NDRC("Sterrenvloot der NdRC", "NdRC Stellar Fleet", IntColor(255, 153, 51)),
+ CCC("Collegium Comitatum Caeleste", "Celestial Caravan Company", IntColor(255, 204, 51)),
+ MJOLNIR_ENERGY("Mjolniri Energia", "Mjölnir Energy", IntColor(34, 68, 136)),
+
+ MASRA_DRAETSEN(null, "Diadochus Masra Draetsen", IntColor(34, 85, 170)),
+ AEDON_CULTISTS(null, "Aedon Cultists", IntColor(136, 68, 204)),
+ FERTHLON_EXILES(null, "Ferthlon Exiles", IntColor(51, 204, 68)),
+
+ RES_NOSTRA(null, "Res Nostra", IntColor(153, 17, 85)),
+ CORSAIRS(null, "Corsairs' Commune", IntColor(34, 34, 34)),
+ FELINAE_FELICES(null, "Felinae Felices", IntColor(255, 119, 187)),
+
+ ISARNAREYKK(null, "Isarnareyksk Federation", IntColor(255, 255, 255)),
+ SWARTAREYKK(null, "Swartareyksk Totalitariat", IntColor(255, 170, 170)),
+ THEUDAREYKK(null, "Theudareyksk Kingdom", IntColor(153, 204, 255)),
+ STAHLAREYKK(null, "Stahlareyksk Binding", IntColor(204, 153, 102)),
+ LYUDAREYKK(null, "Lyudareyksk Baurginassus", IntColor(153, 204, 153)),
+ NEUIA_FULKREYKK(null, "Neuia Fulkreykk Rebellion", IntColor(153, 153, 153)),
+
+ CORVUS_CLUSTER_VESTIGIUM(null, "Vestigium Sect in the Corvus Cluster", IntColor(108, 96, 153)),
+ COLEMAN_SF_BASE_VESTIGIUM(null, "Vestigium Sect at Coleman Space Force Base", IntColor(153, 102, 102)),
+ ;
+
+ companion object {
+ fun defaultForFaction(playerFaction: Faction): FactionFlavor = when (playerFaction) {
+ Faction.MECHYRDIA -> MECHYRDIA
+ Faction.NDRC -> NDRC
+ Faction.MASRA_DRAETSEN -> MASRA_DRAETSEN
+ Faction.FELINAE_FELICES -> FELINAE_FELICES
+ Faction.ISARNAREYKK -> ISARNAREYKK
+ Faction.VESTIGIUM -> CORVUS_CLUSTER_VESTIGIUM
+ }
+
+ fun optionsForAiEnemy(computerFaction: Faction): Set<FactionFlavor> = when (computerFaction) {
+ Faction.MECHYRDIA -> setOf(MECHYRDIA, TYLA, OLYMPIA, TEXANDRIA, NDRC, CORSAIRS, RES_NOSTRA)
+ Faction.NDRC -> setOf(NDRC, CCC, MJOLNIR_ENERGY, RES_NOSTRA, CORSAIRS)
+ Faction.MASRA_DRAETSEN -> setOf(MASRA_DRAETSEN, AEDON_CULTISTS, FERTHLON_EXILES)
+ Faction.FELINAE_FELICES -> setOf(FELINAE_FELICES, RES_NOSTRA, CORSAIRS)
+ Faction.ISARNAREYKK -> setOf(ISARNAREYKK, SWARTAREYKK, THEUDAREYKK, STAHLAREYKK, LYUDAREYKK, NEUIA_FULKREYKK)
+ Faction.VESTIGIUM -> setOf(CORVUS_CLUSTER_VESTIGIUM, COLEMAN_SF_BASE_VESTIGIUM)
+ }
+ }
+}
package externals.threejs
-external fun cloneUniforms(uniforms_src: Any): Any
-
-external fun mergeUniforms(uniforms: Array<Any>): Any
+external object UniformsUtils {
+ fun clone(uniforms_src: Any): Any
+ fun merge(uniforms: Array<Any>): Any
+}
package net.starshipfights.game
import externals.threejs.*
+import net.starshipfights.data.Id
object GameRender {
+ private val shipMeshCache = mutableMapOf<Id<ShipInstance>, Mesh>()
+
fun renderGameState(scene: Scene, state: GameState) {
scene.background = RenderResources.spaceboxes.getValue(state.battleInfo.bg)
scene.getObjectByName("light")?.removeFromParent()
when (state.renderShipAs(ship, mySide)) {
ShipRenderMode.NONE -> {}
ShipRenderMode.SIGNAL -> shipGroup.add(RenderResources.enemySignal.generate(ship.position.location))
- ShipRenderMode.FULL -> shipGroup.add(RenderResources.shipMesh.generate(ship))
+ ShipRenderMode.FULL -> shipGroup.add(shipMeshCache[ship.id]?.also { render ->
+ RenderScaling.toWorldRotation(ship.position.facing, render)
+ render.position.copy(RenderScaling.toWorldPosition(ship.position.location))
+ } ?: RenderResources.shipMesh.generate(ship))
}
}
}
const val METERS_PER_3D_MESH_UNIT = 6.9
fun toWorldRotation(facing: Double, obj: Object3D) {
- obj.rotateY(-facing)
+ obj.rotation.y = -facing
}
fun toBattleLength(length3js: Double) = length3js * METERS_PER_THREEJS_UNIT
outlineRed.material = redOutlineMaterial
CustomRenderFactory { ship ->
+ val faction = ship.ship.shipType.faction
+ val flavor = ship.ship.shipFlavor
val side = ship.owner.relativeTo(mySide)
ShipRender(
mesh.clone(true).unsafeCast<Mesh>().apply {
receiveShadow = true
castShadow = true
+ material = material.unsafeCast<MeshPhongMaterial>().forShip(faction, flavor)
},
when (side) {
LocalSide.GREEN -> outlineGreen
--- /dev/null
+package net.starshipfights.game
+
+import externals.threejs.*
+
+fun IntColor.to3JS() = Color(this.toString())
+
+private val black = Color("#000000")
+private val white = Color("#FFFFFF")
+
+private val shipShaderMaterial: ShaderMaterial by lazy {
+ val customFragmentShader = """
+ |#define PHONG
+ |
+ |uniform vec3 diffuse;
+ |uniform vec3 emissive;
+ |uniform vec3 specular;
+ |uniform float shininess;
+ |uniform float opacity;
+ |
+ |#include <common>
+ |#include <packing>
+ |#include <dithering_pars_fragment>
+ |#include <color_pars_fragment>
+ |#include <uv_pars_fragment>
+ |#include <uv2_pars_fragment>
+ |#include <map_pars_fragment>
+ |#include <alphamap_pars_fragment>
+ |#include <alphatest_pars_fragment>
+ |#include <aomap_pars_fragment>
+ |#include <lightmap_pars_fragment>
+ |#include <emissivemap_pars_fragment>
+ |#include <envmap_common_pars_fragment>
+ |#include <envmap_pars_fragment>
+ |#include <cube_uv_reflection_fragment>
+ |#include <fog_pars_fragment>
+ |#include <bsdfs>
+ |#include <lights_pars_begin>
+ |#include <normal_pars_fragment>
+ |#include <lights_phong_pars_fragment>
+ |#include <shadowmap_pars_fragment>
+ |#include <bumpmap_pars_fragment>
+ |#include <normalmap_pars_fragment>
+ |#include <specularmap_pars_fragment>
+ |#include <logdepthbuf_pars_fragment>
+ |#include <clipping_planes_pars_fragment>
+ |
+ |uniform vec3 oldColorDiff;
+ |uniform vec3 oldColorSpec;
+ |uniform vec3 newColorDiff;
+ |uniform vec3 newColorSpec;
+ |uniform vec3 tintColor;
+ |
+ |// Add the mapTexelToLinear function manually
+ |vec4 mapTexelToLinear( vec4 value ) {
+ | return LinearToLinear( value );
+ |}
+ |
+ |void main() {
+ | #include <clipping_planes_fragment>
+ |
+ | vec4 diffuseColor = vec4( diffuse, opacity );
+ | ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
+ | vec3 totalEmissiveRadiance = emissive;
+ |
+ | #include <logdepthbuf_fragment>
+ |
+ | // Replaces the include of map_fragment
+ |#ifdef USE_MAP
+ | vec4 texelColor = texture2D( map, vUv );
+ | if (texelColor.rgb == oldColorDiff) {
+ | texelColor = vec4(newColorDiff, texelColor.a);
+ | }
+ | texelColor = mapTexelToLinear( texelColor );
+ | diffuseColor *= texelColor;
+ |#endif
+ |
+ | #include <color_fragment>
+ | #include <alphamap_fragment>
+ | #include <alphatest_fragment>
+ |
+ | // Replaces the include of specularmap_fragment
+ | vec3 specularStrength;
+ |#ifdef USE_SPECULARMAP
+ | vec4 texelSpecular = texture2D( specularMap, vUv );
+ | if (texelSpecular.rgb == oldColorSpec) {
+ | texelSpecular = vec4(oldColorSpec, texelSpecular.a);
+ | }
+ | specularStrength = texelSpecular.rgb;
+ |#else
+ | specularStrength = vec3( 1.0, 1.0, 1.0 );
+ |#endif
+ |
+ | #include <normal_fragment_begin>
+ | #include <normal_fragment_maps>
+ | #include <emissivemap_fragment>
+ |
+ | // accumulation
+ | #include <lights_phong_fragment>
+ | #include <lights_fragment_begin>
+ | #include <lights_fragment_maps>
+ | #include <lights_fragment_end>
+ |
+ | // modulation
+ | #include <aomap_fragment>
+ | vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;
+ |
+ | #include <envmap_fragment>
+ | #include <output_fragment>
+ |
+ | // Inserted custom code
+ | gl_FragColor = vec4(gl_FragColor.rgb * tintColor, gl_FragColor.a);
+ | // End of custom code
+ |
+ | #include <tonemapping_fragment>
+ | #include <encodings_fragment>
+ | #include <fog_fragment>
+ | #include <premultiplied_alpha_fragment>
+ | #include <dithering_fragment>
+ |}
+ """.trimMargin()
+
+ ShaderMaterial(
+ configure {
+ uniforms = UniformsUtils.merge(
+ arrayOf(
+ ShaderLib.phong.uniforms,
+ configure<AnonymousStruct8> {
+ this["oldColorDiff"] = configure { value = black }
+ this["oldColorSpec"] = configure { value = black }
+ this["newColorDiff"] = configure { value = black }
+ this["newColorSpec"] = configure { value = black }
+ this["tintColor"] = configure { value = white }
+ }
+ )
+ ).unsafeCast<AnonymousStruct8>()
+ defines = configure<StringDict<Any>> {
+ this["USE_UV"] = ""
+ this["USE_MAP"] = ""
+ this["USE_SPECULARMAP"] = ""
+ }
+ vertexShader = ShaderLib.phong.vertexShader
+ fragmentShader = customFragmentShader
+ lights = true
+ }
+ )
+}
+
+fun MeshPhongMaterial.forShip(faction: Faction, flavor: FactionFlavor): ShaderMaterial {
+ 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["shininess"]?.value = shininess.toDouble().coerceAtLeast(EPSILON)
+
+ map?.let { material.uniforms["map"]?.value = it }
+ specularMap?.let { material.uniforms["specularMap"]?.value = it }
+
+ faction.trimColor?.let { oldColorDiff ->
+ val newColorDiff = flavor.colorReplacement
+
+ val oldColorSpec = oldColorDiff.highlight
+ val newColorSpec = newColorDiff.highlight
+
+ material.uniforms["oldColorDiff"]?.value?.unsafeCast<Color>()?.copy(oldColorDiff.to3JS())
+ material.uniforms["oldColorSpec"]?.value?.unsafeCast<Color>()?.copy(oldColorSpec.to3JS())
+ material.uniforms["newColorDiff"]?.value?.unsafeCast<Color>()?.copy(newColorDiff.to3JS())
+ material.uniforms["newColorSpec"]?.value?.unsafeCast<Color>()?.copy(newColorSpec.to3JS())
+ } ?: material.uniforms["tintColor"]?.value?.unsafeCast<Color>()?.copy(flavor.colorReplacement.to3JS())
+ }
+}
override val id: Id<ShipInDrydock> = Id(),
val name: String,
val shipType: ShipType,
+ val shipFlavor: FactionFlavor = FactionFlavor.defaultForFaction(shipType.faction),
val readyAt: @Contextual Instant,
val owningAdmiral: Id<Admiral>
) : DataDocument<ShipInDrydock> {
val shipData: Ship
- get() = Ship(id.reinterpret(), name, shipType)
+ get() = Ship(id.reinterpret(), name, shipType, shipFlavor)
val fullName: String
get() = shipData.fullName
.associate { it.shipData.id to it.shipData }
}
-fun generateFleet(admiral: Admiral): List<ShipInDrydock> = ShipWeightClass.values()
+fun generateFleet(admiral: Admiral, flavor: FactionFlavor = FactionFlavor.defaultForFaction(admiral.faction)): List<ShipInDrydock> = ShipWeightClass.values()
.flatMap { swc ->
val shipTypes = ShipType.values().filter { st ->
st.weightClass == swc && st.faction == admiral.faction
id = Id(),
name = name,
shipType = st,
+ shipFlavor = flavor,
readyAt = now,
owningAdmiral = admiral.id
)
val guestDeployCenter = Position(Vec2(0.0, (battleLength / 2) - deployLength2))
val aiAdmiral = genAI(enemyFaction, battleInfo.size)
+ val aiFlavor = FactionFlavor.optionsForAiEnemy(enemyFaction).random()
return GameState(
start = GameStart(
-PI / 2,
PickBoundary.Rectangle(guestDeployCenter, deployWidth2, deployLength2),
-PI / 2,
- generateFleet(aiAdmiral)
+ generateFleet(aiAdmiral, aiFlavor)
.associate { it.shipData.id to it.shipData }
.filterValues { it.shipType.weightClass.tier <= battleInfo.size.maxTier }
)