package net.torvald.terrarum import com.badlogic.gdx.Gdx import com.badlogic.gdx.Screen import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.GL20 import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.graphics.glutils.ShaderProgram import com.badlogic.gdx.graphics.glutils.ShapeRenderer import com.badlogic.gdx.utils.GdxRuntimeException import com.jme3.math.FastMath import net.torvald.util.CircularArray import net.torvald.random.HQRNG import net.torvald.terrarum.AppLoader.* import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.ActorID import net.torvald.terrarum.imagefont.TinyAlphNum import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.worlddrawer.CreateTileAtlas import net.torvald.terrarum.worlddrawer.WorldCamera import net.torvald.terrarumsansbitmap.gdx.GameFontBase import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import org.lwjgl.BufferUtils import java.io.File import kotlin.math.absoluteValue typealias RGBA8888 = Int /** * Slick2d Version Created by minjaesong on 2015-12-30. * * LibGDX Version Created by minjaesong on 2017-06-15. */ object Terrarum : Screen { /** * All singleplayer "Player" must have this exact reference ID. */ const val PLAYER_REF_ID: Int = 0x91A7E2 lateinit var batch: SpriteBatch lateinit var shapeRender: ShapeRenderer // DO NOT USE!! for very limited applications e.g. WeatherMixer inline fun inShapeRenderer(shapeRendererType: ShapeRenderer.ShapeType = ShapeRenderer.ShapeType.Filled, action: (ShapeRenderer) -> Unit) { shapeRender.begin(shapeRendererType) action(shapeRender) shapeRender.end() } ////////////////////////////// // GLOBAL IMMUTABLE CONFIGS // ////////////////////////////// val WIDTH: Int get() = AppLoader.screenW val HEIGHT: Int get() = AppLoader.screenH //val WIDTH_MIN = 800 //val HEIGHT_MIN = 600 inline val HALFW: Int get() = WIDTH.ushr(1) inline val HALFH: Int get() = HEIGHT.ushr(1) /** * To be used with physics simulator. This is a magic number. */ val PHYS_TIME_FRAME: Double = 26.0 + (2.0 / 3.0) // 26.0 + (2.0 / 3.0) // lower value == faster gravity response (IT WON'T HOTSWAP!!) // protip: using METER, game unit and SI unit will have same number var previousScreen: Screen? = null // to be used with temporary states like StateMonitorCheck var ingame: IngameInstance? = null private val javaHeapCircularArray = CircularArray(64) private val nativeHeapCircularArray = CircularArray(64) private val updateRateCircularArray = CircularArray(16) val memJavaHeap: Int get() { javaHeapCircularArray.add((Gdx.app.javaHeap shr 20).toInt()) var acc = 0 javaHeapCircularArray.forEach { acc = maxOf(acc, it) } return acc } val memNativeHeap: Int get() { nativeHeapCircularArray.add((Gdx.app.javaHeap shr 20).toInt()) var acc = 0 nativeHeapCircularArray.forEach { acc = maxOf(acc, it) } return acc } val memXmx: Int get() = (Runtime.getRuntime().maxMemory() shr 20).toInt() val updateRateStr: String get() { updateRateCircularArray.add(updateRate) var acc = 0.0 updateRateCircularArray.forEach { acc = maxOf(acc, it) } return String.format("%.2f", acc) } val fontGame: GameFontBase = AppLoader.fontGame val fontSmallNumbers: TinyAlphNum = AppLoader.fontSmallNumbers var gamepadLabelStart = 0xE000.toChar() // lateinit var gamepadLabelSelect = 0xE000.toChar() // lateinit var gamepadLabelEast = 0xE000.toChar() // lateinit var gamepadLabelSouth = 0xE000.toChar() // lateinit var gamepadLabelNorth = 0xE000.toChar() // lateinit var gamepadLabelWest = 0xE000.toChar() // lateinit var gamepadLabelLB = 0xE000.toChar() // lateinit var gamepadLabelRB = 0xE000.toChar() // lateinit var gamepadLabelLT = 0xE000.toChar() // lateinit var gamepadLabelRT = 0xE000.toChar() // lateinit val gamepadLabelLEFT = 0xE068.toChar() val gamepadLabelDOWN = 0xE069.toChar() val gamepadLabelUP = 0xE06A.toChar() val gamepadLabelRIGHT = 0xE06B.toChar() val gamepadLabelUPDOWN = 0xE072.toChar() val gamepadLabelLEFTRIGHT = 0xE071.toChar() val gamepadLabelDPAD = 0xE070.toChar() val gamepadLabelLStick = 0xE044.toChar() val gamepadLabelRStick = 0xE045.toChar() val gamepadLabelLStickPush = 0xE046.toChar() val gamepadLabelRStickPush = 0xE047.toChar() // 0x0 - 0xF: Game-related // 0x10 - 0x1F: Config // 0x100 and onward: unit tests for dev val STATE_ID_SPLASH = 0x0 val STATE_ID_HOME = 0x1 val STATE_ID_GAME = 0x3 val STATE_ID_CONFIG_CALIBRATE = 0x11 val STATE_ID_TEST_FONT = 0x100 val STATE_ID_TEST_GFX = 0x101 val STATE_ID_TEST_TTY = 0x102 val STATE_ID_TEST_BLUR = 0x103 val STATE_ID_TEST_SHADER = 0x104 val STATE_ID_TEST_REFRESHRATE = 0x105 val STATE_ID_TEST_INPUT = 0x106 val STATE_ID_TEST_UI1 = 0x110 val STATE_ID_TOOL_NOISEGEN = 0x200 val STATE_ID_TOOL_RUMBLE_DIAGNOSIS = 0x201 /** Available CPU threads */ val THREADS = Runtime.getRuntime().availableProcessors() + 1 /** * If the game is multithreading. * True if: * * THREADS >= 2 and config "multithread" is true */ val MULTITHREAD: Boolean get() = THREADS >= 3 && getConfigBoolean("multithread") const val NAME = AppLoader.GAME_NAME lateinit var shaderBlur: ShaderProgram lateinit var shaderBayer: ShaderProgram lateinit var shaderSkyboxFill: ShaderProgram lateinit var shaderBlendGlow: ShaderProgram lateinit var shaderRGBOnly: ShaderProgram lateinit var shaderAtoGrey: ShaderProgram lateinit var testTexture: Texture /** Actually just a mesh of four vertices, two triangles -- not a literal glQuad */ val fullscreenQuad = AppLoader.fullscreenQuad init { println("$NAME version ${AppLoader.getVERSION_STRING()}") println("Java Runtime version ${System.getProperty("java.version")}") println("LibGDX version ${com.badlogic.gdx.Version.VERSION}") println("os.arch = $systemArch") // debug info if (is32BitJVM) { printdbgerr(this, "32 Bit JVM detected") } println("processor = $processor") println("vendor = $processorVendor") setGamepadButtonLabels() } private fun setGamepadButtonLabels() { gamepadLabelStart = when (getConfigString("gamepadlabelstyle")) { "nwii" -> 0xE04B.toChar() // + mark "logitech" -> 0xE05A.toChar() // number 10 "msxbone" -> 0xE049.toChar() // trifold equal sign? else -> 0xE042.toChar() // |> mark (sonyps, msxb360, generic) } gamepadLabelSelect = when (getConfigString("gamepadlabelstyle")) { "nwii" -> 0xE04D.toChar() // - mark "logitech" -> 0xE059.toChar() // number 9 "sonyps" -> 0xE043.toChar() // solid rectangle "msxb360" -> 0xE041.toChar() // <| mark "msxbone" -> 0xE048.toChar() // multitask button? else -> 0xE043.toChar() // solid rectangle } when (getConfigString("gamepadlabelstyle")) { "msxb360", "msxbone" -> { gamepadLabelSouth = 0xE061.toChar() gamepadLabelEast = 0xE062.toChar() gamepadLabelWest = 0xE078.toChar() gamepadLabelNorth = 0xE079.toChar() gamepadLabelLB = 0xE06D.toChar() gamepadLabelRB = 0xE06E.toChar() gamepadLabelLT = 0xE06C.toChar() gamepadLabelRT = 0xE06F.toChar() } "nwii" -> { gamepadLabelSouth = 0xE062.toChar() gamepadLabelEast = 0xE061.toChar() gamepadLabelWest = 0xE079.toChar() gamepadLabelNorth = 0xE078.toChar() gamepadLabelLB = 0xE065.toChar() gamepadLabelRB = 0xE066.toChar() gamepadLabelLT = 0xE064.toChar() gamepadLabelRT = 0xE067.toChar() } "sonyps" -> { gamepadLabelSouth = 0xE063.toChar() gamepadLabelEast = 0xE050.toChar() gamepadLabelWest = 0xE073.toChar() gamepadLabelNorth = 0xE074.toChar() gamepadLabelLB = 0xE07B.toChar() gamepadLabelRB = 0xE07C.toChar() gamepadLabelLT = 0xE07A.toChar() gamepadLabelRT = 0xE07D.toChar() } "logitech" -> { gamepadLabelSouth = 0xE052.toChar() gamepadLabelEast = 0xE053.toChar() gamepadLabelWest = 0xE051.toChar() gamepadLabelNorth = 0xE054.toChar() gamepadLabelLB = 0xE055.toChar() gamepadLabelRB = 0xE056.toChar() gamepadLabelLT = 0xE057.toChar() gamepadLabelRT = 0xE058.toChar() } } } val RENDER_FPS = getConfigInt("displayfps") val USE_VSYNC = getConfigBoolean("usevsync") var VSYNC = USE_VSYNC val VSYNC_TRIGGER_THRESHOLD = 56 val GL_VERSION: Int get() = Gdx.graphics.glVersion.majorVersion * 100 + Gdx.graphics.glVersion.minorVersion * 10 + Gdx.graphics.glVersion.releaseVersion val MINIMAL_GL_VERSION = 210 val GL_MAX_TEXTURE_SIZE: Int get() { val intBuffer = BufferUtils.createIntBuffer(16) // size must be at least 16, or else LWJGL complains Gdx.gl.glGetIntegerv(GL20.GL_MAX_TEXTURE_SIZE, intBuffer) intBuffer.rewind() return intBuffer.get() } val MINIMAL_GL_MAX_TEXTURE_SIZE = 4096 override fun show() { testTexture = Texture(Gdx.files.internal("./assets/test_texture.tga")) val glInfo = Gdx.graphics.glVersion.debugVersionString println("GL_VERSION = $GL_VERSION") println("GL_MAX_TEXTURE_SIZE = $GL_MAX_TEXTURE_SIZE") println("GL info:\n$glInfo") // debug info if (GL_VERSION < MINIMAL_GL_VERSION || GL_MAX_TEXTURE_SIZE < MINIMAL_GL_MAX_TEXTURE_SIZE) { // TODO notify properly throw GdxRuntimeException("Graphics device not capable -- device's GL_VERSION: $GL_VERSION, required: $MINIMAL_GL_VERSION; GL_MAX_TEXTURE_SIZE: $GL_MAX_TEXTURE_SIZE, required: $MINIMAL_GL_MAX_TEXTURE_SIZE") } // resize fullscreen quad? TextureRegionPack.globalFlipY = true // !! TO MAKE LEGACY CODE RENDER ON ITS POSITION !! Gdx.graphics.isContinuousRendering = true batch = SpriteBatch() shapeRender = ShapeRenderer() shaderBlur = AppLoader.loadShader("assets/blur.vert", "assets/blur.frag") if (getConfigBoolean("fxdither")) { shaderBayer = AppLoader.loadShader("assets/4096.vert", "assets/4096_bayer.frag") shaderBayer.begin() shaderBayer.setUniformf("rcount", 64f) shaderBayer.setUniformf("gcount", 64f) shaderBayer.setUniformf("bcount", 64f) shaderBayer.end() shaderSkyboxFill = AppLoader.loadShader("assets/4096.vert", "assets/4096_bayer_skyboxfill.frag") shaderSkyboxFill.begin() shaderSkyboxFill.setUniformf("rcount", 64f) shaderSkyboxFill.setUniformf("gcount", 64f) shaderSkyboxFill.setUniformf("bcount", 64f) shaderSkyboxFill.end() } else { shaderBayer = AppLoader.loadShader("assets/4096.vert", "assets/passthrurgb.frag") shaderSkyboxFill = AppLoader.loadShader("assets/4096.vert", "assets/skyboxfill.frag") } shaderBlendGlow = AppLoader.loadShader("assets/blendGlow.vert", "assets/blendGlow.frag") shaderRGBOnly = AppLoader.loadShader("assets/4096.vert", "assets/rgbonly.frag") shaderAtoGrey = AppLoader.loadShader("assets/4096.vert", "assets/aonly.frag") if (!shaderBlendGlow.isCompiled) { Gdx.app.log("shaderBlendGlow", shaderBlendGlow.log) System.exit(1) } if (getConfigBoolean("fxdither")) { if (!shaderBayer.isCompiled) { Gdx.app.log("shaderBayer", shaderBayer.log) System.exit(1) } if (!shaderSkyboxFill.isCompiled) { Gdx.app.log("shaderSkyboxFill", shaderSkyboxFill.log) System.exit(1) } } AppLoader.GAME_LOCALE = getConfigString("language") printdbg(this, "locale = ${AppLoader.GAME_LOCALE}") // jump straight into the ingame /*val ingame = Ingame(batch) ingame.gameLoadInfoPayload = Ingame.NewWorldParameters(2400, 800, HQRNG().nextLong()) ingame.gameLoadMode = Ingame.GameLoadMode.CREATE_NEW LoadScreen.screenToLoad = ingame this.ingame = ingame setScreen(LoadScreen)*/ // title screen AppLoader.getINSTANCE().setScreen(TitleScreen(batch)) } fun setScreen(screen: Screen) { AppLoader.getINSTANCE().setScreen(screen) } override fun render(delta: Float) { AppLoader.setDebugTime("GDX.rawDelta", Gdx.graphics.rawDeltaTime.times(1000_000_000f).toLong()) AppLoader.setDebugTime("GDX.smtDelta", Gdx.graphics.deltaTime.times(1000_000_000f).toLong()) AppLoader.getINSTANCE().screen.render(delta) } override fun pause() { AppLoader.getINSTANCE().screen.pause() } override fun resume() { AppLoader.getINSTANCE().screen.resume() } /** Don't call this! Call AppLoader.dispose() */ override fun dispose() { //dispose any other resources used in this level shaderBayer.dispose() shaderSkyboxFill.dispose() shaderBlur.dispose() shaderBlendGlow.dispose() ingame?.dispose() } override fun hide() { AppLoader.getINSTANCE().screen.hide() } /** For the actual resize, call AppLoader.resize() */ override fun resize(width: Int, height: Int) { ingame?.resize(width, height) printdbg(this, "newsize: ${Gdx.graphics.width}x${Gdx.graphics.height} | internal: ${width}x$height") } val currentSaveDir: File get() { val file = File(defaultSaveDir + "/test") // failsafe? if (!file.exists()) file.mkdir() return file // TODO TEST CODE } /** Position of the cursor in the world */ val mouseX: Double get() = WorldCamera.x + Gdx.input.x / (ingame?.screenZoom ?: 1f).toDouble() /** Position of the cursor in the world */ val mouseY: Double get() = WorldCamera.y + Gdx.input.y / (ingame?.screenZoom ?: 1f).toDouble() /** Position of the cursor in the world */ val oldMouseX: Double get() = WorldCamera.x + (Gdx.input.x - Gdx.input.deltaX) / (ingame?.screenZoom ?: 1f).toDouble() /** Position of the cursor in the world */ val oldMouseY: Double get() = WorldCamera.y + (Gdx.input.y - Gdx.input.deltaY) / (ingame?.screenZoom ?: 1f).toDouble() /** Position of the cursor in the world */ @JvmStatic val mouseTileX: Int get() = (mouseX / CreateTileAtlas.TILE_SIZE).floorInt() /** Position of the cursor in the world */ @JvmStatic val mouseTileY: Int get() = (mouseY / CreateTileAtlas.TILE_SIZE).floorInt() /** Position of the cursor in the world */ @JvmStatic val oldMouseTileX: Int get() = (oldMouseX / CreateTileAtlas.TILE_SIZE).floorInt() /** Position of the cursor in the world */ @JvmStatic val oldMouseTileY: Int get() = (oldMouseY / CreateTileAtlas.TILE_SIZE).floorInt() inline val mouseScreenX: Int get() = Gdx.input.x inline val mouseScreenY: Int get() = Gdx.input.y inline val mouseDeltaX: Int get() = Gdx.input.deltaX inline val mouseDeltaY: Int get() = Gdx.input.deltaY /** Delta converted as it it was a FPS */ inline val updateRate: Double get() = 1.0 / Gdx.graphics.rawDeltaTime /** * Usage: * * override var referenceID: Int = generateUniqueReferenceID() */ fun generateUniqueReferenceID(renderOrder: Actor.RenderOrder): ActorID { fun hasCollision(value: ActorID) = try { Terrarum.ingame!!.theGameHasActor(value) || value < ItemCodex.ACTORID_MIN || value !in when (renderOrder) { Actor.RenderOrder.BEHIND -> Actor.RANGE_BEHIND Actor.RenderOrder.MIDDLE -> Actor.RANGE_MIDDLE Actor.RenderOrder.MIDTOP -> Actor.RANGE_MIDTOP Actor.RenderOrder.FRONT -> Actor.RANGE_FRONT Actor.RenderOrder.OVERLAY-> Actor.RANDE_OVERLAY } } catch (gameNotInitialisedException: KotlinNullPointerException) { false } var ret: Int do { ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID } while (hasCollision(ret)) // check for collision return ret } } inline fun SpriteBatch.inUse(action: (SpriteBatch) -> Unit) { this.begin() action(this) this.end() } inline fun ShapeRenderer.inUse(shapeRendererType: ShapeRenderer.ShapeType = ShapeRenderer.ShapeType.Filled, action: (ShapeRenderer) -> Unit) { this.begin(shapeRendererType) action(this) this.end() } /** Use Batch inside of it! */ inline fun FrameBuffer.inAction(camera: OrthographicCamera?, batch: SpriteBatch?, action: (FrameBuffer) -> Unit) { //this.begin() FrameBufferManager.begin(this) camera?.setToOrtho(true, this.width.toFloat(), this.height.toFloat()) camera?.position?.set((this.width / 2f).round(), (this.height / 2f).round(), 0f) // TODO floor? ceil? round? camera?.update() batch?.projectionMatrix = camera?.combined action(this) //this.end() FrameBufferManager.end() camera?.setToOrtho(true, Terrarum.WIDTH.toFloat(), Terrarum.HEIGHT.toFloat()) camera?.update() batch?.projectionMatrix = camera?.combined } fun Float.round(): Float { return Math.round(this).toFloat() } // ShapeRenderer alternative for rects fun SpriteBatch.fillRect(x: Float, y: Float, w: Float, h: Float) { this.draw(AppLoader.textureWhiteSquare, x, y, w, h) } fun SpriteBatch.fillCircle(x: Float, y: Float, w: Float, h: Float) { this.draw(AppLoader.textureWhiteCircle, x, y, w, h) } fun SpriteBatch.drawStraightLine(x: Float, y: Float, otherEnd: Float, thickness: Float, isVertical: Boolean) { if (!isVertical) this.fillRect(x, y, otherEnd - x, thickness) else this.fillRect(x, y, thickness, otherEnd - y) } infix fun Color.mul(other: Color): Color = this.cpy().mul(other) infix fun Color.mulAndAssign(other: Color): Color { this.r *= other.r this.g *= other.g this.b *= other.b this.a *= other.a return this } fun blendMul(batch: SpriteBatch) { // will break if the colour image contains semitransparency batch.enableBlending() batch.setBlendFunction(GL20.GL_DST_COLOR, GL20.GL_ONE_MINUS_SRC_ALPHA) } fun blendScreen(batch: SpriteBatch) { // will break if the colour image contains semitransparency batch.enableBlending() batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_COLOR) } fun blendDisable(batch: SpriteBatch) { batch.disableBlending() } fun blendNormal(batch: SpriteBatch) { batch.enableBlending() batch.setBlendFunctionSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_SRC_ALPHA, GL20.GL_ONE) // ALPHA *MUST BE* PREMULTIPLIED // // One way to tell: // 1. Check (RGB) and (A) values. // 2. If there exist a pixel such that max(R,G,B) > (A), then the image is NOT premultiplied. // Easy way: // Base game (mods/basegame/blocks/terrain.tga.gz) has impure window glass. When looking at the RGB channel only: // premultipied if the glass looks very dark. // not premultipied if the glass looks VERY GREEN. // helpful links: // - https://gamedev.stackexchange.com/questions/82741/normal-blend-mode-with-opengl-trouble // - https://www.andersriggelsen.dk/glblendfunc.php } fun gdxClearAndSetBlend(r: Float, g: Float, b: Float, a: Float) { Gdx.gl.glClearColor(r,g,b,a) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) gdxSetBlend() } fun gdxSetBlend() { Gdx.gl.glEnable(GL20.GL_TEXTURE_2D) Gdx.gl.glEnable(GL20.GL_BLEND) } fun gdxSetBlendNormal() { gdxSetBlend() Gdx.gl.glBlendFuncSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_SRC_ALPHA, GL20.GL_ONE) //Gdx.gl.glBlendEquationSeparate(GL20.GL_FUNC_ADD, GL30.GL_MAX) // batch.flush does not touch blend equation // ALPHA *MUST BE* PREMULTIPLIED // // One way to tell: // 1. Check (RGB) and (A) values. // 2. If there exist a pixel such that max(R,G,B) > (A), then the image is NOT premultiplied. // Easy way: // Base game (mods/basegame/blocks/terrain.tga.gz) has impure window glass. When looking at the RGB channel only: // premultipied if the glass looks very dark. // not premultipied if the glass looks VERY GREEN. // helpful links: // - https://gamedev.stackexchange.com/questions/82741/normal-blend-mode-with-opengl-trouble // - https://www.andersriggelsen.dk/glblendfunc.php } object BlendMode { const val SCREEN = "screen" const val MULTIPLY = "multiply" const val NORMAL = "normal" //const val MAX = "GL_MAX" // not supported by GLES -- use shader fun resolve(mode: String, batch: SpriteBatch) { when (mode) { SCREEN -> blendScreen(batch) MULTIPLY -> blendMul(batch) NORMAL -> blendNormal(batch) //MAX -> blendLightenOnly() // not supported by GLES -- use shader else -> throw Error("Unknown blend mode: $mode") } } } enum class RunningEnvironment { PC, CONSOLE//, MOBILE } infix fun Color.screen(other: Color) = Color( 1f - (1f - this.r) * (1f - other.r), 1f - (1f - this.g) * (1f - other.g), 1f - (1f - this.b) * (1f - other.b), 1f - (1f - this.a) * (1f - other.a) ) infix fun Color.minus(other: Color) = Color( // don't turn into an operator! this.r - other.r, this.g - other.g, this.b - other.b, this.a - other.a ) fun Int.toHex() = this.toLong().and(0xFFFFFFFF).toString(16).padStart(8, '0').toUpperCase() fun MutableList.shuffle() { for (i in this.size - 1 downTo 1) { val rndIndex = (Math.random() * (i + 1)).toInt() val t = this[rndIndex] this[rndIndex] = this[i] this[i] = t } } val ccW = GameFontBase.toColorCode(0xFFFF) val ccY = GameFontBase.toColorCode(0xFE8F) val ccO = GameFontBase.toColorCode(0xFB2F) val ccR = GameFontBase.toColorCode(0xF88F) val ccF = GameFontBase.toColorCode(0xFAEF) val ccM = GameFontBase.toColorCode(0xEAFF) val ccB = GameFontBase.toColorCode(0x88FF) val ccC = GameFontBase.toColorCode(0x8FFF) val ccG = GameFontBase.toColorCode(0x8F8F) val ccV = GameFontBase.toColorCode(0x080F) val ccX = GameFontBase.toColorCode(0x853F) val ccK = GameFontBase.toColorCode(0x888F) typealias Second = Float fun Int.sqr(): Int = this * this fun Double.floorInt() = Math.floor(this).toInt() fun Float.floorInt() = FastMath.floor(this) fun Float.floor() = FastMath.floor(this).toFloat() fun Double.ceilInt() = Math.ceil(this).toInt() fun Float.ceil(): Float = FastMath.ceil(this).toFloat() fun Float.ceilInt() = FastMath.ceil(this) fun Double.round() = Math.round(this).toDouble() fun Double.floor() = Math.floor(this) fun Double.ceil() = this.floor() + 1.0 fun Double.roundInt(): Int = Math.round(this).toInt() fun Float.roundInt(): Int = Math.round(this) fun Double.abs() = Math.abs(this) fun Double.sqr() = this * this fun Float.sqr() = this * this fun Double.sqrt() = Math.sqrt(this) fun Float.sqrt() = FastMath.sqrt(this) fun Int.abs() = this.absoluteValue fun Double.bipolarClamp(limit: Double) = this.coerceIn(-limit, limit) fun Boolean.toInt() = if (this) 1 else 0 fun absMax(left: Double, right: Double): Double { if (left > 0 && right > 0) if (left > right) return left else return right else if (left < 0 && right < 0) if (left < right) return left else return right else { val absL = left.abs() val absR = right.abs() if (absL > absR) return left else return right } } fun Double.magnSqr() = if (this >= 0.0) this.sqr() else -this.sqr() fun Double.sign() = if (this > 0.0) 1.0 else if (this < 0.0) -1.0 else 0.0 fun interpolateLinear(scale: Double, startValue: Double, endValue: Double): Double { if (startValue == endValue) { return startValue } if (scale <= 0.0) { return startValue } if (scale >= 1.0) { return endValue } return (1.0 - scale) * startValue + scale * endValue } fun List.linearSearch(selector: (T) -> Boolean): Int? { this.forEachIndexed { index, it -> if (selector.invoke(it)) return index } return null } fun List.linearSearchBy(selector: (T) -> Boolean): T? { this.forEach { if (selector.invoke(it)) return it } return null }