Files
Terrarum/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt

523 lines
17 KiB
Kotlin

package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.InputAdapter
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.graphics.glutils.FloatFrameBuffer
import com.jme3.math.FastMath
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.App.printdbgerr
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.console.CommandDict
import net.torvald.terrarum.gameactors.*
import net.torvald.terrarum.gameactors.ai.ActorAI
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.gameparticles.ParticleBase
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.ui.UIRemoCon
import net.torvald.terrarum.modulebasegame.ui.UITitleRemoConYaml
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.serialise.ReadSimpleWorld
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.worlddrawer.WorldCamera
import net.torvald.util.CircularArray
import java.io.IOException
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
/**
* Created by minjaesong on 2017-09-02.
*/
class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// todo register titlescreen as the ingame, similar in a way that the buildingmaker did
var camera = OrthographicCamera(App.scr.wf, App.scr.hf)
// 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)
}
//private var loadDone = false // not required; draw-while-loading is implemented in the AppLoader
private lateinit var demoWorld: GameWorld
private lateinit var cameraNodes: FloatArray // camera Y-pos
private val cameraAI = object : ActorAI {
private var firstTime = true
private val lookaheadDist = 100.0
private fun getPointAt(px: Double): Float {
val ww = TILE_SIZEF * demoWorld.width
val x = px % ww
val indexThis = ((x / ww * cameraNodes.size).floorInt()) fmod cameraNodes.size
val xwstart: Float = indexThis.toFloat() / cameraNodes.size * ww
val xwend: Float = ((indexThis + 1).toFloat() / cameraNodes.size) * ww
val xw: Float = xwend - xwstart
val xperc: Double = (x - xwstart) / xw
return FastMath.interpolateLinear(xperc.toFloat(), cameraNodes[indexThis], cameraNodes[(indexThis + 1) % cameraNodes.size])
}
override fun update(actor: Actor, delta: Float) {
val ww = TILE_SIZEF * demoWorld.width
val actor = actor as CameraPlayer
val px: Double = actor.hitbox.canonicalX + actor.actorValue.getAsDouble(AVKey.SPEED)!!
val pxP = px - lookaheadDist * cos(actor.targetBearing)
val pxN = px + lookaheadDist * cos(actor.targetBearing)
val yP = getPointAt(pxP)
val yN = getPointAt(pxN)
val y = (yP + yN) / 2f
if (firstTime) {
firstTime = false
actor.hitbox.setPositionY(y - 8.0)
}
else {
//actor.moveTo(px, y - 8.0)
//actor.hitbox.setPosition(px, y - 8.0)
actor.moveTo(atan2((yN - yP).toDouble(), pxN - pxP))
}
if (actor.hitbox.canonicalX > ww) {
actor.hitbox.translatePosX(-ww.toDouble())
}
}
}
private lateinit var cameraPlayer: ActorWithBody
private val gradWhiteTop = Color(0xf8f8f8ff.toInt())
private val gradWhiteBottom = Color(0xd8d8d8ff.toInt())
val uiContainer = UIContainer()
internal lateinit var uiRemoCon: UIRemoCon
internal lateinit var uiFakeBlurOverlay: UICanvas
private lateinit var worldFBO: FloatFrameBuffer
private val warning32bitJavaIcon = TextureRegion(Texture(Gdx.files.internal("assets/graphics/gui/32_bit_warning.tga")))
init {
warning32bitJavaIcon.flip(false, false)
gameUpdateGovernor = ConsistentUpdateRate.also { it.reset() }
}
private fun loadThingsWhileIntroIsVisible() {
printdbg(this, "Intro pre-load")
try {
val file = ModMgr.getFile("basegame", "demoworld")
val reader = java.io.FileReader(file)
//ReadWorld.readWorldAndSetNewWorld(Terrarum.ingame!! as TerrarumIngame, reader)
val world = ReadSimpleWorld(reader, file)
demoWorld = world
demoWorld.worldTime.timeDelta = 0//60
printdbg(this, "Demo world loaded")
}
catch (e: IOException) {
demoWorld = GameWorld(LandUtil.CHUNK_W, LandUtil.CHUNK_H, 0L, 0L)
demoWorld.worldTime.timeDelta = 60
printdbg(this, "Demo world not found, using empty world")
}
// set time to summer
demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32)
// construct camera nodes
val nodeCount = demoWorld.width / 10
cameraNodes = kotlin.FloatArray(nodeCount) {
val tileXPos = (demoWorld.width.toFloat() * it / nodeCount).floorInt()
var travelDownCounter = 0
while (travelDownCounter < demoWorld.height &&
!BlockCodex[demoWorld.getTileFromTerrain(tileXPos, travelDownCounter)].isSolid
// !BlockCodex[demoWorld.getTileFromWall(tileXPos, travelDownCounter)].isSolid
) {
travelDownCounter += 2
}
travelDownCounter * TILE_SIZEF
}
// apply gaussian blur to the camera nodes
for (i in cameraNodes.indices) {
val offM2 = cameraNodes[(i-2) fmod cameraNodes.size] * 1f
val offM1 = cameraNodes[(i-1) fmod cameraNodes.size] * 4f
val off0 = cameraNodes[i] * 6f
val off1 = cameraNodes[(i+1) fmod cameraNodes.size] * 4f
val off2 = cameraNodes[(i+2) fmod cameraNodes.size] * 1f
cameraNodes[i] = (offM2 + offM1 + off0 + off1 + off2) / 16f
}
cameraPlayer = CameraPlayer(demoWorld, cameraAI)
IngameRenderer.setRenderedWorld(demoWorld)
// load a half-gradient texture that would be used throughout the titlescreen and its sub UIs
CommonResourcePool.addToLoadingList("title_halfgrad") {
Texture(Gdx.files.internal("./assets/graphics/halfgrad.tga")).also {
it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
}
}
CommonResourcePool.loadAll()
// fake UI for gradient overlay
val uiFakeGradOverlay = UIFakeGradOverlay()
uiFakeGradOverlay.setPosition(0, 0)
uiContainer.add(uiFakeGradOverlay)
// fake UI for blur
uiFakeBlurOverlay = UIFakeBlurOverlay(1f, false)
uiFakeBlurOverlay.setPosition(0,0)
uiContainer.add(uiFakeBlurOverlay)
uiRemoCon = UIRemoCon(this, UITitleRemoConYaml(App.savegamePlayers.isNotEmpty()))
uiRemoCon.setPosition(0, 0)
uiRemoCon.setAsOpen()
uiContainer.add(uiRemoCon)
CommandDict // invoke
// TODO add console here
//loadDone = true
}
override fun hide() {
}
override fun show() {
printdbg(this, "show() called")
initViewPort(App.scr.width, App.scr.height)
Gdx.input.inputProcessor = TitleScreenController(this)
worldFBO = FloatFrameBuffer(App.scr.width, App.scr.height, false)
// load list of savegames
printdbg(this, "update list of savegames")
// to show "Continue" and "Load" on the titlescreen, uncomment this line
App.updateListOfSavegames()
loadThingsWhileIntroIsVisible()
printdbg(this, "show() exit")
}
private val introUncoverTime: Second = 0.3f
private var introUncoverDeltaCounter = 0f
override fun render(updateRate: Float) {
// async update and render
gameUpdateGovernor.update(Gdx.graphics.deltaTime, App.UPDATE_RATE, updateScreen, renderScreen)
}
private val updateScreen = { delta: Float ->
demoWorld.globalLight = WeatherMixer.globalLightNow
demoWorld.updateWorldTime(delta)
WeatherMixer.update(delta, cameraPlayer, demoWorld)
cameraPlayer.update(delta)
// worldcamera update AFTER cameraplayer in this case; the other way is just an exception for actual ingame SFX
WorldCamera.update(demoWorld, cameraPlayer)
// update UIs //
uiContainer.forEach { it?.update(delta) }
}
private val particles = CircularArray<ParticleBase>(16, true)
private val renderScreen = { delta: Float ->
Gdx.graphics.setTitle(TerrarumIngame.getCanonicalTitle())
//camera.setToOrtho(true, AppLoader.terrarumAppConfig.screenWf, AppLoader.terrarumAppConfig.screenHf)
// render world
gdxClearAndEnableBlend(.64f, .754f, .84f, 1f)
if (!demoWorld.layerTerrain.ptr.destroyed) { // FIXME q&d hack to circumvent the dangling pointer issue #26
IngameRenderer.invoke(
false,
1f,
listOf(),
listOf(),
listOf(),
listOf(),
listOf(),
particles,
uiContainer = uiContainer
)
}
else {
printdbgerr(this, "Demoworld is already been destroyed")
}
batch.inUse {
setCameraPosition(0f, 0f)
batch.shader = null
batch.color = Color.WHITE
renderOverlayTexts()
}
}
private fun renderOverlayTexts() {
setCameraPosition(0f, 0f)
blendNormalStraightAlpha(batch)
batch.shader = null
batch.color = Color.LIGHT_GRAY
val COPYTING = arrayOf(
TerrarumAppConfiguration.COPYRIGHT_DATE_NAME,
TerrarumAppConfiguration.COPYRIGHT_LICENSE
)
val drawWidth = Toolkit.drawWidth
COPYTING.forEachIndexed { index, s ->
val textWidth = App.fontGame.getWidth(s)
App.fontGame.draw(batch, s,
(drawWidth - textWidth - 1f).toInt().toFloat(),
(App.scr.height - App.fontGame.lineHeight * (COPYTING.size - index) - 1f).toInt().toFloat()
)
}
batch.color = if (App.IS_DEVELOPMENT_BUILD) Toolkit.Theme.COL_MOUSE_UP else Color.LIGHT_GRAY
App.fontGame.draw(batch, TerrarumPostProcessor.thisIsDebugStr, 5f, App.scr.height - 24f)
batch.color = Color.WHITE
// warn: 32-bit
val linegap = 4
val imgTxtGap = 10
val yoff = App.scr.height - App.scr.tvSafeGraphicsHeight - 64 - (3*(20+linegap)) - imgTxtGap - 9
if (uiRemoCon.currentRemoConContents.parent == null) {
var texts = emptyList<String>()
var textcols = emptyList<Color>()
if (App.is32BitJVM) {
Toolkit.drawCentered(batch, warning32bitJavaIcon, yoff)
texts = (1..3).map { Lang.get("GAME_32BIT_WARNING$it", (it != 3)) }
textcols = (1..3).map { if (it == 3) Toolkit.Theme.COL_SELECTED else Color.WHITE }
}
// warn: rosetta on Apple M-chips
else if (App.getUndesirableConditions() == "apple_execution_through_rosetta") {
texts = listOf(
"It seems you are using a Mac with Apple Silicon but running the game through Rosetta.",
"A native build for the game is available which runs much faster than current version."
)
textcols = texts.map { Color.WHITE }
}
texts.forEachIndexed { i, text ->
batch.color = textcols[i]
App.fontGame.draw(
batch,
text,
((drawWidth - App.fontGame.getWidth(text)) / 2).toFloat(),
yoff + imgTxtGap + 64f + linegap + i * (20 + linegap)
)
}
}
}
override fun pause() {
}
override fun resume() {
}
override fun resize(width: Int, height: Int) {
printdbg(this, "resize() called")
printdbg(this, "called by:")
printStackTrace(this)
// Set up viewport when window is resized
initViewPort(App.scr.width, App.scr.height)
// resize UI by re-creating it (!!)
uiRemoCon.resize(App.scr.width, App.scr.height)
// TODO I forgot what the fuck kind of hack I was talking about
//uiMenu.setPosition(0, UITitleRemoConRoot.menubarOffY)
uiRemoCon.setPosition(0, 0) // shitty hack. Could be:
// 1: Init code and resize code are different
// 2: The UI is coded shit
IngameRenderer.resize(App.scr.width, App.scr.height)
printdbg(this, "resize() exit")
}
override fun dispose() {
uiRemoCon.dispose()
demoWorld.dispose()
warning32bitJavaIcon.texture.dispose()
}
override fun inputStrobed(e: TerrarumKeyboardEvent) {
uiContainer.forEach { it?.inputStrobed(e) }
}
fun setCameraPosition(newX: Float, newY: Float) {
TerrarumIngame.setCameraPosition(batch, camera, newX, newY)
}
class TitleScreenController(val screen: TitleScreen) : InputAdapter() {
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
screen.uiContainer.forEach { it?.touchUp(screenX, screenY, pointer, button) }
return true
}
override fun keyTyped(character: Char): Boolean {
screen.uiContainer.forEach { it?.keyTyped(character) }
return true
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
screen.uiContainer.forEach { it?.scrolled(amountX, amountY) }
return true
}
override fun keyUp(keycode: Int): Boolean {
screen.uiContainer.forEach { it?.keyUp(keycode) }
return true
}
override fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean {
screen.uiContainer.forEach { it?.touchDragged(screenX, screenY, pointer) }
return true
}
override fun keyDown(keycode: Int): Boolean {
screen.uiContainer.forEach { it?.keyDown(keycode) }
return true
}
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
screen.uiContainer.forEach { it?.touchDown(screenX, screenY, pointer, button) }
return true
}
}
private class CameraPlayer(val demoWorld: GameWorld, override var ai: ActorAI) : ActorWithBody(RenderOrder.FRONT, physProp = PhysProperties.MOBILE_OBJECT), AIControlled {
override val hitbox = Hitbox(0.0, 0.0, 2.0, 2.0)
init {
actorValue[AVKey.SPEED] = 1.666
hitbox.setPosition(
HQRNG().nextInt(demoWorld.width) * TILE_SIZED,
0.0 // Y pos: placeholder; camera AI will take it over
)
}
override fun drawBody(batch: SpriteBatch) { }
override fun drawGlow(batch: SpriteBatch) { }
override fun update(delta: Float) {
ai.update(this, delta)
}
override fun onActorValueChange(key: String, value: Any?) { }
override fun dispose() { }
override fun run() { TODO("not implemented") }
override fun moveLeft(amount: Float) {
TODO("not implemented")
}
override fun moveRight(amount: Float) {
TODO("not implemented")
}
override fun moveUp(amount: Float) {
TODO("not implemented")
}
override fun moveDown(amount: Float) {
TODO("not implemented")
}
override fun moveJump(amount: Float) {
TODO("not implemented")
}
var targetBearing = 0.0
var currentBearing = Double.NaN
override fun moveTo(bearing: Double) {
targetBearing = bearing
if (currentBearing.isNaN()) currentBearing = bearing
val v = actorValue.getAsDouble(AVKey.SPEED)!!
//currentBearing = interpolateLinear(1.0 / 22.0, currentBearing, targetBearing)
currentBearing = targetBearing
hitbox.translate(v * cos(currentBearing), v * sin(currentBearing))
}
override fun moveTo(toX: Double, toY: Double) {
val ww = TILE_SIZED * demoWorld.width
// select appropriate toX because ROUNDWORLD
val xdiff1 = toX - hitbox.canonicalX
val xdiff2 = (toX + ww) - hitbox.canonicalX
val xdiff = if (xdiff1 < 0) xdiff2 else xdiff1
val ydiff = toY - hitbox.canonicalY
moveTo(atan2(ydiff, xdiff))
hitbox.setPositionX(hitbox.canonicalX % ww)
}
}
}