Files
Terrarum/src/net/torvald/terrarum/IngameInstance.kt
2020-11-25 13:39:17 +09:00

271 lines
8.7 KiB
Kotlin

package net.torvald.terrarum
import com.badlogic.gdx.Screen
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Queue
import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.ui.ConsoleWindow
import net.torvald.util.SortedArrayList
import java.util.concurrent.locks.Lock
/**
* Although the game (as product) can have infinitely many stages/planets/etc., those stages must be manually managed by YOU;
* this instance only stores the stage that is currently being used.
*/
open class IngameInstance(val batch: SpriteBatch) : Screen {
var screenZoom = 1.0f
val ZOOM_MAXIMUM = 4.0f
val ZOOM_MINIMUM = 1.0f
open var consoleHandler: ConsoleWindow = ConsoleWindow()
var paused: Boolean = false
val consoleOpened: Boolean
get() = consoleHandler.isOpened || consoleHandler.isOpening
init {
consoleHandler.setPosition(0, 0)
printdbg(this, "New ingame instance ${this.hashCode()}, called from")
printStackTrace(this)
}
open var world: GameWorld = GameWorld.makeNullWorld()
set(value) {
printdbg(this, "Ingame instance ${this.hashCode()}, accepting new world ${value.layerTerrain}; called from")
printStackTrace(this)
field = value
}
/** how many different planets/stages/etc. are thenre. Whole stages must be manually managed by YOU. */
var gameworldCount = 0
/** The actor the game is currently allowing you to control.
*
* Most of the time it'd be the "player", but think about the case where you have possessed
* some random actor of the game. Now that actor is now actorNowPlaying, the actual gamer's avatar
* (reference ID of 0x91A7E2) (must) stay in the actorContainerActive, but it's not a actorNowPlaying.
*
* Nullability of this property is believed to be unavoidable (trust me!). I'm sorry for the inconvenience.
*/
open var actorNowPlaying: ActorHumanoid? = null
/**
* The actual gamer
*/
val actorGamer: ActorHumanoid
get() = getActorByID(Terrarum.PLAYER_REF_ID) as ActorHumanoid
open var gameInitialised = false
internal set
open var gameFullyLoaded = false
internal set
val ACTORCONTAINER_INITIAL_SIZE = 64
val actorContainerActive = SortedArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val actorContainerInactive = SortedArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
protected val terrainChangeQueue = Queue<BlockChangeQueueItem>()
protected val wallChangeQueue = Queue<BlockChangeQueueItem>()
protected val wireChangeQueue = Queue<BlockChangeQueueItem>()
override fun hide() {
}
override fun show() {
// the very basic show() implementation
gameInitialised = true
}
override fun render(updateRate: Float) {
}
override fun pause() {
}
override fun resume() {
}
override fun resize(width: Int, height: Int) {
}
/**
* You ABSOLUTELY must call this in your child classes (```super.dispose()```) and the AppLoader to properly
* dispose of the world, which uses unsafe memory allocation.
* Failing to do this will result to a memory leak!
*/
override fun dispose() {
printdbg(this, "Thank you for properly disposing the world!")
printdbg(this, "dispose called by")
printStackTrace(this)
world.dispose()
}
////////////
// EVENTS //
////////////
/**
* Event for triggering held item's `startPrimaryUse(Float)`
*/
open fun worldPrimaryClickStart(delta: Float) {
}
/**
* Event for triggering held item's `endPrimaryUse(Float)`
*/
open fun worldPrimaryClickEnd(delta: Float) {
}
/**
* I have decided that left and right clicks must do the same thing, so no secondary use from now on. --Torvald on 2019-05-26
*
* Event for triggering held item's `startSecondaryUse(Float)`
*/
//open fun worldSecondaryClickStart(delta: Float) { }
/**
* I have decided that left and right clicks must do the same thing, so no secondary use from now on. --Torvald on 2019-05-26
*
* Event for triggering held item's `endSecondaryUse(Float)`
*/
//open fun worldSecondaryClickEnd(delta: Float) { }
/**
* Event for triggering fixture update when something is placed/removed on the world.
* Normally only called by GameWorld.setTileTerrain
*
* Queueing schema is used to make sure things are synchronised.
*/
open fun queueTerrainChangedEvent(old: Int, new: Int, position: Long) {
val (x, y) = LandUtil.resolveBlockAddr(world, position)
terrainChangeQueue.addFirst(BlockChangeQueueItem(old, new, x, y))
}
/**
* Wall version of terrainChanged() event
*/
open fun queueWallChangedEvent(old: Int, new: Int, position: Long) {
val (x, y) = LandUtil.resolveBlockAddr(world, position)
wallChangeQueue.addFirst(BlockChangeQueueItem(old, new, x, y))
}
/**
* Wire version of terrainChanged() event
*
* @param old previous settings of conduits in bit set format.
* @param new current settings of conduits in bit set format.
*/
open fun queueWireChangedEvent(old: Int, new: Int, position: Long) {
val (x, y) = LandUtil.resolveBlockAddr(world, position)
wireChangeQueue.addFirst(BlockChangeQueueItem(old, new, x, y))
}
///////////////////////
// UTILITY FUNCTIONS //
///////////////////////
fun getActorByID(ID: Int): Actor {
if (actorContainerActive.size == 0 && actorContainerInactive.size == 0)
throw IllegalArgumentException("Actor with ID $ID does not exist.")
var actor = actorContainerActive.searchFor(ID) { it.referenceID!! }
if (actor == null) {
actor = actorContainerInactive.searchFor(ID) { it.referenceID!! }
if (actor == null) {
/*JOptionPane.showMessageDialog(
null,
"Actor with ID $ID does not exist.",
null, JOptionPane.ERROR_MESSAGE
)*/
throw IllegalArgumentException("Actor with ID $ID does not exist.")
}
else
return actor
}
else
return actor
}
//fun SortedArrayList<*>.binarySearch(actor: Actor) = this.toArrayList().binarySearch(actor.referenceID!!)
//fun SortedArrayList<*>.binarySearch(ID: Int) = this.toArrayList().binarySearch(ID)
open fun removeActor(ID: Int) = removeActor(getActorByID(ID))
/**
* 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.
*/
open fun removeActor(actor: Actor?) {
if (actor == null) return
val indexToDelete = actorContainerActive.searchFor(actor.referenceID!!) { it.referenceID!! }
if (indexToDelete != null) {
actorContainerActive.remove(indexToDelete)
}
}
/**
* Check for duplicates, append actor and sort the list
*/
open fun addNewActor(actor: Actor?) {
if (actor == null) return
if (theGameHasActor(actor.referenceID!!)) {
throw Error("The actor $actor already exists in the game")
}
else {
actorContainerActive.add(actor)
}
}
fun isActive(ID: Int): Boolean =
if (actorContainerActive.size == 0)
false
else
actorContainerActive.searchFor(ID) { it.referenceID!! } != null
fun isInactive(ID: Int): Boolean =
if (actorContainerInactive.size == 0)
false
else
actorContainerInactive.searchFor(ID) { it.referenceID!! } != null
/**
* actorContainerActive extensions
*/
fun theGameHasActor(actor: Actor?) = if (actor == null) false else theGameHasActor(actor.referenceID!!)
fun theGameHasActor(ID: Int): Boolean =
isActive(ID) || isInactive(ID)
data class BlockChangeQueueItem(val old: Int, val new: Int, val posX: Int, val posY: Int)
open fun sendNotification(messages: Array<String>) {}
open fun sendNotification(messages: List<String>) {}
open fun sendNotification(singleMessage: String) {}
}
inline fun Lock.lock(body: () -> Unit) {
this.lock()
try {
body()
}
finally {
this.unlock()
}
}