mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
963 lines
34 KiB
Kotlin
963 lines
34 KiB
Kotlin
package net.torvald.terrarum.modulebasegame
|
|
|
|
import com.badlogic.gdx.Gdx
|
|
import com.badlogic.gdx.controllers.Controllers
|
|
import com.badlogic.gdx.graphics.Camera
|
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
|
import net.torvald.dataclass.CircularArray
|
|
import net.torvald.terrarum.*
|
|
import net.torvald.terrarum.AppLoader.printdbg
|
|
import net.torvald.terrarum.blockproperties.BlockPropUtil
|
|
import net.torvald.terrarum.blockstats.BlockStats
|
|
import net.torvald.terrarum.concurrent.ThreadParallel
|
|
import net.torvald.terrarum.console.Authenticator
|
|
import net.torvald.terrarum.gameactors.Actor
|
|
import net.torvald.terrarum.gameactors.ActorWithBody
|
|
import net.torvald.terrarum.gamecontroller.IngameController
|
|
import net.torvald.terrarum.gameworld.GameWorld
|
|
import net.torvald.terrarum.itemproperties.GameItem
|
|
import net.torvald.terrarum.modulebasegame.console.AVTracker
|
|
import net.torvald.terrarum.modulebasegame.console.ActorsList
|
|
import net.torvald.terrarum.modulebasegame.gameactors.*
|
|
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
|
|
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
|
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
|
|
import net.torvald.terrarum.modulebasegame.ui.*
|
|
import net.torvald.terrarum.modulebasegame.weather.WeatherMixer
|
|
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
|
|
import net.torvald.terrarum.modulebasegame.worldgenerator.WorldGenerator
|
|
import net.torvald.terrarum.ui.ConsoleWindow
|
|
import net.torvald.terrarum.ui.UICanvas
|
|
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
|
|
import net.torvald.terrarum.worlddrawer.LightmapRenderer
|
|
import net.torvald.terrarum.worlddrawer.WorldCamera
|
|
import java.util.*
|
|
import java.util.concurrent.locks.ReentrantLock
|
|
|
|
|
|
/**
|
|
* Created by minjaesong on 2017-06-16.
|
|
*/
|
|
|
|
open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
|
|
|
private val ACTOR_UPDATE_RANGE = 4096
|
|
|
|
var historicalFigureIDBucket: ArrayList<Int> = ArrayList<Int>()
|
|
|
|
|
|
/**
|
|
* list of Actors that is sorted by Actors' referenceID
|
|
*/
|
|
//val ACTORCONTAINER_INITIAL_SIZE = 64
|
|
val PARTICLES_MAX = AppLoader.getConfigInt("maxparticles")
|
|
//val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
|
//val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
|
|
val particlesContainer = CircularArray<ParticleBase>(PARTICLES_MAX)
|
|
val uiContainer = ArrayList<UICanvas>()
|
|
|
|
private val actorsRenderBehind = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
|
|
private val actorsRenderMiddle = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
|
|
private val actorsRenderMidTop = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
|
|
private val actorsRenderFront = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
|
|
private val actorsRenderOverlay= ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
|
|
|
|
private var visibleActorsRenderBehind: List<ActorWithBody> = ArrayList(1)
|
|
private var visibleActorsRenderMiddle: List<ActorWithBody> = ArrayList(1)
|
|
private var visibleActorsRenderMidTop: List<ActorWithBody> = ArrayList(1)
|
|
private var visibleActorsRenderFront: List<ActorWithBody> = ArrayList(1)
|
|
private var visibleActorsRenderOverlay: List<ActorWithBody> = ArrayList(1)
|
|
|
|
//var screenZoom = 1.0f // definition moved to IngameInstance
|
|
//val ZOOM_MAXIMUM = 4.0f // definition moved to IngameInstance
|
|
//val ZOOM_MINIMUM = 0.5f // definition moved to IngameInstance
|
|
|
|
companion object {
|
|
/** Sets camera position so that (0,0) would be top-left of the screen, (width, height) be bottom-right. */
|
|
fun setCameraPosition(batch: SpriteBatch, camera: Camera, newX: Float, newY: Float) {
|
|
camera.position.set((-newX + Terrarum.HALFW).round(), (-newY + Terrarum.HALFH).round(), 0f)
|
|
camera.update()
|
|
batch.projectionMatrix = camera.combined
|
|
}
|
|
|
|
fun getCanonicalTitle() = AppLoader.GAME_NAME +
|
|
" — F: ${Gdx.graphics.framesPerSecond}" +
|
|
if (AppLoader.IS_DEVELOPMENT_BUILD)
|
|
" (ΔF${Terrarum.updateRateStr})" +
|
|
" — M: J${Terrarum.memJavaHeap}M / N${Terrarum.memNativeHeap}M / X${Terrarum.memXmx}M"
|
|
else
|
|
""
|
|
}
|
|
|
|
|
|
|
|
init {
|
|
}
|
|
|
|
|
|
|
|
lateinit var notifier: UICanvas
|
|
|
|
lateinit var uiPieMenu: UICanvas
|
|
lateinit var uiQuickBar: UICanvas
|
|
lateinit var uiInventoryPlayer: UICanvas
|
|
lateinit var uiInventoryContainer: UICanvas
|
|
lateinit var uiVitalPrimary: UICanvas
|
|
lateinit var uiVitalSecondary: UICanvas
|
|
lateinit var uiVitalItem: UICanvas // itemcount/durability of held block or active ammo of held gun. As for the block, max value is 500.
|
|
|
|
private lateinit var uiWatchBasic: UICanvas
|
|
private lateinit var uiWatchTierOne: UICanvas
|
|
|
|
private lateinit var uiTooltip: UITooltip
|
|
|
|
lateinit var uiCheatMotherfuckerNootNoot: UICheatDetected
|
|
|
|
// UI aliases
|
|
lateinit var uiAliases: ArrayList<UICanvas>
|
|
private set
|
|
lateinit var uiAliasesPausing: ArrayList<UICanvas>
|
|
private set
|
|
|
|
inline val paused: Boolean
|
|
get() = uiAliasesPausing.map { if (it.isOpened) return true else 0 }.isEmpty() // isEmpty is always false, which we want
|
|
/**
|
|
* Set to false if UI is opened; set to true if UI is closed.
|
|
*/
|
|
inline val canPlayerControl: Boolean
|
|
get() = !paused // FIXME temporary behab (block movement if the game is paused or paused by UIs)
|
|
|
|
var particlesActive = 0
|
|
private set
|
|
|
|
|
|
|
|
private lateinit var ingameUpdateThread: ThreadIngameUpdate
|
|
private lateinit var updateThreadWrapper: Thread
|
|
//private val ingameDrawThread: ThreadIngameDraw // draw must be on the main thread
|
|
|
|
|
|
override var gameInitialised = false
|
|
internal set
|
|
override var gameFullyLoaded = false
|
|
internal set
|
|
|
|
|
|
private val TILE_SIZEF = FeaturesDrawer.TILE_SIZE.toFloat()
|
|
|
|
//////////////
|
|
// GDX code //
|
|
//////////////
|
|
|
|
|
|
lateinit var gameLoadMode: GameLoadMode
|
|
lateinit var gameLoadInfoPayload: Any
|
|
lateinit var gameworld: GameWorldExtension
|
|
lateinit var theRealGamer: IngamePlayer
|
|
|
|
enum class GameLoadMode {
|
|
CREATE_NEW, LOAD_FROM
|
|
}
|
|
|
|
override fun show() {
|
|
//initViewPort(Terrarum.WIDTH, Terrarum.HEIGHT)
|
|
|
|
// gameLoadMode and gameLoadInfoPayload must be set beforehand!!
|
|
|
|
when (gameLoadMode) {
|
|
GameLoadMode.CREATE_NEW -> enter(gameLoadInfoPayload as NewWorldParameters)
|
|
GameLoadMode.LOAD_FROM -> enter(gameLoadInfoPayload as GameSaveData)
|
|
}
|
|
|
|
//LightmapRenderer.world = this.world
|
|
//BlocksDrawer.world = this.world
|
|
FeaturesDrawer.world = this.world
|
|
|
|
|
|
super.show() // gameInitialised = true
|
|
}
|
|
|
|
data class GameSaveData(
|
|
val world: GameWorldExtension,
|
|
val historicalFigureIDBucket: ArrayList<Int>,
|
|
val realGamePlayer: IngamePlayer,
|
|
val rogueS0: Long,
|
|
val rogueS1: Long,
|
|
val weatherS0: Long,
|
|
val weatherS1: Long
|
|
)
|
|
|
|
data class NewWorldParameters(
|
|
val width: Int,
|
|
val height: Int,
|
|
val worldGenSeed: Long
|
|
// other worldgen options
|
|
)
|
|
|
|
private fun setTheRealGamerFirstTime(actor: IngamePlayer) {
|
|
actorNowPlaying = actor
|
|
theRealGamer = actor
|
|
addNewActor(actorNowPlaying)
|
|
}
|
|
|
|
/**
|
|
* Init instance by loading saved world
|
|
*/
|
|
private fun enter(gameSaveData: GameSaveData) {
|
|
if (gameInitialised) {
|
|
printdbg(this, "loaded successfully.")
|
|
}
|
|
else {
|
|
LoadScreen.addMessage("Loading world from save")
|
|
|
|
|
|
gameworld = gameSaveData.world
|
|
world = gameworld
|
|
historicalFigureIDBucket = gameSaveData.historicalFigureIDBucket
|
|
setTheRealGamerFirstTime(gameSaveData.realGamePlayer)
|
|
|
|
|
|
// set the randomisers right
|
|
RoguelikeRandomiser.loadFromSave(gameSaveData.rogueS0, gameSaveData.rogueS1)
|
|
WeatherMixer.loadFromSave(gameSaveData.weatherS0, gameSaveData.weatherS1)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Init instance by creating new world
|
|
*/
|
|
private fun enter(worldParams: NewWorldParameters) {
|
|
printdbg(this, "Ingame called")
|
|
printStackTrace()
|
|
|
|
if (gameInitialised) {
|
|
printdbg(this, "loaded successfully.")
|
|
}
|
|
else {
|
|
LoadScreen.addMessage("${Terrarum.NAME} version ${AppLoader.getVERSION_STRING()}")
|
|
LoadScreen.addMessage("Creating new world")
|
|
|
|
|
|
// init map as chosen size
|
|
val timeNow = System.currentTimeMillis() / 1000
|
|
gameworld = GameWorldExtension(1, worldParams.width, worldParams.height, timeNow, timeNow, 0) // new game, so the creation time is right now
|
|
gameworldCount++
|
|
world = gameworld as GameWorld
|
|
|
|
// generate terrain for the map
|
|
WorldGenerator.attachMap(world)
|
|
WorldGenerator.SEED = worldParams.worldGenSeed
|
|
WorldGenerator.generateMap()
|
|
|
|
|
|
historicalFigureIDBucket = ArrayList<Int>()
|
|
}
|
|
}
|
|
|
|
val ingameController = IngameController(this)
|
|
|
|
/** Load rest of the game with GL context */
|
|
fun postInit() {
|
|
//setTheRealGamerFirstTime(PlayerBuilderSigrid())
|
|
setTheRealGamerFirstTime(PlayerBuilderTestSubject1())
|
|
|
|
|
|
|
|
MegaRainGovernor // invoke MegaRain Governor
|
|
|
|
|
|
|
|
// make controls work
|
|
Gdx.input.inputProcessor = ingameController
|
|
Controllers.addListener(ingameController)
|
|
|
|
|
|
|
|
// init console window
|
|
consoleHandler = ConsoleWindow()
|
|
consoleHandler.setPosition(0, 0)
|
|
|
|
|
|
// init notifier
|
|
notifier = Notification()
|
|
notifier.setPosition(
|
|
(Terrarum.WIDTH - notifier.width) / 2,
|
|
Terrarum.HEIGHT - notifier.height - AppLoader.getTvSafeGraphicsHeight()
|
|
)
|
|
|
|
|
|
|
|
|
|
// >- queue up game UIs that should pause the world -<
|
|
uiInventoryPlayer = UIInventoryFull(actorNowPlaying!!,
|
|
toggleKeyLiteral = AppLoader.getConfigInt("keyinventory")
|
|
)
|
|
uiInventoryPlayer.setPosition(0, 0)
|
|
|
|
// >- lesser UIs -<
|
|
// quick bar
|
|
uiQuickBar = UIQuickslotBar()
|
|
uiQuickBar.isVisible = true
|
|
uiQuickBar.setPosition((Terrarum.WIDTH - uiQuickBar.width) / 2, AppLoader.getTvSafeGraphicsHeight())
|
|
|
|
// pie menu
|
|
uiPieMenu = uiQuickslotPie()
|
|
uiPieMenu.setPosition(Terrarum.HALFW, Terrarum.HALFH)
|
|
|
|
// vital metre
|
|
// fill in getter functions by
|
|
// (uiAliases[UI_QUICK_BAR]!!.UI as UIVitalMetre).vitalGetterMax = { some_function }
|
|
//uiVitalPrimary = UIVitalMetre(player, { 80f }, { 100f }, Color.red, 2, customPositioning = true)
|
|
//uiVitalPrimary.setAsAlwaysVisible()
|
|
//uiVitalSecondary = UIVitalMetre(player, { 73f }, { 100f }, Color(0x00dfff), 1) customPositioning = true)
|
|
//uiVitalSecondary.setAsAlwaysVisible()
|
|
//uiVitalItem = UIVitalMetre(player, { null }, { null }, Color(0xffcc00), 0, customPositioning = true)
|
|
//uiVitalItem.setAsAlwaysVisible()
|
|
|
|
// basic watch-style notification bar (temperature, new mail)
|
|
uiWatchBasic = UIBasicNotifier(actorNowPlaying)
|
|
uiWatchBasic.setAsAlwaysVisible()
|
|
uiWatchBasic.setPosition(Terrarum.WIDTH - uiWatchBasic.width, 0)
|
|
|
|
uiWatchTierOne = UITierOneWatch(actorNowPlaying)
|
|
uiWatchTierOne.setAsAlwaysVisible()
|
|
uiWatchTierOne.setPosition(
|
|
((Terrarum.WIDTH - AppLoader.getTvSafeActionWidth()) - (uiQuickBar.posX + uiQuickBar.width) - uiWatchTierOne.width) / 2 + (uiQuickBar.posX + uiQuickBar.width),
|
|
AppLoader.getTvSafeGraphicsHeight() + 8
|
|
)
|
|
|
|
|
|
uiTooltip = UITooltip()
|
|
|
|
|
|
uiCheatMotherfuckerNootNoot = UICheatDetected()
|
|
|
|
|
|
// batch-process uiAliases
|
|
uiAliases = arrayListOf(
|
|
// drawn first
|
|
//uiVitalPrimary,
|
|
//uiVitalSecondary,
|
|
//uiVitalItem,
|
|
|
|
uiPieMenu,
|
|
uiQuickBar,
|
|
uiWatchBasic,
|
|
uiWatchTierOne,
|
|
uiTooltip
|
|
// drawn last
|
|
)
|
|
uiAliasesPausing = arrayListOf(
|
|
uiInventoryPlayer,
|
|
//uiInventoryContainer,
|
|
consoleHandler,
|
|
uiCheatMotherfuckerNootNoot
|
|
)
|
|
uiAliasesPausing.forEach { addUI(it) } // put them all to the UIContainer
|
|
uiAliases.forEach { addUI(it) } // put them all to the UIContainer
|
|
|
|
|
|
|
|
ingameUpdateThread = ThreadIngameUpdate(this)
|
|
updateThreadWrapper = Thread(ingameUpdateThread, "Terrarum UpdateThread")
|
|
|
|
|
|
|
|
// these need to appear on top of any others
|
|
uiContainer.add(notifier)
|
|
|
|
|
|
LightmapRenderer.fireRecalculateEvent()
|
|
|
|
|
|
AppLoader.setDebugTime("Ingame.updateCounter", 0)
|
|
|
|
|
|
|
|
// some sketchy test code here
|
|
|
|
|
|
|
|
}// END enter
|
|
|
|
override fun worldPrimaryClickStart(delta: Float) {
|
|
val itemOnGrip = actorNowPlaying?.inventory?.itemEquipped?.get(GameItem.EquipPosition.HAND_GRIP)
|
|
itemOnGrip?.startPrimaryUse(delta)
|
|
}
|
|
|
|
override fun worldPrimaryClickEnd(delta: Float) {
|
|
val itemOnGrip = actorNowPlaying?.inventory?.itemEquipped?.get(GameItem.EquipPosition.HAND_GRIP)
|
|
itemOnGrip?.endPrimaryUse(delta)
|
|
}
|
|
|
|
override fun worldSecondaryClickStart(delta: Float) {
|
|
val itemOnGrip = actorNowPlaying?.inventory?.itemEquipped?.get(GameItem.EquipPosition.HAND_GRIP)
|
|
itemOnGrip?.startSecondaryUse(delta)
|
|
}
|
|
|
|
override fun worldSecondaryClickEnd(delta: Float) {
|
|
val itemOnGrip = actorNowPlaying?.inventory?.itemEquipped?.get(GameItem.EquipPosition.HAND_GRIP)
|
|
itemOnGrip?.endSecondaryUse(delta)
|
|
}
|
|
|
|
|
|
|
|
private var firstTimeRun = true
|
|
|
|
///////////////
|
|
// prod code //
|
|
///////////////
|
|
private class ThreadIngameUpdate(val ingame: Ingame): Runnable {
|
|
override fun run() {
|
|
TODO()
|
|
}
|
|
}
|
|
|
|
private var updateAkku = 0.0
|
|
|
|
override fun render(delta: Float) {
|
|
// Q&D solution for LoadScreen and Ingame, where while LoadScreen is working, Ingame now no longer has GL Context
|
|
// there's still things to load which needs GL context to be present
|
|
if (!gameFullyLoaded) {
|
|
|
|
if (gameLoadMode == GameLoadMode.CREATE_NEW) {
|
|
// go to spawn position
|
|
actorNowPlaying?.setPosition(
|
|
world.spawnX * FeaturesDrawer.TILE_SIZE.toDouble(),
|
|
world.spawnY * FeaturesDrawer.TILE_SIZE.toDouble()
|
|
)
|
|
}
|
|
|
|
postInit()
|
|
|
|
|
|
gameFullyLoaded = true
|
|
}
|
|
|
|
// ASYNCHRONOUS UPDATE AND RENDER //
|
|
|
|
/** UPDATE CODE GOES HERE */
|
|
val dt = Gdx.graphics.deltaTime
|
|
updateAkku += dt
|
|
|
|
var i = 0L
|
|
while (updateAkku >= delta) {
|
|
AppLoader.measureDebugTime("Ingame.update") { updateGame(delta) }
|
|
updateAkku -= delta
|
|
i += 1
|
|
}
|
|
AppLoader.setDebugTime("Ingame.updateCounter", i)
|
|
|
|
|
|
|
|
/** RENDER CODE GOES HERE */
|
|
AppLoader.measureDebugTime("Ingame.render") { renderGame() }
|
|
AppLoader.setDebugTime("Ingame.render-Light",
|
|
(AppLoader.debugTimers["Ingame.render"] as Long) - ((AppLoader.debugTimers["Renderer.LightTotal"] as? Long) ?: 0)
|
|
)
|
|
|
|
}
|
|
|
|
protected fun updateGame(delta: Float) {
|
|
val world = this.world as GameWorldExtension
|
|
|
|
particlesActive = 0
|
|
|
|
|
|
ingameController.update(delta)
|
|
|
|
|
|
if (!paused) {
|
|
|
|
///////////////////////////
|
|
// world-related updates //
|
|
///////////////////////////
|
|
BlockPropUtil.dynamicLumFuncTickClock()
|
|
world.updateWorldTime(delta)
|
|
WorldSimulator.invoke(actorNowPlaying, delta)
|
|
WeatherMixer.update(delta, actorNowPlaying, world)
|
|
BlockStats.update()
|
|
|
|
|
|
|
|
////////////////////////////
|
|
// camera-related updates //
|
|
////////////////////////////
|
|
FeaturesDrawer.update(delta)
|
|
|
|
|
|
///////////////////////////
|
|
// actor-related updates //
|
|
///////////////////////////
|
|
repossessActor()
|
|
|
|
// determine whether the inactive actor should be activated
|
|
wakeDormantActors()
|
|
// determine whether the actor should keep being activated or be dormant
|
|
KillOrKnockdownActors()
|
|
updateActors(delta)
|
|
particlesContainer.forEach { if (!it.flagDespawn) particlesActive++; it.update(delta) }
|
|
// TODO thread pool(?)
|
|
CollisionSolver.process()
|
|
|
|
WorldCamera.update(gameworld, actorNowPlaying)
|
|
|
|
|
|
// completely consume block change queues because why not
|
|
terrainChangeQueue.clear()
|
|
wallChangeQueue.clear()
|
|
wireChangeQueue.clear()
|
|
}
|
|
|
|
|
|
////////////////////////
|
|
// ui-related updates //
|
|
////////////////////////
|
|
uiContainer.forEach { it.update(delta) }
|
|
//debugWindow.update(delta)
|
|
//notifier.update(delta)
|
|
|
|
// update debuggers using javax.swing //
|
|
if (Authenticator.b()) {
|
|
AVTracker.update()
|
|
ActorsList.update()
|
|
}
|
|
}
|
|
|
|
|
|
private fun renderGame() {
|
|
Gdx.graphics.setTitle(getCanonicalTitle())
|
|
|
|
filterVisibleActors()
|
|
|
|
IngameRenderer.invoke(
|
|
world as GameWorldExtension,
|
|
visibleActorsRenderBehind,
|
|
visibleActorsRenderMiddle,
|
|
visibleActorsRenderMidTop,
|
|
visibleActorsRenderFront,
|
|
visibleActorsRenderOverlay,
|
|
particlesContainer,
|
|
actorNowPlaying,
|
|
uiContainer
|
|
)
|
|
}
|
|
|
|
|
|
private fun filterVisibleActors() {
|
|
visibleActorsRenderBehind = actorsRenderBehind.filter { it.inScreen() }
|
|
visibleActorsRenderMiddle = actorsRenderMiddle.filter { it.inScreen() }
|
|
visibleActorsRenderMidTop = actorsRenderMidTop.filter { it.inScreen() }
|
|
visibleActorsRenderFront = actorsRenderFront.filter { it.inScreen() }
|
|
visibleActorsRenderOverlay=actorsRenderOverlay.filter { it.inScreen() }
|
|
}
|
|
|
|
private fun repossessActor() {
|
|
// check if currently pocessed actor is removed from game
|
|
if (!theGameHasActor(actorNowPlaying)) {
|
|
// re-possess canonical player
|
|
if (theGameHasActor(Terrarum.PLAYER_REF_ID))
|
|
changePossession(Terrarum.PLAYER_REF_ID)
|
|
else
|
|
changePossession(0x51621D) // FIXME fallback debug mode (FIXME is there for a reminder visible in ya IDE)
|
|
}
|
|
}
|
|
|
|
private fun changePossession(newActor: ActorHumanoid) {
|
|
if (!theGameHasActor(actorNowPlaying)) {
|
|
throw IllegalArgumentException("No such actor in the game: $newActor")
|
|
}
|
|
|
|
actorNowPlaying = newActor
|
|
//WorldSimulator(actorNowPlaying, AppLoader.getSmoothDelta().toFloat())
|
|
}
|
|
|
|
private fun changePossession(refid: Int) {
|
|
val actorToChange = getActorByID(refid)
|
|
|
|
if (actorToChange !is ActorHumanoid) {
|
|
throw Error("Unpossessable actor $refid: type expected ActorHumanoid, got ${actorToChange.javaClass.canonicalName}")
|
|
}
|
|
|
|
changePossession(getActorByID(refid) as ActorHumanoid)
|
|
}
|
|
|
|
/** Send message to notifier UI and toggle the UI as opened. */
|
|
fun sendNotification(msg: Array<String>) {
|
|
(notifier as Notification).sendNotification(msg)
|
|
}
|
|
|
|
fun wakeDormantActors() {
|
|
var actorContainerSize = actorContainerInactive.size
|
|
var i = 0
|
|
while (i < actorContainerSize) { // loop through actorContainerInactive
|
|
val actor = actorContainerInactive[i]
|
|
if (actor is ActorWithBody && actor.inUpdateRange()) {
|
|
activateDormantActor(actor) // duplicates are checked here
|
|
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) {
|
|
removeActor(actor)
|
|
actorContainerSize -= 1
|
|
i-- // array removed 1 elem, so we also decrement counter by 1
|
|
}
|
|
// inactivate distant actors
|
|
else if (actor is ActorWithBody && !actor.inUpdateRange()) {
|
|
if (actor !is Projectile) { // if it's a projectile, don't inactivate it; 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(delta: Float) {
|
|
if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD && actorContainer.size > Terrarum.THREADS) {
|
|
val actors = actorContainer.size.toFloat()
|
|
// set up indices
|
|
for (i in 0..Terrarum.THREADS - 1) {
|
|
ThreadParallel.map(
|
|
i, "ActorUpdate",
|
|
ThreadActorUpdate(
|
|
actors.div(Terrarum.THREADS).times(i).roundInt(),
|
|
actors.div(Terrarum.THREADS).times(i + 1).roundInt() - 1
|
|
)
|
|
)
|
|
}
|
|
|
|
ThreadParallel.startAll()
|
|
|
|
actorNowPlaying?.update(delta)
|
|
}
|
|
else {
|
|
actorContainer.forEach {
|
|
if (it != actorNowPlaying) {
|
|
it.update(delta)
|
|
|
|
if (it is Pocketed) {
|
|
it.inventory.forEach { inventoryEntry ->
|
|
inventoryEntry.item.effectWhileInPocket(delta)
|
|
if (it.equipped(inventoryEntry.item)) {
|
|
inventoryEntry.item.effectWhenEquipped(delta)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (it is CuedByTerrainChange) {
|
|
terrainChangeQueue.forEach { cue ->
|
|
it.updateForWorldChange(cue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
actorNowPlaying?.update(delta)
|
|
//AmmoMeterProxy(player, uiVitalItem.UI as UIVitalMetre)
|
|
}
|
|
}
|
|
|
|
fun Double.sqr() = this * this
|
|
fun Int.sqr() = this * this
|
|
fun min(vararg d: Double): Double {
|
|
var ret = Double.MAX_VALUE
|
|
d.forEach { if (it < ret) ret = it }
|
|
return ret
|
|
}
|
|
private fun distToActorSqr(a: ActorWithBody, p: ActorWithBody) =
|
|
min(// take min of normal position and wrapped (x < 0) position
|
|
(a.hitbox.centeredX - p.hitbox.centeredX).sqr() +
|
|
(a.hitbox.centeredY - p.hitbox.centeredY).sqr(),
|
|
(a.hitbox.centeredX - p.hitbox.centeredX + world.width * FeaturesDrawer.TILE_SIZE).sqr() +
|
|
(a.hitbox.centeredY - p.hitbox.centeredY).sqr(),
|
|
(a.hitbox.centeredX - p.hitbox.centeredX - world.width * FeaturesDrawer.TILE_SIZE).sqr() +
|
|
(a.hitbox.centeredY - p.hitbox.centeredY).sqr()
|
|
)
|
|
private fun distToCameraSqr(a: ActorWithBody) =
|
|
min(
|
|
(a.hitbox.startX - WorldCamera.x).sqr() +
|
|
(a.hitbox.startY - WorldCamera.y).sqr(),
|
|
(a.hitbox.startX - WorldCamera.x + world.width * FeaturesDrawer.TILE_SIZE).sqr() +
|
|
(a.hitbox.startY - WorldCamera.y).sqr(),
|
|
(a.hitbox.startX - WorldCamera.x - world.width * FeaturesDrawer.TILE_SIZE).sqr() +
|
|
(a.hitbox.startY - WorldCamera.y).sqr()
|
|
)
|
|
|
|
/** whether the actor is within screen */
|
|
private fun ActorWithBody.inScreen() =
|
|
distToCameraSqr(this) <=
|
|
(Terrarum.WIDTH.plus(this.hitbox.width.div(2)).
|
|
times(1 / screenZoom).sqr() +
|
|
Terrarum.HEIGHT.plus(this.hitbox.height.div(2)).
|
|
times(1 / screenZoom).sqr())
|
|
|
|
|
|
/** whether the actor is within update range */
|
|
private fun ActorWithBody.inUpdateRange() = distToCameraSqr(this) <= ACTOR_UPDATE_RANGE.sqr()
|
|
|
|
override 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.
|
|
*/
|
|
override fun removeActor(actor: Actor?) {
|
|
if (actor == null) return
|
|
|
|
if (actor.referenceID == theRealGamer.referenceID || actor.referenceID == 0x51621D) // do not delete this magic
|
|
throw RuntimeException("Attempted to remove player.")
|
|
val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
|
|
if (indexToDelete >= 0) {
|
|
printdbg(this, "Removing actor $actor")
|
|
printStackTrace()
|
|
|
|
actorContainer.removeAt(indexToDelete)
|
|
|
|
// indexToDelete >= 0 means that the actor certainly exists in the game
|
|
// which means we don't need to check if i >= 0 again
|
|
if (actor is ActorWithBody) {
|
|
when (actor.renderOrder) {
|
|
Actor.RenderOrder.BEHIND -> {
|
|
val i = actorsRenderBehind.binarySearch(actor.referenceID!!)
|
|
actorsRenderBehind.removeAt(i)
|
|
}
|
|
Actor.RenderOrder.MIDDLE -> {
|
|
val i = actorsRenderMiddle.binarySearch(actor.referenceID!!)
|
|
actorsRenderMiddle.removeAt(i)
|
|
}
|
|
Actor.RenderOrder.MIDTOP -> {
|
|
val i = actorsRenderMidTop.binarySearch(actor.referenceID!!)
|
|
actorsRenderMidTop.removeAt(i)
|
|
}
|
|
Actor.RenderOrder.FRONT -> {
|
|
val i = actorsRenderFront.binarySearch(actor.referenceID!!)
|
|
actorsRenderFront.removeAt(i)
|
|
}
|
|
Actor.RenderOrder.OVERLAY-> {
|
|
val i = actorsRenderOverlay.binarySearch(actor.referenceID!!)
|
|
actorsRenderFront.removeAt(i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for duplicates, append actor and sort the list
|
|
*/
|
|
override fun addNewActor(actor: Actor?) {
|
|
if (actor == null) return
|
|
|
|
if (theGameHasActor(actor.referenceID!!)) {
|
|
throw Error("The actor $actor already exists in the game")
|
|
}
|
|
else {
|
|
printdbg(this, "Adding actor $actor")
|
|
printStackTrace()
|
|
|
|
actorContainer.add(actor)
|
|
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
|
|
|
|
if (actor is ActorWithBody) {
|
|
when (actor.renderOrder) {
|
|
Actor.RenderOrder.BEHIND -> {
|
|
actorsRenderBehind.add(actor); insertionSortLastElemAV(actorsRenderBehind)
|
|
}
|
|
Actor.RenderOrder.MIDDLE -> {
|
|
actorsRenderMiddle.add(actor); insertionSortLastElemAV(actorsRenderMiddle)
|
|
}
|
|
Actor.RenderOrder.MIDTOP -> {
|
|
actorsRenderMidTop.add(actor); insertionSortLastElemAV(actorsRenderMidTop)
|
|
}
|
|
Actor.RenderOrder.FRONT -> {
|
|
actorsRenderFront.add(actor); insertionSortLastElemAV(actorsRenderFront)
|
|
}
|
|
Actor.RenderOrder.OVERLAY-> {
|
|
actorsRenderOverlay.add(actor); insertionSortLastElemAV(actorsRenderOverlay)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun activateDormantActor(actor: Actor) {
|
|
if (!isInactive(actor.referenceID!!)) {
|
|
if (isActive(actor.referenceID!!))
|
|
throw Error("The actor $actor is already activated")
|
|
else
|
|
throw Error("The actor $actor already exists in the game")
|
|
}
|
|
else {
|
|
actorContainerInactive.remove(actor)
|
|
actorContainer.add(actor)
|
|
insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor
|
|
|
|
if (actor is ActorWithBody) {
|
|
when (actor.renderOrder) {
|
|
Actor.RenderOrder.BEHIND -> {
|
|
actorsRenderBehind.add(actor); insertionSortLastElemAV(actorsRenderBehind)
|
|
}
|
|
Actor.RenderOrder.MIDDLE -> {
|
|
actorsRenderMiddle.add(actor); insertionSortLastElemAV(actorsRenderMiddle)
|
|
}
|
|
Actor.RenderOrder.MIDTOP -> {
|
|
actorsRenderMidTop.add(actor); insertionSortLastElemAV(actorsRenderMidTop)
|
|
}
|
|
Actor.RenderOrder.FRONT -> {
|
|
actorsRenderFront.add(actor); insertionSortLastElemAV(actorsRenderFront)
|
|
}
|
|
Actor.RenderOrder.OVERLAY-> {
|
|
actorsRenderOverlay.add(actor); insertionSortLastElemAV(actorsRenderOverlay)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun addParticle(particle: ParticleBase) {
|
|
particlesContainer.add(particle)
|
|
}
|
|
|
|
fun addUI(ui: UICanvas) {
|
|
// check for exact duplicates
|
|
if (uiContainer.contains(ui)) {
|
|
throw IllegalArgumentException(
|
|
"Exact copy of the UI already exists: The instance of ${ui.javaClass.simpleName}"
|
|
)
|
|
}
|
|
|
|
uiContainer.add(ui)
|
|
}
|
|
|
|
private fun insertionSortLastElemAV(arr: ArrayList<ActorWithBody>) { // out-projection doesn't work, duh
|
|
ReentrantLock().lock {
|
|
var j = arr.lastIndex - 1
|
|
val x = arr.last()
|
|
while (j >= 0 && arr[j] > x) {
|
|
arr[j + 1] = arr[j]
|
|
j -= 1
|
|
}
|
|
arr[j + 1] = x
|
|
}
|
|
}
|
|
|
|
fun setTooltipMessage(message: String?) {
|
|
if (message == null) {
|
|
uiTooltip.setAsClose()
|
|
}
|
|
else {
|
|
if (uiTooltip.isClosed || uiTooltip.isClosing) {
|
|
uiTooltip.setAsOpen()
|
|
}
|
|
uiTooltip.message = message
|
|
}
|
|
}
|
|
|
|
override fun pause() {
|
|
// TODO no pause when off-focus on desktop
|
|
}
|
|
|
|
override fun resume() {
|
|
}
|
|
|
|
override fun hide() {
|
|
uiContainer.forEach { it.handler.dispose() }
|
|
}
|
|
|
|
|
|
/**
|
|
* @param width same as Terrarum.WIDTH
|
|
* @param height same as Terrarum.HEIGHT
|
|
* @see net.torvald.terrarum.Terrarum
|
|
*/
|
|
override fun resize(width: Int, height: Int) {
|
|
|
|
// FIXME debugger is pointing at this thing, not sure it actually caused memleak
|
|
//MegaRainGovernor.resize()
|
|
|
|
|
|
IngameRenderer.resize(Terrarum.WIDTH, Terrarum.HEIGHT)
|
|
|
|
|
|
if (gameInitialised) {
|
|
LightmapRenderer.fireRecalculateEvent()
|
|
}
|
|
|
|
|
|
if (gameFullyLoaded) {
|
|
// resize UIs
|
|
|
|
notifier.setPosition(
|
|
(Terrarum.WIDTH - notifier.width) / 2, Terrarum.HEIGHT - notifier.height)
|
|
uiQuickBar.setPosition((Terrarum.WIDTH - uiQuickBar.width) / 2, AppLoader.getTvSafeGraphicsHeight())
|
|
|
|
// inventory
|
|
/*uiInventoryPlayer =
|
|
UIInventory(player,
|
|
width = 840,
|
|
height = Terrarum.HEIGHT - 160,
|
|
categoryWidth = 210
|
|
)*/
|
|
|
|
|
|
// basic watch-style notification bar (temperature, new mail)
|
|
uiWatchBasic.setPosition(Terrarum.WIDTH - uiWatchBasic.width, 0)
|
|
uiWatchTierOne.setPosition(
|
|
((Terrarum.WIDTH - AppLoader.getTvSafeGraphicsWidth()) - (uiQuickBar.posX + uiQuickBar.width) - uiWatchTierOne.width) / 2 + (uiQuickBar.posX + uiQuickBar.width),
|
|
AppLoader.getTvSafeGraphicsHeight() + 8
|
|
)
|
|
|
|
}
|
|
|
|
|
|
println("[Ingame] Resize event")
|
|
}
|
|
|
|
override fun dispose() {
|
|
actorsRenderBehind.forEach { it.dispose() }
|
|
actorsRenderMiddle.forEach { it.dispose() }
|
|
actorsRenderMidTop.forEach { it.dispose() }
|
|
actorsRenderFront.forEach { it.dispose() }
|
|
actorsRenderOverlay.forEach { it.dispose() }
|
|
|
|
uiContainer.forEach {
|
|
it.handler.dispose()
|
|
it.dispose()
|
|
}
|
|
}
|
|
|
|
|
|
private fun printStackTrace() {
|
|
Thread.currentThread().getStackTrace().forEach {
|
|
printdbg(this, "-> $it")
|
|
}
|
|
}
|
|
|
|
}
|