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(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() var textcols = emptyList() 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) } } }