package net.fourbytes.shadow; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteCache; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.utils.LongArray; import net.fourbytes.shadow.Input.Key; import net.fourbytes.shadow.Input.Key.Triggerer; import net.fourbytes.shadow.Input.KeyListener; import net.fourbytes.shadow.Input.TouchPoint; import net.fourbytes.shadow.Input.TouchPoint.TouchMode; import net.fourbytes.shadow.map.Converter; import net.fourbytes.shadow.mod.ModManager; import net.fourbytes.shadow.network.NetStream; import net.fourbytes.shadow.systems.ILightSystem; import net.fourbytes.shadow.systems.LightSystemHelper; import net.fourbytes.shadow.utils.*; import net.fourbytes.shadow.utils.backend.BackendHelper; import java.io.File; import java.io.PrintStream; import java.lang.Thread.UncaughtExceptionHandler; import java.net.URLDecoder; import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Random; public final class Shadow implements ApplicationListener, InputProcessor, KeyListener { public static String gameID = "Shadow Engine v0.0.0b0"; public static Thread thread; public static Random rand = new Random(); public static Level level; public static ControllerHelper controllerHelper; public static Camera cam; public static float dispw = 1f; public static float disph = 1f; public static byte viewmode = ViewModes.def; /** * View Fixed Factor */ public static float viewff = 32f; public static float vieww = 1f; public static float viewh = 1f; public static float touchw = 1f; public static float touchh = 1f; public static SpriteBatch spriteBatch; public static SpriteCache spriteCache; public static int frames = 0; public static long lastfmicro = 0; public static int fps = 0; public static int efps = 0; protected static Runtime runtime = Runtime.getRuntime(); public static int ramLogMax = 64; public static long ramTime = 0; public static long ramTimeDelay = 100; public static LongArray ramTotal = new LongArray(); public static LongArray ramFree = new LongArray(); public static LongArray ramUsed = new LongArray(); public static boolean isAndroid = false; public static boolean isOuya = false; public static boolean gdxpaused = false; public static PlayerInfo playerInfo; public static NetStream client; public static NetStream server; public static boolean record = false; public static String recordDirName; public static boolean glclear; public static float shaderTime = 0f; public Shadow() { super(); } /** * Creates an string containing an given amount of random hexadecimal bytes. <br> * It's using SecureRandom instead of Java's default Random. * @param amount Amount of random bytes * @return Random client identifier. */ public static String getSecureRandomBytes(int amount) { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[amount]; random.nextBytes(bytes); String str = ""; for (byte aByte : bytes) { str += Integer.toHexString(aByte & 0xFF); } return str; } public static FileHandle dir; public static FileHandle getDir(String subdir) { if (isAndroid) { if (dir == null) { dir = Gdx.files.external("shadowenginetest"); } if (subdir == null || subdir.isEmpty()) { return dir; } FileHandle child = dir.child(subdir); child.mkdirs(); return child; } else { if (dir == null) { String path = ""; try { String rawpath = Shadow.class.getProtectionDomain().getCodeSource().getLocation().getPath(); path = URLDecoder.decode(rawpath, "UTF-8"); } catch (Exception e1) { e1.printStackTrace(); } dir = Gdx.files.absolute(path).parent(); } FileHandle child = dir.child(subdir); child.mkdirs(); return child; } } @Override public void create() { thread = Thread.currentThread(); Options.setup(); Gdx.input.setCatchBackKey(true); Gdx.input.setCatchMenuKey(true); UncaughtExceptionHandler eh = new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { try { File dir = getDir("logs").file(); File logfile = new File(dir, "log_"+(new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime()))+".txt"); if (logfile.createNewFile()) { PrintStream fos = new PrintStream(logfile); e.printStackTrace(fos); fos.close(); } } catch (Throwable e1) { e1.printStackTrace(); } e.printStackTrace(); System.exit(1); } }; Thread.currentThread().setUncaughtExceptionHandler(eh); Thread.setDefaultUncaughtExceptionHandler(eh); dispw = Gdx.graphics.getWidth(); disph = Gdx.graphics.getHeight(); //TODO replace ViewModes.def viewmode = ViewModes.def; //Alternate values for view: vieww = 12.5f; viewh = 15f; switch (viewmode) { case ViewModes.dynamic: vieww = dispw/viewff; viewh = disph/viewff; case ViewModes.fixedh: vieww = dispw/viewff; break; case ViewModes.fixedw: viewh = disph/viewff; break; default: break; } touchh = 7f; touchw = touchh*dispw/disph; Gdx.input.setInputProcessor(this); Input.setUp(); Input.keylisteners.add(this); cam = new Camera(); resize(); } @Override public void dispose() { ModManager.dispose(); Gdx.input.setInputProcessor(null); if (Shadow.isAndroid && !Shadow.isOuya) { System.exit(0); } //TODO: save data //TODO: cleanup resources } @Override public void render() { float delta = Gdx.graphics.getDeltaTime(); subtick(); if (spriteBatch == null) { return; } shaderTime += delta; ShaderHelper.set("s_time", shaderTime); tick(delta); /* Sidenote: LightSystem.render() is rendering, yes, but it's rendering to another FrameBuffer than the default one and switching "back" while rendering to another FBO creates glitches. */ if (level != null) { Level llevel = level; if (llevel instanceof MenuLevel) { llevel = ((MenuLevel)llevel).bglevel; } if (llevel != null && llevel instanceof LoadingLevel) { llevel = ((LoadingLevel)llevel).bglevel; } if (llevel != null) { ILightSystem lights = llevel.systems.get(ILightSystem.class); if (lights != null) { lights.render(); } } } if (glclear) { Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); } spriteBatch.setColor(1f, 1f, 1f, 1f); ModManager.preRender(delta); Input.isInMenu = level != null && level instanceof MenuLevel; cam.render(delta); ModManager.postRender(delta); if (record) { ScreenshotUtil.frameRecord(); } long time = System.currentTimeMillis(); frames++; if (lastfmicro == 0) { lastfmicro = time; } if (time - lastfmicro >= 1000) { lastfmicro = time; fps = frames; frames = 0; } efps = (int)(1f/delta); if (time - ramTime >= ramTimeDelay) { ramTime = time; ramTotal.insert(0, runtime.totalMemory()); ramTotal.truncate(ramLogMax); ramFree.insert(0, runtime.freeMemory()); ramFree.truncate(ramLogMax); ramUsed.insert(0, ramTotal.items[0] - ramFree.items[0]); ramUsed.truncate(ramLogMax); } } public void subtick() { while (Gdx.graphics == null) { Thread.yield(); } if (spriteBatch == null) { Gdx.gl.glDisable(GL20.GL_DEPTH_TEST); ShaderProgram.pedantic = false; ShaderProgram defaultShader = ShaderHelper.loadShader("shaders/default"); ShaderHelper.addShader(defaultShader); spriteBatch = new SpriteBatch(4096); spriteCache = new SpriteCache(4096, true); ShaderHelper.resetCurrentShader(); Images.loadBasic(); //Init controller helper here as JGLFW needs a window before doing controller stuff controllerHelper = new ControllerHelper(); //At this point it's safe to assume that most stuff needeed for a backend setup is set up. BackendHelper.setUp(); playerInfo = BackendHelper.backend.newPlayerInfo(); playerInfo.setSessionID(getSecureRandomBytes(512)); System.out.println("Username: "+playerInfo.getUserName()); System.out.println("UUID: "+playerInfo.getUserID()); //Init the first loading screen (InitLoadLevel) LoadingLevel loadinglevel = new InitLoadingLevel(); level = loadinglevel; loadinglevel.start(); //Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1f); //Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); //cam.render(); } } public void tick(float delta) { ModManager.preTick(delta); controllerHelper.tick(); Input.tick(); if (!Converter.convertOnly && level != null) { level.tick(delta); } if (!Converter.convertOnly && server != null) { server.tick(); } if (!Converter.convertOnly && client != null) { client.tick(); } ModManager.postTick(delta); } @Override public void resize(int width, int height) { resize(); } public static void resize() { if (Thread.currentThread() != Shadow.thread) { Gdx.app.postRunnable(new Runnable() { public void run() { resize(); } }); return; } if (Gdx.graphics != null) { dispw = Gdx.graphics.getWidth(); disph = Gdx.graphics.getHeight(); switch (viewmode) { case ViewModes.dynamic: break; case ViewModes.fixedh: viewh = vieww*disph/dispw; break; case ViewModes.fixedw: vieww = viewh*dispw/disph; break; case ViewModes.fixed: vieww = dispw/viewff; viewh = disph/viewff; break; case ViewModes.auto: vieww = dispw/viewff; viewh = disph/viewff; if (isAndroid || (dispw/viewff >= 21.75f && disph/viewff >= 18f)) { vieww = vieww/1.5f; viewh = viewh/1.5f; } break; default: break; } touchw = touchh*dispw/disph; cam.resize(); Input.resize(); ShaderHelper.set("s_resolution", dispw, disph); if (Camera.tmpFB != null) { Camera.tmpFB.dispose(); } Camera.tmpFB = new FrameBuffer(Pixmap.Format.RGB888, (int) dispw, (int) disph, false); if (Camera.blurFB != null) { Camera.blurFB.dispose(); } Camera.blurFB = new FrameBuffer(Pixmap.Format.RGB888, (int) (dispw / Camera.blursize), (int) (disph / Camera.blursize), false); Camera.blurFB.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); if (Camera.blurXFB != null) { Camera.blurXFB.dispose(); } Camera.blurXFB = new FrameBuffer(Pixmap.Format.RGB888, (int)(dispw/Camera.blursize), (int)(disph/Camera.blursize), false); Camera.blurXFB.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); if (LightSystemHelper.lightFB != null) { LightSystemHelper.lightFB.dispose(); LightSystemHelper.lightFB = null; } } } @Override public void pause() { gdxpaused = true; } @Override public void resume() { gdxpaused = false; } @Override public boolean keyDown(int keycode) { boolean handle = false; for (Input.Key k : Input.all) { for (int id : k.keyid) { if (id == keycode) { k.triggerer = Triggerer.KEYBOARD; k.nextState = true; handle = true; } } } return handle; } @Override public boolean keyUp(int keycode) { boolean handle = false; for (Input.Key k : Input.all) { for (int id : k.keyid) { if (id == keycode) { k.triggerer = Triggerer.KEYBOARD; k.nextState = false; handle = true; } } } return handle; } @Override public boolean keyTyped(char c) { if (level instanceof TextInputLevel) { ((TextInputLevel)level).keyTyped(c); return true; } return false; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { boolean handle = false; if (!Input.isAndroid) { pointer = -1; } //System.out.println("X: "+screenX+"; Y: "+screenY+"; P: "+pointer+"; B: "+button+"; M: D"); if (Input.isAndroid && !Input.isOuya && !Input.isInMenu) { for (Input.Key k : Input.all) { if (k.rec.contains(screenX, screenY)) { k.triggerer = Triggerer.SCREEN; k.nextState = true; k.pointer = pointer; TouchPoint tp = new TouchPoint(screenX, screenY, pointer, button, TouchMode.KeyInput); if (Input.touches.containsValue(pointer, true)) { Input.touches.remove(pointer); } Input.touches.put(pointer, tp); handle = true; } } } if (!handle && level != null && level.ready && cam.camrec != null) { TouchPoint tp = new TouchPoint(screenX, screenY, pointer, button, TouchMode.Cursor); Input.touches.put(pointer, tp); handle = true; } return handle; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if (!Input.isAndroid) { pointer = -1; } if (Input.isAndroid && !Input.isOuya && !Input.isInMenu) { //System.out.println("X: "+screenX+"; Y: "+screenY+"; P: "+pointer+"; B: "+button+"; M: U"); for (Input.Key k : Input.all) { if (k.rec.contains(screenX, screenY)) { k.triggerer = Triggerer.SCREEN; k.nextState = false; k.pointer = -2; } } } Input.touches.remove(pointer); return true; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { if (!Input.isAndroid) { pointer = -1; } TouchPoint tp = Input.touches.get(pointer); if (tp != null) { tp.pos.set(screenX, screenY); if (Input.isAndroid && !Input.isOuya && !Input.isInMenu && tp.touchmode == TouchMode.KeyInput) { //System.out.println("X: "+screenX+"; Y: "+screenY+"; P: "+pointer+"; M: D"); for (Input.Key k : Input.all) { if (k.pointer == pointer && !k.rec.contains(screenX, screenY)) { k.triggerer = Triggerer.SCREEN; k.nextState = false; k.pointer = -2; } } for (Input.Key k : Input.all) { if (k.rec.contains(screenX, screenY)) { k.triggerer = Triggerer.SCREEN; k.nextState = true; k.pointer = pointer; } } } } return true; } @Override public boolean mouseMoved(int screenX, int screenY) { boolean handled = false; if (level != null && level.c != null) { //No touch point while moving as no "touch" is occurring. Garbage.vec2s.next(); Garbage.vec2s.get().x = screenX; Garbage.vec2s.get().y = screenY; level.c.pos.set(level.c.calcPos(Garbage.vec2s.get())); level.c.render = true; handled = true; } return handled; } @Override public boolean scrolled(int amount) { boolean handled = false; if (level != null && level.c != null) { level.c.scroll(amount); handled = true; } return handled; } @Override public void keyDown(Key key) { if (!(level instanceof MenuLevel || level instanceof LoadingLevel) && (key == Input.pause || (isAndroid && (key == Input.androidBack || key == Input.androidMenu)))) { MenuLevel pause = new PauseLevel(); pause.bglevel = level; level = pause; } if (key == Input.screenshot) { ScreenshotUtil.frameSave(); } if (key == Input.record) { record = !record; if (record) { recordDirName = new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime()); } else { ScreenshotUtil.framesPull(); } } } @Override public void keyUp(Key key) { } }