package net.torvald.terrarum; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Screen; import com.badlogic.gdx.audio.AudioDevice; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.controllers.Controllers; import com.badlogic.gdx.graphics.*; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.JsonValue; import com.github.strikerx3.jxinput.XInputDevice; import net.torvald.gdx.graphics.PixmapIO2; import net.torvald.getcpuname.GetCpuName; import net.torvald.terrarum.concurrent.ThreadExecutor; import net.torvald.terrarum.controller.GdxControllerAdapter; import net.torvald.terrarum.controller.TerrarumController; import net.torvald.terrarum.controller.XinputControllerAdapter; import net.torvald.terrarum.gameactors.BlockMarkerActor; import net.torvald.terrarum.gamecontroller.IME; import net.torvald.terrarum.gamecontroller.InputStrober; import net.torvald.terrarum.gamecontroller.KeyToggler; import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent; import net.torvald.terrarum.gameworld.GameWorld; import net.torvald.terrarum.imagefont.TinyAlphNum; import net.torvald.terrarum.langpack.Lang; import net.torvald.terrarum.modulebasegame.IngameRenderer; import net.torvald.terrarum.modulebasegame.TerrarumIngame; import net.torvald.terrarum.modulebasegame.ui.ItemSlotImageFactory; import net.torvald.terrarum.savegame.DiskSkimmer; import net.torvald.terrarum.serialise.WriteConfig; import net.torvald.terrarum.ui.Toolkit; import net.torvald.terrarum.utils.JsonFetcher; import net.torvald.terrarum.worlddrawer.CreateTileAtlas; import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap; import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack; import net.torvald.util.DebugTimers; import java.io.File; import java.io.IOException; import java.util.*; import static net.torvald.terrarum.TerrarumKt.*; /** * The framework's Application Loader * * * Created by minjaesong on 2017-08-01. */ public class App implements ApplicationListener { public static final String GAME_NAME = TerrarumAppConfiguration.GAME_NAME; public static final int VERSION_RAW = TerrarumAppConfiguration.VERSION_RAW; public static final String getVERSION_STRING() { return String.format("%d.%d.%d", VERSION_RAW >>> 24, (VERSION_RAW & 0xff0000) >>> 16, VERSION_RAW & 0xFFFF); } /** * when FALSE, some assertion and print code will not execute */ public static boolean IS_DEVELOPMENT_BUILD = true; /** * Singleton instance */ private static App INSTANCE = null; /** * Screen injected at init, so that you run THAT screen instead of the main game. */ private static Screen injectScreen = null; /** * Initialise the application with the alternative Screen you choose * * @param appConfig LWJGL3 Application Configuration * @param injectScreen GDX Screen you want to run */ public App(Lwjgl3ApplicationConfiguration appConfig, Screen injectScreen) { App.injectScreen = injectScreen; App.appConfig = appConfig; } /** * Initialise the application with default game screen * * @param appConfig LWJGL3 Application Configuration */ public App(Lwjgl3ApplicationConfiguration appConfig) { App.appConfig = appConfig; } /** * Default null constructor. Don't use it. */ private App() { } /** * Singleton pattern implementation in Java. * * This function exists because the limitation in the Java language and the design of the GDX itself, where * not everything (more like not every method) can be static. * * @return */ public static App getINSTANCE() { if (INSTANCE == null) { INSTANCE = new App(); } return INSTANCE; } public static String GAME_LOCALE = System.getProperty("user.language") + System.getProperty("user.country"); public static final String systemArch = System.getProperty("os.arch"); public static String processor = "(a super-duper virtual processor)"; public static String processorVendor = "(andromeda software development)"; // definitely not taken from "that" demogroup public static String renderer = "(a super-fancy virtual photoradiator)"; public static String rendererVendor = "(aperture science psychovisualcomputation laboratory)"; public static int THREAD_COUNT = ThreadExecutor.INSTANCE.getThreadCount(); public static boolean MULTITHREAD; public static final boolean is32BitJVM = !System.getProperty("sun.arch.data.model").contains("64"); // some JVMs don't have this property, but they probably don't have "sun.misc.Unsafe" either, so it's no big issue \_(ツ)_/ public static final int GLOBAL_FRAMERATE_LIMIT = 300; /** * These languages won't distinguish regional differences (e.g. enUS and enUK, frFR and frCA) */ private static final String[] localeSimple = {"de", "en", "es", "it"}; // must be sorted!! public static String getSysLang() { String lan = System.getProperty("user.language"); String country = System.getProperty("user.country"); return lan + country; } public static void setGAME_LOCALE(String value) { if (value.isEmpty()) { GAME_LOCALE = getSysLang(); } else { try { if (Arrays.binarySearch(localeSimple, value.substring(0, 2)) >= 0) { GAME_LOCALE = value.substring(0, 2); } else { GAME_LOCALE = value; } } catch (StringIndexOutOfBoundsException e) { GAME_LOCALE = value; } } } private static boolean splashDisplayed = false; private static boolean postInitFired = false; private static boolean screenshotRequested = false; private static boolean resizeRequested = false; private static Point2i resizeReqSize; public static Lwjgl3ApplicationConfiguration appConfig; public static TerrarumScreenSize scr; public static TerrarumGLinfo glInfo = new TerrarumGLinfo(); public static CreateTileAtlas tileMaker; public static TerrarumSansBitmap fontGame; public static TerrarumSansBitmap fontGameFBO; public static TerrarumSansBitmap fontUITitle; public static TinyAlphNum fontSmallNumbers; /** A gamepad. Multiple gamepads may controll this single virtualised gamepad. */ public static TerrarumController gamepad = null; public static float gamepadDeadzone = 0.2f; /** * Sorted by the lastplaytime, in reverse order (index 0 is the most recent game played) */ public static TreeMap savegameWorlds = new TreeMap<>(); public static TreeMap savegameWorldsName = new TreeMap<>(); public static TreeMap savegamePlayers = new TreeMap<>(); public static TreeMap savegamePlayersName = new TreeMap<>(); public static void updateListOfSavegames() { AppUpdateListOfSavegames(); } /** * For the events depends on rendering frame (e.g. flicker on post-hit invincibility) */ public static int GLOBAL_RENDER_TIMER = new Random().nextInt(1020) + 1; public static DebugTimers debugTimers = new DebugTimers(); public static final String FONT_DIR = "assets/graphics/fonts/terrarum-sans-bitmap"; public static Texture[] ditherPatterns = new Texture[8]; private static ShaderProgram shaderBayerSkyboxFill; // ONLY to be used by the splash screen public static ShaderProgram shaderHicolour; public static ShaderProgram shaderDebugDiff; public static ShaderProgram shaderPassthruRGB; public static ShaderProgram shaderColLUT; public static ShaderProgram shaderReflect; public static Mesh fullscreenQuad; private static OrthographicCamera camera; private static SpriteBatch logoBatch; public static TextureRegion logo; public static AudioDevice audioDevice; public static SpriteBatch batch; public static ShapeRenderer shapeRender; private static com.badlogic.gdx.graphics.Color gradWhiteTop = new com.badlogic.gdx.graphics.Color(0xf8f8f8ff); private static com.badlogic.gdx.graphics.Color gradWhiteBottom = new com.badlogic.gdx.graphics.Color(0xd8d8d8ff); private static TerrarumGamescreen currentScreen; private static LoadScreenBase currentSetLoadScreen; private void initViewPort(int width, int height) { // Set Y to point downwards camera.setToOrtho(true, width, height); // some elements are pre-flipped, while some are not. The statement itself is absolutely necessary to make edge of the screen as the origin // Update camera matrix camera.update(); // Set viewport to restrict drawing Gdx.gl20.glViewport(0, 0, width, height); } public static final float UPDATE_RATE = 1f / 64f; // apparent framerate will be limited by update rate private static float loadTimer = 0f; private static final float showupTime = 100f / 1000f; private static FrameBuffer renderFBO; public static HashSet tempFilePool = new HashSet<>(); /** *

If your object is not Disposable, try following code:

* * * App.disposables.add(Disposable { vm.dispose() }) * */ public static HashSet disposables = new HashSet<>(); public static char gamepadLabelStart = 0xE000; // lateinit public static char gamepadLabelSelect = 0xE000; // lateinit public static char gamepadLabelEast = 0xE000; // lateinit public static char gamepadLabelSouth = 0xE000; // lateinit public static char gamepadLabelNorth = 0xE000; // lateinit public static char gamepadLabelWest = 0xE000; // lateinit public static char gamepadLabelLB = 0xE000; // lateinit public static char gamepadLabelRB = 0xE000; // lateinit public static char gamepadLabelLT = 0xE000; // lateinit public static char gamepadLabelRT = 0xE000; // lateinit public static char gamepadLabelLEFT = 0xE068; public static char gamepadLabelDOWN = 0xE069; public static char gamepadLabelUP = 0xE06A; public static char gamepadLabelRIGHT = 0xE06B; public static char gamepadLabelUPDOWN = 0xE072; public static char gamepadLabelLEFTRIGHT = 0xE071; public static char gamepadLabelDPAD = 0xE070; public static char gamepadLabelLStick = 0xE044; public static char gamepadLabelRStick = 0xE045; public static char gamepadLabelLStickPush = 0xE046; public static char gamepadLabelRStickPush = 0xE047; public static String[] gamepadWhitelist = { "xinput", "xbox", "game", "joy", "pad" }; public static InputStrober inputStrober; public static Screen getCurrentScreen() { return currentScreen; } public static void main(String[] args) { // print copyright message System.out.println(csiB+GAME_NAME+" "+csiG+getVERSION_STRING()+" "+csiK+"\u2014"+" "+csi0+TerrarumAppConfiguration.COPYRIGHT_DATE_NAME); System.out.println(csiG+TerrarumAppConfiguration.COPYRIGHT_LICENSE_TERMS_SHORT+csi0); try { // load configs getDefaultDirectory(); createDirs(); initialiseConfig(); readConfigJson(); setGamepadButtonLabels(); try { processor = GetCpuName.getModelName(); } catch (IOException e1) { processor = "Unknown CPU"; } try { processorVendor = GetCpuName.getCPUID(); } catch (IOException e2) { processorVendor = "Unknown CPU"; } ShaderProgram.pedantic = false; scr = new TerrarumScreenSize(getConfigInt("screenwidth"), getConfigInt("screenheight")); int width = scr.getWidth(); int height = scr.getHeight(); Lwjgl3ApplicationConfiguration appConfig = new Lwjgl3ApplicationConfiguration(); //appConfig.useGL30 = false; // https://stackoverflow.com/questions/46753218/libgdx-should-i-use-gl30 appConfig.useVsync(getConfigBoolean("usevsync")); appConfig.setResizable(false); appConfig.setWindowedMode(width, height); int fps = Math.min(GLOBAL_FRAMERATE_LIMIT, getConfigInt("displayfps")); if (fps <= 0) fps = GLOBAL_FRAMERATE_LIMIT; appConfig.setIdleFPS(fps); appConfig.setForegroundFPS(fps); appConfig.setTitle(GAME_NAME); //appConfig.forceExit = true; // it seems KDE 5 likes this one better... // (Plasma freezes upon app exit. with forceExit = true, it's only frozen for a minute; with forceExit = false, it's indefinite) //appConfig.samples = 4; // force the AA on, if the graphics driver didn't do already // load app icon int[] appIconSizes = new int[]{256, 128, 64, 32, 16}; ArrayList appIconPaths = new ArrayList<>(); for (int size : appIconSizes) { String name = "assets/appicon" + size + ".png"; if (new File("./" + name).exists()) { appIconPaths.add("./" + name); } } Object[] iconPathsTemp = appIconPaths.toArray(); appConfig.setWindowIcon(Arrays.copyOf(iconPathsTemp, iconPathsTemp.length, String[].class)); IS_DEVELOPMENT_BUILD = true; // set some more configuration vars MULTITHREAD = THREAD_COUNT >= 3 && getConfigBoolean("multithread"); new Lwjgl3Application(new App(appConfig), appConfig); } catch (Throwable e) { if (Gdx.app != null) { Gdx.app.exit(); } new GameCrashHandler(e); } } @Override public void create() { Gdx.graphics.setContinuousRendering(true); GAME_LOCALE = getConfigString("language"); printdbg(this, "locale = " + GAME_LOCALE); glInfo.create(); CommonResourcePool.INSTANCE.addToLoadingList("blockmarkings_common", () -> new TextureRegionPack(Gdx.files.internal("assets/graphics/blocks/block_markings_common.tga"), 16, 16, 0, 0, 0, 0, false, false, false)); CommonResourcePool.INSTANCE.addToLoadingList("blockmarking_actor", () -> new BlockMarkerActor()); CommonResourcePool.INSTANCE.addToLoadingList("loading_circle_64", () -> new TextureRegionPack(Gdx.files.internal("assets/graphics/gui/loading_circle_64.tga"), 64, 64, 0, 0, 0, 0, false, false, false)); CommonResourcePool.INSTANCE.addToLoadingList("inline_loading_spinner", () -> new TextureRegionPack(Gdx.files.internal("assets/graphics/gui/inline_loading_spinner.tga"), 20, 20, 0, 0, 0, 0, false, false, false)); newTempFile("wenquanyi.tga"); // temp file required by the font // set basis of draw logoBatch = new SpriteBatch(); camera = new OrthographicCamera((scr.getWf()), (scr.getHf())); batch = new SpriteBatch(); shapeRender = new ShapeRenderer(); initViewPort(scr.getWidth(), scr.getHeight()); // logo here :p logo = new TextureRegion(new Texture(Gdx.files.internal("assets/graphics/logo_placeholder.tga"))); logo.flip(false, true); CommonResourcePool.INSTANCE.addToLoadingList("title_health1", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_take_a_break.tga"))); CommonResourcePool.INSTANCE.addToLoadingList("title_health2", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_distance.tga"))); // set GL graphics constants for (int i = 0; i < ditherPatterns.length; i++) { Texture t = new Texture(Gdx.files.internal("assets/dither_512_"+i+".tga")); t.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Linear); t.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat); ditherPatterns[i] = t; } shaderBayerSkyboxFill = loadShaderFromFile("assets/4096.vert", "assets/4096_bayer_skyboxfill.frag"); shaderHicolour = loadShaderFromFile("assets/4096.vert", "assets/hicolour.frag"); shaderDebugDiff = loadShaderFromFile("assets/4096.vert", "assets/diff.frag"); shaderPassthruRGB = SpriteBatch.createDefaultShader(); shaderColLUT = loadShaderFromFile("assets/4096.vert", "assets/passthrurgb.frag"); shaderReflect = loadShaderFromFile("assets/4096.vert", "assets/reflect.frag"); fullscreenQuad = new Mesh( true, 4, 6, VertexAttribute.Position(), VertexAttribute.ColorUnpacked(), VertexAttribute.TexCoords(0) ); updateFullscreenQuad(scr.getWidth(), scr.getHeight()); // set up renderer info variables renderer = Gdx.graphics.getGLVersion().getRendererString(); rendererVendor = Gdx.graphics.getGLVersion().getVendorString(); // make gamepad(s) if (App.getConfigBoolean("usexinput")) { try { gamepad = new XinputControllerAdapter(XInputDevice.getDeviceFor(0)); } catch (Throwable e) { gamepad = null; } // nullify if not actually connected try { if (!((XinputControllerAdapter) gamepad).getC().isConnected()) { gamepad = null; } } catch (NullPointerException notQuiteWindows) { gamepad = null; } } if (gamepad == null) { try { gamepad = new GdxControllerAdapter(Controllers.getControllers().get(0)); } catch (Throwable e) { gamepad = null; } } // tell the game that we have a gamepad environment = RunningEnvironment.PC; if (gamepad != null) { String name = gamepad.getName().toLowerCase(); for (String allowedName : gamepadWhitelist) { if (name.contains(allowedName)) { environment = RunningEnvironment.CONSOLE; break; } } } /*if (gamepad != null) { environment = RunningEnvironment.CONSOLE; // calibrate the sticks printdbg(this, "Calibrating the gamepad..."); float[] axesZeroPoints = new float[]{ gamepad.getAxisRaw(0), gamepad.getAxisRaw(1), gamepad.getAxisRaw(2), gamepad.getAxisRaw(3) }; setConfig("control_gamepad_axiszeropoints", axesZeroPoints); for (int i = 0; i < 4; i++) { printdbg(this, "Axis " + i + ": " + axesZeroPoints[i]); } } else { environment = RunningEnvironment.PC; }*/ fontGame = new TerrarumSansBitmap(FONT_DIR, false, true, false, false, 256, false, 0.5f, false ); fontUITitle = new TerrarumSansBitmap(FONT_DIR, false, true, false, false, 64, false, 0.5f, false ); fontUITitle.setInterchar(2); fontGameFBO = new TerrarumSansBitmap(FONT_DIR, false, true, false, false, 64, false, 203f/255f, false ); Lang.invoke(); // make loading list CommonResourcePool.INSTANCE.loadAll(); } @Override public void render() { Gdx.gl.glDisable(GL20.GL_DITHER); if (splashDisplayed && !postInitFired) { postInitFired = true; postInit(); } App.setDebugTime("GDX.rawDelta", (long) (Gdx.graphics.getDeltaTime() * 1000_000_000f)); FrameBufferManager.begin(renderFBO); gdxClearAndSetBlend(.094f, .094f, .094f, 0f); setCameraPosition(0, 0); // draw splash screen when predefined screen is null // because in normal operation, the only time screen == null is when the app is cold-launched // you can't have a text drawn here :v if (currentScreen == null) { drawSplash(); loadTimer += Gdx.graphics.getDeltaTime(); if (loadTimer >= showupTime) { // hand over the scene control to this single class; Terrarum must call // 'AppLoader.getINSTANCE().screen.render(delta)', this is not redundant at all! printdbg(this, "!! Force set current screen and ingame instance to TitleScreen !!"); IngameInstance title = new TitleScreen(batch); Terrarum.INSTANCE.setCurrentIngameInstance(title); setScreen(title); } } // draw the screen else { currentScreen.render(UPDATE_RATE); } KeyToggler.INSTANCE.update(currentScreen instanceof TerrarumIngame); // nested FBOs are just not a thing in GL! net.torvald.terrarum.FrameBufferManager.end(); PostProcessor.INSTANCE.draw(camera.combined, renderFBO); // process resize request if (resizeRequested) { resizeRequested = false; resize(resizeReqSize.getX(), resizeReqSize.getY()); } // process screenshot request if (screenshotRequested) { screenshotRequested = false; try { Pixmap p = Pixmap.createFromFrameBuffer(0, 0, scr.getWidth(), scr.getHeight()); PixmapIO2.writeTGA(Gdx.files.absolute(defaultDir+"/Screenshot-"+String.valueOf(System.currentTimeMillis())+".tga"), p, true); p.dispose(); Terrarum.INSTANCE.getIngame().sendNotification("Screenshot taken"); } catch (Throwable e) { e.printStackTrace(); Terrarum.INSTANCE.getIngame().sendNotification("Failed to take screenshot: "+e.getMessage()); } } splashDisplayed = true; GLOBAL_RENDER_TIMER += 1; } public static Texture getCurrentDitherTex() { int hash = 31 + GLOBAL_RENDER_TIMER + 0x165667B1 + GLOBAL_RENDER_TIMER * 0xC2B2AE3D; hash = Integer.rotateLeft(hash, 17) * 0x27D4EB2F; hash ^= hash >>> 15; hash *= 0x85EBCA77; hash ^= hash >>> 13; hash *= 0xC2B2AE3D; hash ^= hash >>> 16; hash = hash & 0x7FFFFFFF; return ditherPatterns[hash % ditherPatterns.length]; } private void drawSplash() { getCurrentDitherTex().bind(0); shaderBayerSkyboxFill.bind(); shaderBayerSkyboxFill.setUniformMatrix("u_projTrans", camera.combined); shaderBayerSkyboxFill.setUniformi("u_texture", 0); shaderBayerSkyboxFill.setUniformf("parallax_size", 0f); shaderBayerSkyboxFill.setUniformf("topColor", gradWhiteTop.r, gradWhiteTop.g, gradWhiteTop.b, 1f); shaderBayerSkyboxFill.setUniformf("bottomColor", gradWhiteBottom.r, gradWhiteBottom.g, gradWhiteBottom.b, 1f); fullscreenQuad.render(shaderBayerSkyboxFill, GL20.GL_TRIANGLES); setCameraPosition(0f, 0f); int drawWidth = Toolkit.INSTANCE.getDrawWidth(); int safetyTextLen = fontGame.getWidth(Lang.INSTANCE.get("APP_WARNING_HEALTH_AND_SAFETY", true)); int logoPosX = (drawWidth - logo.getRegionWidth() - safetyTextLen) >>> 1; int logoPosY = Math.round(scr.getHeight() / 15f); int textY = logoPosY + logo.getRegionHeight() - 16; // draw logo reflection logoBatch.setShader(shaderReflect); logoBatch.setColor(Color.WHITE); logoBatch.begin(); if (getConfigBoolean("showhealthmessageonstartup")) { logoBatch.draw(logo, logoPosX, logoPosY + logo.getRegionHeight()); } else { logoBatch.draw(logo, (drawWidth - logo.getRegionWidth()) / 2f, (scr.getHeight() - logo.getRegionHeight() * 2) / 2f + logo.getRegionHeight() ); } logoBatch.end(); // draw health messages logoBatch.setShader(null); logoBatch.begin(); if (getConfigBoolean("showhealthmessageonstartup")) { logoBatch.draw(logo, logoPosX, logoPosY); logoBatch.setColor(new Color(0x282828ff)); fontGame.draw(logoBatch, Lang.INSTANCE.get("APP_WARNING_HEALTH_AND_SAFETY", true), logoPosX + logo.getRegionWidth(), textY ); // some chinese stuff if (GAME_LOCALE.contentEquals("zhCN")) { for (int i = 1; i <= 4; i++) { String s = Lang.INSTANCE.get("APP_CHINESE_HEALTHY_GAME_MSG_" + i, true); fontGame.draw(logoBatch, s, (drawWidth - fontGame.getWidth(s)) >>> 1, Math.round(scr.getHeight() * 12f / 15f + fontGame.getLineHeight() * (i - 1)) ); } } logoBatch.setColor(new Color(0x282828ff)); Texture tex1 = CommonResourcePool.INSTANCE.getAsTexture("title_health1"); Texture tex2 = CommonResourcePool.INSTANCE.getAsTexture("title_health2"); int virtualHeight = scr.getHeight() - logoPosY - logo.getRegionHeight() / 4; int virtualHeightOffset = scr.getHeight() - virtualHeight; logoBatch.draw(tex1, (drawWidth - tex1.getWidth()) >>> 1, virtualHeightOffset + (virtualHeight >>> 1) - 16, tex1.getWidth(), -tex1.getHeight()); logoBatch.draw(tex2, (drawWidth - tex2.getWidth()) >>> 1, virtualHeightOffset + (virtualHeight >>> 1) + 16 + tex2.getHeight(), tex2.getWidth(), -tex2.getHeight()); } else { logoBatch.draw(logo, (drawWidth - logo.getRegionWidth()) / 2f, (scr.getHeight() - logo.getRegionHeight() * 2) / 2f ); } logoBatch.end(); } @Override public void resize(int width, int height) { printdbg(this, "Resize called"); printStackTrace(this); //initViewPort(width, height); scr.setDimension(width, height); if (currentScreen != null) currentScreen.resize(scr.getWidth(), scr.getHeight()); updateFullscreenQuad(scr.getWidth(), scr.getHeight()); if (renderFBO == null || (renderFBO.getWidth() != scr.getWidth() || renderFBO.getHeight() != scr.getHeight()) ) { renderFBO = new FrameBuffer( Pixmap.Format.RGBA8888, scr.getWidth(), scr.getHeight(), false ); } printdbg(this, "Resize end"); } public static void resizeScreen(int width, int height) { resizeRequested = true; resizeReqSize = new Point2i(width, height); } @Override public void dispose() { System.out.println("Goodbye !"); if (currentScreen != null) { currentScreen.hide(); currentScreen.dispose(); } //IngameRenderer.INSTANCE.dispose(); //PostProcessor.INSTANCE.dispose(); //MinimapComposer.INSTANCE.dispose(); //FloatDrawer.INSTANCE.dispose(); ThreadExecutor.INSTANCE.killAll(); for (Texture texture : ditherPatterns) { texture.dispose(); } shaderBayerSkyboxFill.dispose(); shaderHicolour.dispose(); shaderDebugDiff.dispose(); shaderPassthruRGB.dispose(); shaderColLUT.dispose(); shaderReflect.dispose(); CommonResourcePool.INSTANCE.dispose(); fullscreenQuad.dispose(); logoBatch.dispose(); batch.dispose(); shapeRender.dispose(); fontGame.dispose(); fontGameFBO.dispose(); fontSmallNumbers.dispose(); ItemSlotImageFactory.INSTANCE.dispose(); logo.getTexture().dispose(); disposables.forEach((it) -> { try { it.dispose(); } catch (NullPointerException | IllegalArgumentException | GdxRuntimeException e) { } }); ModMgr.INSTANCE.disposeMods(); GameWorld.Companion.makeNullWorld().dispose(); Terrarum.INSTANCE.dispose(); inputStrober.dispose(); deleteTempfiles(); } @Override public void pause() { if (currentScreen != null) currentScreen.pause(); } @Override public void resume() { if (currentScreen != null) currentScreen.resume(); } public static LoadScreenBase getLoadScreen() { return currentSetLoadScreen; } public static void setLoadScreen(LoadScreenBase screen) { currentSetLoadScreen = screen; _setScr(screen); } public static void setScreen(Screen screen) { if (!(screen instanceof TerrarumGamescreen)) { throw new IllegalArgumentException("Screen must be instance of TerrarumGameScreen: " + screen.getClass().getCanonicalName()); } if (screen instanceof LoadScreenBase) { throw new RuntimeException( "Loadscreen '" + screen.getClass().getSimpleName() + "' must be set with 'setLoadScreen()' method"); } _setScr((TerrarumGamescreen) screen); } private static void _setScr(TerrarumGamescreen screen) { printdbg("AppLoader-Static", "Changing screen to " + screen.getClass().getCanonicalName()); // this whole thing is directtly copied from com.badlogic.gdx.Game if (currentScreen != null) { printdbg("AppLoader-Static", "Screen before change: " + currentScreen.getClass().getCanonicalName()); currentScreen.hide(); currentScreen.dispose(); } else { printdbg("AppLoader-Static", "Screen before change: null"); } currentScreen = screen; currentScreen.show(); currentScreen.resize(scr.getWidth(), scr.getHeight()); System.gc(); printdbg("AppLoader-Static", "Screen transition complete: " + currentScreen.getClass().getCanonicalName()); } /** * Init stuffs which needs GL context */ private void postInit() { // create tile atlas printdbg(this, "Making terrain textures..."); tileMaker = new CreateTileAtlas(); tileMaker.invoke(false); IME.invoke(); inputStrober = InputStrober.INSTANCE; // check if selected IME is accessible; if not, set selected IME to none String selectedIME = getConfigString("inputmethod"); if (!selectedIME.equals("none") && !IME.INSTANCE.getAllHighLayers().contains(selectedIME)) { setConfig("inputmethod", "none"); } Terrarum.initialise(); TextureRegionPack.Companion.setGlobalFlipY(true); fontSmallNumbers = TinyAlphNum.INSTANCE; try { audioDevice = Gdx.audio.newAudioDevice(48000, false); } catch (NullPointerException deviceInUse) { deviceInUse.printStackTrace(); System.err.println("[AppLoader] failed to create audio device: Audio device occupied by Exclusive Mode Device? (e.g. ASIO4all)"); } // if there is a predefined screen, open that screen after my init process if (injectScreen != null) { setScreen(injectScreen); } else { ModMgr.INSTANCE.invoke(); // invoke Module Manager CommonResourcePool.INSTANCE.loadAll(); printdbg(this, "all modules loaded successfully"); IngameRenderer.initialise(); } printdbg(this, "PostInit done"); } private void setCameraPosition(float newX, float newY) { camera.position.set((-newX + scr.getWidth() / 2), (-newY + scr.getHeight() / 2), 0f); // deliberate integer division camera.update(); logoBatch.setProjectionMatrix(camera.combined); } private void updateFullscreenQuad(int WIDTH, int HEIGHT) { // NOT y-flipped quads! fullscreenQuad.setVertices(new float[]{ 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 1f, WIDTH, 0f, 0f, 1f, 1f, 1f, 1f, 1f, 1f, WIDTH, HEIGHT, 0f, 1f, 1f, 1f, 1f, 1f, 0f, 0f, HEIGHT, 0f, 1f, 1f, 1f, 1f, 0f, 0f }); fullscreenQuad.setIndices(new short[]{0, 1, 2, 2, 3, 0}); } public static void setGamepadButtonLabels() { switch (getConfigString("control_gamepad_labelstyle")) { case "nwii" : gamepadLabelStart = 0xE04B; break; // + mark case "logitech" : gamepadLabelStart = 0xE05A; break; // number 10 case "msxbone" : gamepadLabelStart = 0xE049; break; // trifold equal sign? default : gamepadLabelStart = 0xE042; break; // |> mark (sonyps, msxb360, generic) } switch (getConfigString("control_gamepad_labelstyle")) { case "nwii" : gamepadLabelSelect = 0xE04D; break; // - mark case "logitech" : gamepadLabelSelect = 0xE059; break; // number 9 case "sonyps" : gamepadLabelSelect = 0xE043; break; // solid rectangle case "msxb360" : gamepadLabelSelect = 0xE041; break; // <| mark case "msxbone" : gamepadLabelSelect = 0xE048; break; // multitask button? default : gamepadLabelSelect = 0xE043; break; // solid rectangle } switch (getConfigString("control_gamepad_labelstyle")) { case "msxb360": case "msxbone" : { gamepadLabelSouth = 0xE061; gamepadLabelEast = 0xE062; gamepadLabelWest = 0xE078; gamepadLabelNorth = 0xE079; gamepadLabelLB = 0xE06D; gamepadLabelRB = 0xE06E; gamepadLabelLT = 0xE06C; gamepadLabelRT = 0xE06F; break; } case "nwii": { gamepadLabelSouth = 0xE062; gamepadLabelEast = 0xE061; gamepadLabelWest = 0xE079; gamepadLabelNorth = 0xE078; gamepadLabelLB = 0xE065; gamepadLabelRB = 0xE066; gamepadLabelLT = 0xE064; gamepadLabelRT = 0xE067; break; } case "sonyps": { gamepadLabelSouth = 0xE063; gamepadLabelEast = 0xE050; gamepadLabelWest = 0xE073; gamepadLabelNorth = 0xE074; gamepadLabelLB = 0xE07B; gamepadLabelRB = 0xE07C; gamepadLabelLT = 0xE07A; gamepadLabelRT = 0xE07D; break; } case "logitech": { gamepadLabelSouth = 0xE052; gamepadLabelEast = 0xE053; gamepadLabelWest = 0xE051; gamepadLabelNorth = 0xE054; gamepadLabelLB = 0xE055; gamepadLabelRB = 0xE056; gamepadLabelLT = 0xE057; gamepadLabelRT = 0xE058; break; } } } public static void requestScreenshot() { screenshotRequested = true; } // DEFAULT DIRECTORIES // public static String OSName = System.getProperty("os.name"); public static String OSVersion = System.getProperty("os.version"); public static String operationSystem; /** %appdata%/Terrarum, without trailing slash */ public static String defaultDir; /** For Demo version only. defaultDir + "/Saves", without trailing slash */ public static String saveDir; /** For shared materials (e.g. image of a computer disk). defaultDir + "/Shared", without trailing slash */ public static String saveSharedDir; /** For the main game where any players can access any world (unless flagged as private). defaultDir + "/Players", without trailing slash */ public static String playersDir; /** For the main game. defaultDir + "/Worlds", without trailing slash */ public static String worldsDir; /** defaultDir + "/config.json" */ public static String configDir; public static RunningEnvironment environment; private static void getDefaultDirectory() { String OS = OSName.toUpperCase(); if (OS.contains("WIN")) { operationSystem = "WINDOWS"; defaultDir = System.getenv("APPDATA") + "/Terrarum"; } else if (OS.contains("OS X")) { operationSystem = "OSX"; defaultDir = System.getProperty("user.home") + "/Library/Application Support/Terrarum"; } else if (OS.contains("NUX") || OS.contains("NIX") || OS.contains("BSD")) { operationSystem = "LINUX"; defaultDir = System.getProperty("user.home") + "/.Terrarum"; } else if (OS.contains("SUNOS")) { operationSystem = "SOLARIS"; defaultDir = System.getProperty("user.home") + "/.Terrarum"; } else { operationSystem = "UNKNOWN"; defaultDir = System.getProperty("user.home") + "/.Terrarum"; } saveDir = defaultDir + "/Saves"; // for the demo release saveSharedDir = defaultDir + "/Shared"; playersDir = defaultDir + "/Players"; worldsDir = defaultDir + "/Worlds"; configDir = defaultDir + "/config.json"; System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem)); System.out.println(String.format("os.version = %s", OSVersion)); System.out.println(String.format("default directory: %s", defaultDir)); System.out.println(String.format("java version = %s", System.getProperty("java.version"))); } private static void createDirs() { File[] dirs = {new File(saveDir), new File(saveSharedDir), new File(playersDir), new File(worldsDir)}; for (File it : dirs) { if (!it.exists()) it.mkdirs(); } //dirs.forEach { if (!it.exists()) it.mkdirs() } } public static File newTempFile(String filename) { File tempfile = new File("./tmp_" + filename); tempFilePool.add(tempfile); return tempfile; } private static void deleteTempfiles() { for (File file : tempFilePool) { file.delete(); } } // CONFIG // public static KVHashMap gameConfig = new KVHashMap(); private static void createConfigJson() throws IOException { File configFile = new File(configDir); if (!configFile.exists() || configFile.length() == 0L) { WriteConfig.INSTANCE.invoke(); } } /** * Reads DefaultConfig to populate the gameConfig */ private static void initialiseConfig() { for (Map.Entry entry : DefaultConfig.INSTANCE.getHashMap().entrySet()) { gameConfig.set(entry.getKey(), entry.getValue()); } } /** * * @return true on successful, false on failure. */ private static Boolean readConfigJson() { try { // read from disk and build config from it JsonValue map = JsonFetcher.INSTANCE.invoke(configDir); // make config for (JsonValue entry = map.child; entry != null; entry = entry.next) { gameConfig.set(entry.name, entry.isArray() ? entry.asDoubleArray() : entry.isDouble() ? entry.asDouble() : entry.isBoolean() ? entry.asBoolean() : entry.isLong() ? entry.asInt() : entry.asString() ); } return true; } catch (java.nio.file.NoSuchFileException e) { // write default config to game dir. Call th.is method again to read config from it. try { createConfigJson(); } catch (IOException e1) { System.out.println("[AppLoader] Unable to write config.json file"); e.printStackTrace(); } return false; } } /** * Return config from config set. If the config does not exist, default value will be returned. * @param key * * * @return Config from config set or default config if it does not exist. * * * @throws NullPointerException if the specified config simply does not exist. */ public static int getConfigInt(String key) { Object cfg = getConfigMaster(key); return ((int) cfg); } /** * Return config from config set. If the config does not exist, default value will be returned. * @param key * * * @return Config from config set or default config if it does not exist. * * * @throws NullPointerException if the specified config simply does not exist. */ public static String getConfigString(String key) { Object cfg = getConfigMaster(key); return ((String) cfg); } /** * Return config from config set. If the config does not exist, default value will be returned. * @param key * * * @return Config from config set or default config if it does not exist. If the default value is undefined, will return false. */ public static boolean getConfigBoolean(String key) { try { Object cfg = getConfigMaster(key); return ((boolean) cfg); } catch (NullPointerException keyNotFound) { return false; } } /*public static int[] getConfigIntArray(String key) { Object cfg = getConfigMaster(key); if (cfg instanceof JsonArray) { JsonArray jsonArray = ((JsonArray) cfg).getAsJsonArray(); //return IntArray(jsonArray.size(), { i -> jsonArray[i].asInt }) int[] intArray = new int[jsonArray.size()]; for (int i = 0; i < jsonArray.size(); i++) { intArray[i] = jsonArray.get(i).getAsInt(); } return intArray; } else return ((int[]) cfg); }*/ public static double[] getConfigDoubleArray(String key) { Object cfg = getConfigMaster(key); return ((double[]) cfg); } public static int[] getConfigIntArray(String key) { double[] a = getConfigDoubleArray(key); int[] r = new int[a.length]; for (int i = 0; i < a.length; i++) { r[i] = ((int) a[i]); } return r; } /*public static String[] getConfigStringArray(String key) { Object cfg = getConfigMaster(key); if (cfg instanceof JsonArray) { JsonArray jsonArray = ((JsonArray) cfg).getAsJsonArray(); //return IntArray(jsonArray.size(), { i -> jsonArray[i].asInt }) String[] intArray = new String[jsonArray.size()]; for (int i = 0; i < jsonArray.size(); i++) { intArray[i] = jsonArray.get(i).getAsString(); } return intArray; } else return ((String[]) cfg); }*/ /** * Get config from config file. If the entry does not exist, get from defaults; if the entry is not in the default, NullPointerException will be thrown */ private static HashMap getDefaultConfig() { return DefaultConfig.INSTANCE.getHashMap(); } private static Object getConfigMaster(String key1) { String key = key1.toLowerCase(); Object config; try { config = gameConfig.get(key); } catch (NullPointerException e) { config = null; } Object defaults; try { defaults = getDefaultConfig().get(key); } catch (NullPointerException e) { defaults = null; } if (config == null) { if (defaults == null) { throw new NullPointerException("key not found: '" + key + "'"); } else { return defaults; } } else { return config; } } public static void setConfig(String key, Object value) { gameConfig.set(key.toLowerCase(), value); } // // public static String csiR = "\u001B[31m"; public static String csiG = "\u001B[32m"; public static String csiB = "\u001B[34m"; public static String csiK = "\u001B[37m"; public static String csi0 = "\u001B[m"; public static void printdbg(Object obj, Object message) { if (IS_DEVELOPMENT_BUILD) { String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName(); if (message == null) { System.out.println("[" + out + "] null"); return; } else { String indentation = " ".repeat(out.length() + 3); String[] msgLines = message.toString().split("\\n"); for (int i = 0; i < msgLines.length; i++) { System.out.println((i == 0 ? "[" + out + "] " : indentation) + msgLines[i]); } } } } public static void printdbgerr(Object obj, Object message) { if (IS_DEVELOPMENT_BUILD) { String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName(); if (message == null) System.out.println(csiR + "[" + out + "] null" + csi0); else System.out.println(csiR + "[" + out + "] " + message + csi0); } } public static void printmsg(Object obj, Object message) { String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName(); if (message == null) System.out.println("[" + out + "] null"); else System.out.println("[" + out + "] " + message); } public static ShaderProgram loadShaderFromFile(String vert, String frag) { ShaderProgram s = new ShaderProgram(Gdx.files.internal(vert), Gdx.files.internal(frag)); if (s.getLog().toLowerCase().contains("error")) { throw new Error(String.format("Shader program loaded with %s, %s failed:\n%s", vert, frag, s.getLog())); } return s; } public static ShaderProgram loadShaderInline(String vert, String frag) { ShaderProgram s = new ShaderProgram(vert, frag); if (s.getLog().toLowerCase().contains("error")) { throw new Error(String.format("Shader program loaded with %s, %s failed:\n%s", vert, frag, s.getLog())); } return s; } public static void measureDebugTime(String name, kotlin.jvm.functions.Function0 block) { if (IS_DEVELOPMENT_BUILD) { //debugTimers.put(name, kotlin.system.TimingKt.measureNanoTime(block)); long start = System.nanoTime(); block.invoke(); debugTimers.put(name, System.nanoTime() - start); } } public static void setDebugTime(String name, long value) { if (IS_DEVELOPMENT_BUILD) { debugTimers.put(name, value); } } public static void addDebugTime(String target, String... targets) { if (IS_DEVELOPMENT_BUILD) { long l = 0L; for (String s : targets) { l += ((long) debugTimers.get(s)); } debugTimers.put(target, l); } } public static long getTIME_T() { return System.currentTimeMillis() / 1000L; } /** * Just an event handler I'm slipping in * @param event */ public static void inputStrobed(TerrarumKeyboardEvent event) { currentScreen.inputStrobed(event); } }