Files
Terrarum/src/net/torvald/terrarum/App.java
2023-10-04 21:42:15 +09:00

1734 lines
63 KiB
Java

package net.torvald.terrarum;
import com.badlogic.gdx.*;
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.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.*;
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.getcpuname.GetCpuName;
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.gameitems.GameItem;
import net.torvald.terrarum.gameworld.GameWorld;
import net.torvald.terrarum.imagefont.BigAlphNum;
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.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.unsafe.AddressOverflowException;
import net.torvald.unsafe.DanglingPointerException;
import net.torvald.unsafe.UnsafeHelper;
import net.torvald.util.DebugTimers;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
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 long startupTime = System.currentTimeMillis() / 1000L;
public static final String GAME_NAME = TerrarumAppConfiguration.GAME_NAME;
public static final long VERSION_RAW = TerrarumAppConfiguration.VERSION_RAW;
public static final String VERSION_TAG = TerrarumAppConfiguration.VERSION_TAG;
public static final String getVERSION_STRING() {
var snap = TerrarumAppConfiguration.INSTANCE.getVERSION_SNAPSHOT();
return String.format("%d.%d.%d", VERSION_RAW >>> 48, (VERSION_RAW & 0xffff000000L) >>> 24, VERSION_RAW & 0xffffffL) +
(VERSION_TAG.isBlank() ? "" : "-"+VERSION_TAG) + (snap == null ? "" : (" (" + snap + ")"));
}
/**
* when FALSE, some assertion and print code will not execute
*/
public static boolean IS_DEVELOPMENT_BUILD = false;
/**
* 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)";
/**
* True if the processor's name starts with "Apple M" and not running through Rosetta.
* To detect the presence of the M-chips only, use App.processor.startsWith("Apple M")
*/
public static boolean isAppleM = false;
public static int THREAD_COUNT = Runtime.getRuntime().availableProcessors();
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;
private static String undesirableConditions;
public static String getUndesirableConditions() {
return undesirableConditions;
}
/**
* 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;
/** Vanilla */
public static TerrarumSansBitmap fontGame;
/** Vertically flipped */
public static TerrarumSansBitmap fontGameFBO;
/** Big interchar */
public static TerrarumSansBitmap fontUITitle;
public static TinyAlphNum fontSmallNumbers;
public static BigAlphNum fontBigNumbers;
/** 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 ArrayList<UUID> sortedSavegameWorlds = new ArrayList();
public static HashMap<UUID, SavegameCollection> savegameWorlds = new HashMap<>(); // UNSORTED even with the TreeMap
public static HashMap<UUID, String> savegameWorldsName = new HashMap<>();
public static ArrayList<UUID> sortedPlayers = new ArrayList();
public static HashMap<UUID, SavegameCollection> savegamePlayers = new HashMap<>();
public static HashMap<UUID, String> savegamePlayersName = new HashMap<>();
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[4];
private static ShaderProgram shaderBayerSkyboxFill; // ONLY to be used by the splash screen
public static ShaderProgram shaderHicolour;
public static ShaderProgram shaderDebugDiff;
public static ShaderProgram shaderPassthruRGBA;
public static ShaderProgram shaderColLUT;
public static ShaderProgram shaderReflect;
public static ShaderProgram shaderGhastlyWhite;
public static Hq2x hq2x;
public static Mesh fullscreenQuad;
private static OrthographicCamera camera;
private static FlippingSpriteBatch logoBatch;
public static TextureRegion logo;
public static AudioDevice audioDevice;
public static FlippingSpriteBatch 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 int TICK_SPEED = 64;
public static final float UPDATE_RATE = 1f / TICK_SPEED; // apparent framerate will be limited by update rate
private static float loadTimer = 0f;
private static final float showupTime = 100f / 1000f;
private static Float16FrameBuffer renderFBO;
public static HashSet<File> tempFilePool = new HashSet<>();
/**
* <p>If your object is not Disposable, try following code:</p>
*
* <code>
* App.disposables.add(Disposable { vm.dispose() })
* </code>
*/
public static HashSet<Disposable> 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 long bogoflops = 0L;
private static double bogoflopf = Math.random();
public static boolean hasUpdate = true;
public static Screen getCurrentScreen() {
return currentScreen;
}
public static ShapeRenderer makeShapeRenderer() {
return new ShapeRenderer(5000, DefaultGL32Shaders.INSTANCE.createShapeRendererShader());
}
public static boolean gl40capable = false;
public static void main(String[] args) {
long st = System.nanoTime();
long sc = st;
while (sc - st < 100000000L) {
bogoflopf = Math.random() * bogoflopf;
bogoflops++;
sc = System.nanoTime();
}
bogoflops = Math.round((double)(bogoflops) * (1000000000.0 / (sc - st)));
// System.out.println(sc - st);
// System.out.println(bogoflops);
// if -ea flag is set, turn on all the debug prints
try {
assert false;
}
catch (AssertionError e) {
IS_DEVELOPMENT_BUILD = true;
}
// 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);
System.out.println("IS_DEVELOPMENT_BUILD = " + IS_DEVELOPMENT_BUILD);
try {
try {
processor = GetCpuName.getModelName();
}
catch (IOException e1) {
processor = "Unknown CPU";
}
try {
processorVendor = GetCpuName.getCPUID();
}
catch (IOException e2) {
processorVendor = "Unknown CPU";
}
if (processor.startsWith("Apple M") && systemArch.equals("aarch64")) {
isAppleM = true;
System.out.println("Apple Proprietary "+processor+" detected; don't expect smooth sailing...");
}
if (processor.startsWith("Apple M") && !systemArch.equals("aarch64")) {
undesirableConditions = "apple_execution_through_rosetta";
}
if (!IS_DEVELOPMENT_BUILD) {
var p = UnsafeHelper.INSTANCE.allocate(64);
p.destroy();
try {
p.get(0);
}
catch (DanglingPointerException | AddressOverflowException e) {
throw new RuntimeException("Build Error: App is not Development Build but pointer check is still installed. If the game is a production release, please report this to the developers.");
}
}
// load configs
getDefaultDirectory();
createDirs();
initialiseConfig();
readConfigJson();
setGamepadButtonLabels();
rectifyConfigs();
ShaderProgram.pedantic = false;
scr = new TerrarumScreenSize(getConfigInt("screenwidth"), getConfigInt("screenheight"));
int width = scr.getWindowW();
int height = scr.getWindowH();
boolean useFullscreen = getConfigBoolean("fullscreen");
Lwjgl3ApplicationConfiguration appConfig = new Lwjgl3ApplicationConfiguration();
//appConfig.useGL30 = false; // https://stackoverflow.com/questions/46753218/libgdx-should-i-use-gl30
if (processor.startsWith("Apple M")) appConfig.setOpenGLEmulation(Lwjgl3ApplicationConfiguration.GLEmulation.GL30, 3, 2);
appConfig.useVsync(getConfigBoolean("usevsync"));
appConfig.setResizable(false);
if (useFullscreen) {
// auto resize for fullscreen
var disp = Lwjgl3ApplicationConfiguration.getDisplayMode();
float magn = (float) getConfigDouble("screenmagnifying");
var newWidth = ((int)(disp.width / magn)) & 0x7FFFFFFE;
var newHeight = ((int)(disp.height / magn)) & 0x7FFFFFFE;
scr.setDimension(newWidth, newHeight, magn);
appConfig.setFullscreenMode(Lwjgl3ApplicationConfiguration.getDisplayMode());
}
else
appConfig.setWindowedMode(width, height);
appConfig.setTransparentFramebuffer(false);
int fpsActive = Math.min(GLOBAL_FRAMERATE_LIMIT, getConfigInt("displayfps"));
if (fpsActive <= 0) fpsActive = GLOBAL_FRAMERATE_LIMIT;
int fpsBack = Math.min(GLOBAL_FRAMERATE_LIMIT, getConfigInt("displayfpsidle"));
if (fpsBack <= 0) fpsBack = GLOBAL_FRAMERATE_LIMIT;
appConfig.setIdleFPS(fpsBack);
appConfig.setForegroundFPS(fpsActive);
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
appConfig.setWindowIcon(Files.FileType.Classpath,
"res/appicon512.png",
"res/appicon256.png",
"res/appicon144.png",
"res/appicon128.png",
"res/appicon96.png",
"res/appicon64.png",
"res/appicon48.png",
"res/appicon32.png",
"res/appicon16.png"
);
// 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();
gl40capable = (Gdx.graphics.getGLVersion().getMajorVersion() >= 4);
printdbg(this, "GL40 capable? "+gl40capable);
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")));
// make loading list
CommonResourcePool.INSTANCE.loadAll();
newTempFile("wenquanyi.tga"); // temp file required by the font
// set basis of draw
logoBatch = new FlippingSpriteBatch();
camera = new OrthographicCamera((scr.getWf()), (scr.getHf()));
batch = new FlippingSpriteBatch();
shapeRender = makeShapeRenderer();
initViewPort(scr.getWidth(), scr.getHeight());
// logo here :p
logo = new TextureRegion(new Texture(Gdx.files.internal("assets/graphics/logo_placeholder.tga")));
logo.flip(false, false);
// set GL graphics constants
for (int i = 0; i < ditherPatterns.length; i++) {
Texture t = new Texture(Gdx.files.classpath("shaders/dither_512_"+i+".tga"));
t.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Linear);
t.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);
ditherPatterns[i] = t;
}
shaderBayerSkyboxFill = loadShaderFromClasspath("shaders/default.vert",
"shaders/float_to_disp_dither_static.frag"
);
shaderPassthruRGBA = loadShaderFromClasspath("shaders/gl32spritebatch.vert", "shaders/gl32spritebatch.frag");
shaderReflect = loadShaderFromClasspath("shaders/default.vert", "shaders/reflect.frag");
hq2x = new Hq2x(2);
fullscreenQuad = new Mesh(
true, 4, 4,
VertexAttribute.Position(),
VertexAttribute.ColorUnpacked(),
VertexAttribute.TexCoords(0)
);
updateFullscreenQuad(fullscreenQuad, scr.getWidth(), scr.getHeight());
// set up renderer info variables
renderer = Gdx.graphics.getGLVersion().getRendererString();
rendererVendor = Gdx.graphics.getGLVersion().getVendorString();
fontGame = new TerrarumSansBitmap(FONT_DIR, false, false, false,
false,
256, false, 0.5f, false
);
}
private FrameBuffer postProcessorOutFBO;
private FrameBuffer postProcessorOutFBO2;
@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);
gdxClearAndEnableBlend(.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!
IngameInstance title = ModMgr.INSTANCE.getTitleScreen(batch);
if (title != null) {
Terrarum.INSTANCE.setCurrentIngameInstance(title);
setScreen(title);
}
else {
IngameInstance notitle = new NoModuleDefaultTitlescreen(batch);
setScreen(notitle);
}
}
postProcessorOutFBO = renderFBO;
}
// draw the screen
else {
currentScreen.render(UPDATE_RATE);
postProcessorOutFBO = TerrarumPostProcessor.INSTANCE.draw(camera.combined, renderFBO);
}
KeyToggler.INSTANCE.update(currentScreen instanceof TerrarumIngame);
// nested FBOs are just not a thing in GL!
FrameBufferManager.end();
// process screenshot request
processScreenshotRequest(postProcessorOutFBO);
if (getConfigString("screenmagnifyingfilter").equals("hq2x") ) {
FrameBufferManager.begin(postProcessorOutFBO2);
shaderPassthruRGBA.bind();
shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined);
shaderPassthruRGBA.setUniformi("u_texture", 0);
hq2x.renderToScreen(postProcessorOutFBO.getColorBufferTexture());
FrameBufferManager.end();
shaderPassthruRGBA.bind();
shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined);
shaderPassthruRGBA.setUniformi("u_texture", 0);
postProcessorOutFBO2.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
postProcessorOutFBO2.getColorBufferTexture().bind(0);
fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLE_FAN);
}
else if (getConfigDouble("screenmagnifying") < 1.01 || getConfigString("screenmagnifyingfilter").equals("none")) {
shaderPassthruRGBA.bind();
shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined);
shaderPassthruRGBA.setUniformi("u_texture", 0);
postProcessorOutFBO.getColorBufferTexture().setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
postProcessorOutFBO.getColorBufferTexture().bind(0);
fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLE_FAN);
}
else if (getConfigString("screenmagnifyingfilter").equals("bilinear")) {
shaderPassthruRGBA.bind();
shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined);
shaderPassthruRGBA.setUniformi("u_texture", 0);
postProcessorOutFBO.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
postProcessorOutFBO.getColorBufferTexture().bind(0);
fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLE_FAN);
}
// process resize request
if (resizeRequested) {
resizeRequested = false;
resize(resizeReqSize.getX(), resizeReqSize.getY());
}
splashDisplayed = true;
GLOBAL_RENDER_TIMER += 1;
}
private static void processScreenshotRequest(FrameBuffer fb) {
if (screenshotRequested) {
String msg = "Screenshot taken";
FrameBufferManager.begin(fb);
try {
Pixmap p = Pixmap.createFromFrameBuffer(0, 0, fb.getWidth(), fb.getHeight());
PixmapIO.writePNG(Gdx.files.absolute(defaultDir+"/Screenshot-"+String.valueOf(System.currentTimeMillis())+".png"), p, 9, true);
p.dispose();
}
catch (Throwable e) {
e.printStackTrace();
msg = ("Failed to take screenshot: "+e.getMessage());
}
FrameBufferManager.end();
screenshotRequested = false;
Terrarum.INSTANCE.getIngame().sendNotification(msg);
}
}
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_TRIANGLE_FAN);
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.drawFlipped(tex1, (drawWidth - tex1.getWidth()) >>> 1, virtualHeightOffset + (virtualHeight >>> 1) - 16, tex1.getWidth(), -tex1.getHeight());
logoBatch.drawFlipped(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();
}
/**
* This resize takes the apparent screen size (i.e. zoomed size) as parameters.
*
* All other Terrarum's resize() must take real screen size. (i.e not zoomed)
*
* @param w0 the new width in pixels
* @param h0 the new height in pixels
*/
@Override
public void resize(int w0, int h0) {
float magn = (float) getConfigDouble("screenmagnifying");
int width = (int) Math.floor(w0 / magn);
int height = (int) Math.floor(h0 / magn);
printdbg(this, "Resize called: "+width+","+height);
printStackTrace(this);
if (width < 2 || height < 2) return;
//initViewPort(width, height);
scr.setDimension(width, height, magn);
if (currentScreen != null) currentScreen.resize(scr.getWidth(), scr.getHeight());
TerrarumPostProcessor.INSTANCE.resize(scr.getWidth(), scr.getHeight());
updateFullscreenQuad(fullscreenQuad, scr.getWidth(), scr.getHeight());
if (renderFBO == null ||
(renderFBO.getWidth() != scr.getWidth() ||
renderFBO.getHeight() != scr.getHeight())
) {
renderFBO = new Float16FrameBuffer(
scr.getWidth(),
scr.getHeight(),
false
);
postProcessorOutFBO2 = new Float16FrameBuffer(
scr.getWidth() * 2,
scr.getHeight() * 2,
false
);
if (IS_DEVELOPMENT_BUILD) {
try {
Field field = GLFrameBuffer.class.getDeclaredField("framebufferHandle");
field.setAccessible(true);
System.out.println("Attachment ID for renderFBO: " + field.get(renderFBO));
}
catch (NoSuchFieldException | IllegalAccessException e) {
System.err.println("Attachment ID for renderFBO: X_x");
e.printStackTrace();
}
}
}
Toolkit.INSTANCE.resize();
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();
for (Texture texture : ditherPatterns) {
texture.dispose();
}
shaderBayerSkyboxFill.dispose();
shaderHicolour.dispose();
shaderDebugDiff.dispose();
shaderPassthruRGBA.dispose();
shaderColLUT.dispose();
shaderReflect.dispose();
shaderGhastlyWhite.dispose();
hq2x.dispose();
CommonResourcePool.INSTANCE.dispose();
fullscreenQuad.dispose();
logoBatch.dispose();
batch.dispose();
// shapeRender.dispose();
fontGame.dispose();
fontGameFBO.dispose();
fontSmallNumbers.dispose();
fontBigNumbers.dispose();
ItemSlotImageFactory.INSTANCE.dispose();
logo.getTexture().dispose();
ModMgr.INSTANCE.disposeMods();
GameWorld.Companion.makeNullWorld().dispose();
Terrarum.INSTANCE.dispose();
inputStrober.dispose();
audioDevice.dispose();
deleteTempfiles();
disposables.forEach((it) -> {
try {
it.dispose();
}
catch (NullPointerException | IllegalArgumentException | GdxRuntimeException | ConcurrentModificationException e) { }
});
}
@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());
TerrarumGlobalState.INSTANCE.getHAS_KEYBOARD_INPUT_FOCUS().unset();
System.gc();
printdbg("AppLoader-Static", "Screen transition complete: " + currentScreen.getClass().getCanonicalName());
}
/**
* Init stuffs which needs GL context
*/
private void postInit() {
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));
CommonResourcePool.INSTANCE.addToLoadingList("inventory_category", () -> new TextureRegionPack("./assets/graphics/gui/inventory/category.tga", 20, 20, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.loadAll();
shaderHicolour = loadShaderFromClasspath("shaders/default.vert", "shaders/hicolour.frag");
shaderDebugDiff = loadShaderFromClasspath("shaders/default.vert", "shaders/diff.frag");
shaderColLUT = loadShaderFromClasspath("shaders/default.vert", "shaders/rgbonly.frag");
shaderGhastlyWhite = loadShaderFromClasspath("shaders/default.vert", "shaders/ghastlywhite.frag");
// 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;
}*/
fontUITitle = new TerrarumSansBitmap(FONT_DIR, false, false, false,
false,
64, false, 0.5f, false
);
fontUITitle.setInterchar(1);
fontGameFBO = new TerrarumSansBitmap(FONT_DIR, false, true, false,
false,
64, false, 203f/255f, false
);
Lang.invoke();
ModMgr.INSTANCE.invoke(); // invoke Module Manager
fontSmallNumbers = TinyAlphNum.INSTANCE;
fontBigNumbers = BigAlphNum.INSTANCE;
IME.invoke();
inputStrober = InputStrober.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)");
}
CommonResourcePool.INSTANCE.loadAll();
// 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");
}
if (ModMgr.INSTANCE.getModuleInfo().isEmpty()) {
return;
}
printdbg(this, "all modules loaded successfully");
// test print
if (IS_DEVELOPMENT_BUILD) {
System.out.println("[App] Test printing every registered item");
Terrarum.INSTANCE.getItemCodex().getItemCodex().values().stream().map(GameItem::getOriginalID).forEach(
(String s) -> System.out.print(s + " "));
System.out.println();
}
try {
// create tile atlas
printdbg(this, "Making terrain textures...");
tileMaker = new CreateTileAtlas();
tileMaker.invoke(false);
}
catch (NullPointerException e) {
throw new Error("TileMaker failed to load", e);
}
Terrarum.initialise();
// if there is a predefined screen, open that screen after my init process
if (injectScreen != null) {
setScreen(injectScreen);
}
else {
IngameRenderer.initialise();
}
hasUpdate = CheckUpdate.INSTANCE.hasUpdate();
printdbg(this, "Has update: " + hasUpdate);
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(Mesh mesh, int WIDTH, int HEIGHT) { // NOT y-flipped quads!
mesh.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
});
mesh.setIndices(new short[]{0, 1, 2, 3});
}
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;
}
public static boolean isScreenshotRequested() {
return screenshotRequested;
}
// DEFAULT DIRECTORIES //
public static String OSName = System.getProperty("os.name");
public static String OSVersion = System.getProperty("os.version");
private static String tempDir = System.getProperty("java.io.tmpdir");
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;
/** defaultDir + "/LoadOrder.txt" */
public static String loadOrderDir;
/** defaultDir + "/Imported" */
public static String importDir;
public static RunningEnvironment environment;
/** defaultDir + "/Recycled/Players" */
public static String recycledPlayersDir;
/** defaultDir + "/Recycled/Worlds" */
public static String recycledWorldsDir;
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") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond
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";
loadOrderDir = defaultDir + "/LoadOrder.txt";
recycledPlayersDir = defaultDir + "/Recycled/Players";
recycledWorldsDir = defaultDir + "/Recycled/Worlds";
importDir = defaultDir + "/Imports";
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),
new File(recycledPlayersDir),
new File(recycledWorldsDir),
new File(importDir)
};
for (File it : dirs) {
if (!it.exists())
it.mkdirs();
}
try {
createLoadOrderFile();
}
catch (IOException e) {
e.printStackTrace();
}
//dirs.forEach { if (!it.exists()) it.mkdirs() }
}
public static File newTempFile(String filename) {
File tempfile = new File(tempDir, 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 createLoadOrderFile() throws IOException {
File loadOrderFile = new File(loadOrderDir);
if (!loadOrderFile.exists() || loadOrderFile.length() == 0L) {
var writer = new FileWriter(loadOrderFile);
writer.write(TerrarumAppConfiguration.DEFAULT_LOADORDER_FILE);
writer.flush(); writer.close();
}
}
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<String, Object> entry : DefaultConfig.INSTANCE.getHashMap().entrySet()) {
gameConfig.set(entry.getKey(), entry.getValue());
}
}
/**
* Will forcibly overwrite previously loaded config value.
*
* Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName.
*
* @param value JsonValue (the key-value pair)
* @param modName module name, nullable
*/
public static void setToGameConfigForced(JsonValue value, String modName) {
gameConfig.set((modName == null) ? value.name : modName+":"+value.name,
value.isArray() ? value.asDoubleArray() :
value.isDouble() ? value.asDouble() :
value.isBoolean() ? value.asBoolean() :
value.isLong() ? value.asInt() :
value.asString()
);
}
/**
* Will not overwrite previously loaded config value.
*
* Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName.
*
* @param value JsonValue (the key-value pair)
* @param modName module name, nullable
*/
public static void setToGameConfig(JsonValue value, String modName) {
String key = (modName == null) ? value.name : modName+":"+value.name;
if (gameConfig.get(key) == null) {
gameConfig.set(key,
value.isArray() ? value.asDoubleArray() :
value.isDouble() ? value.asDouble() :
value.isBoolean() ? value.asBoolean() :
value.isLong() ? value.asInt() :
value.asString()
);
}
}
/**
*
* @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) {
setToGameConfigForced(entry, null);
}
return true;
}
catch (IOException 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) {
if (key == null) return -1;
Object cfg = getConfigMaster(key);
if (cfg instanceof Integer) return ((int) cfg);
double value = (double) cfg;
if (Math.abs(value % 1.0) < 0.00000001)
return (int) Math.round(value);
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 double getConfigDouble(String key) {
Object cfg = getConfigMaster(key);
return (cfg instanceof Integer) ? (((Integer) cfg) * 1.0) : ((double) (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<String, Object> 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) {
var timeNow = System.currentTimeMillis();
var ss = (timeNow / 1000) % 60;
var mm = (timeNow / 60000) % 60;
var hh = (timeNow / 3600000) % 24;
var ms = timeNow % 1000;
String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName();
String prompt = csiG+String.format("%02d:%02d:%02d.%03d%s [%s] ",hh,mm,ss,ms,csi0,out);
if (message == null) {
System.out.println(prompt+"null");
}
else {
String indentation = " ".repeat(out.length() + 15);
String[] msgLines = message.toString().split("\\n");
for (int i = 0; i < msgLines.length; i++) {
System.out.println((i == 0 ? prompt : indentation) + msgLines[i]);
}
}
}
}
public static void printdbgerr(Object obj, Object message) {
if (IS_DEVELOPMENT_BUILD) {
var timeNow = System.currentTimeMillis();
var ss = (timeNow / 1000) % 60;
var mm = (timeNow / 60000) % 60;
var hh = (timeNow / 3600000) % 24;
var ms = timeNow % 1000;
String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName();
String prompt = csiB+String.format("%02d:%02d:%02d.%03d%s [%s] ",hh,mm,ss,ms,csiR,out);
if (message == null) {
System.out.println(prompt+"null"+csi0);
}
else {
String indentation = " ".repeat(out.length() + 11);
String[] msgLines = message.toString().split("\\n");
for (int i = 0; i < msgLines.length; i++) {
System.out.println((i == 0 ? prompt : indentation) + msgLines[i] + csi0);
}
}
}
}
public static void printmsg(Object obj, Object message) {
var timeNow = System.currentTimeMillis();
var ss = (timeNow / 1000) % 60;
var mm = (timeNow / 60000) % 60;
var hh = (timeNow / 3600000) % 24;
var ms = timeNow % 1000;
String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName();
String prompt = csiG+String.format("%02d:%02d:%02d.%03d%s [%s] ",hh,mm,ss,ms,csi0,out);
if (message == null)
System.out.println(prompt+"null");
else
System.out.println(prompt+message);
}
public static void printmsgerr(Object obj, Object message) {
var timeNow = System.currentTimeMillis();
var ss = (timeNow / 1000) % 60;
var mm = (timeNow / 60000) % 60;
var hh = (timeNow / 3600000) % 24;
var ms = timeNow % 1000;
String out = (obj instanceof String) ? (String) obj : obj.getClass().getSimpleName();
String prompt = csiB+String.format("%02d:%02d:%02d.%03d%s [%s] ",hh,mm,ss,ms,csiR,out);
if (message == null)
System.out.println(prompt+"null"+csi0);
else
System.out.println(prompt+message+csi0);
}
public static ShaderProgram loadShaderFromClasspath(String vert, String frag) {
String v = Gdx.files.classpath(vert).readString("utf-8");
String f = Gdx.files.classpath(frag).readString("utf-8");
return loadShaderInline(v, f);
}
public static ShaderProgram loadShaderFromFile(String vert, String frag) {
String v = Gdx.files.internal(vert).readString("utf-8");
String f = Gdx.files.internal(frag).readString("utf-8");
return loadShaderInline(v, f);
}
public static ShaderProgram loadShaderInline(String vert0, String frag0) {
// insert version code
String vert, frag;
if (gl40capable) {
vert = "#version 400\n"+vert0;
frag = "#version 400\n"+frag0;
}
else {
vert = "#version 330\n#define fma(a,b,c) (((a)*(b))+(c))\n"+vert0;
frag = "#version 330\n#define fma(a,b,c) (((a)*(b))+(c))\n"+frag0;
}
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<kotlin.Unit> block) {
//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) {
debugTimers.put(name, value);
}
public static void addDebugTime(String target, String... targets) {
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);
}
/**
* Corrects out-of-range config values
*/
private static void rectifyConfigs() {
// force set min autosave interval to 5 minutes
if (getConfigInt("autosaveinterval") < 5 * 60000) {
setConfig("autosaveinterval", 5 * 60000);
}
}
}