diff --git a/README.md b/README.md
index e13b7fe..f665540 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
#
Volynov
-A space artillery game designed for players that want to destroy each other and the planets around them. Fire missiles, bomb, high tech payloads, and sabotage equipment at the enemy and odge their attempts to do the same. All spaceships, planets and warheads are influenced by realistic physics to produce n-body orbits and collisions.
+A space artillery game designed for players that want to destroy each other and the planets around them. Fire missiles, bombs, high tech payloads, and sabotage equipment at the enemy and dodge their attempts to do the same. All spaceships, planets and warheads are influenced by realistic physics to produce n-body orbits and collisions.
2020 03 01, First contact, again
diff --git a/build.gradle.kts b/build.gradle.kts
index 62ea56a..012e691 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,14 +4,8 @@ plugins {
kotlin("jvm") version "1.3.41"
}
-kotlin {
- experimental {
- coroutines = org.jetbrains.kotlin.gradle.dsl.Coroutines.ENABLE
- }
-}
-
group = "blaarkies"
-version = "0.0-SNAPSHOT"
+version = "0.0.0"
repositories {
mavenCentral()
@@ -21,13 +15,6 @@ val kotlinVersion = "1.3.10"
val lwjglVersion = "3.2.3"
val lwjglNatives = "natives-windows"
-tasks.test {
- useJUnitPlatform()
- testLogging {
- events("PASSED", "SKIPPED", "FAILED")
- }
-}
-
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3")
@@ -39,49 +26,114 @@ dependencies {
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
val lwjglList = listOf(
arrayOf("lwjgl", true),
-// arrayOf("lwjgl-assimp", true),
-// arrayOf("lwjgl-bgfx", true),
-// arrayOf("lwjgl-cuda", false),
-// arrayOf("lwjgl-egl", false),
+ // arrayOf("lwjgl-assimp", true),
+ // arrayOf("lwjgl-bgfx", true),
+ // arrayOf("lwjgl-cuda", false),
+ // arrayOf("lwjgl-egl", false),
arrayOf("lwjgl-glfw", true),
arrayOf("lwjgl-jawt", false),
-// arrayOf("lwjgl-jemalloc", true),
-// arrayOf("lwjgl-libdivide", true),
-// arrayOf("lwjgl-llvm", true),
-// arrayOf("lwjgl-lmdb", true),
-// arrayOf("lwjgl-lz4", true),
-// arrayOf("lwjgl-meow", true),
-// arrayOf("lwjgl-nanovg", true),
-// arrayOf("lwjgl-nfd", true),
-// arrayOf("lwjgl-nuklear", true),
-// arrayOf("lwjgl-odbc", false),
-// arrayOf("lwjgl-openal", true),
-// arrayOf("lwjgl-opencl", false),
+ // arrayOf("lwjgl-jemalloc", true),
+ // arrayOf("lwjgl-libdivide", true),
+ // arrayOf("lwjgl-llvm", true),
+ // arrayOf("lwjgl-lmdb", true),
+ // arrayOf("lwjgl-lz4", true),
+ // arrayOf("lwjgl-meow", true),
+ // arrayOf("lwjgl-nanovg", true),
+ // arrayOf("lwjgl-nfd", true),
+ // arrayOf("lwjgl-nuklear", true),
+ // arrayOf("lwjgl-odbc", false),
+ // arrayOf("lwjgl-openal", true),
+ // arrayOf("lwjgl-opencl", false),
arrayOf("lwjgl-opengl", true),
-// arrayOf("lwjgl-opengles", true),
-// arrayOf("lwjgl-openvr", true),
-// arrayOf("lwjgl-opus", true),
-// arrayOf("lwjgl-ovr", true),
-// arrayOf("lwjgl-par", true),
-// arrayOf("lwjgl-remotery", true),
-// arrayOf("lwjgl-rpmalloc", true),
-// arrayOf("lwjgl-shaderc", true),
-// arrayOf("lwjgl-sse", true),
+ // arrayOf("lwjgl-opengles", true),
+ // arrayOf("lwjgl-openvr", true),
+ // arrayOf("lwjgl-opus", true),
+ // arrayOf("lwjgl-ovr", true),
+ // arrayOf("lwjgl-par", true),
+ // arrayOf("lwjgl-remotery", true),
+ // arrayOf("lwjgl-rpmalloc", true),
+ // arrayOf("lwjgl-shaderc", true),
+ // arrayOf("lwjgl-sse", true),
arrayOf("lwjgl-stb", true)
-// arrayOf("lwjgl-tinyexr", true),
-// arrayOf("lwjgl-tinyfd", true),
-// arrayOf("lwjgl-tootle", true),
-// arrayOf("lwjgl-vma", true),
-// arrayOf("lwjgl-vulkan", false),
-// arrayOf("lwjgl-xxhash", true),
-// arrayOf("lwjgl-yoga", true),
-// arrayOf("lwjgl-zstd", true)
+ // arrayOf("lwjgl-tinyexr", true),
+ // arrayOf("lwjgl-tinyfd", true),
+ // arrayOf("lwjgl-tootle", true),
+ // arrayOf("lwjgl-vma", true),
+ // arrayOf("lwjgl-vulkan", false),
+ // arrayOf("lwjgl-xxhash", true),
+ // arrayOf("lwjgl-yoga", true),
+ // arrayOf("lwjgl-zstd", true)
)
lwjglList.forEach { implementation("org.lwjgl", it[0].toString()) }
lwjglList.filter { it[1] == true }
.forEach { runtimeOnly("org.lwjgl", it[0].toString(), classifier = lwjglNatives) }
}
-tasks.withType {
- kotlinOptions.jvmTarget = "11"
+sourceSets {
+ main {
+ resources {
+ exclude("textures", "fonts")
+ }
+ }
+}
+
+tasks {
+
+ withType {
+ kotlinOptions.jvmTarget = "1.8"
+ }
+
+ register("zipFolder") {
+ from("$buildDir/Volynov-$version")
+ destinationDirectory.set(File("$buildDir/"))
+ }
+
+ task("renameFolder") {
+ mustRunAfter("createBat")
+ doLast {
+ file("$buildDir/libs").renameTo(file("$buildDir/Volynov-$version"))
+ }
+ }
+
+ task("createBat") {
+ mustRunAfter("uberJar")
+ doLast {
+ File("$buildDir/libs/run.bat")
+ .writeText("""|chcp 65001
+ |java -Dfile.encoding=UTF-8 -Dorg.lwjgl.util.Debug=true -jar ./volynov-$version-uber.jar
+ |pause
+ """.trimMargin())
+ }
+ }
+
+ register("copyTextures") {
+ from("$projectDir/src/main/resources")
+ exclude("shaders")
+ into("$buildDir/libs")
+ }
+
+ register("uberJar") {
+ dependsOn(configurations.runtimeClasspath)
+
+ archiveClassifier.set("uber")
+ manifest { attributes["Main-Class"] = "MainKt" }
+
+ from(sourceSets.main.get().output)
+ from({
+ configurations.runtimeClasspath.get()
+ .filter { it.name.endsWith("jar") }
+ .map { zipTree(it) }
+ })
+ }
+
+ task("package") {
+ group = "build"
+ dependsOn("clean", "uberJar", "copyTextures", "createBat", "renameFolder", "zipFolder")
+ }
+}
+
+tasks.test {
+ group = "build"
+ useJUnitPlatform()
+ testLogging { events("PASSED", "SKIPPED", "FAILED") }
}
diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt
index d9a7f1b..f4ad33a 100644
--- a/src/main/kotlin/Main.kt
+++ b/src/main/kotlin/Main.kt
@@ -6,7 +6,7 @@ import kotlin.system.exitProcess
fun main() = runBlocking {
try {
val gameLogic: IGameLogic = AppLogic()
- val gameEngine = AppRunner("Volynov", 700, 700, true, gameLogic)
+ val gameEngine = AppRunner("Volynov", 1920, 1080, true, gameLogic)
gameEngine.run()
} catch (exception: Exception) {
exception.printStackTrace()
diff --git a/src/main/kotlin/app/AppLogic.kt b/src/main/kotlin/app/AppLogic.kt
index 04a66e8..9a7db9c 100644
--- a/src/main/kotlin/app/AppLogic.kt
+++ b/src/main/kotlin/app/AppLogic.kt
@@ -13,16 +13,15 @@ class AppLogic : IGameLogic {
private val renderer = Renderer()
private val gameState = GameState()
- private val textures = TextureHolder()
- private val drawer = Drawer(renderer, textures)
- private val gamePhaseHandler = GamePhaseHandler(gameState, drawer, textures)
+ private val drawer = Drawer(renderer)
+ private val gamePhaseHandler = GamePhaseHandler(gameState, drawer)
private val inputHandler = InputHandler(gamePhaseHandler)
@Throws(Exception::class)
override fun init(window: Window) {
gameState.init(window)
renderer.init(gameState.camera)
- textures.init()
+ drawer.init()
gamePhaseHandler.init(window)
inputHandler.init(window)
}
diff --git a/src/main/kotlin/display/Window.kt b/src/main/kotlin/display/Window.kt
index f9eb144..62140ef 100644
--- a/src/main/kotlin/display/Window.kt
+++ b/src/main/kotlin/display/Window.kt
@@ -1,16 +1,21 @@
package display
import display.events.MouseButtonEvent
+import display.events.MouseScrollEvent
import io.reactivex.subjects.PublishSubject
import org.jbox2d.common.Vec2
import org.lwjgl.BufferUtils
import org.lwjgl.glfw.GLFW
+import org.lwjgl.glfw.GLFW.glfwGetKey
+import org.lwjgl.glfw.GLFW.glfwGetKeyName
import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL11.*
import org.lwjgl.system.Callback
import org.lwjgl.system.MemoryUtil
+import utility.Common.makeVec2
+import java.nio.DoubleBuffer
class Window(private val title: String, var width: Int, var height: Int, private var vSync: Boolean) {
@@ -21,7 +26,8 @@ class Window(private val title: String, var width: Int, var height: Int, private
val keyboardEvent = PublishSubject.create()
val mouseButtonEvent = PublishSubject.create()
val cursorPositionEvent = PublishSubject.create()
- val mouseScrollEvent = PublishSubject.create()
+ val mouseScrollEvent = PublishSubject.create()
+ val textInputEvent = PublishSubject.create()
fun init() {
// Setup an error callback. The default implementation
@@ -41,10 +47,11 @@ class Window(private val title: String, var width: Int, var height: Int, private
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 4) // anti-aliasing
// Create the window
- windowHandle = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL)
- if (windowHandle == MemoryUtil.NULL) {
- throw RuntimeException("Failed to create the GLFW window")
- }
+ windowHandle = GLFW.glfwCreateWindow(width, height, title, GLFW.glfwGetPrimaryMonitor(), MemoryUtil.NULL)
+ // windowHandle = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL)
+ // if (windowHandle == MemoryUtil.NULL) {
+ // throw RuntimeException("Failed to create the GLFW window")
+ // }
// Setup resize callback
GLFW.glfwSetFramebufferSizeCallback(windowHandle) { _, width, height ->
this.width = width
@@ -52,9 +59,10 @@ class Window(private val title: String, var width: Int, var height: Int, private
}
// Get the resolution of the primary monitor
- val videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!!
+ // val videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!!
// Center our window
- GLFW.glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2)
+ // GLFW.glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2)
+
GLFW.glfwMakeContextCurrent(windowHandle) // Make the OpenGL context current
if (isVSync()) { // Enable v-sync
GLFW.glfwSwapInterval(1)
@@ -67,7 +75,7 @@ class Window(private val title: String, var width: Int, var height: Int, private
}
private fun setupInputCallbacks() {
- GLFW.glfwSetKeyCallback(windowHandle) { window, key, scancode, action, mods ->
+ GLFW.glfwSetKeyCallback(windowHandle) { _, key, scancode, action, mods ->
if (action == GLFW.GLFW_PRESS) {
when {
key == GLFW.GLFW_KEY_F12 -> {
@@ -82,20 +90,22 @@ class Window(private val title: String, var width: Int, var height: Int, private
}
}?.let { callbacks.add(it) }
- GLFW.glfwSetMouseButtonCallback(windowHandle) { window, button, action, mods ->
+ GLFW.glfwSetMouseButtonCallback(windowHandle) { _, button, action, mods ->
mouseButtonEvent.onNext(MouseButtonEvent(button, action, mods, getCursorPosition()))
}?.let { callbacks.add(it) }
- GLFW.glfwSetCursorPosCallback(windowHandle) { window, xPos, yPos ->
- cursorPositionEvent.onNext(Vec2(xPos.toFloat(), yPos.toFloat()))
+ GLFW.glfwSetCursorPosCallback(windowHandle) { _, xPos, yPos ->
+ cursorPositionEvent.onNext(makeVec2(xPos, yPos))
}?.let { callbacks.add(it) }
- GLFW.glfwSetScrollCallback(windowHandle) { window, xOffset, yOffset ->
- mouseScrollEvent.onNext(Vec2(xOffset.toFloat(), yOffset.toFloat()))
+ GLFW.glfwSetScrollCallback(windowHandle) { _, xOffset, yOffset ->
+ mouseScrollEvent.onNext(MouseScrollEvent(makeVec2(xOffset, yOffset), getCursorPosition()))
}?.let { callbacks.add(it) }
-// glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
-// glfwSetCharCallback(window, character_callback);
+ GLFW.glfwSetCharCallback(windowHandle) { _, codepoint ->
+ textInputEvent.onNext(Character.toChars(codepoint)[0].toString())
+ }?.let { callbacks.add(it) }
+ // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
}
fun setClearColor(r: Float, g: Float, b: Float, alpha: Float) {
@@ -120,7 +130,7 @@ class Window(private val title: String, var width: Int, var height: Int, private
val y = BufferUtils.createDoubleBuffer(1)
GLFW.glfwGetCursorPos(windowHandle, x, y)
- return Vec2(x.get().toFloat(), y.get().toFloat())
+ return makeVec2(x, y)
}
fun exit() {
diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt
index 1fa13d4..a2b492b 100644
--- a/src/main/kotlin/display/draw/Drawer.kt
+++ b/src/main/kotlin/display/draw/Drawer.kt
@@ -3,40 +3,55 @@ package display.draw
import display.graphic.BasicShapes
import display.graphic.Color
import display.graphic.Renderer
-import display.graphic.Texture
+import display.graphic.SnipRegion
+import display.text.TextJustify
import engine.freeBody.FreeBody
+import engine.freeBody.Particle
+import engine.freeBody.Vehicle
import engine.physics.CellLocation
import engine.physics.GravityCell
+import game.GamePlayer
import org.jbox2d.common.Vec2
+import org.lwjgl.opengl.GL11
+import org.lwjgl.opengl.GL12
+import utility.Common.makeVec2
+import utility.Common.makeVec2Circle
+import utility.Common.vectorUnit
import java.util.*
import kotlin.math.sqrt
-class Drawer(val renderer: Renderer, val textures: TextureHolder) {
+class Drawer(val renderer: Renderer) {
- fun drawDebugForces(freeBody: FreeBody) {
- val x = freeBody.worldBody.position.x
- val y = freeBody.worldBody.position.y
- val accelerationX = freeBody.worldBody.m_force.x
- val accelerationY = freeBody.worldBody.m_force.y
-
- val multiplier = 2000f
- val linePoints = listOf(
- x,
- y,
- x + accelerationX * multiplier,
- y + accelerationY * multiplier
- )
- val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, 2f)
- val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints)
- val data = getColoredData(
- triangleStripPoints + arrowHeadPoints,
- Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f)
- ).toFloatArray()
+ val textures = TextureHolder()
- textures.white_pixel.bind()
-// renderer.drawStrip(data)
+ fun init() {
+ textures.init()
+ }
- renderer.drawText(freeBody.id, freeBody.worldBody.position, Vec2(1f, 1f), Color.WHITE)
+ fun drawDebugForces(freeBody: FreeBody) {
+ // val x = freeBody.worldBody.position.x
+ // val y = freeBody.worldBody.position.y
+ // val accelerationX = freeBody.worldBody.m_force.x
+ // val accelerationY = freeBody.worldBody.m_force.y
+ //
+ // val multiplier = 2000f
+ // val linePoints = listOf(
+ // x,
+ // y,
+ // x + accelerationX * multiplier,
+ // y + accelerationY * multiplier
+ // )
+ // val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f)
+ // val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints)
+ // val data = getColoredData(
+ // triangleStripPoints + arrowHeadPoints,
+ // Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f)
+ // ).toFloatArray()
+
+ textures.getTexture(TextureEnum.white_pixel).bind()
+ // renderer.drawStrip(data)
+
+ renderer.drawText(freeBody.id, freeBody.worldBody.position, vectorUnit, Color.WHITE, TextJustify.LEFT)
}
fun drawTrail(freeBody: FreeBody) {
@@ -45,24 +60,28 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) {
if (linePoints.size < 4) {
return
}
- val data = getLine(linePoints, Color(0.4f, 0.7f, 1f, 0.5f), Color.TRANSPARENT, .1f, 0f)
+ val trailColor = when (freeBody) {
+ is Vehicle -> freeBody.textureConfig.color.setAlpha(.3f)
+ else -> Color(.4f, .7f, 1f, .5f)
+ }
+ val data = getLine(linePoints, trailColor, Color.TRANSPARENT, .1f, 0f)
- textures.white_pixel.bind()
+ textures.getTexture(TextureEnum.white_pixel).bind()
renderer.drawStrip(data)
}
fun drawFreeBody(freeBody: FreeBody) {
- freeBody.textureConfig.texture.bind()
+ textures.getTexture(freeBody.textureConfig.texture).bind()
renderer.drawShape(
freeBody.textureConfig.gpuBufferData,
freeBody.worldBody.position,
freeBody.worldBody.angle,
- Vec2(freeBody.radius, freeBody.radius)
+ vectorUnit.mul(freeBody.radius)
)
}
fun drawGravityCells(gravityMap: HashMap, resolution: Float) {
- textures.white_pixel.bind()
+ textures.getTexture(TextureEnum.white_pixel).bind()
val maxMass = gravityMap.maxBy { (_, cell) -> cell.totalMass }!!.value.totalMass
val scale = 0.707106781f * resolution
gravityMap.forEach { (key, cell) ->
@@ -74,12 +93,12 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) {
(it[0] / 2 - 0.5f), (it[1] / 2 - 0.5f)
)
}.toFloatArray()
- renderer.drawShape(data, Vec2(key.x * resolution, key.y * resolution), 0f, Vec2(scale, scale))
+ renderer.drawShape(data, makeVec2(key.x, key.y).mul(resolution), 0f, makeVec2(scale))
}
}
- fun drawPicture(texture: Texture, scale: Vec2 = Vec2(1f, 1f), offset: Vec2 = Vec2()) {
- texture.bind()
+ fun drawBackground(textureEnum: TextureEnum, scale: Vec2 = vectorUnit, offset: Vec2 = Vec2()) {
+ val texture = textures.getTexture(textureEnum).bind()
val left = -texture.width / 2f
val right = texture.width / 2f
@@ -91,12 +110,69 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) {
listOf(
it[0], it[1], 0f,
1f, 1f, 1f, 1f,
- (it[0] / 2 - 0.5f) * scale.x + offset.x,
- (it[1] / 2 - 0.5f) * scale.y + offset.y
+ (it[0] / 2f - 0.5f) * scale.x + offset.x,
+ (it[1] / 2f - 0.5f) * scale.y + offset.y
+ )
+ }.toFloatArray()
+
+ renderer.drawShape(data, scale = vectorUnit.mul(45f))
+ }
+
+ fun drawIcon(textureEnum: TextureEnum,
+ scale: Vec2 = vectorUnit,
+ offset: Vec2 = Vec2(),
+ color: Color
+ ) {
+ val texture = textures.getTexture(textureEnum).bind()
+
+ val left = -texture.width / 2f
+ val right = texture.width / 2f
+ val top = texture.height / 2f
+ val bottom = -texture.height / 2f
+
+ val data = listOf(left, bottom, left, top, right, top, right, bottom).chunked(2)
+ .flatMap {
+ listOf(
+ it[0], it[1], 0f,
+ color.red, color.green, color.blue, color.alpha,
+ (it[0] / 2f - 0.5f),
+ (it[1] / 2f - 0.5f)
)
}.toFloatArray()
- renderer.drawShape(data, scale = Vec2(1f, 1f).mul(45f))
+ renderer.drawShape(data, offset, 0f, scale, useCamera = false,
+ snipRegion = SnipRegion(offset.add(scale.negate()), scale.mul(2f)))
+ }
+
+ fun drawPlayerAimingPointer(player: GamePlayer) {
+ val playerLocation = player.vehicle!!.worldBody.position
+ val angle = player.playerAim.angle
+ val aimLocation = makeVec2Circle(angle).mul(player.playerAim.power / 10f)
+
+ val linePoints = listOf(
+ playerLocation.x,
+ playerLocation.y,
+ playerLocation.x + aimLocation.x,
+ playerLocation.y + aimLocation.y
+ )
+ val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f)
+ val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints, .5f)
+ val data = getColoredData(
+ triangleStripPoints + arrowHeadPoints, Color.RED.setAlpha(.5f), Color.RED.setAlpha(.1f)
+ ).toFloatArray()
+
+ textures.getTexture(TextureEnum.white_pixel).bind()
+ renderer.drawStrip(data)
+ }
+
+ fun drawParticle(particle: Particle) {
+ textures.getTexture(particle.textureConfig.texture).bind()
+ renderer.drawShape(
+ particle.textureConfig.gpuBufferData,
+ particle.worldBody.position,
+ particle.worldBody.angle,
+ vectorUnit.mul(particle.radius)
+ )
}
companion object {
@@ -118,7 +194,8 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) {
chunk[0], chunk[1], 0f, /* pos*/
color.red, color.green, color.blue, color.alpha, /* color*/
0f, 0f /* texture*/
- ) }
+ )
+ }
}
fun getLine(
diff --git a/src/main/kotlin/display/draw/TextureConfig.kt b/src/main/kotlin/display/draw/TextureConfig.kt
index d8d3418..4d52a83 100644
--- a/src/main/kotlin/display/draw/TextureConfig.kt
+++ b/src/main/kotlin/display/draw/TextureConfig.kt
@@ -1,23 +1,29 @@
package display.draw
-import Vector2f
-import display.graphic.Texture
+import display.graphic.Color
+import org.jbox2d.common.Vec2
+import utility.Common.vectorUnit
class TextureConfig(
- val texture: Texture, val scale: Vector2f = Vector2f(1f, 1f), val offset: Vector2f = Vector2f(),
- var chunkedVertices: List> = listOf(), var gpuBufferData: FloatArray = floatArrayOf()
+ var texture: TextureEnum,
+ val scale: Vec2 = vectorUnit,
+ val offset: Vec2 = Vec2(),
+ var chunkedVertices: List> = listOf(),
+ var gpuBufferData: FloatArray = floatArrayOf(),
+ val color: Color = Color.WHITE
) {
- fun updateGpuBufferData() {
+ fun updateGpuBufferData(): TextureConfig {
gpuBufferData = chunkedVertices.flatMap {
val (x, y) = it
listOf(
x, y, 0f,
- 1f, 1f, 1f, 1f,
+ color.red, color.green, color.blue, color.alpha,
(x * .5f - 0.5f) * scale.x + offset.x,
(y * .5f - 0.5f) * scale.y + offset.y
)
}.toFloatArray()
+ return this
}
}
diff --git a/src/main/kotlin/display/draw/TextureEnum.kt b/src/main/kotlin/display/draw/TextureEnum.kt
new file mode 100644
index 0000000..a679399
--- /dev/null
+++ b/src/main/kotlin/display/draw/TextureEnum.kt
@@ -0,0 +1,11 @@
+package display.draw
+
+enum class TextureEnum {
+ marble_earth,
+ full_moon,
+ metal,
+ pavement,
+ white_pixel,
+ stars_2k,
+ icon_aim
+}
diff --git a/src/main/kotlin/display/draw/TextureHolder.kt b/src/main/kotlin/display/draw/TextureHolder.kt
index 4b98944..86c3b8a 100644
--- a/src/main/kotlin/display/draw/TextureHolder.kt
+++ b/src/main/kotlin/display/draw/TextureHolder.kt
@@ -4,20 +4,22 @@ import display.graphic.Texture
class TextureHolder {
- lateinit var marble_earth: Texture
- lateinit var full_moon: Texture
- lateinit var metal: Texture
- lateinit var pavement: Texture
- lateinit var white_pixel: Texture
- lateinit var stars_2k: Texture
+ private val textureHashMap = HashMap()
fun init() {
- marble_earth = Texture.loadTexture("src\\main\\resources\\textures\\marble_earth.png")
- full_moon = Texture.loadTexture("src\\main\\resources\\textures\\full_moon.png")
- metal = Texture.loadTexture("src\\main\\resources\\textures\\metal.png")
- pavement = Texture.loadTexture("src\\main\\resources\\textures\\pavement.png")
- white_pixel = Texture.loadTexture("src\\main\\resources\\textures\\white_pixel.png")
- stars_2k = Texture.loadTexture("src\\main\\resources\\textures\\stars_2k.png")
+ val hMap = textureHashMap
+ hMap[TextureEnum.marble_earth] = Texture.loadTexture("./textures/marble_earth.png")
+ hMap[TextureEnum.full_moon] = Texture.loadTexture("./textures/full_moon.png")
+ hMap[TextureEnum.metal] = Texture.loadTexture("./textures/metal.png")
+ hMap[TextureEnum.pavement] = Texture.loadTexture("./textures/pavement.png")
+ hMap[TextureEnum.white_pixel] = Texture.loadTexture("./textures/white_pixel.png")
+ hMap[TextureEnum.stars_2k] = Texture.loadTexture("./textures/stars_2k.png")
+ hMap[TextureEnum.icon_aim] = Texture.loadTexture("./textures/icon_aim.png")
+ }
+
+ fun getTexture(texture: TextureEnum): Texture = textureHashMap[texture].let {
+ checkNotNull(it) { "Cannot find texture $texture" }
+ it
}
}
diff --git a/src/main/kotlin/display/events/MouseScrollEvent.kt b/src/main/kotlin/display/events/MouseScrollEvent.kt
new file mode 100644
index 0000000..f949e5e
--- /dev/null
+++ b/src/main/kotlin/display/events/MouseScrollEvent.kt
@@ -0,0 +1,8 @@
+package display.events
+
+import org.jbox2d.common.Vec2
+
+class MouseScrollEvent(
+ val movement: Vec2,
+ val location: Vec2
+)
diff --git a/src/main/kotlin/display/graphic/BasicShapes.kt b/src/main/kotlin/display/graphic/BasicShapes.kt
index 3655aa1..1f18aaa 100644
--- a/src/main/kotlin/display/graphic/BasicShapes.kt
+++ b/src/main/kotlin/display/graphic/BasicShapes.kt
@@ -23,19 +23,31 @@ object BasicShapes {
val square = polygon4.map { it * sqrt(2f) }
- private fun getPolygonVertices(corners: Int): List = (0 until corners).flatMap {
- val t = 2 * PI * (it / corners.toFloat()) + PI * .25
+ val polygon4Spiked = getSpikedPolygon(8)
+
+ val verticalLine = listOf(0f, 1f, 0f, -1f)
+
+ private fun getPolygonVertices(corners: Int, rotate: Double = .25): List = (0 until corners).flatMap {
+ val t = 2 * PI * (it / corners.toFloat()) + PI * rotate
listOf(cos(t).toFloat(), sin(t).toFloat())
}
- fun getArrowHeadPoints(linePoints: List): List {
+ private fun getSpikedPolygon(corners: Int, smoothness: Float = .6f): List {
+ return getPolygonVertices(corners).chunked(2)
+ .withIndex()
+ .flatMap { (index, vertex) ->
+ val scale = (if (index.rem(2) == 0) 1f else smoothness)
+ listOf(vertex[0] * scale, vertex[1] * scale)
+ }
+ }
+
+ fun getArrowHeadPoints(linePoints: List, headSize: Float = 1f): List {
val (ax, ay, bx, by) = linePoints
val normalY = bx - ax
val normalX = -by + ay
val magnitude = Director.getDistance(normalX, normalY)
- val headSize = 5f
val x = headSize * normalX / magnitude
val y = headSize * normalY / magnitude
return listOf(
diff --git a/src/main/kotlin/display/graphic/Color.kt b/src/main/kotlin/display/graphic/Color.kt
index 6e3ce0b..1900ecc 100644
--- a/src/main/kotlin/display/graphic/Color.kt
+++ b/src/main/kotlin/display/graphic/Color.kt
@@ -1,6 +1,18 @@
package display.graphic
-class Color(val red: Float = 0f, val green: Float = 0f, val blue: Float = 0f, val alpha: Float = 0f) {
+class Color {
+
+ val red: Float
+ val green: Float
+ val blue: Float
+ val alpha: Float
+
+ constructor(red: Float = 0f, green: Float = 0f, blue: Float = 0f, alpha: Float = 1f) {
+ this.red = getSafeValue(red)
+ this.green = getSafeValue(green)
+ this.blue = getSafeValue(blue)
+ this.alpha = getSafeValue(alpha)
+ }
operator fun times(value: Float) =
Color(this.red * value, this.green * value, this.blue * value, this.alpha * value)
@@ -15,9 +27,18 @@ class Color(val red: Float = 0f, val green: Float = 0f, val blue: Float = 0f, va
getIntToFloat(alpha)
)
+ constructor(hexValue: String) {
+ val (red, green, blue, alpha) = hexValue.dropWhile { it == '#' }.chunked(2)
+ this.red = getHexToFloat(red)
+ this.green = getHexToFloat(green)
+ this.blue = getHexToFloat(blue)
+ this.alpha = getHexToFloat(alpha)
+ }
+
fun setAlpha(newValue: Float): Color = Color(red, green, blue, newValue)
companion object {
+
val WHITE = Color(1f, 1f, 1f, 1f)
val BLACK = Color(0f, 0f, 0f, 1f)
val RED = Color(1f, 0f, 0f, 1f)
@@ -25,8 +46,47 @@ class Color(val red: Float = 0f, val green: Float = 0f, val blue: Float = 0f, va
val BLUE = Color(0f, 0f, 1f, 1f)
val TRANSPARENT = Color(0f, 0f, 0f, 0f)
+ val HEX = Color("#01FF6499")
+ val HSV = createFromHsv(.5f, 1f, .5f, 1f)
+
+ val PALETTE8 = (0..8).map { createFromHsv(it / 7f, 1f, .5f) }
+ val PALETTE_TINT10 =
+ (0..8).map { createFromHsv(it / 7f, 1f, .8f) } + listOf(WHITE, createFromHsv(0f, 0f, .7f))
+
private fun getSafeValue(value: Float) = value.coerceIn(0f, 1f)
private fun getIntToFloat(value: Int) = getSafeValue(value / 255f)
+
+ private fun getHexToFloat(value: String) = value.toShort(16).toFloat().let { getSafeValue(it / 255f) }
+
+ fun createFromHsv(hue: Float = 0f, saturation: Float = 0f, light: Float = 0f, alpha: Float = 1f): Color {
+ val rgbList = hslToRgb(hue, saturation, light)
+ return Color(rgbList[0], rgbList[1], rgbList[2], alpha)
+ }
+
+ private fun hslToRgb(hue: Float, saturation: Float, light: Float): List {
+ return if (saturation == 0f) {
+ listOf(light, light, light)
+ } else {
+ val q = if (light < .5f) light * (1 + saturation) else light + saturation - light * saturation
+ val p = 2 * light - q
+ listOf(
+ hueToRgb(p, q, hue + 1f / 3f),
+ hueToRgb(p, q, hue),
+ hueToRgb(p, q, hue - 1f / 3f)
+ )
+ }
+ }
+
+ private fun hueToRgb(p: Float, q: Float, t: Float): Float {
+ var t = t
+ if (t < 0f) t += 1f
+ if (t > 1f) t -= 1f
+ if (t < 1f / 6f) return p + (q - p) * 6f * t
+ if (t < 1f / 2f) return q
+ return if (t < 2f / 3f) p + (q - p) * (2f / 3f - t) * 6f else p
+ }
+
}
+
}
diff --git a/src/main/kotlin/display/graphic/Renderer.kt b/src/main/kotlin/display/graphic/Renderer.kt
index 9acbecc..8320a4c 100644
--- a/src/main/kotlin/display/graphic/Renderer.kt
+++ b/src/main/kotlin/display/graphic/Renderer.kt
@@ -1,8 +1,9 @@
package display.graphic
import Matrix4f
-import input.CameraView
import display.text.Font
+import display.text.TextJustify
+import input.CameraView
import org.jbox2d.common.Vec2
import org.lwjgl.opengl.GL11.*
import org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER
@@ -11,6 +12,8 @@ import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER
import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
import org.lwjgl.system.MemoryUtil
import utility.Common
+import utility.Common.getSafePath
+import utility.Common.vectorUnit
import java.awt.FontFormatException
import java.io.FileInputStream
import java.io.IOException
@@ -20,7 +23,7 @@ import java.util.logging.Logger
class Renderer {
- var debugOffset: Vec2 = Vec2(1f, 1f)
+ var debugOffset: Vec2 = vectorUnit
private lateinit var vao: VertexArrayObject
private lateinit var vbo: VertexBufferObject
@@ -40,7 +43,8 @@ class Renderer {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
font = try {
- Font(FileInputStream("src\\main\\resources\\fonts\\ALBMT___.TTF"), 80)
+ val fontPath = getSafePath("./fonts/ALBMT___.TTF")
+ Font(FileInputStream(fontPath), 80)
} catch (ex: FontFormatException) {
Logger.getLogger(Renderer::class.java.name).log(Level.CONFIG, null, ex)
Font()
@@ -80,25 +84,33 @@ class Renderer {
numVertices = 0
}
- fun drawText(text: CharSequence, offset: Vec2, scale: Vec2, color: Color, useCamera: Boolean = true) =
- font.drawText(this, text, offset, scale, color, useCamera)
+ fun drawText(text: CharSequence,
+ offset: Vec2,
+ scale: Vec2,
+ color: Color,
+ justify: TextJustify = TextJustify.LEFT,
+ useCamera: Boolean = true,
+ snipRegion: SnipRegion? = null
+ ) = font.drawText(this, text, offset, scale, color, justify, useCamera, snipRegion)
fun drawShape(
data: FloatArray,
offset: Vec2 = Vec2(),
h: Float = 0f,
- scale: Vec2 = Vec2(1f, 1f),
- useCamera: Boolean = true
- ) = drawEntity(data, offset, h, scale, GL_TRIANGLE_FAN, useCamera)
+ scale: Vec2 = vectorUnit,
+ useCamera: Boolean = true,
+ snipRegion: SnipRegion? = null
+ ) = drawEntity(data, offset, h, scale, GL_TRIANGLE_FAN, useCamera, snipRegion)
fun drawStrip(
data: FloatArray,
offset: Vec2 = Vec2(),
h: Float = 0f,
- scale: Vec2 = Vec2(1f, 1f),
- useCamera: Boolean = true
+ scale: Vec2 = vectorUnit,
+ useCamera: Boolean = true,
+ snipRegion: SnipRegion? = null
) =
- drawEntity(data, offset, h, scale, GL_TRIANGLE_STRIP, useCamera)
+ drawEntity(data, offset, h, scale, GL_TRIANGLE_STRIP, useCamera, snipRegion)
private fun drawEntity(
data: FloatArray,
@@ -106,7 +118,8 @@ class Renderer {
h: Float,
scale: Vec2,
drawType: Int,
- useCamera: Boolean
+ useCamera: Boolean,
+ snipRegion: SnipRegion?
) {
begin()
if (vertices.remaining() < data.size) {
@@ -117,7 +130,7 @@ class Renderer {
vertices.put(data)
numVertices += data.size / vertexDimensionCount
- setUniformInputs(offset, 0f, h, scale, useCamera)
+ setUniformInputs(offset, 0f, h, scale, useCamera, snipRegion)
end(drawType)
}
@@ -155,11 +168,17 @@ class Renderer {
offset: Vec2 = Vec2(),
z: Float = 0f,
h: Float = 0f,
- scale: Vec2 = Vec2(1f, 1f),
- useCamera: Boolean
+ scale: Vec2 = vectorUnit,
+ useCamera: Boolean,
+ snipRegion: SnipRegion?
) {
-// val uniTex = program!!.getUniformLocation("texImage")
-// program!!.setUniform(uniTex, 0)
+ // val uniTex = program!!.getUniformLocation("texImage")
+ // program!!.setUniform(uniTex, 0)
+
+ val gameCamera = cameraView.getRenderCamera()
+ val guiCamera = Matrix4f()
+ glDisable(GL_SCISSOR_TEST)
+
val model = Matrix4f.translate(offset.x, offset.y, z)
.multiply(Matrix4f.rotate(h * Common.radianToDegree, 0f, 0f, 1f))
@@ -167,11 +186,17 @@ class Renderer {
val uniModel = program.getUniformLocation("model")
program.setUniform(uniModel, model)
- val zoomScale = 1f / cameraView.z
val view = when (useCamera) {
- true -> Matrix4f.scale(zoomScale, zoomScale, 1f)
- .multiply(Matrix4f.translate(-cameraView.location.x, -cameraView.location.y, 0f))
- false -> Matrix4f()
+ true -> gameCamera
+ false -> {
+ if (snipRegion != null) {
+ glScissor(cameraView.windowWidth.div(2).toInt() + snipRegion.offset.x.toInt(),
+ cameraView.windowHeight.div(2).toInt() + snipRegion.offset.y.toInt(),
+ snipRegion.scale.x.toInt(), snipRegion.scale.y.toInt())
+ glEnable(GL_SCISSOR_TEST)
+ }
+ guiCamera
+ }
}
val uniView = program.getUniformLocation("view")
program.setUniform(uniView, view)
@@ -202,3 +227,4 @@ class Renderer {
program.pointVertexAttribute(texAttribute, 2, 9 * java.lang.Float.BYTES, 7 * java.lang.Float.BYTES)
}
}
+
diff --git a/src/main/kotlin/display/graphic/SnipRegion.kt b/src/main/kotlin/display/graphic/SnipRegion.kt
new file mode 100644
index 0000000..77a7fb8
--- /dev/null
+++ b/src/main/kotlin/display/graphic/SnipRegion.kt
@@ -0,0 +1,5 @@
+package display.graphic
+
+import org.jbox2d.common.Vec2
+
+class SnipRegion(val offset: Vec2, val scale: Vec2)
diff --git a/src/main/kotlin/display/graphic/Texture.kt b/src/main/kotlin/display/graphic/Texture.kt
index b09660c..db28d7f 100644
--- a/src/main/kotlin/display/graphic/Texture.kt
+++ b/src/main/kotlin/display/graphic/Texture.kt
@@ -4,6 +4,8 @@ import org.lwjgl.BufferUtils
import org.lwjgl.opengl.GL11.*
import org.lwjgl.stb.STBImage.*
import org.lwjgl.system.MemoryStack
+import utility.Common.getSafePath
+import java.io.File
import java.nio.ByteBuffer
class Texture {
@@ -62,7 +64,9 @@ class Texture {
return texture
}
- fun loadTexture(path: String): Texture {
+ fun loadTexture(resourcePath: String): Texture {
+ val safePath = getSafePath(resourcePath)
+
var image = BufferUtils.createByteBuffer(0)
var width = 0
var height = 0
@@ -72,14 +76,15 @@ class Texture {
val comp = stack.mallocInt(1)
stbi_set_flip_vertically_on_load(true)
- image = stbi_load(path, w, h, comp, 0)
- ?: throw RuntimeException("Failed to load a texture file!\n${stbi_failure_reason()}")
+ image = stbi_load(safePath, w, h, comp, 0)
+ ?: throw RuntimeException("Cannot load texture file at $safePath \n${stbi_failure_reason()}")
width = w.get()
height = h.get()
}
return createTexture(width, height, image)
}
+
}
}
diff --git a/src/main/kotlin/display/gui/GuiButton.kt b/src/main/kotlin/display/gui/GuiButton.kt
index 7a1c241..6b97b32 100644
--- a/src/main/kotlin/display/gui/GuiButton.kt
+++ b/src/main/kotlin/display/gui/GuiButton.kt
@@ -1,9 +1,13 @@
package display.gui
import display.draw.Drawer
+import display.draw.TextureEnum
import display.graphic.BasicShapes
import display.graphic.Color
+import display.graphic.SnipRegion
+import display.text.TextJustify
import org.jbox2d.common.Vec2
+import utility.Common.vectorUnit
class GuiButton(
drawer: Drawer,
@@ -12,8 +16,9 @@ class GuiButton(
title: String,
textSize: Float = .2f,
color: Color = Color.WHITE.setAlpha(.7f),
- private val onClick: () -> Unit = {}
-) : GuiElement(drawer, offset, scale, title, textSize, color) {
+ private val onClick: () -> Unit = {},
+ updateCallback: (GuiElement) -> Unit = {}
+) : GuiElement(drawer, offset, scale, title, textSize, color, updateCallback) {
private var buttonOutline: FloatArray
private var buttonBackground: FloatArray
@@ -29,27 +34,29 @@ class GuiButton(
calculateElementRegion(this)
}
- override fun render() {
- drawer.textures.white_pixel.bind()
+ override fun render(snipRegion: SnipRegion?) {
+ drawer.textures.getTexture(TextureEnum.white_pixel).bind()
when (currentPhase) {
- GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false)
+ GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false,
+ snipRegion = snipRegion)
}
when (currentPhase) {
GuiElementPhases.CLICKED -> drawer.renderer.drawStrip(
buttonOutline,
offset.add(Vec2(0f, -2f)),
- useCamera = false
+ useCamera = false,
+ snipRegion = snipRegion
)
- else -> drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false)
+ else -> drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false, snipRegion = snipRegion)
}
- GuiElement.drawLabel(drawer, this)
- super.render()
+ super.render(snipRegion)
+ drawLabel(drawer, this, TextJustify.CENTER, snipRegion)
}
- override fun handleClick(location: Vec2) {
+ override fun handleLeftClick(location: Vec2) {
when {
isHover(location) -> {
currentPhase = GuiElementPhases.CLICKED
@@ -59,6 +66,4 @@ class GuiButton(
}
}
-
-
}
diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt
index eee14a1..9e1edd6 100644
--- a/src/main/kotlin/display/gui/GuiController.kt
+++ b/src/main/kotlin/display/gui/GuiController.kt
@@ -1,27 +1,50 @@
package display.gui
import display.draw.Drawer
+import display.draw.TextureEnum
import display.graphic.Color
+import display.text.TextJustify
import game.GamePlayer
import org.jbox2d.common.Vec2
+import utility.Common.roundFloat
+import utility.Common.vectorUnit
+import kotlin.math.roundToInt
class GuiController(private val drawer: Drawer) {
private val elements = mutableListOf()
- fun render() = elements.forEach { it.render() }
+ fun render() = elements.forEach { it.render(null) }
+
+ fun update() = elements.forEach { it.update() }
fun clear() = elements.clear()
fun checkHover(location: Vec2) = elements.forEach { it.handleHover(location) }
- fun checkLeftClick(location: Vec2) = elements.toList().forEach { it.handleClick(location) }
+ fun checkLeftClick(location: Vec2) = elements.toList().forEach { it.handleLeftClick(location) }
+
+ fun checkLeftClickDrag(location: Vec2, movement: Vec2) =
+ elements.forEach { it.handleLeftClickDrag(location, movement) }
+
+ fun checkScroll(movement: Vec2, location: Vec2) = elements.forEach { it.handleScroll(location, movement) }
+
+ fun checkAddTextInput(text: String) = elements.filterIsInstance().forEach { it.handleAddTextInput(text) }
+
+ fun checkRemoveTextInput() = elements.filterIsInstance().forEach { it.handleRemoveTextInput() }
+
+ fun stopTextInput() = elements.filterIsInstance().forEach { it.stopTextInput() }
+
+ fun textInputIsBusy(): Boolean = elements.filterIsInstance().toList().any { it.textInputIsBusy }
+
+ fun locationIsGui(location: Vec2): Boolean = elements.any { it.isHover(location) }
fun createMainMenu(
onClickNewGame: () -> Unit,
onClickSettings: () -> Unit,
onClickQuit: () -> Unit
) {
+ clear()
val buttonScale = Vec2(200f, 44f)
val menuButtons = listOf(
GuiButton(drawer, scale = buttonScale, title = "New Game", textSize = .27f, onClick = onClickNewGame),
@@ -32,11 +55,10 @@ class GuiController(private val drawer: Drawer) {
setElementsInRows(menuButtons, 40f, false)
menuButtons.forEach { it.addOffset(Vec2(0f, 150f)) }
- elements.add(GuiLabel(drawer, Vec2(-10f, 250f), "Volynov", .6f))
+ elements.add(GuiLabel(drawer, Vec2(-10f, 250f), TextJustify.CENTER, "Volynov", .6f))
elements.addAll(menuButtons)
}
-
fun createMainMenuSelectPlayers(
onClickStart: () -> Unit,
onClickCancel: () -> Unit,
@@ -44,7 +66,8 @@ class GuiController(private val drawer: Drawer) {
onRemovePlayer: () -> Unit,
playerList: MutableList
) {
- elements.add(GuiLabel(drawer, Vec2(-10f, 250f), "Select Players", .2f))
+ clear()
+ elements.add(GuiLabel(drawer, Vec2(0f, 250f), TextJustify.CENTER, "Select Players", .2f))
updateMainMenuSelectPlayers(playerList, onAddPlayer, onRemovePlayer)
@@ -82,13 +105,18 @@ class GuiController(private val drawer: Drawer) {
)
val addRemoveButtonsList = listOf(addPlayerButton, removePlayerButton)
setElementsInRows(addRemoveButtonsList)
- val addRemoveContainer = GuiWindow(
+ val addRemoveContainer = GuiPanel(
drawer, scale = playerButtonSize, color = Color.TRANSPARENT,
draggable = false, childElements = addRemoveButtonsList.toMutableList()
)
- val playerButtons = players
- .map { GuiButton(drawer, scale = playerButtonSize, title = "P${it.name}", textSize = .3f) }
+ val playerButtons = players.withIndex()
+ .map { (index, player) ->
+ val playerName = if (player.name.length == 1) "" else player.name
+ GuiInput(drawer, scale = Vec2(75f, 50f), placeholder = "Player ${index + 1}",
+ onChange = { text -> player.name = text })
+ .setTextValue(playerName)
+ }
.let { listOf(addRemoveContainer) + it }
setElementsInColumns(playerButtons, 40f)
when (indexOfPlayersButtons) {
@@ -100,56 +128,128 @@ class GuiController(private val drawer: Drawer) {
}
fun createPlayersPickShields(player: GamePlayer, onClickShield: (player: GamePlayer) -> Unit) {
- val shieldPickerWindow =
- GuiWindow(
- drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "Player ${player.name} to pick a shield",
+ clear()
+ val shieldPickerPanel =
+ GuiPanel(
+ drawer, Vec2(710f, -340f), Vec2(250f, 200f), title = "${player.name} to pick a shield",
draggable = true
)
- shieldPickerWindow.addChild(
+ shieldPickerPanel.addChild(
GuiButton(drawer, scale = Vec2(100f, 25f), title = "Pick one", onClick = { onClickShield(player) })
)
- elements.add(shieldPickerWindow)
+ elements.add(shieldPickerPanel)
}
- fun createPlayerCommandPanel(player: GamePlayer, onClickFire: (player: GamePlayer) -> Unit) {
- val commandPanelWindow =
- GuiWindow(
- drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "Player ${player.name}",
- draggable = true
+ fun createPlayerCommandPanel(
+ player: GamePlayer,
+ onClickAim: (player: GamePlayer) -> Unit,
+ onClickPower: (player: GamePlayer) -> Unit,
+ onClickFire: (player: GamePlayer) -> Unit
+ ) {
+ clear()
+ val commandPanel = GuiPanel(
+ drawer, Vec2(710f, -340f), Vec2(250f, 200f),
+ title = player.name, draggable = true
+ )
+ val weaponsList = GuiScroll(drawer, Vec2(50f, -50f), Vec2(100f, 100f)).addChildren(
+ (1..5).map {
+ GuiButton(drawer, scale = Vec2(100f, 25f), title = "Boom $it", textSize = .15f,
+ onClick = { println("clicked [Boom $it]") })
+ }
+ )
+ commandPanel.addChildren(
+ listOf(
+ GuiButton(drawer, Vec2(-200f, 0f), Vec2(50f, 25f), title = "Aim",
+ onClick = { onClickAim(player) }),
+ GuiButton(drawer, Vec2(-200f, -50f), Vec2(50f, 25f), title = "Power",
+ onClick = { onClickPower(player) }),
+ GuiButton(drawer, Vec2(-200f, -100f), Vec2(50f, 25f), title = "Fire",
+ onClick = { onClickFire(player) }),
+
+ GuiLabel(drawer, Vec2(-210f, 110f), justify = TextJustify.LEFT,
+ title = getPlayerAimAngleDisplay(player),
+ textSize = .15f,
+ updateCallback = { it.title = getPlayerAimAngleDisplay(player) }),
+ GuiLabel(drawer, Vec2(-210f, 80f), justify = TextJustify.LEFT, title = getPlayerAimPowerDisplay(player),
+ textSize = .15f,
+ updateCallback = { it.title = getPlayerAimPowerDisplay(player) }),
+
+ weaponsList,
+
+ GuiIcon(drawer, Vec2(-230f, 110f), vectorUnit.mul(20f), texture = TextureEnum.icon_aim)
+ )
+ )
+ elements.add(commandPanel)
+ }
+
+ private fun getPlayerAimPowerDisplay(player: GamePlayer): String =
+ player.playerAim.power.let { displayNumber(it, 2) + "%" }
+
+ private fun getPlayerAimAngleDisplay(player: GamePlayer): String =
+ player.playerAim.getDegreesAngle().let { displayNumber(it, 2) + "ยบ" }
+
+ fun createRoundLeaderboard(players: MutableList, onClickNextRound: () -> Unit) {
+ clear()
+ val leaderBoardPanel = GuiPanel(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = false)
+ val playerLines = listOf(GuiLabel(
+ drawer,
+ Vec2(-50f, 100f),
+ justify = TextJustify.LEFT,
+ title = "Player Score".padStart(10, ' '),
+ textSize = .2f
+ )) + players.sortedByDescending { it.score }.map {
+ GuiLabel(
+ drawer,
+ Vec2(-50f, 100f),
+ justify = TextJustify.LEFT,
+ title = "${it.name.padEnd(20, ' ')}${it.score.roundToInt()}".padStart(10, ' '),
+ textSize = .2f
+ )
+ }
+ setElementsInRows(playerLines, 10f)
+
+ leaderBoardPanel.addChildren(playerLines)
+ leaderBoardPanel.addChild(
+ GuiButton(
+ drawer, Vec2(0f, -250f), Vec2(100f, 25f), "Next Match",
+ onClick = onClickNextRound
)
- commandPanelWindow.addChild(
- GuiButton(drawer, scale = Vec2(100f, 25f), title = "Fire all guns!", onClick = { onClickFire(player) })
)
- elements.add(commandPanelWindow)
+ elements.add(leaderBoardPanel)
}
- private fun setElementsInColumns(elements: List, gap: Float = 0f, centered: Boolean = true) {
- val totalWidth = elements.map { it.scale.x * 2f }.sum() + gap * (elements.size - 1)
- val columnSize = totalWidth / elements.size
+ private fun displayNumber(value: Float, decimals: Int): String = roundFloat(value, decimals).toString()
+
+ companion object {
+
+ fun setElementsInColumns(elements: List, gap: Float = 0f, centered: Boolean = true) {
+ val totalWidth = elements.map { it.scale.x * 2f }.sum() + gap * (elements.size - 1)
+ val columnSize = totalWidth / elements.size
- elements.withIndex()
- .forEach { (index, element) ->
- val newXOffset = when (centered) {
- true -> columnSize * (index - (elements.size - 1) * .5f)
- else -> columnSize * index + columnSize * .5f
+ elements.withIndex()
+ .forEach { (index, element) ->
+ val newXOffset = when (centered) {
+ true -> columnSize * (index - (elements.size - 1) * .5f)
+ else -> columnSize * index + columnSize * .5f
+ }
+ element.addOffset(Vec2(newXOffset, element.offset.y))
}
- element.addOffset(Vec2(newXOffset, element.offset.y))
- }
- }
+ }
- private fun setElementsInRows(elements: List, gap: Float = 0f, centered: Boolean = true) {
- val totalHeight = elements.map { it.scale.y * 2f }.sum() + gap * (elements.size - 1)
- val rowSize = totalHeight / elements.size
-
- elements.withIndex()
- .forEach { (index, element) ->
- val newYOffset = when (centered) {
- true -> rowSize * (index - (elements.size - 1) * .5f)
- else -> rowSize * index + rowSize * .5f
- } * -1f
- element.addOffset(Vec2(element.offset.x, newYOffset))
- }
- }
+ fun setElementsInRows(elements: List, gap: Float = 0f, centered: Boolean = true) {
+ val totalHeight = elements.map { it.scale.y * 2f }.sum() + gap * (elements.size - 1)
+ val rowSize = totalHeight / elements.size
+
+ elements.withIndex()
+ .forEach { (index, element) ->
+ val newYOffset = when (centered) {
+ true -> rowSize * (index - (elements.size - 1) * .5f)
+ else -> rowSize * index + rowSize * .5f
+ } * -1f
+ element.addOffset(Vec2(element.offset.x, newYOffset))
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/display/gui/GuiElement.kt b/src/main/kotlin/display/gui/GuiElement.kt
index 5e6e38b..c8c2a39 100644
--- a/src/main/kotlin/display/gui/GuiElement.kt
+++ b/src/main/kotlin/display/gui/GuiElement.kt
@@ -2,16 +2,19 @@ package display.gui
import display.draw.Drawer
import display.graphic.Color
+import display.graphic.SnipRegion
+import display.text.TextJustify
import org.jbox2d.common.Vec2
-import utility.Common
+import utility.Common.vectorUnit
open class GuiElement(
protected val drawer: Drawer,
override var offset: Vec2,
- override val scale: Vec2 = Vec2(1f, 1f),
- override val title: String,
+ override val scale: Vec2 = vectorUnit,
+ override var title: String,
override val textSize: Float,
override val color: Color,
+ override val updateCallback: (GuiElement) -> Unit,
override var id: GuiElementIdentifierType = GuiElementIdentifierType.DEFAULT
) : GuiElementInterface {
@@ -20,55 +23,55 @@ open class GuiElement(
protected var topRight: Vec2 = Vec2()
protected var bottomLeft: Vec2 = Vec2()
- override fun render() {
- }
+ override fun render(snipRegion: SnipRegion?) = Unit
- override fun addOffset(newOffset: Vec2) {
- GuiElement.addOffset(this, newOffset)
- }
+ override fun update() = updateCallback(this)
- override fun updateOffset(newOffset: Vec2) {
- GuiElement.updateOffset(this, newOffset)
- }
+ override fun addOffset(newOffset: Vec2) = addOffset(this, newOffset)
- override fun handleHover(location: Vec2) {
- when {
- isHover(location) -> currentPhase = GuiElementPhases.HOVERED
- else -> currentPhase = GuiElementPhases.IDLE
- }
- }
+ override fun updateOffset(newOffset: Vec2) = updateOffset(this, newOffset)
- override fun handleClick(location: Vec2) {
+ override fun handleHover(location: Vec2) = when {
+ isHover(location) -> currentPhase = GuiElementPhases.HOVERED
+ else -> currentPhase = GuiElementPhases.IDLE
}
- protected fun isHover(location: Vec2): Boolean {
- return location.x > bottomLeft.x
+ override fun handleLeftClick(location: Vec2) = Unit
+
+ override fun handleLeftClickDrag(location: Vec2, movement: Vec2) = Unit
+
+ override fun handleScroll(location: Vec2, movement: Vec2) = Unit
+
+ fun isHover(location: Vec2): Boolean =
+ location.x > bottomLeft.x
&& location.x < topRight.x
&& location.y > bottomLeft.y
&& location.y < topRight.y
- }
companion object {
- fun drawLabel(drawer: Drawer, element: GuiElementInterface) {
- drawer.renderer.drawText(
- element.title, element.offset,
- Common.vectorUnit.mul(element.textSize),
- element.color, false
- )
- }
+ fun drawLabel(drawer: Drawer,
+ element: GuiElementInterface,
+ justify: TextJustify = TextJustify.CENTER,
+ snipRegion: SnipRegion?
+ ) = drawer.renderer.drawText(
+ element.title,
+ element.offset,
+ vectorUnit.mul(element.textSize),
+ element.color,
+ justify,
+ false,
+ snipRegion)
fun calculateElementRegion(element: GuiElement) {
element.bottomLeft = element.offset.add(element.scale.negate())
element.topRight = element.offset.add(element.scale)
}
- fun addOffset(element: GuiElement, newOffset: Vec2) {
- updateOffset(element, element.offset.add(newOffset))
- }
+ fun addOffset(element: GuiElement, newOffset: Vec2) = updateOffset(element, element.offset.add(newOffset))
fun updateOffset(element: GuiElement, newOffset: Vec2) {
- element.offset = newOffset
+ element.offset.set(newOffset)
calculateElementRegion(element)
}
diff --git a/src/main/kotlin/display/gui/GuiElementInterface.kt b/src/main/kotlin/display/gui/GuiElementInterface.kt
index 8c4f7a5..f915b45 100644
--- a/src/main/kotlin/display/gui/GuiElementInterface.kt
+++ b/src/main/kotlin/display/gui/GuiElementInterface.kt
@@ -1,6 +1,7 @@
package display.gui
import display.graphic.Color
+import display.graphic.SnipRegion
import org.jbox2d.common.Vec2
interface GuiElementInterface {
@@ -10,11 +11,15 @@ interface GuiElementInterface {
val title: String
val textSize: Float
val color: Color
+ val updateCallback: (GuiElement) -> Unit
var id: GuiElementIdentifierType
- fun render()
+ fun render(snipRegion: SnipRegion?)
+ fun update()
+ fun handleHover(location: Vec2)
+ fun handleLeftClick(location: Vec2)
+ fun handleLeftClickDrag(location: Vec2, movement: Vec2)
+ fun handleScroll(location: Vec2, movement: Vec2)
fun addOffset(newOffset: Vec2)
fun updateOffset(newOffset: Vec2)
- fun handleHover(location: Vec2)
- fun handleClick(location: Vec2)
}
diff --git a/src/main/kotlin/display/gui/GuiElementPhases.kt b/src/main/kotlin/display/gui/GuiElementPhases.kt
index d2cbae5..5cccbb9 100644
--- a/src/main/kotlin/display/gui/GuiElementPhases.kt
+++ b/src/main/kotlin/display/gui/GuiElementPhases.kt
@@ -3,6 +3,7 @@ package display.gui
enum class GuiElementPhases {
IDLE,
HOVERED,
- CLICKED
+ CLICKED,
+ INPUT
}
diff --git a/src/main/kotlin/display/gui/GuiIcon.kt b/src/main/kotlin/display/gui/GuiIcon.kt
new file mode 100644
index 0000000..e9581d5
--- /dev/null
+++ b/src/main/kotlin/display/gui/GuiIcon.kt
@@ -0,0 +1,34 @@
+package display.gui
+
+import display.draw.Drawer
+import display.draw.TextureEnum
+import display.graphic.Color
+import display.graphic.SnipRegion
+import org.jbox2d.common.Vec2
+import utility.Common.vectorUnit
+
+class GuiIcon(
+ drawer: Drawer,
+ offset: Vec2 = Vec2(),
+ scale: Vec2 = vectorUnit,
+ title: String = "",
+ textSize: Float = 0f,
+ color: Color = Color.WHITE.setAlpha(.7f),
+ val texture: TextureEnum = TextureEnum.white_pixel
+) : GuiElement(
+ drawer,
+ offset,
+ scale,
+ title,
+ textSize,
+ color,
+ {}
+) {
+
+ override fun render(snipRegion: SnipRegion?) {
+ super.render(snipRegion)
+
+ drawer.drawIcon(texture, scale, offset, color)
+ }
+
+}
diff --git a/src/main/kotlin/display/gui/GuiInput.kt b/src/main/kotlin/display/gui/GuiInput.kt
new file mode 100644
index 0000000..43761d7
--- /dev/null
+++ b/src/main/kotlin/display/gui/GuiInput.kt
@@ -0,0 +1,122 @@
+package display.gui
+
+import display.draw.Drawer
+import display.draw.TextureEnum
+import display.graphic.BasicShapes
+import display.graphic.Color
+import display.graphic.SnipRegion
+import display.text.TextJustify
+import org.jbox2d.common.Vec2
+import utility.Common.makeVec2
+import utility.Common.vectorUnit
+import utility.toList
+
+class GuiInput(
+ drawer: Drawer,
+ offset: Vec2 = Vec2(),
+ scale: Vec2 = Vec2(200f, 50f),
+ placeholder: String,
+ textSize: Float = .15f,
+ color: Color = Color.WHITE.setAlpha(.7f),
+ private val onClick: () -> Unit = {},
+ updateCallback: (GuiElement) -> Unit = {},
+ val onChange: (String) -> Unit
+) : GuiElement(drawer, offset, scale, placeholder, textSize, color, updateCallback) {
+
+ private val blinkRate = 400
+ private var cursorLine: FloatArray
+ private var buttonOutline: FloatArray
+ private var buttonBackground: FloatArray
+ private var backgroundColor = color.setAlpha(.1f)
+
+ private var inputText = ""
+ private val paddedScale = Vec2(scale.x - 8f, 20f)
+ val textInputIsBusy
+ get() = currentPhase == GuiElementPhases.INPUT
+
+ init {
+ val verticalCursorLinePoint = BasicShapes.verticalLine.chunked(2)
+ .flatMap {
+ val location = makeVec2(it[0] * paddedScale.x, it[1] * paddedScale.y)
+ .also { vec -> vec.x -= paddedScale.x }
+ location.toList()
+ }
+ cursorLine = Drawer.getLine(verticalCursorLinePoint, color, startWidth = 1.2f)
+
+ val linePoints = BasicShapes.square.chunked(2)
+ .flatMap { listOf(it[0] * scale.x, it[1] * scale.y) }
+ buttonOutline = Drawer.getLine(linePoints, color, startWidth = 1f, wrapAround = true)
+ buttonBackground = Drawer.getColoredData(linePoints, backgroundColor).toFloatArray()
+
+ calculateElementRegion(this)
+ }
+
+ override fun render(snipRegion: SnipRegion?) {
+ drawer.textures.getTexture(TextureEnum.white_pixel).bind()
+
+ when (currentPhase) {
+ GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false,
+ snipRegion = snipRegion)
+ GuiElementPhases.INPUT -> {
+ drawer.renderer.drawShape(buttonBackground, offset, useCamera = false, snipRegion = snipRegion)
+
+ if (System.currentTimeMillis().rem(blinkRate * 2) < blinkRate) {
+ drawer.renderer.drawStrip(cursorLine, offset, useCamera = false, snipRegion = snipRegion)
+ }
+ }
+ }
+
+ drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false)
+
+ val paddedOffset = offset.clone().also { it.x -= paddedScale.x }
+ when (inputText.length) {
+ 0 -> drawer.renderer.drawText(title, paddedOffset, vectorUnit.mul(textSize),
+ color.setAlpha(.4f), TextJustify.LEFT, false, snipRegion)
+ else -> drawer.renderer.drawText(inputText, paddedOffset, vectorUnit.mul(textSize),
+ color, TextJustify.LEFT, false, snipRegion)
+ }
+ super.render(snipRegion)
+ }
+
+ override fun handleHover(location: Vec2) {
+ if (textInputIsBusy) return
+ super.handleHover(location)
+ }
+
+ override fun handleLeftClick(location: Vec2) {
+ when {
+ isHover(location) -> {
+ currentPhase = GuiElementPhases.INPUT
+ onClick()
+ }
+ else -> currentPhase = GuiElementPhases.IDLE
+ }
+ }
+
+ fun handleAddTextInput(text: String) {
+ if (textInputIsBusy) {
+ inputText += text
+ onChange(inputText)
+ }
+ }
+
+ fun handleRemoveTextInput() {
+ if (textInputIsBusy) {
+ inputText = inputText.dropLast(1)
+ onChange(inputText)
+ }
+ }
+
+ fun stopTextInput() {
+ if (textInputIsBusy) {
+ currentPhase = GuiElementPhases.IDLE
+ }
+ }
+
+ fun setTextValue(text: String): GuiInput {
+ inputText = text
+ onChange(inputText)
+ return this
+ }
+
+}
diff --git a/src/main/kotlin/display/gui/GuiLabel.kt b/src/main/kotlin/display/gui/GuiLabel.kt
index caf9728..1aee370 100644
--- a/src/main/kotlin/display/gui/GuiLabel.kt
+++ b/src/main/kotlin/display/gui/GuiLabel.kt
@@ -2,20 +2,31 @@ package display.gui
import display.draw.Drawer
import display.graphic.Color
+import display.graphic.SnipRegion
+import display.text.TextJustify
import org.jbox2d.common.Vec2
class GuiLabel(
drawer: Drawer,
offset: Vec2 = Vec2(),
+ val justify: TextJustify = TextJustify.LEFT,
title: String,
textSize: Float = 0f,
- color: Color = Color.WHITE.setAlpha(.7f)
-) : GuiElement(drawer, offset, title = title, textSize = textSize, color = color) {
+ color: Color = Color.WHITE.setAlpha(.7f),
+ updateCallback: (GuiElement) -> Unit = {}
+) : GuiElement(
+ drawer,
+ offset,
+ Vec2(title.length * 16f, textSize * 100f),
+ title,
+ textSize,
+ color,
+ updateCallback
+) {
- override fun render() {
- super.render()
-
- GuiElement.drawLabel(drawer, this)
+ override fun render(snipRegion: SnipRegion?) {
+ super.render(snipRegion)
+ drawLabel(drawer, this, justify, snipRegion)
}
}
diff --git a/src/main/kotlin/display/gui/GuiPanel.kt b/src/main/kotlin/display/gui/GuiPanel.kt
new file mode 100644
index 0000000..fc6e81b
--- /dev/null
+++ b/src/main/kotlin/display/gui/GuiPanel.kt
@@ -0,0 +1,104 @@
+package display.gui
+
+import display.draw.Drawer
+import display.draw.TextureEnum
+import display.graphic.BasicShapes
+import display.graphic.Color
+import display.graphic.SnipRegion
+import display.text.TextJustify
+import org.jbox2d.common.Vec2
+import utility.Common
+
+class GuiPanel(
+ drawer: Drawer,
+ offset: Vec2 = Vec2(),
+ scale: Vec2 = Vec2(100f, 100f),
+ title: String = "",
+ textSize: Float = .3f,
+ color: Color = Color.BLACK.setAlpha(.5f),
+ private val childElements: MutableList = mutableListOf(),
+ private val draggable: Boolean = true,
+ updateCallback: (GuiElement) -> Unit = {}
+) : GuiElement(drawer, offset, scale, title, textSize, color, updateCallback) {
+
+ private val childElementOffsets = HashMap()
+
+ init {
+ childElementOffsets.putAll(childElements.map { Pair(it, it.offset.clone()) })
+ calculateElementRegion(this)
+ }
+
+ override fun render(snipRegion: SnipRegion?) {
+ drawer.textures.getTexture(TextureEnum.white_pixel).bind()
+ drawer.renderer.drawShape(BasicShapes.square
+ .let { Drawer.getColoredData(it, color) }
+ .toFloatArray(),
+ offset, 0f, scale, useCamera = false, snipRegion = snipRegion
+ )
+
+ drawer.renderer.drawText(
+ title,
+ offset.add(Vec2(0f, scale.y - 25f)),
+ Common.vectorUnit.mul(.15f),
+ Color.WHITE,
+ TextJustify.CENTER,
+ false,
+ snipRegion
+ )
+
+ childElements.forEach { it.render(snipRegion) }
+
+ super.render(snipRegion)
+ }
+
+ override fun update() = childElements.forEach { it.update() }
+
+ override fun addOffset(newOffset: Vec2) {
+ addOffset(this, newOffset)
+ calculateNewOffsets()
+ }
+
+ override fun handleHover(location: Vec2) {
+ if (isHover(location)) {
+ super.handleHover(location)
+ childElements.forEach { it.handleHover(location) }
+ }
+ }
+
+ override fun handleLeftClick(location: Vec2) {
+ if (isHover(location)) {
+ super.handleLeftClick(location)
+ childElements.forEach { it.handleLeftClick(location) }
+ }
+ }
+
+ override fun handleLeftClickDrag(location: Vec2, movement: Vec2) {
+ if (draggable && isHover(location)) { // TODO: use custom isHover() to only allow a small region as drag handle
+ childElements.forEach { it.handleLeftClickDrag(location, movement) }
+ super.handleLeftClickDrag(location, movement)
+ addOffset(movement)
+ }
+ }
+
+ override fun handleScroll(location: Vec2, movement: Vec2) {
+ if (isHover(location)) {
+ super.handleScroll(location, movement)
+ childElements.forEach { it.handleScroll(location, movement) }
+ }
+ }
+
+ private fun calculateNewOffsets() = childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) }
+
+ fun addChildren(elements: List) {
+ childElements.addAll(elements)
+ childElementOffsets.putAll(elements.map { Pair(it, it.offset.clone()) })
+ calculateNewOffsets()
+ }
+
+ fun addChild(element: GuiElement) {
+ childElements.add(element)
+ childElementOffsets[element] = element.offset.clone()
+ calculateNewOffsets()
+ }
+
+}
diff --git a/src/main/kotlin/display/gui/GuiScroll.kt b/src/main/kotlin/display/gui/GuiScroll.kt
new file mode 100644
index 0000000..4811a7f
--- /dev/null
+++ b/src/main/kotlin/display/gui/GuiScroll.kt
@@ -0,0 +1,130 @@
+package display.gui
+
+import display.draw.Drawer
+import display.draw.TextureEnum
+import display.graphic.BasicShapes
+import display.graphic.Color
+import display.graphic.SnipRegion
+import display.gui.GuiController.Companion.setElementsInRows
+import org.jbox2d.common.Vec2
+
+class GuiScroll(
+ drawer: Drawer,
+ offset: Vec2 = Vec2(),
+ scale: Vec2 = Vec2(100f, 100f),
+ color: Color = Color.WHITE.setAlpha(.5f),
+ private val childElements: MutableList = mutableListOf()
+) : GuiElement(drawer, offset, scale, "", 0f, color, {}) {
+
+ private var scrollOutline: FloatArray
+ private val childElementOffsets = HashMap()
+
+ private var scrollBarPosition = 0f
+ private var scrollBarMin: Float = 0f
+ private var scrollBarMax: Float = 0f
+
+ init {
+ val linePoints = BasicShapes.square
+ .chunked(2)
+ .flatMap { listOf(it[0] * scale.x, it[1] * scale.y) }
+ scrollOutline = Drawer.getLine(linePoints, color, startWidth = 1f, wrapAround = true)
+
+ childElementOffsets.putAll(childElements.map { Pair(it, it.offset.clone()) })
+ calculateElementRegion(this)
+ calculateNewOffsets()
+ }
+
+ override fun render(snipRegion: SnipRegion?) {
+ // TODO: handle nested snipRegions, if this element is inside parent scroll
+ drawer.textures.getTexture(TextureEnum.white_pixel).bind()
+ drawer.renderer.drawStrip(scrollOutline, offset, useCamera = false, snipRegion = snipRegion)
+ super.render(snipRegion)
+
+ childElements.filter {
+ it.offset.add(it.scale.negate()).y < offset.add(scale).y
+ && it.offset.add(it.scale).y > offset.add(scale.negate()).y
+ }
+ .forEach { it.render(SnipRegion(offset.add(scale.negate()), scale.mul(2f))) }
+ }
+
+ override fun update() = childElements.forEach { it.update() }
+
+ override fun addOffset(newOffset: Vec2) {
+ addOffset(this, newOffset)
+ calculateNewOffsets()
+ }
+
+ override fun updateOffset(newOffset: Vec2) {
+ super.updateOffset(newOffset)
+ calculateNewOffsets()
+ }
+
+ override fun handleHover(location: Vec2) {
+ if (isHover(location)) {
+ super.handleHover(location)
+ childElements.forEach { it.handleHover(location) }
+ }
+ }
+
+ override fun handleLeftClick(location: Vec2) {
+ if (isHover(location)) {
+ super.handleLeftClick(location)
+ childElements.forEach { it.handleLeftClick(location) }
+ }
+ }
+
+ override fun handleLeftClickDrag(location: Vec2, movement: Vec2) {
+ if (isHover(location)) {
+ addScrollBarPosition(movement.y * -.1f)
+ calculateNewOffsets()
+ super.handleLeftClickDrag(location, movement)
+ childElements.forEach { it.handleLeftClickDrag(location, movement) }
+ }
+ }
+
+ override fun handleScroll(location: Vec2, movement: Vec2) {
+ if (isHover(location)) {
+ addScrollBarPosition(movement.y)
+ calculateNewOffsets()
+ }
+ }
+
+ private fun addScrollBarPosition(movement: Float) {
+ scrollBarPosition = (scrollBarPosition + movement * 10f).coerceIn(scrollBarMin, scrollBarMax)
+ }
+
+ private fun updateScrollBarRange() {
+ scrollBarMin = childElements.minBy { it.offset.y }
+ .let { childElementOffsets[it]!!.y + scale.y * 2 - it!!.scale.y }
+ scrollBarMax = 0f
+ }
+
+ private fun calculateNewOffsets() {
+ childElements.forEach {
+ it.updateOffset(childElementOffsets[it]!!
+ .add(offset)
+ .add(Vec2(0f, scale.y - scrollBarPosition)))
+ }
+ }
+
+ fun addChildren(elements: List): GuiScroll {
+ childElements.addAll(elements)
+ setElementsInRows(childElements, centered = false)
+
+ childElementOffsets.putAll(elements.map { Pair(it, it.offset.clone()) })
+ calculateNewOffsets()
+ updateScrollBarRange()
+ return this
+ }
+
+ fun addChild(element: GuiElement): GuiScroll {
+ childElements.add(element)
+ setElementsInRows(childElements)
+
+ childElementOffsets[element] = element.offset.clone()
+ calculateNewOffsets()
+ updateScrollBarRange()
+ return this
+ }
+
+}
diff --git a/src/main/kotlin/display/gui/GuiWindow.kt b/src/main/kotlin/display/gui/GuiWindow.kt
deleted file mode 100644
index 62805c7..0000000
--- a/src/main/kotlin/display/gui/GuiWindow.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package display.gui
-
-import display.draw.Drawer
-import display.graphic.BasicShapes
-import display.graphic.Color
-import org.jbox2d.common.Vec2
-import utility.Common
-
-class GuiWindow(
- drawer: Drawer,
- offset: Vec2 = Vec2(),
- scale: Vec2 = Vec2(100f, 100f),
- title: String = " ",
- textSize: Float = .3f,
- color: Color = Color.BLACK.setAlpha(.5f),
- private val childElements: MutableList = mutableListOf(),
- private val draggable: Boolean = true
-) : GuiElement(drawer, offset, scale, title, textSize, color) {
-
- private val childElementOffsets = HashMap()
-
- init {
- childElementOffsets.putAll(childElements.map { Pair(it, it.offset.clone()) })
- }
-
- override fun render() {
- drawer.textures.white_pixel.bind()
- drawer.renderer.drawShape(BasicShapes.square
- .let { Drawer.getColoredData(it, color) }
- .toFloatArray(),
- offset, 0f, scale, useCamera = false
- )
-
- drawer.renderer.drawText(
- title,
- offset.add(Vec2(0f, scale.y - 25f)),
- Common.vectorUnit.mul(.15f),
- Color.WHITE,
- false
- )
-
- childElements.forEach { it.render() }
-
- super.render()
- }
-
- override fun addOffset(newOffset: Vec2) {
- GuiElement.addOffset(this, newOffset)
- update()
- }
-
- override fun handleHover(location: Vec2) {
- childElements.forEach { it.handleHover(location) }
- }
-
- override fun handleClick(location: Vec2) {
- childElements.forEach { it.handleClick(location) }
- }
-
- fun update() {
- childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) }
- }
-
- fun addChildren(elements: List) {
- childElements.addAll(elements)
- childElementOffsets.putAll(elements.map { Pair(it, it.offset.clone()) })
- update()
- }
-
- fun addChild(element: GuiElement) {
- childElements.add(element)
- childElementOffsets[element] = element.offset.clone()
- update()
- }
-
-}
diff --git a/src/main/kotlin/display/text/Font.kt b/src/main/kotlin/display/text/Font.kt
index 46203bb..b08bc29 100644
--- a/src/main/kotlin/display/text/Font.kt
+++ b/src/main/kotlin/display/text/Font.kt
@@ -1,9 +1,6 @@
package display.text
-import display.graphic.BasicShapes
-import display.graphic.Color
-import display.graphic.Renderer
-import display.graphic.Texture
+import display.graphic.*
import org.jbox2d.common.Vec2
import org.lwjgl.system.MemoryUtil
import java.awt.Font
@@ -13,10 +10,10 @@ import java.awt.geom.AffineTransform
import java.awt.image.AffineTransformOp
import java.awt.image.BufferedImage
import java.io.InputStream
+import java.lang.NullPointerException
import kotlin.math.hypot
import java.awt.Color as AwtColor
-
class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boolean = true) {
private val glyphs: MutableMap
@@ -185,10 +182,12 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole
offset: Vec2,
scale: Vec2,
color: Color,
- useCamera: Boolean
+ justify: TextJustify,
+ useCamera: Boolean,
+ snipRegion: SnipRegion?
) {
- drawLetters(fontBitMapShadow, offset, scale, renderer, text, Color.BLACK, useCamera)
- drawLetters(fontBitMap, offset, scale, renderer, text, color, useCamera)
+ drawLetters(fontBitMapShadow, offset, scale, renderer, text, Color.BLACK, justify, useCamera, snipRegion)
+ drawLetters(fontBitMap, offset, scale, renderer, text, color, justify, useCamera, snipRegion)
}
private fun drawLetters(
@@ -198,19 +197,31 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole
renderer: Renderer,
text: CharSequence,
color: Color,
- useCamera: Boolean
+ justify: TextJustify,
+ useCamera: Boolean,
+ snipRegion: SnipRegion?
) {
- val glyphs = text.map { glyphs[it]!! }
- val centerText = Vec2(-getTextTotalWidth(glyphs, scale) * .75f, 0f)
+ val glyphs = text.mapNotNull {
+ try {
+ glyphs[it]
+ } catch (e: NullPointerException) {
+ throw Exception("Could not find text character in font", e)
+ }
+ }
+ val justifyment = when (justify) {
+ TextJustify.LEFT -> Vec2()
+ TextJustify.CENTER -> Vec2(-getTextTotalWidth(glyphs, scale), 0f)
+ TextJustify.RIGHT -> Vec2()
+ }
- var x = -glyphs[0].width * scale.x
+ var x = 0f//glyphs[0].width * scale.x
var y = 0f
glyphs.forEach {
x += it.width * scale.x
drawTextPosition(
- texture, renderer, Vec2(x, y).add(offset).add(centerText),
- scale, it, color, useCamera
+ texture, renderer, Vec2(x, y).add(offset).add(justifyment),
+ scale, it, color, useCamera, snipRegion
)
x += it.width * scale.x
}
@@ -223,10 +234,13 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole
scale: Vec2,
glyph: Glyph,
color: Color,
- useCamera: Boolean
+ useCamera: Boolean,
+ snipRegion: SnipRegion?
) {
- val glyphScale = Vec2(glyph.width / texture.width.toFloat(), glyph.height / texture.height.toFloat())
- val glyphOffset = Vec2((glyph.x + glyph.width) / texture.width.toFloat(), glyph.y / texture.height.toFloat())
+ val textureWidth = texture.width.toFloat()
+ val textureHeight = texture.height.toFloat()
+ val glyphScale = Vec2(glyph.width / textureWidth, glyph.height / textureHeight)
+ val glyphOffset = Vec2((glyph.x + glyph.width) / textureWidth, glyph.y / textureHeight)
val debug = renderer.debugOffset
val data = BasicShapes.square
@@ -242,10 +256,10 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole
}.toFloatArray()
texture.bind()
- renderer.drawShape(data, offset, 0f, scale, useCamera)
+ renderer.drawShape(data, offset, 0f, scale, useCamera, snipRegion)
-// textures.white_pixel.bind()
-// renderer.drawShape(data, offset, 0f, scale, useCamera)
+ // textures.white_pixel.bind()
+ // renderer.drawShape(data, offset, 0f, scale, useCamera)
}
fun dispose() {
diff --git a/src/main/kotlin/display/text/TextJustify.kt b/src/main/kotlin/display/text/TextJustify.kt
new file mode 100644
index 0000000..f310af0
--- /dev/null
+++ b/src/main/kotlin/display/text/TextJustify.kt
@@ -0,0 +1,7 @@
+package display.text
+
+enum class TextJustify {
+ LEFT,
+ CENTER,
+ RIGHT
+}
diff --git a/src/main/kotlin/engine/FreeBodyCallback.kt b/src/main/kotlin/engine/FreeBodyCallback.kt
new file mode 100644
index 0000000..5d6239b
--- /dev/null
+++ b/src/main/kotlin/engine/FreeBodyCallback.kt
@@ -0,0 +1,6 @@
+package engine
+
+import engine.freeBody.FreeBody
+import org.jbox2d.dynamics.Body
+
+class FreeBodyCallback(val freeBody: FreeBody, val callback: (FreeBody, Body) -> Unit)
diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt
index 5dcc430..d79974a 100644
--- a/src/main/kotlin/engine/GameState.kt
+++ b/src/main/kotlin/engine/GameState.kt
@@ -2,16 +2,26 @@ package engine
import input.CameraView
import display.Window
-import engine.freeBody.Planet
-import engine.freeBody.Vehicle
+import display.draw.TextureConfig
+import display.draw.TextureEnum
+import display.graphic.Color
+import engine.freeBody.*
+import engine.motion.Director
import engine.motion.Motion
import engine.physics.CellLocation
import engine.physics.Gravity
import engine.physics.GravityCell
import game.GamePlayer
-import game.GamePlayerTypes
+import org.jbox2d.callbacks.ContactImpulse
+import org.jbox2d.callbacks.ContactListener
+import org.jbox2d.collision.Manifold
import org.jbox2d.common.Vec2
+import org.jbox2d.dynamics.Body
import org.jbox2d.dynamics.World
+import org.jbox2d.dynamics.contacts.Contact
+import org.jbox2d.dynamics.contacts.ContactEdge
+import utility.Common
+import utility.Common.makeVec2Circle
class GameState {
@@ -21,26 +31,30 @@ class GameState {
lateinit var camera: CameraView
var world = World(Vec2())
- var vehicles = mutableListOf()
- var planets = mutableListOf()
+ val vehicles = mutableListOf()
+ val planets = mutableListOf()
+ val warheads = mutableListOf()
+ val particles = mutableListOf()
-// private var worlds = mutableListOf()
-// private var asteroids = mutableListOf()
-// private var stars = mutableListOf()
-// private var warheads = mutableListOf()
+ // private var asteroids = mutableListOf()
+ // private var stars = mutableListOf()
- val tickables
- get() = vehicles + planets
+ val gravityBodies
+ get() = vehicles + planets + warheads
+
+ val trailerBodies
+ get() = vehicles + warheads
var gravityMap = HashMap()
var resolution = 0f
+ val activeCallbacks = mutableListOf<() -> Unit>()
fun init(window: Window) {
camera = CameraView(window)
}
private fun tickGravityChanges() {
- Gravity.addGravityForces(tickables)
+ Gravity.addGravityForces(gravityBodies)
.let { (gravityMap, resolution) ->
this.gravityMap = gravityMap
this.resolution = resolution
@@ -54,8 +68,64 @@ class GameState {
) {
world.step(timeStep, velocityIterations, positionIterations)
+ activeCallbacks.forEach { it() }
+ activeCallbacks.clear()
+
tickGravityChanges()
- Motion.addNewTrailers(tickables.filter { it.radius > .5f })
+ Motion.addNewTrailers(trailerBodies)
+
+ tickWarheads()
+ tickParticles(timeStep)
+ }
+
+ private fun tickParticles(timeStep: Float) {
+ particles.toList().forEach {
+ it.worldBody.position.addLocal(it.worldBody.linearVelocity.mul(timeStep))
+ if (it.ageTime > 1000f) {
+ particles.remove(it)
+ return@forEach
+ }
+
+ val scale = Common.getTimingFunctionEaseOut(it.ageTime / 1000f)
+ it.radius = it.fullRadius * scale
+ }
+ }
+
+ private fun tickWarheads() {
+ warheads.toList()
+ .filter { it.ageTime > it.selfDestructTime }
+ .forEach { detonateWarhead(it) }
+ }
+
+ private fun detonateWarhead(warhead: Warhead, body: Body? = null) {
+ val particle = warhead.createParticles(particles, world, body ?: warhead.worldBody)
+
+ checkToDamageVehicles(particle, warhead)
+
+ world.destroyBody(warhead.worldBody)
+ warheads.remove(warhead)
+ }
+
+ private fun checkToDamageVehicles(particle: Particle, warhead: Warhead) {
+ vehicles.toList().map {
+ Pair(it, (Director.getDistance(it.worldBody, particle.worldBody)
+ - it.radius - warhead.radius).coerceAtLeast(0f))
+ }
+ .filter { (_, distance) -> distance < particle.radius }
+ .forEach { (vehicle, distance) ->
+ val damageUnit = (1f - distance / particle.radius).coerceAtLeast(0f)
+ .let { Common.getTimingFunctionEaseIn(it) }
+ val totalDamage = damageUnit * warhead.damage
+ vehicle.hitPoints -= totalDamage
+ warhead.firedBy.scoreDamage(warhead, totalDamage, vehicle)
+
+ if (vehicle.hitPoints <= 0) {
+ warhead.firedBy.scoreKill(vehicle)
+
+ world.destroyBody(vehicle.worldBody)
+ vehicles.remove(vehicle)
+ }
+ }
}
fun reset() {
@@ -64,6 +134,87 @@ class GameState {
world = World(Vec2())
vehicles.clear()
planets.clear()
+
+ world.setContactListener(MyContactListener(this))
+ }
+
+ fun fireWarhead(player: GamePlayer, warheadType: String = "will make this some class later"): Warhead {
+ checkNotNull(player.vehicle) { "Player does not have a vehicle." }
+ val vehicle = player.vehicle!!
+ val angle = player.playerAim.angle
+ val power = player.playerAim.power * .15f
+ val originLocation = vehicle.worldBody.position
+ val originVelocity = vehicle.worldBody.linearVelocity
+
+ val warheadRadius = .2f
+ val minimumSafeDistance = 1.5f * vehicle.radius
+ val angleVector = makeVec2Circle(angle)
+
+ val warheadLocation = angleVector.mul(minimumSafeDistance).add(originLocation)
+ val warheadVelocity = angleVector.mul(power).add(originVelocity)
+
+ val warheadMass = .1f
+ // val body = player.vehicle!!.worldBody
+ // body.applyLinearImpulse(angleVector.mul(power).negate(), body.localCenter)
+ // TODO: recoil causes excessive spin
+
+ return Warhead.create(
+ world, player, warheadLocation.x, warheadLocation.y, angle,
+ warheadVelocity.x, warheadVelocity.y, 0f,
+ warheadMass, warheadRadius,
+ textureConfig = TextureConfig(TextureEnum.metal, color = Color.createFromHsv(0f, 1f, .3f, 1f)),
+ onWarheadCollision = { self, body -> detonateWarhead(self as Warhead, body) }
+ )
+ .also { warheads.add(it) }
+
+ }
+
+ companion object {
+
+ fun getContactBodies(contactEdge: ContactEdge): Sequence = sequence {
+ var currentContact = contactEdge
+ yield(currentContact.other)
+
+ while (currentContact.next != null) {
+ yield(currentContact.other)
+ currentContact = currentContact.next
+ }
+ }
+
+ fun getContactEdges(contactEdge: ContactEdge): Sequence = sequence {
+ var currentContact = contactEdge
+ yield(currentContact.contact)
+
+ while (currentContact.next != null) {
+ yield(currentContact.contact)
+ currentContact = currentContact.next
+ }
+ }
+
+ }
+}
+
+class MyContactListener(val gameState: GameState) : ContactListener {
+
+ override fun beginContact(contact: Contact) {
+ val bodies = listOf(contact.fixtureA, contact.fixtureB).map { it.body }
+ bodies.mapNotNull { it.userData }
+ .map { it as FreeBodyCallback }
+ .filter { it.freeBody is Warhead }
+ .forEach { warhead ->
+ val otherBody = bodies.find { body -> body != warhead.freeBody.worldBody }!!
+ gameState.activeCallbacks.add { warhead.callback(warhead.freeBody, otherBody) }
+ }
+
+ }
+
+ override fun endContact(contact: Contact) {
+ }
+
+ override fun preSolve(contact: Contact, oldManifold: Manifold) {
+ }
+
+ override fun postSolve(contact: Contact, impulse: ContactImpulse) {
}
}
diff --git a/src/main/kotlin/engine/freeBody/FreeBody.kt b/src/main/kotlin/engine/freeBody/FreeBody.kt
index bf98569..1523ea3 100644
--- a/src/main/kotlin/engine/freeBody/FreeBody.kt
+++ b/src/main/kotlin/engine/freeBody/FreeBody.kt
@@ -11,7 +11,6 @@ import kotlin.math.pow
open class FreeBody(
val id: String,
var motion: Motion,
- var shapeBox: Shape,
var worldBody: Body,
var radius: Float,
val textureConfig: TextureConfig
@@ -28,28 +27,29 @@ open class FreeBody(
world: World,
bodyDef: BodyDef
): Body {
- val fixtureDef = FixtureDef()
- fixtureDef.shape = shapeBox
- fixtureDef.density = mass / (PI.toFloat() * radius.pow(2f))
- fixtureDef.friction = friction
- fixtureDef.restitution = restitution
-
- val worldBody = world.createBody(bodyDef)
- worldBody.createFixture(fixtureDef)
-
- return worldBody
+ val fixtureDef = FixtureDef().also {
+ it.shape = shapeBox
+ it.density = mass / (PI.toFloat() * radius.pow(2f))
+ it.friction = friction
+ it.restitution = restitution
+ }
+
+ return world.createBody(bodyDef)
+ .also { it.createFixture(fixtureDef) }
}
- fun createBodyDef(bodyType: BodyType,
- x: Float, y: Float, h: Float,
- dx: Float, dy: Float, dh: Float): BodyDef {
- val bodyDef = BodyDef()
- bodyDef.type = bodyType
- bodyDef.position.set(x, y)
- bodyDef.angle = h
- bodyDef.linearVelocity = Vec2(dx, dy)
- bodyDef.angularVelocity = dh
- return bodyDef
+ fun createBodyDef(
+ bodyType: BodyType,
+ x: Float, y: Float, h: Float,
+ dx: Float, dy: Float, dh: Float
+ ): BodyDef {
+ return BodyDef().also {
+ it.type = bodyType
+ it.position.set(x, y)
+ it.angle = h
+ it.linearVelocity = Vec2(dx, dy)
+ it.angularVelocity = dh
+ }
}
}
diff --git a/src/main/kotlin/engine/freeBody/Particle.kt b/src/main/kotlin/engine/freeBody/Particle.kt
new file mode 100644
index 0000000..6986af9
--- /dev/null
+++ b/src/main/kotlin/engine/freeBody/Particle.kt
@@ -0,0 +1,23 @@
+package engine.freeBody
+
+import display.draw.TextureConfig
+import org.jbox2d.dynamics.Body
+
+class Particle(
+ val id: String,
+ val worldBody: Body,
+ var radius: Float,
+ val textureConfig: TextureConfig
+) {
+
+ var fullRadius: Float = radius
+
+ private val currentTime
+ get() = System.currentTimeMillis()
+
+ val ageTime
+ get() = (currentTime - createdAt)
+
+ private val createdAt = currentTime
+
+}
diff --git a/src/main/kotlin/engine/freeBody/Planet.kt b/src/main/kotlin/engine/freeBody/Planet.kt
index e6ae3b4..c0adaf7 100644
--- a/src/main/kotlin/engine/freeBody/Planet.kt
+++ b/src/main/kotlin/engine/freeBody/Planet.kt
@@ -3,17 +3,15 @@ package engine.freeBody
import display.draw.TextureConfig
import engine.motion.Motion
import org.jbox2d.collision.shapes.CircleShape
-import org.jbox2d.collision.shapes.Shape
import org.jbox2d.dynamics.*
class Planet(
id: String,
motion: Motion,
- shapeBox: Shape,
worldBody: Body,
radius: Float,
textureConfig: TextureConfig
-) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) {
+) : FreeBody(id, motion, worldBody, radius, textureConfig) {
companion object {
@@ -38,7 +36,7 @@ class Planet(
val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh)
val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef)
- return Planet(id, Motion(), shapeBox, worldBody, radius, textureConfig)
+ return Planet(id, Motion(), worldBody, radius, textureConfig)
}
}
diff --git a/src/main/kotlin/engine/freeBody/Vehicle.kt b/src/main/kotlin/engine/freeBody/Vehicle.kt
index 7ad5168..234ced1 100644
--- a/src/main/kotlin/engine/freeBody/Vehicle.kt
+++ b/src/main/kotlin/engine/freeBody/Vehicle.kt
@@ -6,20 +6,22 @@ import engine.motion.Motion
import engine.shields.VehicleShield
import game.GamePlayer
import org.jbox2d.collision.shapes.PolygonShape
-import org.jbox2d.collision.shapes.Shape
import org.jbox2d.common.Vec2
import org.jbox2d.dynamics.*
+import utility.Common.makeVec2
+import kotlin.math.PI
+import kotlin.math.pow
class Vehicle(
id: String,
motion: Motion,
- shapeBox: Shape,
worldBody: Body,
radius: Float,
textureConfig: TextureConfig
-) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) {
+) : FreeBody(id, motion, worldBody, radius, textureConfig) {
var shield: VehicleShield? = null
+ var hitPoints: Float = 100f
companion object {
@@ -38,20 +40,36 @@ class Vehicle(
friction: Float = .6f,
textureConfig: TextureConfig
): Vehicle {
- val shapeBox = PolygonShape()
- val vertices = BasicShapes.polygon4.chunked(2).map { Vec2(it[0] * radius, it[1] * radius) }.toTypedArray()
- shapeBox.set(vertices, vertices.size)
-
+ val fullShape = BasicShapes.polygon4Spiked.chunked(2)
val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh)
- val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef)
- textureConfig.chunkedVertices =
- shapeBox.vertices.flatMap { listOf(it.x / radius, it.y / radius) }.chunked(2)
-
- return Vehicle(player.name, Motion(), shapeBox, worldBody, radius, textureConfig)
- .let {
- player.vehicle = it
- it
+ val worldBody = world.createBody(bodyDef)
+
+ (listOf(fullShape.last()) + fullShape + listOf(fullShape.first()))
+ .map { listOf(it[0] * radius, it[1] * radius) }
+ .windowed(3, 2)
+ .map { (a, b, c) ->
+ val shapeBox = PolygonShape()
+ val vertices = listOf(
+ makeVec2(a),
+ makeVec2(b),
+ makeVec2(c),
+ Vec2()
+ )
+ .toTypedArray()
+ shapeBox.set(vertices, vertices.size)
+ FixtureDef().also {
+ it.shape = shapeBox
+ it.density = mass / (PI.toFloat() * radius.pow(2f) * (fullShape.size * .5f))
+ it.friction = friction
+ it.restitution = restitution
+ }
}
+ .forEach { worldBody.createFixture(it) }
+
+ textureConfig.chunkedVertices = listOf(listOf(0f, 0f)) + fullShape + listOf(fullShape.first())
+
+ return Vehicle(player.name, Motion(), worldBody, radius, textureConfig)
+ .also { player.vehicle = it }
}
}
diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt
new file mode 100644
index 0000000..87f0f5a
--- /dev/null
+++ b/src/main/kotlin/engine/freeBody/Warhead.kt
@@ -0,0 +1,100 @@
+package engine.freeBody
+
+import display.draw.TextureConfig
+import display.draw.TextureEnum
+import display.graphic.BasicShapes
+import engine.FreeBodyCallback
+import engine.motion.Motion
+import game.GamePlayer
+import org.jbox2d.collision.shapes.CircleShape
+import org.jbox2d.collision.shapes.PolygonShape
+import org.jbox2d.common.Vec2
+import org.jbox2d.dynamics.Body
+import org.jbox2d.dynamics.BodyType
+import org.jbox2d.dynamics.World
+
+class Warhead(
+ id: String,
+ val firedBy: GamePlayer,
+ motion: Motion,
+ worldBody: Body,
+ radius: Float,
+ textureConfig: TextureConfig
+) : FreeBody(id, motion, worldBody, radius, textureConfig) {
+
+ private val currentTime
+ get() = System.currentTimeMillis()
+
+ val ageTime
+ get() = (currentTime - createdAt)
+
+ private val createdAt = currentTime
+ val selfDestructTime = 45000f
+ // TODO: player in current aiming phase could just wait out this time if they wanted to
+ // also influences score
+
+ val damage = 100f
+
+ fun createParticles(
+ particles: MutableList,
+ world: World,
+ impacted: Body
+ ): Particle {
+ val shapeBox = CircleShape()
+ shapeBox.radius = 2f
+
+ val location = worldBody.position
+ val velocity = impacted.linearVelocity
+ val bodyDef = createBodyDef(BodyType.STATIC, location.x, location.y, 0f, velocity.x, velocity.y, 0f)
+ val worldBody = world.createBody(bodyDef)
+ // createWorldBody(shapeBox, 0f, radius, 0f, 0f, world, bodyDef)
+
+ val textureConfig = TextureConfig(TextureEnum.white_pixel, chunkedVertices = BasicShapes.polygon30.chunked(2))
+ .updateGpuBufferData()
+ return Particle(id, worldBody, shapeBox.radius, textureConfig)
+ .also { particles.add(it) }
+
+ }
+
+ companion object {
+
+ fun create(
+ world: World,
+ firedBy: GamePlayer,
+ x: Float,
+ y: Float,
+ h: Float,
+ dx: Float,
+ dy: Float,
+ dh: Float,
+ mass: Float,
+ radius: Float = .7F,
+ restitution: Float = .3f,
+ friction: Float = .6f,
+ textureConfig: TextureConfig,
+ onWarheadCollision: (FreeBody, Body) -> Unit
+ ): Warhead {
+ val shapeBox = PolygonShape()
+ val vertices = BasicShapes.polygon4.chunked(2)
+ .map { Vec2(it[0] * radius * 2f, it[1] * radius) }
+ .toTypedArray()
+ shapeBox.set(vertices, vertices.size)
+
+ val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh)
+ val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef)
+ worldBody.isBullet = true
+
+ textureConfig.chunkedVertices =
+ shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) }
+
+ return Warhead("1", firedBy, Motion(), worldBody, radius, textureConfig)
+ .also {
+ it.textureConfig.updateGpuBufferData()
+ firedBy.warheads.add(it)
+ worldBody.userData = FreeBodyCallback(it, onWarheadCollision)
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt
index caa5f1d..f05c5d0 100644
--- a/src/main/kotlin/game/GamePhaseHandler.kt
+++ b/src/main/kotlin/game/GamePhaseHandler.kt
@@ -1,21 +1,25 @@
package game
-import display.draw.Drawer
-import display.draw.TextureHolder
import display.KeyboardEvent
import display.Window
+import display.draw.Drawer
+import display.draw.TextureEnum
import display.events.MouseButtonEvent
+import display.events.MouseScrollEvent
import display.graphic.Color
import display.gui.GuiController
+import display.text.TextJustify
import engine.GameState
+import engine.GameState.Companion.getContactBodies
+import engine.motion.Director
import engine.shields.VehicleShield
import org.jbox2d.common.Vec2
+import utility.Common.getTimingFunctionEaseIn
import utility.Common.getTimingFunctionEaseOut
-import utility.Common.getTimingFunctionSineEaseIn
import utility.Common.vectorUnit
import kotlin.math.roundToInt
-class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val textures: TextureHolder) {
+class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) {
private val timeStep = 1f / 60f
private val velocityIterations = 8
@@ -36,31 +40,30 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
private var lastPhaseTimestamp = currentTime
private val guiController = GuiController(drawer)
+ private val textInputIsBusy
+ get() = guiController.textInputIsBusy()
private lateinit var exitCall: () -> Unit
fun init(window: Window) {
exitCall = { window.exit() }
+ when (0) {
+ 0 -> setupMainMenu()
+ 1 -> setupMainMenuSelectPlayers()
+ 2 -> {
- setupMainMenu()
-
-// gameState.gamePlayers.add(GamePlayer("Bob"))
-// setupStartGame()
- }
-
- fun dragMouseRightClick(movement: Vec2) {
- camera.moveLocation(movement.mulLocal(-camera.z))
- }
+ currentPhase = GamePhases.PLAYERS_PICK_SHIELDS
+ isTransitioning = false
+ gameState.reset()
+ gameState.gamePlayers.addAll((1..3).map { GamePlayer("Player $it") })
+ MapGenerator.populateNewGameMap(gameState)
- fun scrollCamera(movement: Float) {
- camera.moveZoom(movement * -.001f)
- }
+ gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() }
+ gameState.playerOnTurn = gameState.gamePlayers.first()
- fun pauseGame(event: KeyboardEvent) {
- when (currentPhase) {
- GamePhases.PAUSE -> currentPhase = GamePhases.PLAY
- GamePhases.PLAY -> currentPhase = GamePhases.PAUSE
+ setupNextPlayersTurn()
+ }
+ else -> throw Throwable("Enter a debug step number to start game")
}
- startTransition()
}
private fun startTransition() {
@@ -68,26 +71,92 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
isTransitioning = true
}
+ private fun startNewPhase(newPhase: GamePhases) {
+ currentPhase = newPhase
+ startTransition()
+ }
+
fun update() {
camera.update()
val cp = currentPhase
when {
- cp == GamePhases.PAUSE && isTransitioning -> tickGamePausing(pauseDownDuration, GamePhases.PAUSE)
+ cp == GamePhases.PAUSE && isTransitioning -> tickGamePausing()
cp == GamePhases.PAUSE -> return
- cp == GamePhases.PLAY && isTransitioning -> tickGameUnpausing(pauseDownDuration, GamePhases.PLAY)
+ cp == GamePhases.PLAY && isTransitioning -> tickGameUnpausing()
cp == GamePhases.MAIN_MENU -> return
cp == GamePhases.MAIN_MENU_SELECT_PLAYERS -> return
- cp == GamePhases.NEW_GAME_INTRO && isTransitioning -> tickGameUnpausing(
- pauseDownDuration, GamePhases.NEW_GAME_INTRO
- )
+ cp == GamePhases.NEW_GAME_INTRO && isTransitioning -> tickGameUnpausing()
cp == GamePhases.NEW_GAME_INTRO -> handleIntro()
cp == GamePhases.PLAYERS_PICK_SHIELDS && isTransitioning -> {
- if (elapsedTime < pauseDownDuration) isTransitioning = false
+ if (elapsedTime > pauseTime) isTransitioning = false
}
cp == GamePhases.PLAYERS_PICK_SHIELDS -> return
cp == GamePhases.PLAYERS_TURN -> return
+ cp == GamePhases.PLAYERS_TURN_FIRED && isTransitioning -> tickGameUnpausing(quickStartTime)
+ cp == GamePhases.PLAYERS_TURN_FIRED -> handlePlayerShot()
+ cp == GamePhases.PLAYERS_TURN_FIRED_ENDS_EARLY -> handlePlayerShotEndsEarly()
+ cp == GamePhases.PLAYERS_TURN_AIMING -> return
+ cp == GamePhases.PLAYERS_TURN_POWERING -> return
+ cp == GamePhases.END_ROUND && isTransitioning -> tickGamePausing(outroDuration, endSpeed = .1f)
+ cp == GamePhases.END_ROUND -> gameState.tickClock(timeStep * .1f, velocityIterations, positionIterations)
+
+ else -> gameState.tickClock(timeStep, velocityIterations, positionIterations)
+ }
+ }
+
+ fun render() {
+ when (currentPhase) {
+ GamePhases.MAIN_MENU -> guiController.render()
+ GamePhases.MAIN_MENU_SELECT_PLAYERS -> guiController.render()
+ GamePhases.PLAYERS_PICK_SHIELDS -> drawWorldAndGui()
+ GamePhases.PLAYERS_TURN -> drawWorldAndGui()
+ GamePhases.PLAYERS_TURN_AIMING -> {
+ drawWorldAndGui()
+ drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!)
+ }
+ GamePhases.PLAYERS_TURN_POWERING -> {
+ drawWorldAndGui()
+ drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!)
+ }
+ GamePhases.END_ROUND -> drawWorldAndGui()
+ else -> drawPlayPhase()
+ }
+
+ drawer.renderer.drawText(
+ "Animating: ${isTransitioning.toString().padEnd(5, ' ')} ${currentPhase.name}",
+ Vec2(120f - camera.windowWidth * .5f, -10f + camera.windowHeight * .5f),
+ vectorUnit.mul(0.1f), Color.GREEN, TextJustify.LEFT, false
+ )
+
+ drawer.renderer.drawText(
+ "${elapsedTime.div(100f).roundToInt().div(10f)} seconds",
+ Vec2(40f - camera.windowWidth * .5f, -30f + camera.windowHeight * .5f),
+ vectorUnit.mul(0.1f), Color.GREEN, TextJustify.LEFT, false
+ )
+ }
+
+ private fun handlePlayerShotEndsEarly() {
+ when {
+ isTransitioning -> tickGamePausing()
+ else -> setupNextPlayersTurn()
+ }
+ }
+ private fun handlePlayerShot() {
+ val roundEndsEarly = (gameState.warheads.none()
+ && gameState.particles.none()
+ && gameState.vehicles
+ .all {
+ it.worldBody.contactList != null
+ && getContactBodies(it.worldBody.contactList).any { other -> other.mass > 50f }
+ })
+ when {
+ roundEndsEarly -> if (!checkStateEndOfRound()) startNewPhase(GamePhases.PLAYERS_TURN_FIRED_ENDS_EARLY)
+ elapsedTime > maxTurnDuration -> setupNextPlayersTurn()
+ elapsedTime > (maxTurnDuration - pauseTime) -> tickGamePausing(
+ pauseTime, calculatedElapsedTime = (elapsedTime - maxTurnDuration + pauseTime)
+ )
else -> gameState.tickClock(timeStep, velocityIterations, positionIterations)
}
}
@@ -96,7 +165,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
when {
elapsedTime > introDuration -> playerSelectsShield()
elapsedTime > (introDuration - introStartSlowdown) -> tickGamePausing(
- introStartSlowdown, GamePhases.PLAYERS_PICK_SHIELDS, (elapsedTime - introDuration + introStartSlowdown)
+ introStartSlowdown, calculatedElapsedTime = (elapsedTime - introDuration + introStartSlowdown)
)
else -> gameState.tickClock(timeStep, velocityIterations, positionIterations)
}
@@ -106,97 +175,101 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
player?.vehicle?.shield = VehicleShield()
if (gameState.gamePlayers.all { it.vehicle?.shield != null }) {
- setupPlayersTurn()
+ setupNextPlayersTurn()
return
}
- guiController.clear()
+
setNextPlayerOnTurn()
setupPlayersPickShields()
currentPhase = GamePhases.PLAYERS_PICK_SHIELDS
- startTransition()
+ // startTransition()
}
- private fun setupPlayersTurn() {
- guiController.clear()
+ private fun setupNextPlayersTurn() {
+ if (checkStateEndOfRound()) {
+ return
+ }
+
+ gameState.gamePlayers
+ .joinToString { "${it.name} HP:${it.vehicle!!.hitPoints.toInt()}; " }
+ .also { println(it) }
+
setNextPlayerOnTurn()
setupPlayerCommandPanel()
- currentPhase = GamePhases.PLAYERS_TURN
- startTransition()
+ startNewPhase(GamePhases.PLAYERS_TURN)
+ }
+
+ private fun checkStateEndOfRound(): Boolean {
+ val vehiclesDestroyed = gameState.gamePlayers.count { it.vehicle!!.hitPoints > 0 } < 2
+ if (vehiclesDestroyed) {
+ startNewPhase(GamePhases.END_ROUND)
+ guiController.createRoundLeaderboard(gameState.gamePlayers,
+ onClickNextRound = { setupMainMenuSelectPlayers() })
+ return true
+ }
+ return false
}
private fun setupPlayerCommandPanel() {
- guiController.clear()
guiController.createPlayerCommandPanel(
player = gameState.playerOnTurn!!,
+ onClickAim = { startNewPhase(GamePhases.PLAYERS_TURN_AIMING) },
+ onClickPower = { startNewPhase(GamePhases.PLAYERS_TURN_POWERING) },
onClickFire = { player -> playerFires(player) }
)
}
private fun playerFires(player: GamePlayer) {
- println("Player ${player.name} fired a gun!")
+ // check() {} player has enough funds && in stable position to fire large warheads
+
+ val firedWarhead = gameState.fireWarhead(player, "boom small")
+ camera.trackFreeBody(firedWarhead, 200f)
+
+ startNewPhase(GamePhases.PLAYERS_TURN_FIRED)
}
private fun setNextPlayerOnTurn() {
- check(gameState.playerOnTurn != null) { "Cannot play a game with no players." }
+ checkNotNull(gameState.playerOnTurn) { "No player is on turn." }
val playerOnTurn = gameState.playerOnTurn!!
- val players = gameState.gamePlayers
+ val players = gameState.gamePlayers.filter { it.vehicle!!.hitPoints > 0 }
gameState.playerOnTurn = players[(players.indexOf(playerOnTurn) + 1).rem(players.size)]
camera.trackFreeBody(gameState.playerOnTurn!!.vehicle!!)
}
private fun setupPlayersPickShields() {
- guiController.clear()
guiController.createPlayersPickShields(
onClickShield = { player -> playerSelectsShield(player) },
player = gameState.playerOnTurn!!
)
}
- fun render() {
- when (currentPhase) {
- GamePhases.MAIN_MENU -> guiController.render()
- GamePhases.MAIN_MENU_SELECT_PLAYERS -> guiController.render()
- GamePhases.PLAYERS_PICK_SHIELDS -> {
- drawPlayPhase()
- guiController.render()
- }
- GamePhases.PLAYERS_TURN -> {
- drawPlayPhase()
- guiController.render()
- }
- else -> drawPlayPhase()
- }
-
- drawer.renderer.drawText(
- "Animating: ${isTransitioning.toString().padEnd(5, ' ')} ${currentPhase.name}",
- Vec2(120f - camera.windowWidth * .5f, -10f + camera.windowHeight * .5f),
- vectorUnit.mul(0.1f), Color.GREEN, false
- )
-
- drawer.renderer.drawText(
- "${elapsedTime.div(100f).roundToInt().div(10f)} seconds",
- Vec2(40f - camera.windowWidth * .5f, -30f + camera.windowHeight * .5f),
- vectorUnit.mul(0.1f), Color.GREEN, false
- )
+ private fun drawWorldAndGui() {
+ drawPlayPhase()
+ guiController.render()
}
private fun drawPlayPhase() {
- drawer.drawPicture(textures.stars_2k)
+ drawer.drawBackground(TextureEnum.stars_2k)
- val allFreeBodies = gameState.tickables
+ val allFreeBodies = gameState.gravityBodies
allFreeBodies.forEach { drawer.drawTrail(it) }
+ gameState.particles.forEach { drawer.drawParticle(it) }
allFreeBodies.forEach { drawer.drawFreeBody(it) }
// allFreeBodies.forEach { drawDebugForces(it) }
// drawer.drawGravityCells(gameState.gravityMap, gameState.resolution)
}
- private fun tickGameUnpausing(duration: Float = pauseDownDuration, endPhase: GamePhases) {
- val interpolateStep = elapsedTime / duration
+ private fun tickGameUnpausing(
+ duration: Float = pauseTime,
+ endPhase: GamePhases? = null,
+ calculatedElapsedTime: Float? = null
+ ) {
+ val interpolateStep = (calculatedElapsedTime ?: elapsedTime.toFloat()) / duration
if (interpolateStep >= 1f) {
- currentPhase = endPhase
+ currentPhase = endPhase ?: currentPhase
isTransitioning = false
} else {
val timeFunctionStep = getTimingFunctionEaseOut(interpolateStep)
@@ -205,24 +278,52 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
}
private fun tickGamePausing(
- duration: Float = pauseDownDuration,
- endPhase: GamePhases,
- calculatedElapsedTime: Float? = null
+ duration: Float = pauseTime,
+ endPhase: GamePhases? = null,
+ calculatedElapsedTime: Float? = null,
+ endSpeed: Float = 0f
) {
val interpolateStep = (calculatedElapsedTime ?: elapsedTime.toFloat()) / duration
if (interpolateStep >= 1f) {
- currentPhase = endPhase
+ currentPhase = endPhase ?: currentPhase
isTransitioning = false
} else {
- val timeFunctionStep = getTimingFunctionSineEaseIn(1f - interpolateStep)
+ val timeFunctionStep = getTimingFunctionEaseIn(1f - interpolateStep) * (1f - endSpeed) + endSpeed
gameState.tickClock(timeStep * timeFunctionStep, velocityIterations, positionIterations)
+ // println("${roundFloat(interpolateStep, 2).toString().padEnd(4, '0')} <> " + roundFloat(timeFunctionStep, 2).toString().padEnd(4, '0'))
+ }
+ }
+
+ fun dragMouseRightClick(movement: Vec2) {
+ camera.moveLocation(movement.mulLocal(-camera.z))
+ }
+
+ fun dragMouseLeftClick(location: Vec2, movement: Vec2) {
+ guiController.checkLeftClickDrag(getScreenLocation(location), movement)
+ }
+
+ fun scrollMouse(event: MouseScrollEvent) {
+ val screenLocation = getScreenLocation(event.location)
+ if (guiController.locationIsGui(screenLocation)) {
+ guiController.checkScroll(event.movement, screenLocation)
+ } else {
+ camera.moveZoom(event.movement.y * -.001f)
}
}
- fun doubleLeftClick(location: Vec2, click: MouseButtonEvent) {
+ fun pauseGame(event: KeyboardEvent) {
+ currentPhase = when (currentPhase) {
+ GamePhases.PAUSE -> GamePhases.PLAY
+ GamePhases.PLAY -> GamePhases.PAUSE
+ else -> GamePhases.PAUSE
+ }
+ startTransition()
+ }
+
+ fun doubleLeftClick(location: Vec2) {
val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location)
- val clickedBody = gameState.tickables.find {
+ val clickedBody = gameState.gravityBodies.find {
it.worldBody.position
.add(transformedLocation.mul(-1f))
.length() <= it.radius
@@ -241,34 +342,80 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
}
fun moveMouse(location: Vec2) {
- if (mouseElementPhases.any { currentPhase == it }) {
- guiController.checkHover(getScreenLocation(location))
+ when {
+ mouseElementPhases.any { currentPhase == it } -> guiController.checkHover(getScreenLocation(location))
+ currentPhase == GamePhases.PLAYERS_TURN_AIMING -> {
+ val (playerOnTurn, transformedLocation, playerLocation) = getPlayerAndMouseLocations(location)
+ val aimDirection = Director.getDirection(
+ transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y
+ )
+ playerOnTurn.playerAim.angle = aimDirection
+ guiController.update()
+ }
+ currentPhase == GamePhases.PLAYERS_TURN_POWERING -> {
+ val (playerOnTurn, transformedLocation, playerLocation) = getPlayerAndMouseLocations(location)
+ val distance = Director.getDistance(
+ transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y
+ )
+ playerOnTurn.playerAim.power = (distance - 1f) * 10f
+ guiController.update()
+ }
}
}
+ private fun getPlayerAndMouseLocations(location: Vec2): Triple {
+ checkNotNull(gameState.playerOnTurn) { "No player is on turn." }
+ val playerOnTurn = gameState.playerOnTurn!!
+ val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location)
+ val playerLocation = playerOnTurn.vehicle!!.worldBody.position
+ return Triple(playerOnTurn, transformedLocation, playerLocation)
+ }
+
fun leftClickMouse(event: MouseButtonEvent) {
- if (mouseElementPhases.any { currentPhase == it }) {
- guiController.checkLeftClick(getScreenLocation(event.location))
+ when {
+ mouseElementPhases.any { currentPhase == it } -> guiController.checkLeftClick(
+ getScreenLocation(event.location))
+ currentPhase == GamePhases.PLAYERS_TURN_AIMING -> currentPhase = GamePhases.PLAYERS_TURN
+ currentPhase == GamePhases.PLAYERS_TURN_POWERING -> currentPhase = GamePhases.PLAYERS_TURN
}
}
private fun getScreenLocation(location: Vec2): Vec2 =
- location.add(Vec2(-camera.windowWidth * .5f, -camera.windowHeight * .5f))
- .let {
- it.y *= -1f
- it
- }
+ location.add(Vec2(-camera.windowWidth, -camera.windowHeight).mul(.5f))
+ .also { it.y *= -1f }
fun keyPressEscape(event: KeyboardEvent) {
+ if (textInputIsBusy) {
+ guiController.stopTextInput()
+ return
+ }
when (currentPhase) {
GamePhases.MAIN_MENU -> exitCall()
else -> setupMainMenu()
}
}
+ fun keyPressBackspace(event: KeyboardEvent) {
+ if (textInputIsBusy) {
+ guiController.checkRemoveTextInput()
+ }
+ }
+
+ fun keyPressEnter(event: KeyboardEvent) {
+ if (textInputIsBusy) {
+ guiController.stopTextInput()
+ }
+ }
+
+ fun inputText(text: String) {
+ if (textInputIsBusy) {
+ guiController.checkAddTextInput(text)
+ }
+ }
+
private fun setupMainMenu() {
currentPhase = GamePhases.MAIN_MENU
- guiController.clear()
+ gameState.reset()
guiController.createMainMenu(
onClickNewGame = { setupMainMenuSelectPlayers() },
onClickSettings = {},
@@ -279,7 +426,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
private fun setupMainMenuSelectPlayers() {
currentPhase = GamePhases.MAIN_MENU_SELECT_PLAYERS
gameState.reset()
- guiController.clear()
guiController.createMainMenuSelectPlayers(
onClickStart = { setupStartGame() },
onClickCancel = { setupMainMenu() },
@@ -302,28 +448,33 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val
}
private fun setupStartGame() {
- currentPhase = GamePhases.NEW_GAME_INTRO
- startTransition()
+ gameState.gamePlayers.withIndex()
+ .filter { (_, player) -> player.name.length <= 1 }
+ .forEach { (index, player) -> player.name = "Player ${index + 1}" }
+ guiController.clear()
+ startNewPhase(GamePhases.NEW_GAME_INTRO)
- MapGenerator.populateNewGameMap(gameState, textures)
+ MapGenerator.populateNewGameMap(gameState)
- if (gameState.gamePlayers.size > 0) {
- val startingPlayer = gameState.gamePlayers.random()
- gameState.playerOnTurn = startingPlayer
- }
+ check(gameState.gamePlayers.size > 1) { "Cannot play a game with less than 2 players." }
+ gameState.playerOnTurn = gameState.gamePlayers.random()
}
companion object {
- private const val pauseDownDuration = 1000f
- private const val introDuration = 5000f
+ private const val pauseTime = 1000f
+ private const val introDuration = 3500f
private const val introStartSlowdown = 2000f
- private const val maxPlayDuration = 30000f
+ private const val maxTurnDuration = 20000f
+ private const val quickStartTime = 300f
+ private const val outroDuration = 5000f
+
private val mouseElementPhases = listOf(
GamePhases.MAIN_MENU,
GamePhases.MAIN_MENU_SELECT_PLAYERS,
GamePhases.PLAYERS_PICK_SHIELDS,
- GamePhases.PLAYERS_TURN
+ GamePhases.PLAYERS_TURN,
+ GamePhases.END_ROUND
)
}
diff --git a/src/main/kotlin/game/GamePhases.kt b/src/main/kotlin/game/GamePhases.kt
index e5a7360..7024f65 100644
--- a/src/main/kotlin/game/GamePhases.kt
+++ b/src/main/kotlin/game/GamePhases.kt
@@ -8,6 +8,11 @@ enum class GamePhases {
NEW_GAME_INTRO,
PLAYERS_PICK_SHIELDS,
NONE,
- PLAYERS_TURN
+ PLAYERS_TURN,
+ PLAYERS_TURN_FIRED,
+ PLAYERS_TURN_FIRED_ENDS_EARLY,
+ PLAYERS_TURN_AIMING,
+ PLAYERS_TURN_POWERING,
+ END_ROUND
}
diff --git a/src/main/kotlin/game/GamePlayer.kt b/src/main/kotlin/game/GamePlayer.kt
index 384a2c4..16c2b66 100644
--- a/src/main/kotlin/game/GamePlayer.kt
+++ b/src/main/kotlin/game/GamePlayer.kt
@@ -1,9 +1,40 @@
package game
import engine.freeBody.Vehicle
+import engine.freeBody.Warhead
+import utility.Common.getTimingFunctionEaseIn
class GamePlayer(
- val name: String,
+ var name: String,
val type: GamePlayerTypes = GamePlayerTypes.HUMAN,
- var vehicle: Vehicle? = null
-)
+ var vehicle: Vehicle? = null,
+ val playerAim: PlayerAim = PlayerAim(),
+ var score: Float = 0f
+) {
+
+ fun scoreDamage(warhead: Warhead, totalDamage: Float, vehicle: Vehicle) {
+ val selfHarm = when (vehicle) {
+ this.vehicle -> -.5f
+ else -> 1f
+ }
+ val noAgeBonusTime = 2000f
+ val age = warhead.ageTime
+ .minus(noAgeBonusTime)
+ .coerceAtLeast(0f)
+ .div(warhead.selfDestructTime - noAgeBonusTime)
+ .let { getTimingFunctionEaseIn(it) * 5 + 1f }
+
+ score += selfHarm * totalDamage * age
+ }
+
+ fun scoreKill(vehicle: Vehicle) {
+ val selfHarm = when (vehicle) {
+ this.vehicle -> -.5f
+ else -> 1f
+ }
+ score += selfHarm * 2f
+ }
+
+ val warheads = mutableListOf()
+
+}
diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt
index 06d2565..a8db1de 100644
--- a/src/main/kotlin/game/MapGenerator.kt
+++ b/src/main/kotlin/game/MapGenerator.kt
@@ -1,86 +1,90 @@
package game
-import Vector2f
import display.draw.TextureConfig
-import display.draw.TextureHolder
+import display.draw.TextureEnum
import display.graphic.BasicShapes
+import display.graphic.Color
import engine.GameState
import engine.freeBody.Planet
import engine.freeBody.Vehicle
import engine.motion.Director
+import org.jbox2d.common.Vec2
import org.jbox2d.dynamics.World
+import utility.Common.vectorUnit
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
object MapGenerator {
- fun populateNewGameMap(gameState: GameState, textures: TextureHolder) {
+ fun populateNewGameMap(gameState: GameState) {
val terra = Planet.create(
- gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1800f, 4.5f, .3f,
- textureConfig = TextureConfig(textures.marble_earth, chunkedVertices = BasicShapes.polygon30.chunked(2))
+ gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1600f, 4.5f, .3f,
+ textureConfig = TextureConfig(TextureEnum.marble_earth, chunkedVertices = BasicShapes.polygon30.chunked(2))
)
val luna = Planet.create(
- gameState.world, "luna", -20f, 0f, 0f, 0f, 4.4f, -.4f, 100f, 1.25f, .5f,
- textureConfig = TextureConfig(textures.full_moon, chunkedVertices = BasicShapes.polygon30.chunked(2))
+ gameState.world, "luna", -25f, 0f, 0f, 0f, 4.8f, -.4f, 100f, 1.25f, .5f,
+ textureConfig = TextureConfig(TextureEnum.full_moon, chunkedVertices = BasicShapes.polygon30.chunked(2))
)
+ val colorStartIndex = Math.random().times(11).toInt()
val vehicles = gameState.gamePlayers.withIndex().map { (index, player) ->
Vehicle.create(
- gameState.world, player, -30f * index + 15f * gameState.gamePlayers.size,
+ gameState.world, player, -10f * index + .5f * gameState.gamePlayers.size,
10f, index * 1f, 0f, 0f, 1f, 3f, radius = .75f,
- textureConfig = TextureConfig(textures.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f))
+ textureConfig = TextureConfig(TextureEnum.metal, vectorUnit.mul(.7f),
+ color = Color.PALETTE_TINT10[((colorStartIndex + index) * 2).rem(11)])
)
}
gameState.vehicles.addAll(vehicles)
gameState.planets.addAll(listOf(terra, luna))
- gameState.planets.addAll(createPlanets(gameState.world, 5, textures))
+ gameState.planets.addAll(createAsteroids(gameState.world, 15))
- gameState.tickables.forEach { it.textureConfig.updateGpuBufferData() }
+ gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() }
}
- fun populateTestMap(gameState: GameState, textures: TextureHolder) {
+ fun populateTestMap(gameState: GameState) {
val terra = Planet.create(
gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1800f, 4.5f, .3f,
textureConfig = TextureConfig(
- textures.marble_earth,
- Vector2f(1f, 1f),
- Vector2f(0f, 0f),
+ TextureEnum.marble_earth,
+ vectorUnit,
+ Vec2(),
BasicShapes.polygon30.chunked(2)
)
)
val luna = Planet.create(
gameState.world, "luna", -20f, 0f, 0f, 0f, 4.4f, -.4f, 100f, 1.25f, .5f,
textureConfig = TextureConfig(
- textures.full_moon,
- Vector2f(1f, 1f),
- Vector2f(0f, 0f),
+ TextureEnum.full_moon,
+ vectorUnit.mul(.7f),
+ Vec2(),
BasicShapes.polygon30.chunked(2)
)
)
val alice = Vehicle.create(
gameState.world, GamePlayer("alice"), -30f, 5f, 0f, -2f, 2.7f, 1f, 3f, radius = .75f,
- textureConfig = TextureConfig(textures.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf())
+ textureConfig = TextureConfig(TextureEnum.metal, vectorUnit.mul(.7f))
)
val bob = Vehicle.create(
gameState.world, GamePlayer("bob"), 25f, 0f, 0f, 2f, -3f, 0f, 3f, radius = .75f,
- textureConfig = TextureConfig(textures.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf())
+ textureConfig = TextureConfig(TextureEnum.metal, vectorUnit.mul(.7f))
)
gameState.vehicles.addAll(listOf(alice, bob))
gameState.planets.addAll(listOf(terra, luna))
- gameState.planets.addAll(createPlanets(gameState.world, 20, textures))
+ gameState.planets.addAll(createAsteroids(gameState.world, 20))
- gameState.tickables.forEach { it.textureConfig.updateGpuBufferData() }
+ gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() }
}
- private fun createPlanets(world: World, count: Int, textures: TextureHolder): List {
+ private fun createAsteroids(world: World, count: Int): List {
return (1..count)
.withIndex()
.map { (i, _) ->
val ratio = (2 * PI * 0.07 * i).toFloat()
- val radius = 10f
+ val radius = 35f
floatArrayOf(
(i * .04f + radius) * cos(ratio),
(i * .04f + radius) * sin(ratio),
@@ -89,7 +93,7 @@ object MapGenerator {
}
.map {
val direction = Director.getDirection(-it[0], -it[1]) + PI * .5f
- val speed = 8f
+ val speed = 4.7f
Planet.create(
world, "${it[2].toInt()}", it[0], it[1], 0f,
cos(direction).toFloat() * speed,
@@ -97,9 +101,9 @@ object MapGenerator {
.5f, 0.3f * it[2].rem(6f), .2f + it[2].rem(6f) * .05f,
friction = .6f,
textureConfig = TextureConfig(
- textures.pavement,
- Vector2f(.3f, .3f),
- Vector2f(it[0].rem(20f) / 10 - 1, it[1].rem(20f) / 10 - 1),
+ TextureEnum.pavement,
+ vectorUnit.mul(.3f),
+ Vec2(it[0].rem(20f) / 10 - 1, it[1].rem(20f) / 10 - 1),
BasicShapes.polygon9.chunked(2)
)
)
diff --git a/src/main/kotlin/game/PlayerAim.kt b/src/main/kotlin/game/PlayerAim.kt
new file mode 100644
index 0000000..d133228
--- /dev/null
+++ b/src/main/kotlin/game/PlayerAim.kt
@@ -0,0 +1,16 @@
+package game
+
+import utility.Common
+import utility.Common.Pi2
+import utility.Common.radianToDegree
+
+class PlayerAim(var angle: Float = 0f, power: Float = 100f) {
+
+ var power = power
+ set(value) {
+ field = value.coerceIn(0f, 100f)
+ }
+
+ fun getDegreesAngle() = (angle + Pi2) % Pi2 * radianToDegree
+
+}
diff --git a/src/main/kotlin/input/CameraView.kt b/src/main/kotlin/input/CameraView.kt
index 2e3a1c9..5217306 100644
--- a/src/main/kotlin/input/CameraView.kt
+++ b/src/main/kotlin/input/CameraView.kt
@@ -1,5 +1,6 @@
package input
+import Matrix4f
import display.Window
import engine.freeBody.FreeBody
import org.jbox2d.common.Vec2
@@ -19,7 +20,7 @@ class CameraView(private val window: Window) {
var currentPhase = CameraPhases.STATIC
var lastStaticLocation = location
private var lastPhaseTimestamp = System.currentTimeMillis()
- private val transitionDuration = 1000f
+ private var transitionDuration = 1000f
private lateinit var trackFreeBody: FreeBody
@@ -47,12 +48,13 @@ class CameraView(private val window: Window) {
location = position
}
- fun trackFreeBody(newFreeBody: FreeBody) {
+ fun trackFreeBody(newFreeBody: FreeBody, transitionTime: Float = 1000f) {
currentPhase = CameraPhases.TRANSITION_TO_TARGET
lastPhaseTimestamp = System.currentTimeMillis()
trackFreeBody = newFreeBody
lastStaticLocation = location
+ transitionDuration = transitionTime
}
fun moveLocation(movement: Vec2) {
@@ -68,6 +70,12 @@ class CameraView(private val window: Window) {
z = .05f
}
+ fun getRenderCamera(): Matrix4f {
+ val zoomScale = 1f / z
+ return Matrix4f.scale(zoomScale, zoomScale, 1f)
+ .multiply(Matrix4f.translate(-location.x, -location.y, 0f))
+ }
+
}
enum class CameraPhases {
diff --git a/src/main/kotlin/input/InputHandler.kt b/src/main/kotlin/input/InputHandler.kt
index 12523f5..b1fd179 100644
--- a/src/main/kotlin/input/InputHandler.kt
+++ b/src/main/kotlin/input/InputHandler.kt
@@ -2,6 +2,7 @@ package input
import display.events.MouseButtonEvent
import display.Window
+import display.events.MouseScrollEvent
import game.GamePhaseHandler
import io.reactivex.subjects.PublishSubject
import org.jbox2d.common.Vec2
@@ -12,12 +13,19 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) {
private val unsubscribe = PublishSubject.create()
fun init(window: Window) {
- setupDragRightClick(window)
+ setupDragClick(window)
setupDoubleLeftClick(window)
setupMouseScroll(window)
setupKeyboard(window)
setupMouseMove(window)
setupMouseClicks(window)
+ setupTextInput(window)
+ }
+
+ private fun setupTextInput(window: Window) {
+ window.textInputEvent.takeUntil(unsubscribe).subscribe {
+ gamePhaseHandler.inputText(it)
+ }
}
private fun setupMouseClicks(window: Window) {
@@ -41,10 +49,12 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) {
when (it.action) {
GLFW.GLFW_PRESS -> {
when (it.key) {
- GLFW.GLFW_KEY_SPACE -> gamePhaseHandler.pauseGame(it)
+ // GLFW.GLFW_KEY_SPACE -> gamePhaseHandler.pauseGame(it)
GLFW.GLFW_KEY_LEFT -> gamePhaseHandler.keyPressArrowLeft(it)
GLFW.GLFW_KEY_RIGHT -> gamePhaseHandler.keyPressArrowRight(it)
GLFW.GLFW_KEY_ESCAPE -> gamePhaseHandler.keyPressEscape(it)
+ GLFW.GLFW_KEY_BACKSPACE -> gamePhaseHandler.keyPressBackspace(it)
+ GLFW.GLFW_KEY_ENTER -> gamePhaseHandler.keyPressEnter(it)
}
}
}
@@ -53,13 +63,7 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) {
private fun setupMouseScroll(window: Window) {
window.mouseScrollEvent.takeUntil(unsubscribe)
- .subscribe { gamePhaseHandler.scrollCamera(it.y) }
- }
-
- private fun setupDragRightClick(window: Window) {
- val mouseButtonRelease = PublishSubject.create()
- window.mouseButtonEvent.takeUntil(unsubscribe)
- .subscribe { click -> dragRightClick(click, window, mouseButtonRelease) }
+ .subscribe { gamePhaseHandler.scrollMouse(it) }
}
private fun setupDoubleLeftClick(window: Window) {
@@ -73,27 +77,56 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) {
}
.filter { (isDoubleClick, _) -> isDoubleClick }
.takeUntil(unsubscribe)
- .subscribe { (_, click) -> gamePhaseHandler.doubleLeftClick(window.getCursorPosition(), click) }
+ .subscribe { (_, click) -> gamePhaseHandler.doubleLeftClick(window.getCursorPosition()) }
}
- private fun dragRightClick(click: MouseButtonEvent, window: Window, mouseButtonRelease: PublishSubject) {
- if (click.button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && click.action == GLFW.GLFW_PRESS) {
+ private fun setupDragClick(window: Window) {
+ val mouseButtonLeftRelease = PublishSubject.create()
+ val mouseButtonRightRelease = PublishSubject.create()
+ window.mouseButtonEvent.takeUntil(unsubscribe)
+ .subscribe { click -> dragClick(click, window, mouseButtonLeftRelease, mouseButtonRightRelease) }
+ }
- var startLocation: Vec2? = null
- window.cursorPositionEvent.takeUntil(mouseButtonRelease).subscribe { location ->
- location.x *= -1f
+ private fun dragClick(click: MouseButtonEvent,
+ window: Window,
+ mouseButtonLeftRelease: PublishSubject,
+ mouseButtonRightRelease: PublishSubject
+ ) {
+ if (click.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
+ when (click.action) {
+ GLFW.GLFW_PRESS -> {
+ window.cursorPositionEvent.takeUntil(mouseButtonLeftRelease)
+ .subscribe {
+ handleMouseMovement(click.location, it) { movement ->
+ gamePhaseHandler.dragMouseLeftClick(click.location, movement)
+ }
+ }
+ }
+ GLFW.GLFW_RELEASE -> mouseButtonLeftRelease.onNext(true)
+ }
+ }
- if (startLocation != null) {
- val movement = startLocation!!.add(location.mul(-1f))
- gamePhaseHandler.dragMouseRightClick(movement)
+ if (click.button == GLFW.GLFW_MOUSE_BUTTON_RIGHT) {
+ when (click.action) {
+ GLFW.GLFW_PRESS -> {
+ window.cursorPositionEvent.takeUntil(mouseButtonRightRelease)
+ .subscribe {
+ handleMouseMovement(click.location, it) { movement ->
+ gamePhaseHandler.dragMouseRightClick(movement)
+ }
+ }
}
- startLocation = location
+ GLFW.GLFW_RELEASE -> mouseButtonRightRelease.onNext(true)
}
- } else if (click.button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && click.action == GLFW.GLFW_RELEASE) {
- mouseButtonRelease.onNext(true)
}
}
+ private fun handleMouseMovement(startLocation: Vec2, location: Vec2, callback: (Vec2) -> Unit) {
+ val movement = startLocation.add(location.mul(-1f)).also { it.x *= -1f }
+ callback(movement)
+ startLocation.set(location)
+ }
+
fun dispose() {
unsubscribe.onNext(true)
}
diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt
index 82be51f..47dc789 100644
--- a/src/main/kotlin/utility/Common.kt
+++ b/src/main/kotlin/utility/Common.kt
@@ -1,6 +1,9 @@
package utility
+import org.jbox2d.common.MathUtils
import org.jbox2d.common.Vec2
+import java.io.File
+import java.nio.DoubleBuffer
import java.util.*
import kotlin.math.*
@@ -17,6 +20,14 @@ object Common {
return result
}
+ fun getSafePath(resourcePath: String): String {
+ val alternativePath = "src/main/resources/${resourcePath.substring(1)}"
+ return when {
+ File(alternativePath).exists() -> alternativePath
+ else -> resourcePath
+ }
+ }
+
fun joinLists(aList: List, bList: List): Sequence> = sequence {
aList.forEach { aItem ->
bList.forEach { bItem ->
@@ -35,21 +46,34 @@ object Common {
fun roundFloat(value: Float, decimals: Int = 2): Float {
val multiplier = 10f.pow(decimals)
- return (value * multiplier).roundToInt() / multiplier
+ return value.times(multiplier).roundToInt().div(multiplier)
}
+ const val Pi2 = 2f * PI.toFloat()
+
val vectorUnit = Vec2(1f, 1f)
+ fun makeVec2(list: List): Vec2 = Vec2(list[0], list[1])
+
+ fun makeVec2(x: Number, y: Number): Vec2 = Vec2(x.toFloat(), y.toFloat())
+
+ fun makeVec2(x: DoubleBuffer, y: DoubleBuffer): Vec2 = makeVec2(x.get(), y.get())
+
+ fun makeVec2(duplicate: Number): Vec2 = makeVec2(duplicate, duplicate)
+
+ fun makeVec2Circle(angle: Float): Vec2 = Vec2(cos(angle), sin(angle))
+
val radianToDegree = Math.toDegrees(1.0).toFloat()
fun getTimingFunctionEaseOut(interpolateStep: Float) = getTimingFunctionFullSine(sqrt(interpolateStep))
- fun getTimingFunctionSineEaseIn(interpolateStep: Float) = 1f - getTimingFunctionEaseOut(1f - interpolateStep)
+ fun getTimingFunctionEaseIn(interpolateStep: Float) = 1f - getTimingFunctionEaseOut(1f - interpolateStep)
fun getTimingFunctionFullSine(interpolateStep: Float) = (sin(interpolateStep * PI - PI * .5) * .5 + .5).toFloat()
fun getTimingFunctionSigmoid(interpolateStep: Float, centerGradient: Float = 1f) =
- (1f / (1f + exp((-(interpolateStep - .5f) * 10f)) * centerGradient))
-
+ (1f / (1f + exp((-(interpolateStep - .5f) * 10f)) * centerGradient)) * 1.023f - 0.0022f
}
+
+fun Vec2.toList(): List = listOf(this.x, this.y)
diff --git a/src/main/resources/textures/icon_aim.png b/src/main/resources/textures/icon_aim.png
new file mode 100644
index 0000000..895e8b7
Binary files /dev/null and b/src/main/resources/textures/icon_aim.png differ
diff --git a/src/test/kotlin/engine/motion/DirectorTest.kt b/src/test/kotlin/engine/motion/DirectorTest.kt
index be0c1ed..bc797ff 100644
--- a/src/test/kotlin/engine/motion/DirectorTest.kt
+++ b/src/test/kotlin/engine/motion/DirectorTest.kt
@@ -3,6 +3,7 @@ package engine.motion
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import utility.Common
+import utility.Common.roundFloat
import kotlin.math.PI
internal class DirectorTest {
@@ -10,41 +11,45 @@ internal class DirectorTest {
@Test
fun distance() {
val rightPointed = Director.getDistance(0f, 0f, 1f, 1f)
- assertEquals(1.414f, Common.roundFloat(rightPointed, 3))
+ assertEquals(1.414f, roundFloat(rightPointed, 3))
val leftPointed = Director.getDistance(0f, 0f, -1f, -1f)
- assertEquals(1.414f, Common.roundFloat(leftPointed, 3))
+ assertEquals(1.414f, roundFloat(leftPointed, 3))
}
@Test
fun direction() {
// Test direction from client, to server
val pointed0 = Director.getDirection(0f, 0f, -1f, 0f)
- assertEquals(0f, pointed0, "0")
+ assertRoundEquals(0, pointed0, "0")
val pointed45 = Director.getDirection(0f, 0f, -1f, -1f)
- assertEquals(PI / 4, pointed45, "45")
+ assertRoundEquals(PI / 4, pointed45, "45")
val pointed90 = Director.getDirection(0f, 0f, 0f, -1f)
- assertEquals(PI / 2, pointed90, "90")
+ assertRoundEquals(PI / 2, pointed90, "90")
val pointed135 = Director.getDirection(0f, 0f, 1f, -1f)
- assertEquals(PI * 3 / 4, pointed135, "135")
+ assertRoundEquals(PI * 3 / 4, pointed135, "135")
val pointed180 = Director.getDirection(0f, 0f, 1f, 0f)
- assertEquals(PI, pointed180, "180")
+ assertRoundEquals(PI, pointed180, "180")
val pointed225 = Director.getDirection(0f, 0f, 1f, 1f)
- assertEquals(-PI * 3 / 4, pointed225, "225")
+ assertRoundEquals(-PI * 3 / 4, pointed225, "225")
val pointed270 = Director.getDirection(0f, 0f, 0f, 1f)
- assertEquals(-PI / 2, pointed270, "270")
+ assertRoundEquals(-PI / 2, pointed270, "270")
val pointed315 = Director.getDirection(0f, 0f, -1f, 1f)
- assertEquals(-PI / 4, pointed315, "315")
+ assertRoundEquals(-PI / 4, pointed315, "315")
val vector = Director.getDirection(1f, 0f)
- assertEquals(0f, vector, "vector")
+ assertRoundEquals(0, vector, "vector")
+ }
+
+ private fun assertRoundEquals(expected: Number, actual: Float, message: String) {
+ assertEquals(roundFloat(expected.toFloat(), 2), roundFloat(actual, 2), message)
}
}
diff --git a/src/test/kotlin/engine/physics/GravityTest.kt b/src/test/kotlin/engine/physics/GravityTest.kt
index b51d40a..43db042 100644
--- a/src/test/kotlin/engine/physics/GravityTest.kt
+++ b/src/test/kotlin/engine/physics/GravityTest.kt
@@ -1,7 +1,7 @@
package engine.physics
import display.draw.TextureConfig
-import display.graphic.Texture
+import display.draw.TextureEnum
import engine.freeBody.Planet
import org.jbox2d.common.Vec2
import org.jbox2d.dynamics.World
@@ -29,17 +29,17 @@ internal class GravityTest {
assertTrue(forceUpLeft.y > 0)
}
- @Test
- fun in_binary_system_the_massive_body_moves_less() {
- }
+// @Test
+// fun in_binary_system_the_massive_body_moves_less() {
+// }
private fun getGravityForceBetweenPlanetSatellite(sx: Float = 0f, sy: Float = 0f): Vec2 {
- val world = World(Vec2(0f, 0f))
+ val world = World(Vec2())
val terra = Planet.create(world, "terra", sx, sy, 0f, 0f, 0f, 0f, 100f, 10f,
- textureConfig = TextureConfig(Texture()))
+ textureConfig = TextureConfig(TextureEnum.white_pixel))
val luna = Planet.create(world, "luna", 0f, 0f, 0f, 0f, 0f, 0f, 100f, 10f,
- textureConfig = TextureConfig(Texture()))
+ textureConfig = TextureConfig(TextureEnum.white_pixel))
return Gravity.gravitationalForce(luna, terra)
}
diff --git a/src/test/kotlin/utility/CommonTest.kt b/src/test/kotlin/utility/CommonTest.kt
new file mode 100644
index 0000000..a19db5a
--- /dev/null
+++ b/src/test/kotlin/utility/CommonTest.kt
@@ -0,0 +1,45 @@
+package utility
+
+import org.jbox2d.common.Vec2
+import org.junit.jupiter.api.Test
+
+import utility.Common.getTimingFunctionEaseIn
+import utility.Common.getTimingFunctionEaseOut
+import utility.Common.getTimingFunctionFullSine
+import utility.Common.getTimingFunctionSigmoid
+import utility.Common.joinLists
+import utility.Common.roundFloat
+
+internal class CommonTest {
+
+ fun testest() {
+
+ val data = getChartedData {
+ getTimingFunctionEaseIn(1f - it)
+ }
+ println(data)
+ }
+
+ private fun getChartedData(timingFunction: (Float) -> Float): String {
+ val resolution = 50
+ val values = (0 until resolution).map { it / resolution.minus(1).toFloat() }
+ .map { timingFunction(it) }
+ .map { roundFloat(it, 2) }
+
+ val visualGraph = HashMap, Float>()
+ values.withIndex().forEach { (index, y) ->
+ visualGraph[Pair(index, (y * (resolution)).toInt())] = y
+ }
+
+ val intRange = (0 until resolution).toList()
+ val result = joinLists(intRange, intRange)
+ .sortedBy { (x, y) -> -y }
+ .sortedBy { (x, y) -> x }
+ .map { (x, y) -> visualGraph[Pair(y, x)] }
+ .map { if (it != null) "x" else "." }
+ .chunked(resolution)
+ .map { it.joinToString("") }
+ .joinToString("\n")
+ return result
+ }
+}