Files
Terrarum/src/net/torvald/terrarum/StateInGame.kt
Song Minjae 433f27bef2 proper error handling in ROMBASIC
Former-commit-id: b5bd084e6807c765cdd6d3ffff1b1628321b9c6a
Former-commit-id: 55c3bb3cd56c7867809c0819f178aeebf1e46676
2016-09-22 18:16:38 +09:00

575 lines
20 KiB
Kotlin

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<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val uiContainer = LinkedList<UIHandler>()
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<String, UIHandler>()
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<String>) {
(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<Actor>) {
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<Actor>.binarySearch(actor: Actor) = this.binarySearch(actor.referenceID)
private fun ArrayList<Actor>.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