Files
Terrarum/src/net/torvald/terrarum/FuckingWorldRenderer.kt.unused
2018-07-01 01:38:07 +09:00

1509 lines
52 KiB
Plaintext

package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.Screen
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.FrameBuffer
import net.torvald.dataclass.CircularArray
import net.torvald.terrarum.blockproperties.BlockPropUtil
import net.torvald.terrarum.blockstats.BlockStats
import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.console.*
import net.torvald.terrarum.gameactors.*
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
import net.torvald.terrarum.gamecontroller.IngameController
import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.worlddrawer.BlocksDrawer
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.worlddrawer.LightmapRenderer
import net.torvald.terrarum.worlddrawer.WorldCamera
import java.util.ArrayList
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import javax.swing.JOptionPane
import com.badlogic.gdx.graphics.OrthographicCamera
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.modulebasegame.console.AVTracker
import net.torvald.terrarum.modulebasegame.console.ActorsList
import net.torvald.terrarum.console.Authenticator
import net.torvald.terrarum.console.SetGlobalLightOverride
import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.modulebasegame.gameactors.*
import net.torvald.terrarum.modulebasegame.imagefont.Watch7SegMain
import net.torvald.terrarum.modulebasegame.imagefont.WatchDotAlph
import net.torvald.terrarum.modulebasegame.ui.*
import net.torvald.terrarum.ui.*
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.modulebasegame.worldgenerator.WorldGenerator
import kotlin.system.measureNanoTime
/**
* Created by minjaesong on 2017-06-16.
*/
class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
private val ACTOR_UPDATE_RANGE = 4096
lateinit var world: GameWorld
lateinit var historicalFigureIDBucket: ArrayList<Int>
/**
* list of Actors that is sorted by Actors' referenceID
*/
//val ACTORCONTAINER_INITIAL_SIZE = 64
val PARTICLES_MAX = Terrarum.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)
lateinit var playableActorDelegate: PlayableActorDelegate // player must exist; use dummy player if there is none (required for camera)
private set
inline val player: ActorHumanoid // currently POSSESSED actor :)
get() = playableActorDelegate.actor
//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 {
//val lightmapDownsample = 4f //2f: still has choppy look when the camera moves but unnoticeable when blurred
/** 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
}
/**
* 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
}
}
catch (gameNotInitialisedException: KotlinNullPointerException) {
false
}
var ret: Int
do {
ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID
} while (hasCollision(ret)) // check for collision
return ret
}
}
private val worldFBOformat = if (Terrarum.environment == RunningEnvironment.MOBILE) Pixmap.Format.RGBA4444 else Pixmap.Format.RGBA8888
private val lightFBOformat = Pixmap.Format.RGB888
var worldDrawFrameBuffer = FrameBuffer(worldFBOformat, Terrarum.WIDTH, Terrarum.HEIGHT, false)
var worldGlowFrameBuffer = FrameBuffer(worldFBOformat, Terrarum.WIDTH, Terrarum.HEIGHT, false)
var worldBlendFrameBuffer = FrameBuffer(worldFBOformat, Terrarum.WIDTH, Terrarum.HEIGHT, false)
// RGB elements of Lightmap for Color Vec4(R, G, B, 1.0) 24-bit
private lateinit var lightmapFboA: FrameBuffer
private lateinit var lightmapFboB: FrameBuffer
init {
}
private val useShader: Boolean = false
private val shaderProgram = 0
val KEY_LIGHTMAP_RENDER = Input.Keys.F7
lateinit var debugWindow: UICanvas
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 uiAlasesPausing: ArrayList<UICanvas>
private set
inline val paused: Boolean
get() = uiAlasesPausing.map { if (it.isOpened) return true else 0 }.isEmpty() // isEmply 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
var gameInitialised = false
private set
var gameFullyLoaded = false
private set
private val TILE_SIZEF = FeaturesDrawer.TILE_SIZE.toFloat()
//////////////
// GDX code //
//////////////
// invert Y
fun initViewPort(width: Int, height: Int) {
// Set Y to point downwards
camera.setToOrtho(true, width.toFloat(), height.toFloat())
// Update camera matrix
camera.update()
// Set viewport to restrict drawing
Gdx.gl20.glViewport(0, 0, width, height)
}
lateinit var gameLoadMode: GameLoadMode
lateinit var gameLoadInfoPayload: Any
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
gameInitialised = true
}
data class GameSaveData(
val world: GameWorld,
val historicalFigureIDBucket: ArrayList<Int>,
val realGamePlayer: ActorHumanoid
)
data class NewWorldParameters(
val width: Int,
val height: Int,
val worldGenSeed: Long
// other worldgen options
)
/**
* Init instance by loading saved world
*/
private fun enter(gameSaveData: GameSaveData) {
if (gameInitialised) {
println("[Ingame] loaded successfully.")
}
else {
LoadScreen.addMessage("Loading world from save")
world = gameSaveData.world
historicalFigureIDBucket = gameSaveData.historicalFigureIDBucket
playableActorDelegate = PlayableActorDelegate(gameSaveData.realGamePlayer)
addNewActor(player)
//initGame()
}
}
/**
* Init instance by creating new world
*/
private fun enter(worldParams: NewWorldParameters) {
if (gameInitialised) {
println("[Ingame] loaded successfully.")
}
else {
LoadScreen.addMessage("${Terrarum.NAME} version ${AppLoader.getVERSION_STRING()}")
LoadScreen.addMessage("Creating new world")
// init map as chosen size
world = GameWorld(worldParams.width, worldParams.height)
// generate terrain for the map
WorldGenerator.attachMap(world)
WorldGenerator.SEED = worldParams.worldGenSeed
WorldGenerator.generateMap()
historicalFigureIDBucket = ArrayList<Int>()
RoguelikeRandomiser.seed = HQRNG().nextLong()
// add new player and put it to actorContainer
//playableActorDelegate = PlayableActorDelegate(PlayerBuilderSigrid())
//playableActorDelegate = PlayableActorDelegate(PlayerBuilderTestSubject1())
//addNewActor(player)
// test actor
//addNewActor(PlayerBuilderCynthia())
//initGame()
}
}
private val ingameController = IngameController(this)
/** Load rest of the game with GL context */
fun postInit() {
//LightmapRenderer.world = this.world
BlocksDrawer.world = this.world
//FeaturesDrawer.world = this.world
MegaRainGovernor // invoke MegaRain Governor
Gdx.input.inputProcessor = ingameController
initViewPort(Terrarum.WIDTH, Terrarum.HEIGHT)
// init console window
consoleHandler = ConsoleWindow()
consoleHandler.setPosition(0, 0)
// init debug window
debugWindow = BasicDebugInfoWindow()
debugWindow.setPosition(0, 0)
// init notifier
notifier = Notification()
notifier.setPosition(
(Terrarum.WIDTH - notifier.width) / 2, Terrarum.HEIGHT - notifier.height)
// >- queue up game UIs that should pause the world -<
// inventory
/*uiInventoryPlayer = UIInventory(player,
width = 900,
height = Terrarum.HEIGHT - 160,
categoryWidth = 210,
toggleKeyLiteral = Terrarum.getConfigInt("keyinventory")
)*/
/*uiInventoryPlayer.setPosition(
-uiInventoryPlayer.width,
70
)*/
uiInventoryPlayer = UIInventoryFull(player,
toggleKeyLiteral = Terrarum.getConfigInt("keyinventory")
)
uiInventoryPlayer.setPosition(0, 0)
// >- lesser UIs -<
// quick bar
uiQuickBar = UIQuickBar()
uiQuickBar.isVisible = true
uiQuickBar.setPosition((Terrarum.WIDTH - uiQuickBar.width) / 2 + 12, -10)
// pie menu
uiPieMenu = UIPieMenu()
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(player)
uiWatchBasic.setAsAlwaysVisible()
uiWatchBasic.setPosition(Terrarum.WIDTH - uiWatchBasic.width, 0)
uiWatchTierOne = UITierOneWatch(player)
uiWatchTierOne.setAsAlwaysVisible()
uiWatchTierOne.setPosition(Terrarum.WIDTH - uiWatchTierOne.width, uiWatchBasic.height - 2)
uiTooltip = UITooltip()
uiCheatMotherfuckerNootNoot = UICheatDetected()
// batch-process uiAliases
uiAliases = arrayListOf(
// drawn first
//uiVitalPrimary,
//uiVitalSecondary,
//uiVitalItem,
uiPieMenu,
uiQuickBar,
uiWatchBasic,
uiWatchTierOne,
uiTooltip
// drawn last
)
uiAlasesPausing = arrayListOf(
uiInventoryPlayer,
//uiInventoryContainer,
consoleHandler,
uiCheatMotherfuckerNootNoot
)
uiAlasesPausing.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")
LightmapRenderer.fireRecalculateEvent()
// some sketchy test code here
}// END enter
protected var updateDeltaCounter = 0.0
protected val updateRate = 1.0 / Terrarum.TARGET_INTERNAL_FPS
private var firstTimeRun = true
///////////////
// prod code //
///////////////
private class ThreadIngameUpdate(val ingame: Ingame): Runnable {
override fun run() {
var updateTries = 0
while (ingame.updateDeltaCounter >= ingame.updateRate) {
ingame.updateGame(Terrarum.deltaTime)
ingame.updateDeltaCounter -= ingame.updateRate
updateTries++
if (updateTries >= Terrarum.UPDATE_CATCHUP_MAX_TRIES) {
break
}
}
}
}
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) {
playableActorDelegate = PlayableActorDelegate(PlayerBuilderSigrid())
// go to spawn position
player.setPosition(
world.spawnX * FeaturesDrawer.TILE_SIZE.toDouble(),
world.spawnY * FeaturesDrawer.TILE_SIZE.toDouble()
)
addNewActor(player)
}
postInit()
gameFullyLoaded = true
}
Gdx.graphics.setTitle(AppLoader.GAME_NAME +
" — F: ${Gdx.graphics.framesPerSecond} (${Terrarum.TARGET_INTERNAL_FPS})" +
" — M: ${Terrarum.memInUse}M / ${Terrarum.memTotal}M / ${Terrarum.memXmx}M"
)
// ASYNCHRONOUS UPDATE AND RENDER //
/** UPDATE CODE GOES HERE */
updateDeltaCounter += delta
if (false && Terrarum.getConfigBoolean("multithread")) { // NO MULTITHREADING: camera don't like concurrent modification (jittery actor movements)
if (firstTimeRun || updateThreadWrapper.state == Thread.State.TERMINATED) {
updateThreadWrapper = Thread(ingameUpdateThread, "Terrarum UpdateThread")
updateThreadWrapper.start()
if (firstTimeRun) firstTimeRun = false
}
// else, NOP;
}
else {
var updateTries = 0
while (updateDeltaCounter >= updateRate) {
//updateGame(delta)
Terrarum.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) }
updateDeltaCounter -= updateRate
updateTries++
if (updateTries >= Terrarum.UPDATE_CATCHUP_MAX_TRIES) {
break
}
}
}
/** RENDER CODE GOES HERE */
//renderGame(batch)
Terrarum.debugTimers["Ingame.render"] = measureNanoTime { renderGame(batch) }
}
protected fun updateGame(delta: Float) {
particlesActive = 0
KeyToggler.update()
ingameController.update(delta)
if (!paused) {
///////////////////////////
// world-related updates //
///////////////////////////
BlockPropUtil.dynamicLumFuncTickClock()
world.updateWorldTime(delta)
//WorldSimulator(player, delta)
WeatherMixer.update(delta, player)
BlockStats.update()
if (!(CommandDict["setgl"] as SetGlobalLightOverride).lightOverride)
world.globalLight = WeatherMixer.globalLightNow
////////////////////////////
// camera-related updates //
////////////////////////////
FeaturesDrawer.update(delta)
WorldCamera.update(world, player)
///////////////////////////
// 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()
}
////////////////////////
// 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(batch: SpriteBatch) {
Gdx.gl.glClearColor(.094f, .094f, .094f, 0f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
Gdx.gl.glEnable(GL20.GL_BLEND)
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
//camera.position.set(-WorldCamera.x.toFloat(), -WorldCamera.y.toFloat(), 0f) // make camara work
//camera.position.set(0f, 0f, 0f) // make camara work
//batch.projectionMatrix = camera.combined
LightmapRenderer.fireRecalculateEvent()
worldBlendFrameBuffer.inAction(null, null) {
Gdx.gl.glClearColor(0f,0f,0f,0f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
Gdx.gl.glEnable(GL20.GL_BLEND)
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
}
worldDrawFrameBuffer.inAction(null, null) {
Gdx.gl.glClearColor(0f,0f,0f,0f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
Gdx.gl.glEnable(GL20.GL_BLEND)
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
}
worldGlowFrameBuffer.inAction(null, null) {
Gdx.gl.glClearColor(0f,0f,0f,1f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
Gdx.gl.glEnable(GL20.GL_BLEND)
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
}
fun moveCameraToWorldCoord() {
// using custom code for camera; this is obscure and tricky
camera.position.set(WorldCamera.gdxCamX, WorldCamera.gdxCamY, 0f) // make camara work
camera.update()
batch.projectionMatrix = camera.combined
}
///////////////////////////
// draw world to the FBO //
///////////////////////////
processBlur(lightmapFboA, lightmapFboB, LightmapRenderer.DRAW_FOR_RGB)
worldDrawFrameBuffer.inAction(camera, batch) {
// draw-with-poly doesn't want to co-op with peasant spriteBatch... (it hides sprites)
batch.inUse {
batch.shader = null
batch.color = Color.WHITE
blendNormal()
}
setCameraPosition(0f, 0f)
BlocksDrawer.renderWall(batch)
batch.inUse {
moveCameraToWorldCoord()
actorsRenderBehind.forEach { it.drawBody(batch) }
particlesContainer.forEach { it.drawBody(batch) }
}
setCameraPosition(0f, 0f)
BlocksDrawer.renderTerrain(batch)
batch.inUse {
/////////////////
// draw actors //
/////////////////
moveCameraToWorldCoord()
actorsRenderMiddle.forEach { it.drawBody(batch) }
actorsRenderMidTop.forEach { it.drawBody(batch) }
player.drawBody(batch)
actorsRenderFront.forEach { it.drawBody(batch) }
// --> Change of blend mode <-- introduced by children of ActorWithBody //
/////////////////////////////
// draw map related stuffs //
/////////////////////////////
}
setCameraPosition(0f, 0f)
BlocksDrawer.renderFront(batch, false)
batch.inUse {
// --> blendNormal() <-- by BlocksDrawer.renderFront
FeaturesDrawer.drawEnvOverlay(batch)
// mix lighpmap canvas to this canvas (Colors -- RGB channel)
if (!KeyToggler.isOn(Input.Keys.F6)) { // F6 to disable lightmap draw
setCameraPosition(0f, 0f)
batch.shader = Terrarum.shaderBayer
batch.shader.setUniformf("rcount", 64f)
batch.shader.setUniformf("gcount", 64f)
batch.shader.setUniformf("bcount", 64f) // de-banding
val lightTex = lightmapFboB.colorBufferTexture // A or B? flipped in Y means you chose wrong buffer; use one that works correctly
lightTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest) // blocky feeling for A E S T H E T I C S
if (KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) blendNormal()
else blendMul()
batch.color = Color.WHITE
val xrem = -(WorldCamera.x.toFloat() fmod TILE_SIZEF)
val yrem = -(WorldCamera.y.toFloat() fmod TILE_SIZEF)
batch.draw(lightTex,
xrem,
yrem,
lightTex.width * lightmapDownsample, lightTex.height * lightmapDownsample
//lightTex.width.toFloat(), lightTex.height.toFloat() // for debugging
)
}
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // don't know why it is REALLY needed; it really depresses me
batch.shader = null
// move camera back to its former position
// using custom code for camera; this is obscure and tricky
camera.position.set(WorldCamera.gdxCamX, WorldCamera.gdxCamY, 0f) // make camara work
camera.update()
batch.projectionMatrix = camera.combined
}
}
//////////////////////////
// draw glow to the FBO //
//////////////////////////
processBlur(lightmapFboA, lightmapFboB, LightmapRenderer.DRAW_FOR_ALPHA)
worldGlowFrameBuffer.inAction(camera, batch) {
batch.inUse {
batch.shader = null
batch.color = Color.WHITE
blendNormal()
//////////////////////
// draw actor glows //
//////////////////////
moveCameraToWorldCoord()
actorsRenderBehind.forEach { it.drawGlow(batch) }
particlesContainer.forEach { it.drawGlow(batch) }
actorsRenderMiddle.forEach { it.drawGlow(batch) }
actorsRenderMidTop.forEach { it.drawGlow(batch) }
player.drawGlow(batch)
actorsRenderFront.forEach { it.drawGlow(batch) }
// --> blendNormal() <-- introduced by childs of ActorWithBody //
// mix lighpmap canvas to this canvas (UV lights -- A channel written on RGB as greyscale image)
if (!KeyToggler.isOn(Input.Keys.F6)) { // F6 to disable lightmap draw
setCameraPosition(0f, 0f)
batch.shader = Terrarum.shaderBayer
batch.shader.setUniformf("rcount", 64f)
batch.shader.setUniformf("gcount", 64f)
batch.shader.setUniformf("bcount", 64f) // de-banding
val lightTex = lightmapFboB.colorBufferTexture // A or B? flipped in Y means you chose wrong buffer; use one that works correctly
lightTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest) // blocky feeling for A E S T H E T I C S
if (KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) blendNormal()
else blendMul()
batch.color = Color.WHITE
val xrem = -(WorldCamera.x.toFloat() fmod TILE_SIZEF)
val yrem = -(WorldCamera.y.toFloat() fmod TILE_SIZEF)
batch.draw(lightTex,
xrem,
yrem,
lightTex.width * lightmapDownsample, lightTex.height * lightmapDownsample
//lightTex.width.toFloat(), lightTex.height.toFloat() // for debugging
)
}
blendNormal()
}
}
worldBlendFrameBuffer.inAction(camera, batch) {
Gdx.gl.glClearColor(0f, 0f, 0f, 0f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
// draw blended world
val worldTex = worldDrawFrameBuffer.colorBufferTexture // WORLD: light_color must be applied beforehand
val glowTex = worldGlowFrameBuffer.colorBufferTexture // GLOW: light_uvlight must be applied beforehand
worldTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
glowTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
worldTex.bind(0)
glowTex.bind(1)
Terrarum.shaderBlendGlow.begin()
Terrarum.shaderBlendGlow.setUniformMatrix("u_projTrans", camera.combined)
Terrarum.shaderBlendGlow.setUniformi("u_texture", 0)
Terrarum.shaderBlendGlow.setUniformi("tex1", 1)
Terrarum.fullscreenQuad.render(Terrarum.shaderBlendGlow, GL20.GL_TRIANGLES)
Terrarum.shaderBlendGlow.end()
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // don't know why it is REALLY needed; it really depresses me
batch.inUse {
batch.color = Color.WHITE
blendNormal()
batch.shader = null
}
}
/////////////////////////
// draw to main screen //
/////////////////////////
camera.setToOrtho(true, Terrarum.WIDTH.toFloat(), Terrarum.HEIGHT.toFloat())
batch.projectionMatrix = camera.combined
batch.inUse {
batch.shader = null
setCameraPosition(0f, 0f)
batch.color = Color.WHITE
blendNormal()
///////////////////////////
// draw skybox to screen //
///////////////////////////
WeatherMixer.render(camera, world)
/////////////////////////////////
// draw framebuffers to screen //
/////////////////////////////////
val blendedTex = worldBlendFrameBuffer.colorBufferTexture
blendedTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
batch.color = Color.WHITE
batch.shader = null
blendNormal()
batch.draw(blendedTex, 0f, 0f, blendedTex.width.toFloat(), blendedTex.height.toFloat())
// an old code.
/*batch.shader = null
val worldTex = worldDrawFrameBuffer.colorBufferTexture // WORLD: light_color must be applied beforehand
val glowTex = worldGlowFrameBuffer.colorBufferTexture // GLOW: light_uvlight must be applied beforehand
batch.draw(worldTex, 0f, 0f, worldTex.width.toFloat(), worldTex.height.toFloat())*/
batch.shader = null
////////////////////////
// debug informations //
////////////////////////
blendNormal()
// draw reference ID if debugWindow is open
if (debugWindow.isVisible) {
actorContainer.forEachIndexed { i, actor ->
if (actor is ActorWithBody) {
batch.color = Color.WHITE
Terrarum.fontSmallNumbers.draw(batch,
actor.referenceID.toString(),
actor.hitbox.startX.toFloat(),
actor.hitbox.canonicalY.toFloat() + 4
)
}
}
}
// debug physics
if (KeyToggler.isOn(Input.Keys.F11)) {
actorContainer.forEachIndexed { i, actor ->
if (actor is ActorWithPhysics) {
/*shapeRenderer.inUse(ShapeRenderer.ShapeType.Line) {
shapeRenderer.color = Color(1f, 0f, 1f, 1f)
//shapeRenderer.lineWidth = 1f
shapeRenderer.rect(
actor.hitbox.startX.toFloat(),
actor.hitbox.startY.toFloat(),
actor.hitbox.width.toFloat(),
actor.hitbox.height.toFloat()
)
}*/
// velocity
batch.color = Color.CHARTREUSE//GameFontBase.codeToCol["g"]
Terrarum.fontSmallNumbers.draw(batch,
"${0x7F.toChar()}X ${actor.externalForce.x}",
actor.hitbox.startX.toFloat(),
actor.hitbox.canonicalY.toFloat() + 4 + 8
)
Terrarum.fontSmallNumbers.draw(batch,
"${0x7F.toChar()}Y ${actor.externalForce.y}",
actor.hitbox.startX.toFloat(),
actor.hitbox.canonicalY.toFloat() + 4 + 8 * 2
)
}
}
}
// fluidmap debug
if (KeyToggler.isOn(Input.Keys.F4)) {
WorldSimulator.drawFluidMapDebug(batch)
}
/////////////////////////////
// draw some overlays (UI) //
/////////////////////////////
uiContainer.forEach {
if (it != consoleHandler) {
batch.color = Color.WHITE
it.render(batch, camera)
}
}
debugWindow.render(batch, camera)
// make sure console draws on top of other UIs
consoleHandler.render(batch, camera)
notifier.render(batch, camera)
blendNormal()
}
}
fun processBlur(lightmapFboA: FrameBuffer, lightmapFboB: FrameBuffer, mode: Int) {
val blurIterations = 5 // ideally, 4 * radius; must be even/odd number -- odd/even number will flip the image
val blurRadius = 4f / lightmapDownsample // (5, 4f); using low numbers for pixel-y aesthetics
var blurWriteBuffer = lightmapFboA
var blurReadBuffer = lightmapFboB
lightmapFboA.inAction(null, null) {
Gdx.gl.glClearColor(0f, 0f, 0f, 0f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
}
lightmapFboB.inAction(null, null) {
Gdx.gl.glClearColor(0f, 0f, 0f, 0f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
}
if (mode == LightmapRenderer.DRAW_FOR_RGB) {
// initialise readBuffer with untreated lightmap
blurReadBuffer.inAction(camera, batch) {
batch.inUse {
blendNormal(batch)
batch.color = Color.WHITE
LightmapRenderer.draw(batch, LightmapRenderer.DRAW_FOR_RGB)
}
}
}
else {
// initialise readBuffer with untreated lightmap
blurReadBuffer.inAction(camera, batch) {
batch.inUse {
blendNormal(batch)
batch.color = Color.WHITE
LightmapRenderer.draw(batch, LightmapRenderer.DRAW_FOR_ALPHA)
}
}
}
if (!KeyToggler.isOn(Input.Keys.F8)) {
for (i in 0 until blurIterations) {
blurWriteBuffer.inAction(camera, batch) {
batch.inUse {
val texture = blurReadBuffer.colorBufferTexture
texture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
batch.shader = Terrarum.shaderBlur
batch.shader.setUniformf("iResolution",
blurWriteBuffer.width.toFloat(), blurWriteBuffer.height.toFloat())
batch.shader.setUniformf("flip", 1f)
if (i % 2 == 0)
batch.shader.setUniformf("direction", blurRadius, 0f)
else
batch.shader.setUniformf("direction", 0f, blurRadius)
batch.color = Color.WHITE
batch.draw(texture, 0f, 0f)
// swap
val t = blurWriteBuffer
blurWriteBuffer = blurReadBuffer
blurReadBuffer = t
}
}
}
}
else {
blurWriteBuffer = blurReadBuffer
}
}
private fun repossessActor() {
// check if currently pocessed actor is removed from game
if (!theGameHasActor(player)) {
// re-possess canonical player
if (theGameHasActor(Player.PLAYER_REF_ID))
changePossession(Player.PLAYER_REF_ID)
else
changePossession(0x51621D) // FIXME fallback debug mode (FIXME is there for a reminder visible in ya IDE)
}
}
private fun changePossession(newActor: PlayableActorDelegate) {
if (!theGameHasActor(player)) {
throw IllegalArgumentException("No such actor in the game: $newActor")
}
playableActorDelegate = newActor
WorldSimulator(player, Terrarum.deltaTime)
}
private fun changePossession(refid: Int) {
// TODO prevent possessing other player on multiplayer
if (!theGameHasActor(refid)) {
throw IllegalArgumentException(
"No such actor in the game: $refid (elemsActive: ${actorContainer.size}, " +
"elemsInactive: ${actorContainerInactive.size})")
}
// take care of old delegate
playableActorDelegate!!.actor.collisionType = HumanoidNPC.DEFAULT_COLLISION_TYPE
// accept new delegate
playableActorDelegate = PlayableActorDelegate(getActorByID(refid) as ActorHumanoid)
playableActorDelegate!!.actor.collisionType = ActorWithPhysics.COLLISION_KINEMATIC
WorldSimulator(player, Terrarum.deltaTime)
}
/** 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,
ThreadActorUpdate(
actors.div(Terrarum.THREADS).times(i).roundInt(),
actors.div(Terrarum.THREADS).times(i.plus(1)).roundInt() - 1
),
"ActorUpdate"
)
}
ThreadParallel.startAll()
playableActorDelegate?.update(delta)
}
else {
actorContainer.forEach {
if (it != playableActorDelegate?.actor) {
it.update(delta)
if (it is Pocketed) {
it.inventory.forEach { inventoryEntry ->
inventoryEntry.item.effectWhileInPocket(delta)
if (it.equipped(inventoryEntry.item)) {
inventoryEntry.item.effectWhenEquipped(delta)
}
}
}
}
}
playableActorDelegate?.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.referenceID == player.referenceID || actor.referenceID == 0x51621D) // do not delete this magic
throw RuntimeException("Attempted to remove player.")
val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
if (indexToDelete >= 0) {
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)
}
}
}
}
}
/**
* Check for duplicates, append actor and sort the list
*/
override fun addNewActor(actor: Actor) {
if (theGameHasActor(actor.referenceID!!)) {
throw Error("The actor $actor already exists in the game")
}
else {
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)
}
}
}
}
}
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)
}
}
}
}
}
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
lock(ReentrantLock()) {
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() {
dispose()
}
private var lightmapInitialised = false // to avoid nullability of lightmapFBO
/**
* @param width same as Terrarum.WIDTH
* @param height same as Terrarum.HEIGHT
* @see net.torvald.terrarum.Terrarum
*/
override fun resize(width: Int, height: Int) {
BlocksDrawer.resize(Terrarum.WIDTH, Terrarum.HEIGHT)
LightmapRenderer.resize(Terrarum.WIDTH, Terrarum.HEIGHT)
MegaRainGovernor.resize()
worldDrawFrameBuffer.dispose()
worldDrawFrameBuffer = FrameBuffer(worldFBOformat, Terrarum.WIDTH, Terrarum.HEIGHT, false)
worldGlowFrameBuffer.dispose()
worldGlowFrameBuffer = FrameBuffer(worldFBOformat, Terrarum.WIDTH, Terrarum.HEIGHT, false)
worldBlendFrameBuffer.dispose()
worldBlendFrameBuffer = FrameBuffer(worldFBOformat, Terrarum.WIDTH, Terrarum.HEIGHT, false)
if (lightmapInitialised) {
lightmapFboA.dispose()
lightmapFboB.dispose()
}
lightmapFboA = FrameBuffer(
lightFBOformat,
LightmapRenderer.lightBuffer.width * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
LightmapRenderer.lightBuffer.height * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
false
)
lightmapFboB = FrameBuffer(
lightFBOformat,
LightmapRenderer.lightBuffer.width * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
LightmapRenderer.lightBuffer.height * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
false
)
lightmapInitialised = true // are you the first time?
// Set up viewport when window is resized
initViewPort(Terrarum.WIDTH, Terrarum.HEIGHT)
if (gameInitialised) {
LightmapRenderer.fireRecalculateEvent()
}
if (gameFullyLoaded) {
// resize UIs
notifier.setPosition(
(Terrarum.WIDTH - notifier.width) / 2, Terrarum.HEIGHT - notifier.height)
// 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 - uiWatchTierOne.width, uiWatchBasic.height - 2)
}
println("[Ingame] Resize event")
}
override fun dispose() {
worldDrawFrameBuffer.dispose()
worldGlowFrameBuffer.dispose()
worldBlendFrameBuffer.dispose()
lightmapFboA.dispose()
lightmapFboB.dispose()
actorsRenderBehind.forEach { it.dispose() }
actorsRenderMiddle.forEach { it.dispose() }
actorsRenderMidTop.forEach { it.dispose() }
actorsRenderFront.forEach { it.dispose() }
uiAliases.forEach { it.dispose() }
uiAlasesPausing.forEach { it.dispose() }
WatchDotAlph.dispose()
Watch7SegMain.dispose()
WatchDotAlph.dispose()
ItemSlotImageBuilder.dispose()
MessageWindow.SEGMENT_BLACK.dispose()
MessageWindow.SEGMENT_WHITE.dispose()
}
/**
* WARNING! this function flushes batch; use this sparingly!
*
* Camera will be moved so that (newX, newY) would be sit on the top-left edge.
*/
fun setCameraPosition(newX: Float, newY: Float) {
setCameraPosition(batch, camera, newX, newY)
}
}