package net.torvald.terrarum import net.torvald.imagefont.GameFontBase import net.torvald.terrarum.audio.AudioResourceLibrary import net.torvald.terrarum.concurrent.ThreadPool import net.torvald.terrarum.gameactors.* import net.torvald.terrarum.console.Authenticator import net.torvald.terrarum.console.CommandDict import net.torvald.terrarum.console.SetGlobalLightOverride import net.torvald.terrarum.gameactors.physicssolver.CollisionSolver import net.torvald.terrarum.gamecontroller.GameController import net.torvald.terrarum.gamecontroller.Key import net.torvald.terrarum.gamecontroller.KeyMap import net.torvald.terrarum.gamecontroller.KeyToggler import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.WorldSimulator import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.mapdrawer.LightmapRenderer import net.torvald.terrarum.mapdrawer.LightmapRenderer.constructRGBFromInt import net.torvald.terrarum.mapdrawer.MapCamera import net.torvald.terrarum.mapdrawer.MapDrawer import net.torvald.terrarum.mapgenerator.WorldGenerator import net.torvald.terrarum.mapgenerator.RoguelikeRandomiser import net.torvald.terrarum.tileproperties.TilePropCodex import net.torvald.terrarum.tilestats.TileStats import net.torvald.terrarum.ui.* import net.torvald.terrarum.weather.WeatherMixer import org.lwjgl.opengl.GL11 import org.newdawn.slick.* import org.newdawn.slick.fills.GradientFill import org.newdawn.slick.geom.Rectangle import org.newdawn.slick.state.BasicGameState import org.newdawn.slick.state.StateBasedGame import shader.Shader import java.lang.management.ManagementFactory import java.util.* /** * Created by minjaesong on 15-12-30. */ class StateInGame @Throws(SlickException::class) constructor() : BasicGameState() { private val ACTOR_UPDATE_RANGE = 4096 internal var game_mode = 0 lateinit var world: GameWorld /** * list of Actors that is sorted by Actors' referenceID */ val ACTORCONTAINER_INITIAL_SIZE = 128 val actorContainer = ArrayList(ACTORCONTAINER_INITIAL_SIZE) val actorContainerInactive = ArrayList(ACTORCONTAINER_INITIAL_SIZE) val uiContainer = LinkedList() lateinit var consoleHandler: UIHandler lateinit var debugWindow: UIHandler lateinit var notifier: UIHandler lateinit internal var player: Player //private var GRADIENT_IMAGE: Image? = null //private var skyBox: Rectangle? = null var screenZoom = 1.0f val ZOOM_MAX = 2.0f val ZOOM_MIN = 0.5f private lateinit var shader12BitCol: Shader private lateinit var shaderBlurH: Shader private lateinit var shaderBlurV: Shader private val useShader: Boolean = false private val shaderProgram = 0 private val CORES = ThreadPool.POOL_SIZE val auth = Authenticator() val KEY_LIGHTMAP_RENDER = Key.F7 val KEY_LIGHTMAP_SMOOTH = Key.F8 var UPDATE_DELTA: Int = 0 // UI aliases val uiAliases = HashMap() private val UI_PIE_MENU = "uiPieMenu" private val UI_QUICK_BAR = "uiQuickBar" private val UI_INVENTORY_PLAYER = "uiInventoryPlayer" private val UI_INVENTORY_ANON = "uiInventoryAnon" @Throws(SlickException::class) override fun init(gameContainer: GameContainer, stateBasedGame: StateBasedGame) { // state init code. Executed before the game goes into any "state" in states in StateBasedGame.java } override fun enter(gc: GameContainer, sbg: StateBasedGame) { // load things when the game entered this "state" // load necessary shaders shader12BitCol = Shader.makeShader("./assets/4096.vrt", "./assets/4096.frg") shaderBlurH = Shader.makeShader("./assets/blurH.vrt", "./assets/blur.frg") shaderBlurV = Shader.makeShader("./assets/blurV.vrt", "./assets/blur.frg") // init map as chosen size world = GameWorld(8192, 2048) // generate terrain for the map WorldGenerator.attachMap(world) WorldGenerator.SEED = 0x51621D2 //mapgenerator.setSeed(new HQRNG().nextLong()); WorldGenerator.generateMap() RoguelikeRandomiser.seed = 0x540198 //RoguelikeRandomiser.setSeed(new HQRNG().nextLong()); // add new player and put it to actorContainer player = PBSigrid.create() //player = PBCynthia.create() //player.setNoClip(true); addActor(player) // init console window consoleHandler = UIHandler(ConsoleWindow()) consoleHandler.setPosition(0, 0) // init debug window debugWindow = UIHandler(BasicDebugInfoWindow()) debugWindow.setPosition(0, 0) // init notifier notifier = UIHandler(Notification()) notifier.UI.handler = notifier notifier.setPosition( (Terrarum.WIDTH - notifier.UI.width) / 2, Terrarum.HEIGHT - notifier.UI.height) // set smooth lighting as in config KeyToggler.forceSet(KEY_LIGHTMAP_SMOOTH, Terrarum.getConfigBoolean("smoothlighting")) // queue up game UIs // lesser UIs // quick bar uiAliases[UI_QUICK_BAR] = UIHandler(UIQuickBar()) uiAliases[UI_QUICK_BAR]!!.isVisible = true uiAliases[UI_QUICK_BAR]!!.setPosition(0, 0) uiAliases[UI_QUICK_BAR]!!.UI.handler = uiAliases[UI_QUICK_BAR] uiContainer.add(uiAliases[UI_QUICK_BAR]!!) // pie menu uiAliases[UI_PIE_MENU] = UIHandler(UIPieMenu()) uiAliases[UI_PIE_MENU]!!.setPosition( (Terrarum.WIDTH - uiAliases[UI_PIE_MENU]!!.UI.width) / 2, (Terrarum.HEIGHT - uiAliases[UI_PIE_MENU]!!.UI.height) / 2 ) uiAliases[UI_PIE_MENU]!!.UI.handler = uiAliases[UI_PIE_MENU] uiContainer.add(uiAliases[UI_PIE_MENU]!!) // audio test //AudioResourceLibrary.ambientsWoods[0].play() } override fun update(gc: GameContainer, sbg: StateBasedGame, delta: Int) { UPDATE_DELTA = delta setAppTitle() /////////////////////////// // world-related updates // /////////////////////////// world.updateWorldTime(delta) WorldSimulator(world, player, delta) WeatherMixer.update(gc, delta) TileStats.update() if (!(CommandDict["setgl"] as SetGlobalLightOverride).lightOverride) world.globalLight = constructRGBFromInt( WeatherMixer.globalLightNow.redByte, WeatherMixer.globalLightNow.greenByte, WeatherMixer.globalLightNow.blueByte ) /////////////////////////// // input-related updates // /////////////////////////// GameController.processInput(gc.input) uiContainer.forEach { it.processInput(gc.input) } //////////////////////////// // camera-related updates // //////////////////////////// MapDrawer.update(gc, delta) MapCamera.update(gc, delta) /////////////////////////// // actor-related updates // /////////////////////////// // determine whether the inactive actor should be re-active wakeDormantActors() // determine whether the actor should be active or dormant KillOrKnockdownActors() updateActors(gc, delta) // TODO thread pool(?) CollisionSolver.process() //////////////////////// // ui-related updates // //////////////////////// uiContainer.forEach { ui -> ui.update(gc, delta) } consoleHandler.update(gc, delta) debugWindow.update(gc, delta) notifier.update(gc, delta) ///////////////////////// // app-related updates // ///////////////////////// Terrarum.appgc.setVSync(Terrarum.appgc.fps >= Terrarum.VSYNC_TRIGGER_THRESHOLD) // determine if lightmap blending should be done Terrarum.gameConfig["smoothlighting"] = KeyToggler.isOn(KEY_LIGHTMAP_SMOOTH) } private fun setAppTitle() { Terrarum.appgc.setTitle( "${Terrarum.NAME}" + " — F: ${Terrarum.appgc.fps} (${Terrarum.TARGET_INTERNAL_FPS})" + " — M: ${Terrarum.memInUse}M / ${Terrarum.totalVMMem}M") } override fun render(gc: GameContainer, sbg: StateBasedGame, g: Graphics) { blendNormal() drawSkybox(g) // make camara work // // compensate for zoom. UIs have to be treated specially! (see UIHandler) g.translate(-MapCamera.cameraX * screenZoom, -MapCamera.cameraY * screenZoom) ///////////////////////////// // draw map related stuffs // ///////////////////////////// MapCamera.renderBehind(gc, g) ///////////////// // draw actors // ///////////////// actorContainer.forEach { actor -> if (actor is Visible && actor.inScreen() && actor !is Player) { // if echo and within screen actor.drawBody(gc, g) } } player.drawBody(gc, g) ///////////////////////////// // draw map related stuffs // ///////////////////////////// LightmapRenderer.renderLightMap() MapCamera.renderFront(gc, g) MapDrawer.render(gc, g) blendMul() MapDrawer.drawEnvOverlay(g) if (!KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) blendMul() else blendNormal() LightmapRenderer.draw(g) blendNormal() ////////////////////// // draw actor glows // ////////////////////// actorContainer.forEach { actor -> if (actor is Visible && actor.inScreen() && actor !is Player) { // if echo and within screen actor.drawGlow(gc, g) } } player.drawGlow(gc, g) //////////////////////// // debug informations // //////////////////////// // draw reference ID if debugWindow is open if (debugWindow.isVisible) { actorContainer.forEachIndexed { i, actor -> if (actor is Visible) { g.color = Color.white g.font = Terrarum.fontSmallNumbers g.drawString( actor.referenceID.toString(), actor.hitbox.posX.toFloat(), actor.hitbox.pointedY.toFloat() + 4 ) if (DEBUG_ARRAY) { g.color = GameFontBase.codeToCol["g"] g.drawString( i.toString(), actor.hitbox.posX.toFloat(), actor.hitbox.pointedY.toFloat() + 4 + 10 ) } } } } // fluidmap debug if (KeyToggler.isOn(Key.F4)) WorldSimulator.drawFluidMapDebug(player, g) ////////////// // draw UIs // ////////////// uiContainer.forEach { ui -> ui.render(gc, sbg, g) } debugWindow.render(gc, sbg, g) consoleHandler.render(gc, sbg, g) notifier.render(gc, sbg, g) } override fun keyPressed(key: Int, c: Char) { GameController.keyPressed(key, c) if (Terrarum.getConfigIntArray("keyquickselalt").contains(key) || key == Terrarum.getConfigInt("keyquicksel")) { uiAliases[UI_PIE_MENU]!!.setAsOpen() uiAliases[UI_QUICK_BAR]!!.setAsClose() } uiContainer.forEach { it.keyPressed(key, c) } // for KeyboardControlled UIcanvases } override fun keyReleased(key: Int, c: Char) { GameController.keyReleased(key, c) if (Terrarum.getConfigIntArray("keyquickselalt").contains(key) || key == Terrarum.getConfigInt("keyquicksel")) { uiAliases[UI_PIE_MENU]!!.setAsClose() uiAliases[UI_QUICK_BAR]!!.setAsOpen() } uiContainer.forEach { it.keyReleased(key, c) } // for KeyboardControlled UIcanvases } override fun mouseMoved(oldx: Int, oldy: Int, newx: Int, newy: Int) { GameController.mouseMoved(oldx, oldy, newx, newy) uiContainer.forEach { it.mouseMoved(oldx, oldy, newx, newy) } // for MouseControlled UIcanvases } override fun mouseDragged(oldx: Int, oldy: Int, newx: Int, newy: Int) { GameController.mouseDragged(oldx, oldy, newx, newy) uiContainer.forEach { it.mouseDragged(oldx, oldy, newx, newy) } // for MouseControlled UIcanvases } override fun mousePressed(button: Int, x: Int, y: Int) { GameController.mousePressed(button, x, y) uiContainer.forEach { it.mousePressed(button, x, y) } // for MouseControlled UIcanvases } override fun mouseReleased(button: Int, x: Int, y: Int) { GameController.mouseReleased(button, x, y) uiContainer.forEach { it.mouseReleased(button, x, y) } // for MouseControlled UIcanvases } override fun mouseWheelMoved(change: Int) { GameController.mouseWheelMoved(change) uiContainer.forEach { it.mouseWheelMoved(change) } // for MouseControlled UIcanvases } override fun controllerButtonPressed(controller: Int, button: Int) { GameController.controllerButtonPressed(controller, button) uiContainer.forEach { it.controllerButtonPressed(controller, button) } // for GamepadControlled UIcanvases } override fun controllerButtonReleased(controller: Int, button: Int) { GameController.controllerButtonReleased(controller, button) uiContainer.forEach { it.controllerButtonReleased(controller, button) } // for GamepadControlled UIcanvases } override fun getID(): Int = Terrarum.STATE_ID_GAME private fun drawSkybox(g: Graphics) = WeatherMixer.render(g) /** Send message to notifier UI and toggle the UI as opened. */ fun sendNotification(msg: Array) { (notifier.UI as Notification).sendNotification(msg) } fun wakeDormantActors() { var actorContainerSize = actorContainerInactive.size var i = 0 while (i < actorContainerSize) { // loop through actorContainerInactive val actor = actorContainerInactive[i] val actorIndex = i if (actor is Visible && actor.inUpdateRange()) { addActor(actor) // duplicates are checked here actorContainerInactive.removeAt(actorIndex) actorContainerSize -= 1 i-- // array removed 1 elem, so we also decrement counter by 1 } i++ } } /** * determine whether the actor should be active or dormant by its distance from the player. * If the actor must be dormant, the target actor will be put to the list specifically for them. * if the actor is not to be dormant, it will be just ignored. */ fun KillOrKnockdownActors() { var actorContainerSize = actorContainer.size var i = 0 while (i < actorContainerSize) { // loop through actorContainer val actor = actorContainer[i] val actorIndex = i // kill actors flagged to despawn if (actor.flagDespawn) { actorContainer.removeAt(actorIndex) actorContainerSize -= 1 i-- // array removed 1 elem, so we also decrement counter by 1 } // inactivate distant actors else if (actor is Visible && !actor.inUpdateRange()) { if (actor !is Projectile) { // if it's a projectile, just kill it. actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated } actorContainer.removeAt(actorIndex) actorContainerSize -= 1 i-- // array removed 1 elem, so we also decrement counter by 1 } i++ } } /** * Update actors concurrently. * * NOTE: concurrency for actor updating is currently disabled because of it's poor performance */ fun updateActors(gc: GameContainer, delta: Int) { if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD) { val actors = actorContainer.size.toFloat() // set up indices for (i in 0..ThreadPool.POOL_SIZE - 1) { ThreadPool.map( i, ThreadActorUpdate( ((actors / CORES) * i).toInt(), ((actors / CORES) * i.plus(1)).toInt() - 1, gc, delta ), "ActorUpdate" ) } ThreadPool.startAll() } else { actorContainer.forEach { it.update(gc, delta) } } } fun Double.sqr() = this * this fun Int.sqr() = this * this private fun distToActorSqr(a: Visible, p: Player): Double = (a.hitbox.centeredX - p.hitbox.centeredX).sqr() + (a.hitbox.centeredY - p.hitbox.centeredY).sqr() /** whether the actor is within screen */ private fun Visible.inScreen() = distToActorSqr(this, player) <= (Terrarum.WIDTH.plus(this.hitbox.width.div(2)).times(1 / Terrarum.ingame.screenZoom).sqr() + Terrarum.HEIGHT.plus(this.hitbox.height.div(2)).times(1 / Terrarum.ingame.screenZoom).sqr()) /** whether the actor is within update range */ private fun Visible.inUpdateRange() = distToActorSqr(this, player) <= ACTOR_UPDATE_RANGE.sqr() /** * actorContainer extensions */ fun hasActor(actor: Actor) = hasActor(actor.referenceID) fun hasActor(ID: Int): Boolean = if (actorContainer.size == 0) false else actorContainer.binarySearch(ID) >= 0 fun removeActor(actor: Actor) = removeActor(actor.referenceID) /** * get index of the actor and delete by the index. * we can do this as the list is guaranteed to be sorted * and only contains unique values. * * Any values behind the index will be automatically pushed to front. * This is how remove function of [java.util.ArrayList] is defined. */ fun removeActor(ID: Int) { if (ID == player.referenceID) throw RuntimeException("Attempted to remove player.") val indexToDelete = actorContainer.binarySearch(ID) if (indexToDelete >= 0) actorContainer.removeAt(indexToDelete) } /** * Check for duplicates, append actor and sort the list */ fun addActor(actor: Actor) { if (hasActor(actor.referenceID)) throw RuntimeException("Actor with ID ${actor.referenceID} already exists.") actorContainer.add(actor) insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor } /** * Whether the game should display actorContainer elem number when F3 is on */ val DEBUG_ARRAY = false fun getActorByID(ID: Int): Actor { if (actorContainer.size == 0) throw IllegalArgumentException("Actor with ID $ID does not exist.") val index = actorContainer.binarySearch(ID) if (index < 0) throw IllegalArgumentException("Actor with ID $ID does not exist.") else return actorContainer[index] } private fun insertionSortLastElem(arr: ArrayList) { var j: Int val index: Int = arr.size - 1 val x = arr[index] j = index - 1 while (j > 0 && arr[j] > x) { arr[j + 1] = arr[j] j -= 1 } arr[j + 1] = x } private fun ArrayList.binarySearch(actor: Actor) = this.binarySearch(actor.referenceID) private fun ArrayList.binarySearch(ID: Int): Int { // code from collections/Collections.kt var low = 0 var high = actorContainer.size - 1 while (low <= high) { val mid = (low + high).ushr(1) // safe from overflows val midVal = get(mid) if (ID > midVal.referenceID) low = mid + 1 else if (ID < midVal.referenceID) high = mid - 1 else return mid // key found } return -(low + 1) // key not found } } fun Color.toInt() = redByte.shl(16) or greenByte.shl(8) or blueByte