package client;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.net.IDN;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.function.Function;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;

import client.audio.AudioInterface;
import client.audio.SoundManager;
import client.audio.Volume;
import client.gui.FileCallback;
import client.gui.Font;
import client.gui.Gui;
import client.gui.GuiConnect.ServerInfo;
import client.gui.GuiConsole;
import client.gui.GuiInfo;
import client.gui.GuiLoading;
import client.gui.GuiMenu;
import client.gui.GuiServer;
import client.gui.Style;
import client.gui.character.GuiChar;
import client.gui.container.GuiContainer;
import client.gui.container.GuiInventory;
import client.gui.element.Area;
import client.gui.ingame.GuiGameOver;
import client.gui.ingame.GuiRename;
import client.network.ClientLoginHandler;
import client.network.ClientPlayer;
import client.network.DummyConnection;
import client.renderer.BlockRenderer;
import client.renderer.Drawing;
import client.renderer.EffectRenderer;
import client.renderer.EntityRenderer;
import client.renderer.GlState;
import client.renderer.ItemRenderer;
import client.renderer.RenderGlobal;
import client.renderer.blockmodel.ModelBlock;
import client.renderer.blockmodel.ModelManager;
import client.renderer.chunk.RenderChunk;
import client.renderer.entity.RenderItem;
import client.renderer.entity.RenderManager;
import client.renderer.texture.ColormapLoader;
import client.renderer.texture.EntityTexManager;
import client.renderer.texture.TextureManager;
import client.renderer.texture.TextureMap;
import client.util.FileUtils;
import client.util.Message;
import client.util.PerfSection;
import client.util.PlayerController;
import client.vars.BoolVar;
import client.vars.CVar;
import client.vars.CVarCategory;
import client.vars.ColorVar;
import client.vars.EnumVar;
import client.vars.FloatVar;
import client.vars.IntVar;
import client.vars.StringVar;
import client.vars.Variable;
import client.vars.BaseVar.VarFunction;
import client.vars.BoolVar.BoolFunction;
import client.vars.EnumVar.EnumFunction;
import client.vars.FloatVar.FloatFunction;
import client.vars.IntVar.IntFunction;
import client.vars.StringVar.StringFunction;
import client.vars.Variable.IntType;
import client.window.Bind;
import client.window.Button;
import client.window.DisplayMode;
import client.window.KeyEvent;
import client.window.Keysym;
import client.window.Wheel;
import client.window.Window;
import client.window.WindowEvent;
import client.world.ChunkClient;
import client.world.WorldClient;
import common.Version;
import common.biome.Biome;
import common.block.Block;
import common.collect.Lists;
import common.collect.Maps;
import common.color.TextColor;
import common.dimension.Space;
import common.entity.Entity;
import common.entity.animal.EntityHorse;
import common.entity.npc.Energy;
import common.entity.npc.EntityCpu;
import common.entity.npc.EntityNPC;
import common.entity.npc.EntityWaterNPC;
import common.entity.npc.PlayerCharacter;
import common.entity.types.EntityLiving;
import common.future.Futures;
import common.future.ListenableFuture;
import common.future.ListenableFutureTask;
import common.future.ThreadFactoryBuilder;
import common.init.BlockRegistry;
import common.init.Blocks;
import common.init.EntityRegistry;
import common.init.ItemRegistry;
import common.init.Items;
import common.init.Registry;
import common.init.SoundEvent;
import common.init.UniverseRegistry;
import common.inventory.InventoryPlayer;
import common.item.Item;
import common.item.ItemControl;
import common.item.ItemStack;
import common.item.block.ItemBlock;
import common.log.Log;
import common.log.LogLevel;
import common.net.bootstrap.Bootstrap;
import common.net.channel.Channel;
import common.net.channel.ChannelException;
import common.net.channel.ChannelHandler;
import common.net.channel.ChannelInitializer;
import common.net.channel.ChannelOption;
import common.net.channel.nio.NioEventLoopGroup;
import common.net.channel.socket.nio.NioSocketChannel;
import common.net.handler.timeout.ReadTimeoutHandler;
import common.net.util.concurrent.Future;
import common.net.util.concurrent.GenericFutureListener;
import common.network.IThreadListener;
import common.network.NetConnection;
import common.network.PacketDecoder;
import common.network.PacketEncoder;
import common.network.PacketPrepender;
import common.network.PacketRegistry;
import common.network.PacketSplitter;
import common.network.NetHandler.ThreadQuickExitException;
import common.packet.CPacketAction;
import common.packet.CPacketCheat;
import common.packet.CPacketMessage;
import common.packet.HPacketHandshake;
import common.packet.CPacketAction.Action;
import common.potion.Effect;
import common.potion.StatusEffect;
import common.properties.Property;
import common.sound.EventType;
import common.sound.PositionedSound;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.CharValidator;
import common.util.ExtMath;
import common.util.Facing;
import common.util.HitPosition;
import common.util.LazyLoader;
import common.util.Util;
import common.util.HitPosition.ObjectType;
import common.world.LightType;
import common.world.State;
import common.world.World;

/*
	Een net ganz funktionierndes Programm ...
	
	Absolute Mindestanforderungen:
	
	Prozessor       : Intel Core i3 530 oder AMD Phenom 2 X2 545
	Grafikkarte     : NVIDIA GeForce GTS 450 oder AMD Radeon HD 7730
	Arbeitsspeicher : Etwa 512 MB ~ 1 GB, 2 - 4 GB empfohlen
	Mainboard       : Was gerade passt (auch mit Hammer)
	Kühler          : Ne Klimaanlage oder ordentlich Pusten
	Festplatte      : Richtet sich nach der Geduld des Nutzers
	Netzteil        : Atomkraftwerk oder stabiler Energiekern
	Gehäuse         : Pappkarton, Bierkasten oder auch gar keins
	Tastatur        : Hlb fktonrnd odr bsr
	Maus            : Aus Plastik oder mit extra Quietsch-Effekt
	Monitor         : Olle Röhre oder gebrochener Flachbild (Mindestens 1280x800)
	Stuhl           : Interessiert niemanden (sonst siehe Gehäuse)
	
	[[oder einfach einen Toaster mit nem LCD und Maus]]
*/

public class Client implements IThreadListener {
	public static class SyncFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.sync(value);
		}
	}

	public static class TickFunction implements FloatFunction {
		public void apply(FloatVar cv, float value) {
			Client.CLIENT.tick_target(value);
		}
	}

	public static class ConsoleFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.resizeConsole();
		}
	}

	public static class ChatFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.resizeChat();
		}
	}

	public static class FeedFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.resizeFeed();
		}
	}

	public static class HotbarFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.resizeHotbar();
		}
	}
	
	public static class DistanceFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.distance(value);
		}
	}
	
	public static class RedrawFunction implements IntFunction {
		public void apply(IntVar cv, int value) {
			Client.CLIENT.rescale();
		}
	}
	
	public static class LevelFunction implements EnumFunction<LogLevel> {
		public void apply(EnumVar cv, LogLevel value) {
			Log.setLevel(value);
		}
	}
	
	public static class FontFunction implements EnumFunction<Font> {
		public void apply(EnumVar cv, Font value) {
			Font.unload();
			Font.load(value);
			Client.CLIENT.rescale();
		}
	}
	
	public static class StyleFunction implements EnumFunction<Style> {
		public void apply(EnumVar cv, Style value) {
			Client.CLIENT.rescale();
		}
	}
	
	private interface DebugRunner {
		void execute(Keysym key);
	}
	
	private class DebugFunction {
		public final DebugRunner runner;
		public final Keysym key;
		public final String help;
		
		public DebugFunction(Keysym key, DebugRunner runner, String help) {
			this.key = key;
			this.runner = runner;
			this.help = help;
		}
	}

	public static final String VERSION = Version.NAME + " Client " + Util.VERSION;
	public static final int LOG_BUFFER = 32768;
	public static final int MIN_WIDTH = 800;
	public static final int MIN_HEIGHT = 450;
    private static final LazyLoader<NioEventLoopGroup> CLIENT_NIO_EVENTLOOP = new LazyLoader<NioEventLoopGroup>()
    {
        protected NioEventLoopGroup load()
        {
            return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
        }
    };
	
    private final Queue<FutureTask<?>> tasks = new ArrayDeque<FutureTask<?>>();
	private final Map<Integer, Long> bars = Maps.newTreeMap();
    private final Thread clThread = Thread.currentThread();
	private final Map<String, CVar> cvars = Maps.newTreeMap();
	private final Map<Keysym, DebugFunction> debug = Maps.newTreeMap();
	private final List<Message> console = Lists.newArrayList();
	private final List<Message> chat = Lists.newArrayList();
	private final List<Message> feed = Lists.newArrayList();
	private final List<Message> hotbar = Lists.newArrayList();
	private final File config = new File(System.getProperty("config.file", "client.cfg"));
    public final Map<String, Integer> playerList = Maps.<String, Integer>newTreeMap();
    public final List<PlayerCharacter> characterList = Lists.<PlayerCharacter>newArrayList();
    
    private boolean primary;
    private boolean secondary;
    private boolean tertiary;
    private boolean quarternary;
    private boolean showHud = true;
    public boolean jump;
    public boolean sneak;
    public boolean debugCamEnable;
    public boolean debugWorld;
    public boolean zooming;
    public boolean sprint;
    public boolean renderOutlines;
    public boolean setGamma;
    public boolean nickChanged;
    public boolean xrayActive;
    public boolean tileOverlay;
    public boolean itemCheat;
    public boolean dayCycle = true;
    public boolean debugPlayer;
    public boolean cameraUsed;
    public boolean charEditor;

    private int leftClickCounter;
    private int rightClickTimer;
    private int chunkLoadTimer;
    
    public int thirdPersonView;
    public int timeFactor = 1;
    public int chunksUpdated;
    public int selectedCharacter = -1;
    
    private long lastTicked = 0L;
    private long debugUpdateTime = System.currentTimeMillis();
	private long tickStart;

    private float deltaX;
    private float deltaY;
    public float moveStrafe;
    public float moveForward;
    public float zoomLevel;
    public float gravity = 1.0f;

	private long tmr_timer;
	private long tmr_start;
	private long tmr_current;
	private long tmr_last;
	
	private long tmr_delta;
	private long tmr_update;
	private long tmr_frames;
	private long tmr_iters;
	
	private long tick_torun;
	private long tick_done;
	private long tick_total;
	private long tick_time;
	private long tick_stime;
	private long tick_ftime;
	
	private long tick_ttime;
	private long tick_update;
	
	private double tick_fraction;
	private float framerate;
	private float tickrate;
	private float fdelta;
	private int tickTarget;
	private int tickFrame;
	
    private TextureManager textureManager;
    private RenderManager renderManager;
    private RenderItem renderItem;
    private ItemRenderer itemRenderer;
    public RenderGlobal renderGlobal;
    public EffectRenderer effectRenderer;
    public EntityRenderer entityRenderer;
    private TextureMap textureMap;
    private ModelManager modelManager;
    private BlockRenderer blockRenderer;
    public Gui open;
    
    private SoundManager soundManager;
    
    private NetConnection connection;
    private Entity viewEntity;
    private Entity pointedEntity;
    private BufferedImage skin;
    private Object popupTarget;
    public PlayerController controller;
    public WorldClient world;
    public EntityNPC player;
    public HitPosition pointed;

	@Variable(name = "chunk_view_distance", category = CVarCategory.RENDER, min = 2, max = 16, callback = DistanceFunction.class, display = "Sichtweite", unit = "Chunks")
    public int renderDistance = 8;
	@Variable(name = "chunk_build_time", category = CVarCategory.RENDER, min = 1, max = 100, display = "Zeit für Chunk-Bau", unit = "ms")
    public int maxBuildTime = 8;
    @Variable(name = "gl_fov", category = CVarCategory.RENDER, min = 1.0f, max = 179.0f, display = "Sichtfeld (FOV)", unit = "°", precision = 1)
    public float fov = 70.0f;
    @Variable(name = "gl_wireframe", category = CVarCategory.RENDER, display = "Gitter-Render-Modus")
    public boolean wireframe = false;

	@Variable(name = "con_timestamps", category = CVarCategory.CONSOLE, display = "Zeiten")
	public boolean conTimestamps = false;
	@Variable(name = "con_loglevel", category = CVarCategory.CONSOLE, display = "Ausgabe", callback = LevelFunction.class)
	public LogLevel level = LogLevel.INFO;

	@Variable(name = "win_sync", category = CVarCategory.WINDOW, min = -1, max = 16384, callback = SyncFunction.class, display = "Maximale Bildrate")
	public int sync = 0;
	
	@Variable(name = "win_width", category = CVarCategory.WINDOW, min = MIN_WIDTH, max = 65536, display = "Fensterbreite")
	public int xsize = 1280;
	@Variable(name = "win_height", category = CVarCategory.WINDOW, min = MIN_HEIGHT, max = 65536, display = "Fensterhöhe")
	public int ysize = 800;
	@Variable(name = "win_pos_x", category = CVarCategory.WINDOW, min = -65536, max = 65536, display = "Fenster X-Position")
	public int saved_xpos = 0x80000000;
	@Variable(name = "win_pos_y", category = CVarCategory.WINDOW, min = -65536, max = 65536, display = "Fenster Y-Position")
	public int saved_ypos = 0x80000000;
	
	public int fb_raw_x;
	public int fb_raw_y;
	private int mouse_raw_x;
	private int mouse_raw_y;
	public int fb_x;
	public int fb_y;
	public int mouse_x;
	public int mouse_y;
	private int scale = 1;
	
	@Variable(name = "gui_scale", category = CVarCategory.GUI, min = 1, max = 5, display = "Skalierung", unit = "x", callback = RedrawFunction.class)
	private int scaleVar = 2;

	@Variable(name = "phy_sensitivity", category = CVarCategory.INPUT, min = 0.01f, max = 10.0f, display = "Mausempfindlichkeit", precision = 2, unit = "%")
	public float sensitivity = 1.0f;
	public boolean fullscreen;
	public long syncLimit;
	public boolean mouseFirst;
	public boolean nograb = System.getProperty("mouse.nograb") != null;
	public DisplayMode vidMode;
	@Variable(name = "gui_dclick_delay", category = CVarCategory.INPUT, min = 150, max = 750, display = "Doppelklick bei", unit = "ms")
	public int dclickDelay = 250;
	@Variable(name = "console_size", category = CVarCategory.CONSOLE, min = 0, max = 128, callback = ConsoleFunction.class, display = "Nachrichten in Konsole")
	public int consoleSize = 32;
	@Variable(name = "chat_size", category = CVarCategory.CONSOLE, min = 0, max = 128, callback = ChatFunction.class, display = "Nachrichten im Chat")
	public int chatSize = 32;
	@Variable(name = "feed_size", category = CVarCategory.CONSOLE, min = 0, max = 128, callback = FeedFunction.class, display = "Nachrichten im Feed")
	public int feedSize = 8;
	@Variable(name = "hotbar_size", category = CVarCategory.CONSOLE, min = 0, max = 16, callback = HotbarFunction.class, display = "Nachrichten in Hotbar")
	public int hotbarSize = 2;
	@Variable(name = "overlay_fadeout", category = CVarCategory.CONSOLE, min = 1, max = 60, display = "Anzeigedauer", unit = "s")
	public int hudFadeout = 8;
	@Variable(name = "overlay_enabled", category = CVarCategory.CONSOLE, display = "Nachrichten-Overlay")
	public boolean hudOverlay = true;
	@Variable(name = "chat_permanent", category = CVarCategory.CONSOLE, display = "Chat immer einblenden")
	public boolean chatPermanent = false;
	@Variable(name = "overlay_opacity", category = CVarCategory.CONSOLE, min = 0x00, max = 0xff, display = "Deckkraft Hintergrund")
	public int hudOpacity = 0x40;
	public boolean syncLimited;
	public boolean vsync;
	public boolean screenshot;
	public boolean saving;
	public boolean drawFps;
	public boolean drawDebug;
	@Variable(name = "gl_vsync_flush", category = CVarCategory.RENDER, display = "Puffer leeren")
	public boolean glFlush = false;
	@Variable(name = "con_autoclose", category = CVarCategory.CONSOLE, display = "Schließen")
	public boolean conAutoclose = true;
	public boolean interrupted;
	@Variable(name = "gui_theme", category = CVarCategory.GUI, display = "Design", callback = StyleFunction.class)
	public Style style = Style.DEFAULT;
	@Variable(name = "gui_scroll_lines", category = CVarCategory.GUI, min = 1, max = 10, display = "Scrollbreite", unit = "Zeilen")
	public int scrollLines = 3;
	@Variable(name = "gui_font", category = CVarCategory.GUI, display = "Schriftart", callback = FontFunction.class)
	public Font font = Font.SMALL;

	@Variable(name = "draw_downfall_range", category = CVarCategory.RENDER, min = 0, max = 15, display = "Niederschlag-Radius")
	public int downfallRange = 4;
	@Variable(name = "draw_rain_particle_range", category = CVarCategory.RENDER, min = 0, max = 25, display = "Regen-Partikel-Radius")
	public int rainParticleRange = 10;
	@Variable(name = "crosshair_size", category = CVarCategory.GUI, min = 0, max = 32, display = "Größe des Fadenkreuzes")
	public int crosshairSize = 6;
	@Variable(name = "crosshair_color_notarget", type = IntType.COLOR, category = CVarCategory.GUI, display = "Fadenkreuz-Farbe (ohne Ziel)")
	public int crosshairColorBase = 0xffcfcfcf;
	@Variable(name = "crosshair_color_target", type = IntType.COLOR, category = CVarCategory.GUI, display = "Fadenkreuz-Farbe (mit Ziel)")
	public int crosshairColorTarget = 0xffffffff;
	
	@Variable(name = "tic_target", category = CVarCategory.SYSTEM, min = 1.0f, max = 1200.0f, callback = TickFunction.class, display = "Tickrate")
	public float tpsTarget = 20.0f;
	@Variable(name = "tic_timeout", category = CVarCategory.SYSTEM, min = 1, max = 60000, display = "Tick-Timeout-Zeit")
	public int tickTimeout = 2000;
	
	@Variable(name = "snd_enabled", category = CVarCategory.SOUND, display = "Tonausgabe")
	public boolean soundEnabled = true;
	@Variable(name = "snd_buffer_size", category = CVarCategory.SOUND, min = 0, max = 1048576, display = "Puffer")
	public int soundBufferSize = 16384;
	@Variable(name = "snd_frame_size", category = CVarCategory.SOUND, min = 2, max = 8192, display = "Intervall")
	public int soundFrameSize = 32;
	
    private String serverInfo;
    private int lastTickTime = -1;
    private AudioInterface audio;
	private boolean cfgDirty;
	private String buffer = "";
    private boolean waitingForFile;
    private boolean refreshing;

    public String dimensionName;

	public String message;
	public int total;
	public int progress = -1;

    private final int[] tickTimes = new int[240];
    private final long[] frames = new long[240];
    private int tickIndex;
    private int lastIndex;
    private int frameCounter;
    private int frameIndex;
    private long frameLast;
    private long frameWait;
    private long startNanoTime = System.nanoTime();

	public static final Client CLIENT = new Client();
    
    private final Bind[] keyBindsHotbar = new Bind[] {
    	Bind.SELECT1, Bind.SELECT2, Bind.SELECT3, Bind.SELECT4, Bind.SELECT5, Bind.SELECT6, Bind.SELECT7, Bind.SELECT8, Bind.SELECT9
	};
        
    private Client() {
    }

	private static NetConnection createNetworkManagerAndConnect(InetAddress address, int serverPort)
    {
        final NetConnection networkmanager = new NetConnection();
        ((Bootstrap)((Bootstrap)((Bootstrap)(new Bootstrap()).group(CLIENT_NIO_EVENTLOOP.getValue())).handler(new ChannelInitializer<Channel>()
        {
            protected void initChannel(Channel p_initChannel_1_) throws Exception
            {
                try
                {
                    p_initChannel_1_.config().setOption(ChannelOption.TCP_NODELAY, Boolean.valueOf(true));
                }
                catch (ChannelException var3)
                {
                    ;
                }

                p_initChannel_1_.pipeline().addLast((String)"timeout", (ChannelHandler)(new ReadTimeoutHandler(30))).addLast((String)"splitter", (ChannelHandler)(new PacketSplitter())).addLast((String)"decoder", (ChannelHandler)(new PacketDecoder(false))).addLast((String)"prepender", (ChannelHandler)(new PacketPrepender())).addLast((String)"encoder", (ChannelHandler)(new PacketEncoder(true))).addLast((String)"packet_handler", (ChannelHandler)networkmanager);
            }
        })).channel(NioSocketChannel.class)).connect(address, serverPort).syncUninterruptibly();
        return networkmanager;
    }
	
	public void displayConnecting(ServerInfo server) {
		this.show(GuiLoading.makeWaitTask("Verbinde zu " + (server.getAddress() == null ? "localhost" : server.getAddress()) + ":" + server.getPort() + " ..."));
	}

    public void connect(final ServerInfo server) {
    	this.displayConnecting(server);
        Log.NETWORK.info("Verbinde zu " + (server.getAddress() == null ? "localhost" : server.getAddress()) + ":" + server.getPort());
        new Thread(new Runnable() {
			public void run() {
				final NetConnection connection;
		        try
		        {
		            connection = createNetworkManagerAndConnect(server.getAddress() == null ? InetAddress.getLoopbackAddress() : InetAddress.getByName(IDN.toASCII(server.getAddress())), server.getPort());
		            connection.setNetHandler(new ClientLoginHandler(connection, Client.this, server));
		            connection.sendPacket(new HPacketHandshake(Util.PROTOCOL), new GenericFutureListener<Future<? super Void>>() {
						public void operationComplete(Future<? super Void> u) throws Exception {
							connection.setConnectionState(PacketRegistry.LOGIN);
						}
		            });
		        }
		        catch (UnknownHostException u)
		        {
		            Log.NETWORK.error(u, "Konnte nicht zu Server verbinden");
		            Client.this.schedule(new Runnable() {
						public void run() {
							Client.this.disconnected("Unbekannter Hostname: " + (server.getAddress() == null ? "localhost" : server.getAddress()));
						}
					});
		            return;
		        }
		        catch (Exception e)
		        {
		            Log.NETWORK.error(e, "Konnte nicht zu Server verbinden");
		            Client.this.schedule(new Runnable() {
						public void run() {
							Client.this.disconnected((e instanceof RuntimeException && !(e.getCause() instanceof RuntimeException) ? e.getCause() : e).toString());
						}
					});
		            return;
		        }
	            Client.this.schedule(new Runnable() {
					public void run() {
						Client.this.connection = connection;
					}
	            });
			}
        }, "Server connector").start();
    }

    public void joinDebugWorld() {
		ClientPlayer player = new ClientPlayer(this, new DummyConnection());
		this.debugWorld = true;
		this.charEditor = false;
		this.controller = new PlayerController(this, player);
		WorldClient world = new WorldClient(this, Space.INSTANCE);
		this.loadWorld(world, EntityRegistry.getEntityID(EntityCpu.class));
		this.player.setId(0);
		this.show(null);
		this.player.flying = true;
		this.player.noclip = true;
		this.player.addEffect(new StatusEffect(Effect.FLYING, Integer.MAX_VALUE, 1));
		this.player.setHeight(2.0f);
		world.setGravity(this.gravity = 1.0f);
		world.setTimeFactor(this.timeFactor = 1);
		this.dayCycle = true;
	}

    public void unloadWorld() {
        ClientPlayer netHandler = this.getNetHandler();
        if(netHandler != null)
            netHandler.cleanup();
        this.debugWorld = false;
        this.charEditor = false;
        this.viewEntity = null;
        this.connection = null;
        this.world = null;
        this.player = null;
        this.serverInfo = null;
        this.lastTickTime = -1;
        this.selectedCharacter = -1;
        this.playerList.clear();
        this.characterList.clear();
        this.soundManager.stopSounds();
    }

    public void refreshResources()
    {
		this.logFeed("Lade Texturen neu");
    	Font.unload();
    	Font.load(this.font);
		this.textureManager.onReload();
		ColormapLoader.reload();
		this.modelManager.onReload();
		this.renderItem.onReload();
		this.blockRenderer.onReload();
		this.renderGlobal.onReload();
		EntityTexManager.loadNpcTextures();
		this.renderGlobal.loadRenderers();
		this.logFeed("Texturen wurden neu geladen");
    }
    
    public void init()
    {
        this.textureManager = new TextureManager();
        this.textureManager.onReload();
        this.soundManager = new SoundManager(this);
        ColormapLoader.reload();
        GlState.enableTexture2D();
        GlState.shadeModel(GL11.GL_SMOOTH);
        GL11.glClearDepth(1.0D);
        GlState.enableDepth();
        GlState.depthFunc(GL11.GL_LEQUAL);
        GlState.enableAlpha();
        GlState.alphaFunc(GL11.GL_GREATER, 0.1F);
        GlState.cullFace(GL11.GL_BACK);
        GlState.enableCull();
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glLoadIdentity();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        this.textureMap = new TextureMap();
        this.textureManager.loadTexture(TextureMap.BLOCKS, this.textureMap);
        this.textureManager.bindTexture(TextureMap.BLOCKS);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
        this.modelManager = new ModelManager(this.textureMap);
        this.modelManager.onReload();
        this.renderItem = new RenderItem(this.textureManager, this.modelManager);
        this.renderManager = new RenderManager(this.textureManager, this.renderItem);
        this.itemRenderer = new ItemRenderer(this);
        this.renderItem.onReload();
        this.entityRenderer = new EntityRenderer(this);
        this.blockRenderer = new BlockRenderer(this.modelManager, this);
        this.blockRenderer.onReload();
        this.renderGlobal = new RenderGlobal(this);
        this.renderGlobal.onReload();
        EntityTexManager.loadNpcTextures();
        this.effectRenderer = new EffectRenderer(this.textureManager);
    }
    
    public void start()
    {
        this.tickStart = System.nanoTime();
        synchronized (this.tasks)
        {
            while (!this.tasks.isEmpty())
            {
            	FutureTask<?> task = this.tasks.poll();
				try {
					task.run();
					task.get();
				}
				catch(ExecutionException e1) {
					if(!(e1.getCause() instanceof ThreadQuickExitException))
						Log.SYSTEM.error(e1, "Fehler beim Ausführen von Render-Task " + task);
				}
				catch(InterruptedException e2) {
					Log.SYSTEM.error(e2, "Fehler beim Ausführen von Render-Task " + task);
				}
            }
        }
    }
    
    public void inputGui() {
        if (this.open != null)
        {
        	if(!(this.open.selected instanceof client.gui.element.Field || this.open.selected instanceof Area)) {
	            if(Bind.THROW.isPressed())
	                this.open.dropItem();
	            else if(Bind.RENAME.isPressed())
	                this.open.renameItem();
	            for(int z = 0; z < this.keyBindsHotbar.length; z++) {
	                if(this.keyBindsHotbar[z].isPressed())
	                    this.open.useHotbar(z);
	            }
        	}
            
            this.primary = this.secondary = this.tertiary = this.quarternary = false;
            this.zooming = false;
        }

        else
        {
            if (Bind.PERSPECTIVE.isPressed())
            {
                ++this.thirdPersonView;

                if (this.thirdPersonView > 2)
                {
                    this.thirdPersonView = 0;
                }
                if (this.debugCamEnable && this.thirdPersonView == 2)
                {
                    this.thirdPersonView = 0;
                }

                this.renderGlobal.setDisplayListEntitiesDirty();
            }

            boolean hadZoom = this.zooming;
        	this.zooming = Bind.ZOOM.isDown();
            if(this.zooming && !hadZoom) {
                this.zoomLevel = 2.0f;
            }
//        	this.overlay = this.keyBindPlayerList.isKeyDown();
            
            for (int l = 0; l < 9; ++l)
            {
                if (this.keyBindsHotbar[l].isPressed())
                {
//                	if(!this.showDebugProfilerChart) {
                	if(this.player.inventory.currentItem != l)
                        this.controller.resetUseCooldown();
                    this.player.inventory.currentItem = l;
//                    }
                }
            }

//            if (SKC.isBindPressed(SKC.KEY_INVENTORY))
//            {
//            }

            if (Bind.THROW.isPressed())
            {
                this.player.dropOneItem(this.ctrl());
            }
            else if(Bind.RENAME.isPressed()) {
            	ItemStack stack = this.player.getCurrentEquippedItem();
            	if(stack != null)
            		this.show(new GuiRename(-1, stack, null));
            }

            this.primary |= Bind.PRIMARY.isPressed();
            this.secondary |= Bind.SECONDARY.isPressed();
            this.tertiary |= Bind.TERTIARY.isPressed();
            this.quarternary |= Bind.QUARTERNARY.isPressed();
        }
    }
    
    public void tick() {
    	if (this.rightClickTimer > 0)
        {
            --this.rightClickTimer;
        }
//        this.ingameGui.updateTick();
        this.entityRenderer.getMouseOver(1.0F);
        if (/* !this.paused && */ this.world != null)
        {
            this.controller.update();
        }
        this.textureMap.update();
        if (this.open != null)
        {
            this.open.updateScreen();
        }

        if (this.open == null && this.player != null)
        {
            if (this.player.getHealth() <= 0)
            {
                this.show(null);
            }
        }

        if (this.open != null)
        {
            this.leftClickCounter = 10000;
        }
        else if (this.leftClickCounter > 0)
        {
            --this.leftClickCounter;
        }
        
        if (this.open == null && this.player != null) {
	        if (this.player.isUsingItem())
	        {
	            if (!Bind.SECONDARY.isDown())
	            {
	                this.controller.stopUsing(this.player);
	            }
	        }
	        else
	        {
	            if (this.primary)
	            {
	                this.primary();
	            }
	
	            if (this.secondary)
	            {
	                this.secondary();
	            }
	
	            if (this.tertiary)
	            {
	                this.tertiary();
	            }
	
	            if (this.quarternary)
	            {
	                this.quarternary();
	            }
	        }
	
	        if (Bind.SECONDARY.isDown() && this.rightClickTimer == 0 && !this.player.isUsingItem())
	        {
	            this.secondary();
	        }
	
	        this.sendClickBlockToController(this.open == null && Bind.PRIMARY.isDown());
        }
        
        this.primary = this.secondary = this.tertiary = this.quarternary = false;

        if (this.world != null)
        {
            if (this.player != null)
            {
                ++this.chunkLoadTimer;

                if (this.chunkLoadTimer == 30)
                {
                    this.chunkLoadTimer = 0;
                    this.world.ensureAreaLoaded(this.player);
                }
            }
            this.entityRenderer.updateRenderer();
            this.renderGlobal.updateClouds();
            this.world.decrLightning();
            this.world.updateEntities();
        }
        this.soundManager.update();
        if (this.world != null)
        {
            this.world.tick();
            if (/* !this.paused && */ this.world != null)
            {
                this.world.displayTick(ExtMath.floord(this.player.posX), ExtMath.floord(this.player.posY), ExtMath.floord(this.player.posZ));
            }
            this.effectRenderer.update();
        }
        else if (this.connection != null)
        {
        	if(this.connection.isChannelOpen())
        		this.connection.processReceivedPackets();
        	else
        		this.connection.checkDisconnected();
        }
    }
    
    public void render() {
		GlState.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GlState.enableDepth();
		GlState.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		if(this.wireframe) {
			GL11.glLineWidth(1.0f);
			GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE); // GL_FRONT_AND_BACK, GL_LINE
		}
        if(this.open == null) {
        	if(this.player != null)
        		this.player.setAngles(this.deltaX, this.deltaY);
            this.deltaX = this.deltaY = 0.0f;
        }
        if(this.player != null)
        	this.soundManager.setListener(this.player, (float)this.tick_fraction);
        if(this.player != null && this.player.isEntityInsideOpaqueBlock())
            this.thirdPersonView = 0;
        GL11.glPushMatrix();
        GL11.glClear(16640);
        GlState.enableTexture2D();
        if(this.world != null)
        	this.entityRenderer.renderWorld((float)this.tick_fraction, System.nanoTime() - this.tickStart);
        GL11.glPopMatrix();
        
        GlState.disableTexture2D();
        GlState.disableCull();
        GlState.enableBlend();
		if(this.wireframe)
			GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); // GL_FRONT_AND_BACK, GL_FILL
    }
	
	private static int stats(int x, int y, String name, int value, int max, int color) {
		int w = (int)(246.0f * (float)value / (float)max);
		Drawing.drawTextboxRight(name, x + 250, y, 0x3f000000);
		Drawing.drawTextbox(String.format(TextColor.GREEN + "%d " + TextColor.GRAY + "/ " + TextColor.NEON + "%d", value, max), x, y, 0x3f000000);
//		Drawing.drawRectColor(x, y + 20, 250, 10, 0xff000000);
		Drawing.drawRectBorder(x, y + 20, 250, 10, 0xff000000, 0xff202020, 0xffcfcfcf, 0xff6f6f6f);
		Drawing.drawGradient(x + 2 + 246 - w, y + 20 + 2, w, 6, color | 0xff000000, Util.mixColor(color | 0xff000000, 0xff000000));
		return y + 40;
	}
	
	private static int bar(int x, int y, String name, float fill, int color) {
		Drawing.drawTextbox(name, x, y, 0x3f000000);
//		Drawing.drawRectColor(x, y + 20, 250, 10, 0xff000000);
//		Drawing.drawRectColor(x, y + 20, (int)(250.0f * fill), 10, color | 0xff000000);
		Drawing.drawRectBorder(x, y + 20, 250, 10, 0xff000000, 0xff202020, 0xffcfcfcf, 0xff6f6f6f);
		Drawing.drawGradient(x + 2, y + 20 + 2, (int)(246.0f * fill), 6, color | 0xff000000, Util.mixColor(color | 0xff000000, 0xff000000));
		return y + 40;
	}
	
	public void renderHud() {
		this.setupOverlay();
		if(this.world != null && !(this.open instanceof GuiConsole) && this.player != null && this.viewEntity == this.player) {
			if(this.open == null) {
				if(this.thirdPersonView == 0) {
					if(this.drawDebug) {
						this.renderWorldDirections((float)this.tick_fraction);
					}
					else if(this.crosshairSize > 0) {
						Drawing.drawRect(this.fb_x / 2 - 1, this.fb_y / 2 - this.crosshairSize, 2, this.crosshairSize * 2, this.pointed != null && this.pointed.type != ObjectType.MISS ? this.crosshairColorTarget : this.crosshairColorBase);
						Drawing.drawRect(this.fb_x / 2 - this.crosshairSize, this.fb_y / 2 - 1, this.crosshairSize * 2, 2, this.pointed != null && this.pointed.type != ObjectType.MISS ? this.crosshairColorTarget : this.crosshairColorBase);
					}
				}
				
				int selected = this.player.inventory.currentItem;
				for(int n = 0; n < 9; n++) {
					int x = this.fb_x / 2 - 180 + n * 40 + 4;
					int y = this.fb_y - 40;
					Drawing.drawRectBorder(x - 1, y - 1, 36, 36, 0xff6f6f6f, selected == n ? 0xffffffff : 0xff000000, 0xffafafaf, 0xff4f4f4f);
				}

				ItemStack itemstack = this.player.inventory.getCurrentItem();
				String current = itemstack != null ? itemstack.getItem().getHotbarText(this.player, itemstack) : "";
				if(!current.isEmpty())
					Drawing.drawTextUpward(current, this.fb_x / 2, this.fb_y - 60, 0xffffffff);
			}
			
			int x = this.fb_x / 2;
			int y = 0;
			Iterator<Entry<Integer, Long>> iter = this.bars.entrySet().iterator();
			long now = System.currentTimeMillis();
			while(iter.hasNext()) {
				Entry<Integer, Long> status = iter.next();
				Entity ent;
				if(this.player != null && now - status.getValue() < 10000L && (ent = this.player.worldObj.getEntityByID(status.getKey())) instanceof EntityLiving) {
					EntityLiving entity = (EntityLiving)ent;
					String s = entity.getName() + TextColor.GRAY + " [" + 
							EntityLiving.getHealthColor(entity.getHealth(), entity.getMaxHealth()) + 
							entity.getHealth() + TextColor.GRAY + " / " + EntityLiving.getMaxHpColor(entity.getMaxHealth()) + 
							entity.getMaxHealth() + TextColor.GRAY + "]";
					Drawing.drawTextboxCentered(s, x, y, 0x3f000000);
					Drawing.drawRectBorder(x - 200, y + 20, 400, 10, 0xff000000, 0xff202020, 0xffcfcfcf, 0xff6f6f6f);
					Drawing.drawGradient(x - 200 + 2, y + 20 + 2, (int)(396.0f * ((float)entity.getHealth() / (float)entity.getMaxHealth())), 6, entity.getColor() | 0xff000000, Util.mixColor(entity.getColor() | 0xff000000, 0xff000000));
//					Drawing.drawRectColor(x - 200, y + 20, 400, 10, 0xff000000);
//					Drawing.drawRectColor(x - 200, y + 20, , 0xff000000 | );
					y += 40;
				}
				else {
					iter.remove();
				}
			}
			
			if(!this.drawDebug || this.open != null) {
				x = 40;
				y = 40;
		        for(StatusEffect effect : this.player.getEffects()) {
		            Effect potion = effect.getPotion();
		            int color = potion.getColor();
		            String name = (potion.isBadEffect() ? TextColor.ORANGE : TextColor.ACID) + effect.getEffectName();
		            String desc = TextColor.NEON + effect.getDurationString();
					Drawing.drawRectBorder(x, y, 250, Font.YGLYPH + 6, 0xff000000, 0xff202020, 0xffcfcfcf, 0xff6f6f6f);
					Drawing.drawGradient(x + 2, y + 2, effect.isInfinite() ? 246 : ((int)(246.0f * ((float)effect.getRemaining() / (float)effect.getDuration()))), Font.YGLYPH + 2, color | 0xff000000, Util.mixColor(color | 0xff000000, 0xff000000));
					Drawing.drawText(name, x + 4, y + 3, 0xffffffff);
					Drawing.drawTextRight(desc, x + 250 - 4, y + 3, 0xffffffff);
					y += 24;
		        }
			}
			
			EntityNPC entity = this.player;
			int absorb = entity.getAbsorptionAmount();
			int armor = entity.getTotalArmorValue();
			int stats = 1 + (absorb > 0 ? 1 : 0) + (armor > 0 ? 1 : 0) + (entity.vehicle instanceof EntityLiving ? 1 : 0)
					 + (entity.getManaPoints() > 0 ? 1 : 0) + (entity instanceof EntityWaterNPC ? 1 : 0);
			for(Energy energy : Energy.values()) {
				if(entity.getEnergy(energy) > 0)
					stats += 1;
			}
			x = this.fb_x - 40 - 250;
			y = this.fb_y - 40 - stats * 40;
			int hp = entity.getHealth();
			int max = entity.getMaxHealth();
			y = stats(x, y, TextColor.RED + "Schaden", hp, max, 0xff0000);
			if(absorb > 0)
				y = stats(x, y, TextColor.YELLOW + "Schadenspuffer", absorb, 1024, 0xffff00);
			if(entity instanceof EntityWaterNPC npc) {
				int moist = npc.getMoisture();
				int maxm = npc.getMaxMoisture();
				y = stats(x, y, TextColor.BLUE + "Feuchtigkeit", moist, maxm, 0x4f4fff);
			}
			if(armor > 0)
				y = stats(x, y, TextColor.GRAY + "Rüstung", armor, 1024, 0x707070);
			if(entity.vehicle instanceof EntityLiving living) {
				int vh = living.getHealth();
				int vhMax = living.getMaxHealth();
				y = stats(x, y, living.getDisplayName(), vh, vhMax, 0xff6060);
			}
			if(entity.getManaPoints() > 0) {
				int mana = entity.getManaPoints();
				int maxm = entity.getMaxMana();
				y = stats(x, y, TextColor.CYAN + "Mana", mana, maxm, 0x0000ff);
			}
			for(int z = 0; z < Energy.values().length; z++) {
				Energy type = Energy.values()[z];
				int energy = entity.getEnergy(type);
				if(energy > 0) {
					int emax = entity.getEnergyCap(type);
					y = stats(x, y, TextColor.LGRAY + type.display, energy, emax, entity.hasEnergyEffect(type) ? type.effect : type.color);
				}
			}
		
			x = 40;
			y = this.fb_y - 40 - (this.player.isRidingHorse() && this.player.getHorseJumpPower() != 0.0f ? 2 : 1) * 40;
			if(this.player.isRidingHorse() && this.player.getHorseJumpPower() != 0.0f) // {
				y = bar(x, y, String.format(TextColor.NEON + "Sprungkraft: " + TextColor.CYAN + "%d %%", (int)(this.player.getHorseJumpPower() * 100.0f)), 
						this.player.getHorseJumpPower(), 0x4040ff);
			y = bar(x, y, String.format(TextColor.ACID + "EXP: " + TextColor.GREEN + "Level %d, %d/%d", this.player.experienceLevel, (int)((float)this.player.xpBarCap() * this.player.experience), this.player.xpBarCap()), this.player.experience, 0x40ff40);
		}

//		gdr.shader = 0;
//		glUseProgram(0);
//		glBindVertexArray(0);
//		glActiveTexture(GL_TEXTURE0);
//		GlState.setActiveTexture(WCF.GL_TEXTURE0);
//		glBindTexture(GL_TEXTURE_2D, 0);
		GlState.bindTexture(0);
//		jsys.tooltip[0] = 0;
//		this.tooltip = null;
//		GlState.resetTextures();
    	GlState.setActiveTexture(GL13.GL_TEXTURE0);
    	GlState.enableTexture2D();
//    	GlState.disableDepth();
        if(this.world != null && this.open == null && this.player != null && this.viewEntity == this.player) {
			GlState.enableRescaleNormal();
			GlState.enableBlend();
	        GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
			// GlState.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
			ItemRenderer.enableGUIStandardItemLighting();
//	        this.getTextureManager().bindTexture("textures/gui/inventory.png");
        	GL11.glPushMatrix();
        	GL11.glTranslatef((float)(this.fb_x / 2 - 180 + 4 + 1), (float)(this.fb_y - 40 + 1), 0.0f);
        	GL11.glScalef(2.0f, 2.0f, 2.0f);

			for(int index = 0; index < 9; ++index) {
				int xPos = index * 20;
//				int yPos = this.fb_y - 40;
				ItemStack itemstack = this.player.inventory.mainInventory[index];
				if(itemstack != null) {
					GlState.enableDepth();
					this.renderItem.renderItemAndEffectIntoGUI(itemstack, xPos, 0);
//					this.renderItem.renderItemOverlayIntoGUI(itemstack, xPos, 0, null);
				}
			}

            GL11.glPopMatrix();
			ItemRenderer.disableStandardItemLighting();
			GlState.disableRescaleNormal();
			GlState.disableBlend();
        }
        
        GlState.disableTexture2D();
        GlState.disableCull();
        GlState.enableBlend();
//		SKC.gui();
//		java_callv(gui, (sys.console->mouse_x - jsys.container_x) / 2, (sys.console->mouse_y - jsys.container_y) / 2);
//		java_check_exc();
		GlState.disableDepth();
//		SKC.foreground();
        if(this.world != null && this.open == null && this.player != null && this.viewEntity == this.player) {
			for(int index = 0; index < 9; ++index) {
				ItemStack itemstack = this.player.inventory.mainInventory[index];
				if(itemstack != null) {
					GuiContainer.renderItemOverlay(itemstack, 
							this.fb_x / 2 - 180 + 4 + 1 + index * 40, this.fb_y - 40 + 1, null, index == this.player.inventory.currentItem ? this.controller.getUseCooldown() : 0, this.controller.getUseCooldownMax());
				}
			}
        }
        if(this.open != null)
        	this.open.render();
        else if(this.world == null || this.charEditor)
        	Drawing.drawScaled(this, Gui.BACKGROUND);
        if(Bind.INFO.isDown() && (this.open == null || !(this.open.selected instanceof client.gui.element.Field || this.open.selected instanceof Area)))
        	this.drawInfo();
		if(this.hudOverlay && !(this.open instanceof GuiConsole)) {
			this.drawOverlay(this.feed, this.feedSize, false, 1, 0, 0);
			this.drawOverlay(this.console, this.consoleSize, true, 1, 0, this.fb_y);
			this.drawOverlay(this.hotbar, this.hotbarSize, true, 0, this.fb_x / 2, this.fb_y - 120);
			this.drawOverlay(this.chat, this.chatSize, true, -1, this.fb_x, this.fb_y);
		}
		if(this.drawFps) { //  && !(this.open instanceof GuiConsole)
			if(this.drawDebug) {
				if(this.open == null) {
					this.renderStats();
					this.renderLagometer();
				}
				else {
					Drawing.drawText(String.format("%s%.2f" + TextColor.RESET + " %s [%s" + TextColor.RESET + "], %.3f ms, W %d x %d%s", 
							this.framecode(), this.framerate < 1.0f ? 1.0f / this.framerate : this.framerate, this.framerate < 1.0f ? "SPF" : "FPS", 
							this.vsync ? TextColor.DGRAY + "VSYNC" : (this.syncLimited ? TextColor.GREEN + "" + this.syncLimit : TextColor.RED + "UNL"),
									(float)PerfSection.getTotal(false) / 1000.0f, this.fb_raw_x, this.fb_raw_y,
									this.fullscreen ? " @ " + (this.vidMode == null ? "?" : this.vidMode.refresh()) + " Hz" : ""), 
							0, 0, 0xffffffff);
				}
			}
			else {
				Drawing.drawText(String.format("%s%.2f", framecode(), this.framerate), 0, 0, 0xffffffff);
			}
		}
        GlState.enableBlend();
	}
	
	public void renderStats() {
		int offset = 5;
		int x1 = this.fb_x - 320;
		int y1 = 0;
		int y2 = this.fb_y;
//		int pos = 0;
		String str = this.getLeft();
//		if(jstr)
//			str = (*jsys.env)->GetStringUTFChars(jsys.env, jstr, NULL);
		// elem->r_dirty = 1;
		// if(!value)
		// 	return;
		offset += str != null ? 18 : 0;
		String draw = String.format(
			"OpenGL: %s\n" +
			"Renderer: %s (%s)\n" +
			"Bildrate: %s%.2f" + TextColor.RESET + " %s [%s" + TextColor.RESET + "], %.3f ms, W %d x %d%s\n" +
			"Tickrate: %s%.2f" + TextColor.RESET + " %s [" + TextColor.GREEN + "%.1f" + TextColor.RESET + "], %.3f ms, E %d ms" +
			"%s%s"
			,
			GL11.glGetString(GL11.GL_VERSION), // WCF.glGetString(WCF.GL_SHADING_LANGUAGE_VERSION),
			GL11.glGetString(GL11.GL_RENDERER), GL11.glGetString(GL11.GL_VENDOR),
			this.framecode(), this.framerate < 1.0f ? 1.0f / this.framerate : this.framerate, this.framerate < 1.0f ? "SPF" : "FPS", 
			this.vsync ? TextColor.DGRAY + "VSYNC" : (this.syncLimited ? TextColor.GREEN + "" + this.syncLimit : TextColor.RED + "UNL"),
			(float)PerfSection.getTotal(false) / 1000.0f, this.fb_raw_x, this.fb_raw_y,
			this.fullscreen ? " @ " + (this.vidMode == null ? "?" : this.vidMode.refresh()) + " Hz" : "",
			this.tpscode(), this.tickrate < 1.0f ? 1.0f / this.tickrate : this.tickrate, 
			this.tickrate < 1.0f ? "SPT" : "TPS", (float)this.tickTarget / 1000.0f,
			(float)this.tick_time / 1000.0f, this.tickTimeout,
			str != null ? "\n" : "", str != null ? str : ""
		);
//		if(str)
//			(*jsys.env)->ReleaseStringUTFChars(jsys.env, jstr, str);
		// gui_render_text(elem, 0, 0, sys.style.text_label, sys.work_buf);
		Drawing.drawText(draw, 0, 0, 0xffffffff);
		
		str = this.getRight(this.debugPlayer);
//		str = jstr ? (*jsys.env)->GetStringUTFChars(jsys.env, jstr, NULL) : NULL;
		// offset = 10 + (str ? 10 : 0);
		StringBuilder sb = new StringBuilder();
		for(PerfSection perf : PerfSection.values()) {
			if(sb.length() > 0)
				sb.append('\n');
			sb.append(perf.getName());
		}
		Drawing.drawText(sb.toString(), x1 + 0, y1, 0xffffffff);
//		pos = 0;
		sb.setLength(0);
		for(PerfSection perf : PerfSection.values()) {
			if(sb.length() > 0)
				sb.append('\n');
			sb.append(String.format("%.3f ms", (float)perf.getLast() / 1000.0f));
		}
		Drawing.drawText(sb.toString(), x1 + 120, y1, 0xffffffff);
//		pos = 0;
		sb.setLength(0);
		long total = PerfSection.getTotal(true);
		for(PerfSection perf : PerfSection.values()) {
			if(sb.length() > 0)
				sb.append('\n');
			sb.append(String.format("%.2f %%", ((float)perf.getLast() / (float)total) * 100.0f));
		}
		Drawing.drawText(sb.toString(), x1 + 240, y1, 0xffffffff);
		if(str != null) {
			y1 = Font.YGLYPH * 10;
			Drawing.drawText(str, x1, y1, 0xffffffff);
//			(*jsys.env)->ReleaseStringUTFChars(jsys.env, jstr, str);
		}
	}
    
    public void finish() {
		if(this.screenshot) {
			this.screenshot = false;
			screenshot();
		}
		if(this.cameraUsed) {
			this.cameraUsed = false;
			if(this.world != null)
				this.world.setLastLightning(1, 0xffffff);
		}
		if(this.isDirty())
			this.save();
    	Thread.yield();
    	while(System.currentTimeMillis() >= this.debugUpdateTime + 1000L) {
    		this.chunksUpdated = RenderChunk.renderChunksUpdated;
    		RenderChunk.renderChunksUpdated = 0;
    		this.debugUpdateTime += 1000L;
    	}
    }
	
//	public void action(int btn) {
//		if(this.open != null) {
//			this.soundManager.playSound(new PositionedSound(SoundEvent.CLICK, 1.0F));
//			this.open.actionPerformed(btn);
//		}
//	}

	public void scroll(int dir) {
//		if(this.open != null) // {
//			return;
//			if(this.open instanceof GuiCheat)
//				((GuiCheat)this.open).scroll(dir);
//		}
		if(this.zooming)
    		this.zoomLevel = ExtMath.clampf(this.zoomLevel + (dir < 0 ? -0.25f : 0.25f), 2.0f, 16.0f);
        else if(this.player != null) {
            this.player.inventory.changeCurrentItem(dir);
            this.controller.resetUseCooldown();
        }
	}
    
//    public void resize(int width, int height)
//    {
//        this.fb_x = width;
//        this.fb_y = height;
//        // if(this.open != null)
//			// this.open.initialize(this, this.fb_x, this.fb_y);
//    }
    
//    public void distance(int distance)
//    {
//        this.renderDistance = distance;
//        this.updateViewDistance();
//    }
    
//    public void buildTime(int time)
//    {
//        this.maxBuildTime = time;
//    }
    
    public void moveCamera(float x, float y) {
    	this.deltaX += x;
    	this.deltaY += y;
    }
    
//    public void closeScreen() {
//    	if(this.open != null) {
//    		if(this.thePlayer != null)
//    			this.thePlayer.setScreenClosed();
//        	this.displayGuiScreen(null);
//    	}
//    }
    
    public void show(Gui gui)
    {
    	if(!this.refreshing)
    		this.waitingForFile = false;
    	if(this.player != null)
    		this.player.setScreenClosed();
        if (this.open != null)
        {
            this.open.onGuiClosed();
        }
//		if(this.thePlayer != null && this.thePlayer.getHealth() <= 0 && this.open instanceof GuiGameOver)
//			this.thePlayer.respawnPlayer();

//        if (guiScreenIn == null && this.theWorld == null)
//        {
//            guiScreenIn = new GuiMainMenu();
//        }
//        else
        if (gui == null && this.world != null && this.player.getHealth() <= 0)
        {
            gui = GuiGameOver.INSTANCE;
        }
//        else if(gui == null && this.open instanceof GuiInventory && this.itemCheat) {
//        	gui = new GuiCheat();
//        }

        this.open = gui;
        if (gui != null)
        {
    		this.menu(true);
//    		if(this.open != null) {
//    			this.open.init();
//    		}
//    		else {
//    		}
            gui.init();
//        	if(gui instanceof GuiEmpty)
//        		SKC.setGuiMenu();
//        	else
//        		SKC.setGuiAny();
    		Window.setTitle(String.format("%s - %s", VERSION, gui.getTitle()));
        }
        else
        {
    		this.menu(false);
            this.leftClickCounter = 10000;
			Bind.disableMouse();
			Window.setTitle(String.format("%s - %s%s", VERSION, "Welt / Render", this.nograb ? "" : " (Maus gefangen)"));
//            SKC.setGuiNone();
        }
    }

    private void sendClickBlockToController(boolean leftClick)
    {
        if (!leftClick)
        {
            this.leftClickCounter = 0;
            this.controller.resetInteraction();
        }

        if (this.leftClickCounter <= 0 && !this.player.isUsingItem())
        {
            if (leftClick && this.pointed != null && this.pointed.type == HitPosition.ObjectType.BLOCK)
            {
                BlockPos blockpos = this.pointed.block;

                if (this.world.getState(blockpos).getBlock() != Blocks.air && this.controller.damageBlock(blockpos, this.pointed.side))
                {
                    this.effectRenderer.damageBlock(blockpos, this.pointed.side);
                    this.player.swingItem();
                }
            }
            else
            {
                this.controller.resetProgress();
            }
        }
    }

    private void primary()
    {
        if (this.leftClickCounter <= 0)
        {
            if (this.pointed == null)
            {
                this.player.swingItem();
                Log.TICK.warn("Null zurückgegeben als 'hitResult', das sollte niemals passieren!");
                this.leftClickCounter = 10;
            }
            else
            {
                ItemStack itemstack = this.player.inventory.getCurrentItem();
                if ((this.pointed.type != ObjectType.BLOCK || this.world.getState(this.pointed.block).getBlock() == Blocks.air) && itemstack != null && itemstack.getItem().onAction(itemstack, this.player, this.world, ItemControl.PRIMARY, null))
                {
                    this.player.swingItem();
            		this.player.client.addToSendQueue(new CPacketAction(Action.ITEM_ACTION, ItemControl.PRIMARY.ordinal()));
            		if(this.player.getHeldItem() != null && this.player.getHeldItem().isEmpty())
            			this.player.inventory.mainInventory[this.player.inventory.currentItem] = null;
                    this.leftClickCounter = 10;
            		return;
                }
                
                switch (this.pointed.type)
                {
                    case ENTITY:
                    	this.player.swingItem();
                        this.controller.attackEntity(this.player, this.pointed.entity);
                        break;
                    case BLOCK:
                    	this.player.swingItem();
                        BlockPos blockpos = this.pointed.block;
                        if (this.world.getState(blockpos).getBlock() != Blocks.air)
                        {
                            this.controller.clickBlock(blockpos, this.pointed.side);
                            break;
                        }
                        this.leftClickCounter = 10;
                        break;
                    case MISS:
                    default:
                    	this.player.swingItem();
                        this.leftClickCounter = 10;
                }
            }
        }
    }
    
    private void secondary()
    {
        if (!this.controller.isHittingBlock())
        {
            this.rightClickTimer = 4;
            boolean flag = true;
            ItemStack itemstack = this.player.inventory.getCurrentItem();

            if (itemstack != null && itemstack.getItem() == Items.camera && !this.saving)
            {
                this.screenshot = this.cameraUsed = true;
            }
            
            if (this.pointed == null)
            {
                Log.TICK.warn("Null zurückgegeben als 'hitResult', das sollte niemals passieren!");
            }
            else
            {
                if ((this.pointed.type != ObjectType.BLOCK || this.world.getState(this.pointed.block).getBlock() == Blocks.air) && itemstack != null && itemstack.getItem().onAction(itemstack, this.player, this.world, ItemControl.SECONDARY, null))
                {
                    this.player.swingItem();
            		this.player.client.addToSendQueue(new CPacketAction(Action.ITEM_ACTION, ItemControl.SECONDARY.ordinal()));
            		if(this.player.getHeldItem() != null && this.player.getHeldItem().isEmpty())
            			this.player.inventory.mainInventory[this.player.inventory.currentItem] = null;
            		return;
                }
            	
                switch (this.pointed.type)
                {
                    case ENTITY:
//                        if (this.controller.isPlayerRightClickingOnEntity(this.thePlayer, this.pointed.entityHit, this.pointed))
//                        {
//                            flag = false;
//                        }
//                        else
                        if (this.controller.interact(this.player, this.pointed.entity))
                        {
                            flag = false;
                        }

                        break;

                    case BLOCK:
                        BlockPos blockpos = this.pointed.block;

                        if (this.world.getState(blockpos).getBlock() != Blocks.air)
                        {
                            int i = itemstack != null ? itemstack.getSize() : 0;

                            if (this.controller.clickRight(this.player, this.world, itemstack, blockpos, this.pointed.side, this.pointed.vec))
                            {
                                flag = false;
                                this.player.swingItem();
                            }

                            if (itemstack == null)
                            {
                                return;
                            }

                            if (itemstack.isEmpty())
                            {
                                this.player.inventory.mainInventory[this.player.inventory.currentItem] = null;
                            }
                            else if (itemstack.getSize() != i) // || this.controller.isCreative())
                            {
                                this.entityRenderer.itemRenderer.resetProgress();
                            }
                        }
                }
            }

            if (flag)
            {
                ItemStack itemstack1 = this.player.inventory.getCurrentItem();
                
                if (itemstack1 != null && this.controller.sendUseItem(this.player, this.world, itemstack1))
                {
                    this.entityRenderer.itemRenderer.resetProgress();
                }
            }
        }
    }
    
    private void tertiary()
    {
        if (this.pointed != null)
        {
        	if(this.player.getHeldItem() != null && this.player.getHeldItem().getItem().onAction(this.player.getHeldItem(), this.player, this.world, ItemControl.TERTIARY, null)) {
        		this.player.client.addToSendQueue(new CPacketAction(Action.ITEM_ACTION, ItemControl.TERTIARY.ordinal()));
        		if(this.player.getHeldItem() != null && this.player.getHeldItem().isEmpty())
        			this.player.inventory.mainInventory[this.player.inventory.currentItem] = null;
        		return;
        	}
        	
            Item item = null;

            if (this.pointed.type == HitPosition.ObjectType.BLOCK)
            {
                BlockPos blockpos = this.pointed.block;
                Block block = this.world.getState(blockpos).getBlock();

                if (block == Blocks.air)
                {
                    return;
                }

                item = block.getItem(this.world, blockpos);

                if (item == null)
                {
                    return;
                }

                Block block1 = item instanceof ItemBlock && !block.isPickStrict() ? item.getBlock() : block;
                // meta = block1.getDamageValue(this.world, blockpos); TODO: data
            }
            else
            {
                if (this.pointed.type != HitPosition.ObjectType.ENTITY || this.pointed.entity == null || !this.itemCheat)
                {
                	return;
                }
                item = this.pointed.entity.getItem();
                if(item == null)
                    return;
            }

            InventoryPlayer inventoryplayer = this.player.inventory;
            
            if(inventoryplayer.setCurrentItem(item))
            	this.controller.resetUseCooldown();
            if(this.itemCheat) {
                this.player.client.addToSendQueue(new CPacketCheat(item, -2 - inventoryplayer.currentItem, this.ctrl()));
            }
        }
    }

    private void quarternary()
    {
        if (this.pointed != null)
        {
        	if(this.player.getHeldItem() != null && this.player.getHeldItem().getItem().onAction(this.player.getHeldItem(), this.player, this.world, ItemControl.QUARTERNARY, null)) {
        		this.player.client.addToSendQueue(new CPacketAction(Action.ITEM_ACTION, ItemControl.QUARTERNARY.ordinal()));
        		if(this.player.getHeldItem() != null && this.player.getHeldItem().isEmpty())
        			this.player.inventory.mainInventory[this.player.inventory.currentItem] = null;
        		return;
        	}
        }
    }

//    private void printDebugMessage(String text) {
//    	this.printDebugMessage(text, null);
//    }
//    
//    private void printDebugMessage(String text, String desc) {
//    	SKC.notify(text, desc);
//		this.soundManager.playSound(new PositionedSound(SoundEvent.CLICK));
//    }
    
    public void loadWorld(WorldClient world, int type)
    {
        this.viewEntity = null;
        this.connection = null;
        this.soundManager.stopSounds();
        this.world = world;

        if (this.renderGlobal != null)
        {
            this.renderGlobal.setWorldAndLoadRenderers(world);
        }

        if (this.effectRenderer != null)
        {
            this.effectRenderer.setWorld(world);
        }

        if (this.player == null)
        {
            this.player = this.controller.createPlayerEntity(world, type);
            this.player.rotYaw = -180.0F;
        }

        this.player.preparePlayerToSpawn();
        world.spawnEntityInWorld(this.player);
        this.viewEntity = this.player;

        System.gc();
//        SKC.loaded(true);
    }

    public void setDimensionAndSpawnPlayer(int type)
    {
        this.world.removeAllEntities();
        int i = 0;

        if (this.player != null)
        {
            i = this.player.getId();
            this.world.removeEntity(this.player);
        }

        this.viewEntity = null;
        EntityNPC entityplayersp = this.player;
        this.player = this.controller.createPlayerEntity(this.world, type);
        this.player.getDataWatcher().updateWatchedObjectsFromList(entityplayersp.getDataWatcher().getAllWatched());
        this.viewEntity = this.player;
        this.player.preparePlayerToSpawn();
        this.world.spawnEntityInWorld(this.player);
        this.player.rotYaw = -180.0F;
        this.player.setId(i);

//        if (this.open instanceof GuiGameOver)
//        {
//        if(!this.charEditor)
//        	this.displayGuiScreen(null);
//        }
    }

    public ClientPlayer getNetHandler()
    {
        return this.player != null ? (ClientPlayer)this.player.client : null;
    }

//    public void setSkin(BufferedImage skin, String id, ModelType model, boolean slim)
//    {
//        this.skinSet = id == null ? "" : id;
//        this.skinSlim = slim;
//        if(this.getNetHandler() != null) {
//        	this.getNetHandler().addToSendQueue(new CPacketSkin(skin, model));
//        }
//        else {
//        	this.confirmSkin(true);
//        }
//    }
    
//    public void confirmSkin(boolean changed) {
//    	if(changed && this.skinSet != null) {
//    		this.skinId = this.skinSet;
//        	if(this.thePlayer != null)
//        		this.thePlayer.sendQueue.addToSendQueue(new CPacketAction(CPacketAction.Action.SET_MODELPARTS, 
//        			(this.thePlayer.getModelParts() & ~ModelPart.ARMS_SLIM.getMask()) | 
//        			(this.skinSlim ? ModelPart.ARMS_SLIM.getMask() : 0)));
//    	}
//    	else if(changed) {
//    		this.skinId = "";
//    	}
//        this.skinSet = null;
//        this.skinSlim = false;
//    }

    public TextureManager getTextureManager()
    {
        return this.textureManager;
    }

    public TextureMap getTextureMapBlocks()
    {
        return this.textureMap;
    }

    public SoundManager getSoundManager()
    {
        return this.soundManager;
    }
    
    public void performAction(Action action) {
    	if(this.player != null)
        	this.player.client.addToSendQueue(new CPacketAction(action));
    }
    
    public void performAction(Action action, int data) {
    	if(this.player != null)
        	this.player.client.addToSendQueue(new CPacketAction(action, data));
    }

    public void setBossStatus(EntityLiving entity) {
    	if(this.bars.size() < 5 || this.bars.containsKey(entity.getId()))
    		this.bars.put(entity.getId(), System.currentTimeMillis());
    }

    public Entity getRenderViewEntity()
    {
        return this.viewEntity;
    }

    public Entity getPointedEntity()
    {
        return this.pointedEntity;
    }

    public void setPointedEntity(Entity entity)
    {
        this.pointedEntity = entity;
    }

    public void setRenderViewEntity(Entity viewingEntity)
    {
        this.viewEntity = viewingEntity;
    }

    private <V> ListenableFuture<V> addScheduledTask(Callable<V> callableToSchedule)
    {
        if (!this.isMainThread())
        {
            ListenableFutureTask<V> listenablefuturetask = ListenableFutureTask.<V>create(callableToSchedule);

            synchronized (this.tasks)
            {
                this.tasks.add(listenablefuturetask);
                return listenablefuturetask;
            }
        }
        else
        {
            try
            {
                return Futures.<V>immediateFuture(callableToSchedule.call());
            }
            catch (Exception exception)
            {
				Log.SYSTEM.error(exception, "Fehler beim sofortigen Ausführen von Render-Task " + callableToSchedule);
                return Futures.immediateFailedFuture(exception);
            }
        }
    }

    public ListenableFuture<Object> schedule(Runnable runnableToSchedule)
    {
        return this.<Object>addScheduledTask(Executors.callable(runnableToSchedule));
    }

    public boolean isMainThread()
    {
        return Thread.currentThread() == this.clThread;
    }

    public BlockRenderer getBlockRendererDispatcher()
    {
        return this.blockRenderer;
    }

    public RenderManager getRenderManager()
    {
        return this.renderManager;
    }

    public RenderItem getRenderItem()
    {
        return this.renderItem;
    }

    public ItemRenderer getItemRenderer()
    {
        return this.itemRenderer;
    }
    
    public void setTicked(String info) {
    	this.lastTicked = System.currentTimeMillis();
    	this.serverInfo = info;
    }
    
    public void setLastTick(int time) {
    	if(this.lastTickTime >= 0) {
	    	this.lastTicked = System.currentTimeMillis();
	    	this.lastTickTime = time;
    	}
    }
    
    public void updatePlayerMoveState()
    {
        this.moveStrafe = 0.0F;
        this.moveForward = 0.0F;

        if (this.open == null && Bind.FORWARD.isDown())
        {
            ++this.moveForward;
        }

        if (this.open == null && Bind.BACKWARD.isDown())
        {
            --this.moveForward;
        }

        if (this.open == null && Bind.LEFT.isDown())
        {
            ++this.moveStrafe;
        }

        if (this.open == null && Bind.RIGHT.isDown())
        {
            --this.moveStrafe;
        }

        this.jump = this.open == null && Bind.UP.isDown();
        this.sneak = this.open == null && Bind.DOWN.isDown();
    	this.sprint = this.open == null && Bind.FAST.isDown();

        if (this.sneak)
        {
            this.moveStrafe = (float)((double)this.moveStrafe * 0.3D);
            this.moveForward = (float)((double)this.moveForward * 0.3D);
        }
    }

	public String getLeft() {
		long maxMem = Runtime.getRuntime().maxMemory();
		long totalMem = Runtime.getRuntime().totalMemory();
		long freeMem = Runtime.getRuntime().freeMemory();
		long usedMem = totalMem - freeMem;
		
		String mem = String.format("JVM-Speicher: %d%% %d/%dMB", usedMem * 100L / maxMem, usedMem / 1024L / 1024L, maxMem / 1024L / 1024L) 
				+ "\n" + 
        		String.format("JVM-Reserviert: %d%% %dMB", totalMem * 100L / maxMem, totalMem / 1024L / 1024L);
		if(this.world == null) {
			return mem;
		}
		
		BlockPos blockpos = new BlockPos(this.viewEntity.posX, this.viewEntity.getEntityBoundingBox().minY,
				this.viewEntity.posZ);

		Entity entity = this.viewEntity;
		Facing facing = entity.getHorizontalFacing();
		String dirStr = "Ungültig";
		switch(facing) {
			case NORTH:
				dirStr = "Norden (Nach negativer Z)";
				break;
			case SOUTH:
				dirStr = "Süden (Nach positiver Z)";
				break;
			case WEST:
				dirStr = "Westen (Nach negativer X)";
				break;
			case EAST:
				dirStr = "Osten (Nach positiver X)";
				break;
		}
		
		Biome biome = null;
		String bline;
		String lline;
		if(this.world.isBlockLoaded(blockpos)) {
			ChunkClient chunk = this.world.getChunk(blockpos);
			biome = chunk.getBiome(blockpos);
			bline = "Biom: " + biome.display + " (" + biome.name + ")" + /* (this.debugHideInfo ? "" : */ (", D: " + 
					TextColor.stripCodes(this.world.dimension.getFormattedName(false)) + 
					" (" + (this.dimensionName == null ? UniverseRegistry.getName(this.world.dimension) : this.dimensionName) + ")");
			lline = "Licht: " + chunk.getLightSub(blockpos, 0) + " (" + chunk.getLight(LightType.SKY, blockpos) + " Himmel, "
					+ chunk.getLight(LightType.BLOCK, blockpos) + " Blöcke, " + String.format(
					"%.1f", this.world.getSunBrightness(1.0f) * 15.0f) + " Welt), A: " 
					+ String.format("%.3f", this.world.getCelestialAngle(1.0f));
		}
		else {
			bline = "Biom: <?>, D: " + 
					TextColor.stripCodes(this.world.dimension.getFormattedName(false)) + 
					" (" + (this.dimensionName == null ? UniverseRegistry.getName(this.world.dimension) : this.dimensionName) + ")";
			lline = "Licht: " + String.format(
					"%.1f", this.world.getSunBrightness(1.0f) * 15.0f) + " Welt, A: " 
					+ String.format("%.3f", this.world.getCelestialAngle(1.0f));
		}

		float temp = Math.max(this.world.getTempOffset() + (biome != null ? biome.getTemperature(blockpos) : 0.0f), 0.0f);
		long ticked = System.currentTimeMillis() - this.lastTicked;
		
        return 
        		mem + "\n" +  
//        		(this.debugWorld ? "Debug-Welt" : 
//        				(this.isServer() ? "ES: " + this.server.getFolderName() :
//        				"MS: " + (
//        				this.connected != null ? this.connected : "[???]"))), 
        		this.renderGlobal.getDebugInfoRenders() + "\n" +  
        		this.renderGlobal.getDebugInfoEntities() + "\n" +  
        		"Partikel: " + this.effectRenderer.getParticleCount() + ". O: " + this.world.getDebugLoadedEntities() + "\n" +  
        		this.world.getInfo() + "\n" +  
        		// "", 
        		String.format("XYZ: %.3f / %.3f / %.3f", this.viewEntity.posX, 
        				this.viewEntity.getEntityBoundingBox().minY, this.viewEntity.posZ) + "\n" +  
        		String.format("Block: %d %d %d, R: '%s/%s'", blockpos.getX(), blockpos.getY(), blockpos.getZ(), 
        				Util.getRegionFolder(blockpos.getX() >> 4, blockpos.getZ() >> 4), 
        				Util.getRegionName(blockpos.getX() >> 4, blockpos.getZ() >> 4)) + "\n" +  
        		String.format("Chunk: %d %d %d + %d %d %d, FOV: %.1f °%s", blockpos.getX() >> 4, blockpos.getY() >> 4, blockpos.getZ() >> 4, 
						blockpos.getX() & 15, blockpos.getY() & 15, blockpos.getZ() & 15, this.zooming ? 
								(this.fov / this.zoomLevel) : this.fov, this.zooming ? 
								String.format(" (Zoom x%.1f)", this.zoomLevel) : "") + "\n" +  
        		String.format("Richtung: %s (%.1f / %.1f)", dirStr, 
        				ExtMath.wrapf(entity.rotYaw), ExtMath.wrapf(entity.rotPitch)) + "\n" + 
        		bline + "\n" + 
        		lline + "\n" + 
        		String.format("Zeit: %d T, R %d / %d T, U %d / %d T", 
				this.world.getDayTime(),
				this.world.getDayTime() % this.world.dimension.getRotationalPeriod(),
				this.world.dimension.getRotationalPeriod(),
				this.world.getDayTime() % this.world.dimension.getOrbitalPeriod(),
				this.world.dimension.getOrbitalPeriod()
        		) + "\n" + 
        		String.format("Laub: %s%s, T: %.2f K / %.2f °C, %s (R %.1f, %.1f)", 
				!this.world.dimension.getType().days ? "*" : "",
				this.world.getLeavesGen(blockpos).getDisplayName(), 
				temp, World.ABSOLUTE_ZERO + temp,
				this.world.getWeather().getDisplay(), this.world.getRainStrength(), 
				this.world.getDarkness()
        		) + "\n" + 
        		String.format("Zeitfaktor: %dx, Schwerkraft: %.2f m/s²", 
				this.timeFactor, this.world.gravity * 10.0
        		) + "\n" + 
        		String.format("Letzte Zeitsynch.: + %d.%d s", 
				ticked / 1000L, (ticked / 100L) % 10L
        		) + "\n" +
				"Startwert: " + this.world.dimension.getSeed() + 
        		(this.serverInfo != null ? "\n" + this.serverInfo : "")
//			IWorldServer world = this.server.getWorld(this.theWorld.dimension.getDimensionId());
//			if(world != null)
//				list.add("Seed: " + world.getSeed());
        ;
	}

	public String getRight(boolean showPlayerInfo) {
        if(this.world == null) {
			return null;
		}
        
		if(!showPlayerInfo && this.pointed != null && this.pointed.type == HitPosition.ObjectType.BLOCK
				&& this.pointed.block != null) {
			BlockPos pos = this.pointed.block;
			State block = this.world.getState(pos);

			if(!this.debugWorld) {
				block = block.getBlock().getActualState(block, this.world, pos);
			}
			
			StringBuilder str = new StringBuilder(
					"Schaue auf: " + BlockRegistry.getName(block.getBlock()) + "\n" + 
					String.format("Position: %d %d %d", pos.getX(), pos.getY(), pos.getZ())
				);
			for(Entry<Property, Comparable> entry : block.getProperties().entrySet()) {
				str.append("\n" + (block.getBlock().getSavedProperties().contains(entry.getKey()) ? TextColor.NEON : TextColor.LGRAY) + entry.getKey().getName() + TextColor.RESET + ": " + (entry.getValue() instanceof Boolean bool ? (bool ? TextColor.GREEN : TextColor.RED) + "" + entry.getValue() + TextColor.RESET : entry.getValue()));
			}

			return str.toString();
		}
		else if((this.pointed != null && this.pointed.type == HitPosition.ObjectType.ENTITY && this.pointed.entity != null) || showPlayerInfo) {
			Entity entity = showPlayerInfo ? this.player : this.pointed.entity;
			ItemStack held = entity instanceof EntityLiving && ((EntityLiving)entity).getHeldItem() != null ? ((EntityLiving)entity).getHeldItem() : null;

			return 
				(showPlayerInfo ? "Charakter: " : "Schaue auf: ") + EntityRegistry.getEntityString(entity) + " (" + entity.getId() + ")" + "\n" + 
				String.format("Position: %.3f %.3f %.3f", entity.posX, entity.posY, entity.posZ) + "\n" + 
				"Geladen: " + entity.ticksExisted + "t" + (entity instanceof EntityLiving ? (", Alter: " + ((EntityLiving)entity).getGrowingAge()) + "t" : "") + "\n" + 
				String.format("Größe: %.2fx%.2f, A: %.2f", entity.width, entity.height, entity.getEyeHeight()) + "\n" + 
				String.format("Bewegung: %.3f %.3f %.3f", entity.motionX, entity.motionY, entity.motionZ) + "\n" + 
				String.format("Drehung: %.1f %.1f, K: %.1f", entity.rotYaw, entity.rotPitch, entity.getRotationYawHead()) + "\n" + 
				(entity instanceof EntityLiving
					? String.format("Leben: %d/%d, A: %d", ((EntityLiving)entity).getHealth(), ((EntityLiving)entity).getMaxHealth(), 
							((EntityLiving)entity).getAbsorptionAmount()) : "Leben: n/a") + "\n" + 
				"Schaden: " + entity.hurtResistance + "t" + (showPlayerInfo ? String.format(", Fallh.: %.3f", entity.fallDistance) : "") + "\n" +
				(entity instanceof EntityLiving ?
					(((EntityLiving)entity).deathTime != 0 ? "Tod: " + ((EntityLiving)entity).deathTime + "t, " : "") + "Rüstung: " + ((EntityLiving)entity).getTotalArmorValue() + ", Pfeile: " + ((EntityLiving)entity).getArrowCountInEntity() 
					: "Rüstung: n/a, Pfeile: n/a") + "\n" + 
				(held != null ?
						"Gegens.: " + ItemRegistry.getName(held.getItem()) + " x" + held.getSize() : "Gegens.: n/a") + "\n" + 
				"Eigens.: " + (entity.dead ? "D" : "") + (entity.noClip ? "N" : "") + (entity.onGround ? "G" : "") 
						+ (entity.canBeCollidedWith() ? "C" : "") + (entity.canBePushed() ? "P" : "") 
						+ (entity.isBurning() ? "B" : "") + (entity.isPlayer() ? "S" : "") 
						+ (entity.isWet() ? "W" : "") + (entity.canAttackWithItem() ? "A" : "")
						+ (entity.passenger != null ? "H" : "") + (entity.vehicle != null ? "R" : "")
						+ (entity.doesEntityNotTriggerPressurePlate() ? "L" : "") + "\n" + 
				(entity.hasCustomName() ? "Name: '" + TextColor.stripCodes(entity.getCustomNameTag()) + "'" : "Name: n/a")
			;
		}

		return null;
	}
	
	public boolean canRenderHud() {
		return (this.showHud || this.open != null) && !this.cameraUsed;
	}
	
	public boolean shift() {
		return Bind.isWindowActive() && (Keysym.LEFT_SHIFT.read() || Keysym.RIGHT_SHIFT.read());
	}
	
	public boolean ctrl() {
		return Bind.isWindowActive() && (Keysym.LEFT_CONTROL.read() || Keysym.RIGHT_CONTROL.read());
	}

	private void fbsize(int x, int y) {
		GL11.glViewport(0, 0, x, y);
		fb_raw_x = x;
		fb_raw_y = y;
		this.rescale();
		if(!fullscreen) {
			xsize = x;
			ysize = y;
		}
	}
	
	private void pos(int x, int y) {
		if(!fullscreen) {
			saved_xpos = x;
			saved_ypos = y;
		}
	}
	
	private void mouse(int x, int y) {
		if(!mouseFirst && open == null)
			this.moveCamera((float)(x - mouse_raw_x) * sensitivity, (float)(mouse_raw_y - y) * sensitivity);
		mouse_raw_x = x;
		mouse_raw_y = y;
		mouse_x = x / this.scale;
		mouse_y = y / this.scale;
		mouseFirst = false;
		if(open != null && Bind.isInputEnabled() && Button.isMouseDown() /* && !(win->mouse & 0xfc) */ && !ctrl()) {
//			if(mouse_clickx < 0 || mouse_clicky < 0) {
//				return;
//			}
			
//				SKC.drag(mouse_x, mouse_y);
			open.drag(mouse_x, mouse_y);
		}
	}
	
	private void scroll(int x, int y) {
		x *= -1;
		if(open != null && Bind.isInputEnabled()) {
//			if(y != 0)
//				this.scroll(ExtMath.clampi(y, -1, 1));
			open.scroll(x, y, mouse_x, mouse_y, ctrl(), shift());
		}
		else if(open != null) {
			if((x != 0) || (y != 0))
				Bind.setBind((x < 0) ? Wheel.SCROLL_LEFT : ((x > 0) ? Wheel.SCROLL_RIGHT : ((y < 0) ? Wheel.SCROLL_DOWN : Wheel.SCROLL_UP)), false);
		}
		else if(Bind.isInputEnabled()) {
			if(y != 0)
				this.scroll(ExtMath.clampi(y, -1, 1));
		}
		else {
			if(x < 0)
				Wheel.SCROLL_LEFT.setUsed();
			else if(x > 0)
				Wheel.SCROLL_RIGHT.setUsed();
			if(y < 0)
				Wheel.SCROLL_DOWN.setUsed();
			else if(y > 0)
				Wheel.SCROLL_UP.setUsed();
		}
	}
	
	private void button(Button btn, boolean press) {
//		if(btn < 0 || btn >= 8) {
//			return;
//		}
//		byte press = act == KEY_PRESS;
		boolean prev = btn.isDown();
		if(open != null && prev != press && Bind.isInputEnabled() && !Button.isMouseDown()) {
//			SKC.press(btn, mouse_x, mouse_y);
			open.mouse(btn, mouse_x, mouse_y, ctrl(), shift());
		}
		else if(open != null && press) {
			Bind.setBind(btn, false);
		}
		btn.setDown(press);
		if(open != null && prev != press && Bind.isInputEnabled() && !Button.isMouseDown()) {
//			SKC.release(btn, mouse_x, mouse_y);
			open.mouserel(btn, mouse_x, mouse_y);
		}
	}
	
	private void key(Keysym key, KeyEvent act) {
		if((act == KeyEvent.PRESS) && Bind.isInputEnabled() && Bind.CHEAT.isDown() && this.handleDebugKey(key))
			return;
		if(open != null && (act != KeyEvent.RELEASE) && Bind.isInputEnabled()) {
			open.key(key, ctrl(), shift());
		}
		else if(open != null && (act != KeyEvent.REPEAT)) {
			Bind.setBind(key, act == KeyEvent.RELEASE);
		}
	}
	
	private void character(char code) {
		if(open != null && Bind.isInputEnabled()) {
			open.character(code);
		}
	}
	
	private void focus(boolean focus) {
		Bind.setWindowActive(focus);
		if(open != null)
			open.mouse(Button.MOUSE_LEFT, -1, -1, false, false);
//		open.restyle();
	}
	
	private void rescale() {
		this.scale = this.scaleVar;
		while(this.scale > 1 && (this.fb_raw_x / this.scale < MIN_WIDTH || this.fb_raw_y / this.scale < MIN_HEIGHT)) {
			this.scale--;
		}
		this.fb_x = Math.max(this.fb_raw_x / this.scale, MIN_WIDTH);
		this.fb_y = Math.max(this.fb_raw_y / this.scale, MIN_HEIGHT);
		if(this.open != null) {
			this.refreshing = true;
			this.show(this.open);
			this.refreshing = false;
		}
	}
	
	private void closed() {
		interrupted = true;
	}
	
	public void poll() {
		for(WindowEvent event : Window.poll()) {
			switch(event.action()) {
				case BUTTON:
					if(event.param1() >= 0 && event.param1() < Button.values().length)
						button(Button.values()[event.param1()], event.param2() != 0);
					break;
				case CHARACTER:
					if(event.param1() >= (int)Log.CHR_SPC && event.param1() <= (int)Character.MAX_VALUE)
						character((char)event.param1());
					break;
				case CLOSED:
					closed();
					break;
				case CURSOR:
					mouse(event.param1(), event.param2());
					break;
				case FOCUS:
					focus(event.param1() != 0);
					break;
				case KEY:
					if(event.param1() >= 0 && event.param1() < Keysym.values().length)
						key(Keysym.values()[event.param1()], KeyEvent.values()[event.param2() % KeyEvent.values().length]);
					break;
				case POSITION:
					pos(event.param1(), event.param2());
					break;
				case REDRAW:
					// redraw(); disable as it is pretty useless
					break;
				case RESIZE:
					fbsize(event.param1(), event.param2());
					break;
				case SCROLL:
					if(event.param1() != 0 || event.param2() != 0)
						scroll(event.param1(), event.param2());
					break;
			}
		}
	}
	
	
	
	public void menu(boolean menu) {
		Window.grabCursor(!menu && !nograb);
		mouseFirst = true;
	}
	
	public void full(boolean full) {
		if((full != fullscreen || full) && (!full || vidMode != null)) {
			if(full) {
				Window.setFullscreen(vidMode.width(), vidMode.height(), vidMode.refresh());
			}
			else {
				Window.setWindowed(saved_xpos, saved_ypos, xsize, ysize);
			}
			Window.setVSync(vsync);
			fullscreen = full;
		}
	}
	
	public void sync(int sync) {
		vsync = sync == 0;
		syncLimited = sync > 0;
		if(sync > 0) {
			syncLimit = sync;
		}
		else {
			DisplayMode mode = Window.getDisplayMode();
			syncLimit = mode != null ? mode.refresh() : 60;
		}
		Window.setVSync(vsync);
	}
	
	public void setupOverlay() {
		GlState.disableDepth();
		GlState.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GlState.setActiveTexture(GL13.GL_TEXTURE0);
        GlState.color(1.0f, 1.0f, 1.0f, 1.0f);
    	GL11.glClear(256);
    	GL11.glMatrixMode(GL11.GL_PROJECTION);
    	GL11.glLoadIdentity();
    	GL11.glOrtho(0.0D, (double)this.fb_raw_x, (double)this.fb_raw_y, 0.0D, 1000.0D, 3000.0D);
    	GL11.glMatrixMode(GL11.GL_MODELVIEW);
    	GL11.glLoadIdentity();
    	GL11.glTranslatef(0.0F, 0.0F, -2000.0F);
    	if(this.scale != 1)
    		GL11.glScalef((float)this.scale, (float)this.scale, 1.0f);
	}
	
	public void scissor(int x, int y, int width, int height) {
		GL11.glScissor(x * this.scale, y * this.scale, width * this.scale, height * this.scale);
	}
	
	private void addFrame(long runningTime)
    {
        this.frames[this.frameIndex] = runningTime;
        ++this.frameIndex;

        if (this.frameIndex == 240)
        {
            this.frameIndex = 0;
        }

        if (this.frameCounter < 240)
        {
            this.lastIndex = 0;
            ++this.frameCounter;
        }
        else
        {
            this.lastIndex = (this.frameIndex + 1) % 240;
        }
    }
	
	private void updateTick() {
		long n = System.nanoTime();
		if(n < this.frameLast + this.frameWait)
			return;
		this.frameWait = 50000000L - ((n - (this.frameLast + this.frameWait)) < 50000000L ? (n - (this.frameLast + this.frameWait)) : 0L);
		this.frameLast = n;
        this.tickTimes[this.tickIndex++] = this.lastTickTime;
		if(this.tickIndex == 240) {
        	this.tickIndex = 0;
        }
	}
		
	public void run(long time) {
		if(!Window.createWindow(VERSION, System.getProperty("opengl.debug") != null))
			System.exit(1);
		Log.SYSTEM.info("OpenGL %s", GL11.glGetString(GL11.GL_VERSION));
		Log.SYSTEM.info("GL_VENDOR: %s", GL11.glGetString(GL11.GL_VENDOR));
		Log.SYSTEM.info("GL_RENDERER: %s", GL11.glGetString(GL11.GL_RENDERER));
		Log.SYSTEM.info("Starte ...");

		this.init();
		this.registerDebug();
		System.gc();
		System.gc();
		Font.load(this.font);
		GlState.enableBlend();
		GlState.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		this.initConsole();
		this.startSound(true);
		this.vidMode = Window.getDisplayMode();
		if(this.vidMode != null && (this.vidMode.width() < MIN_WIDTH || this.vidMode.height() < MIN_HEIGHT))
			this.vidMode = null;
		Window.initWindow(this.saved_xpos, this.saved_ypos, this.xsize, this.ysize, MIN_WIDTH, MIN_HEIGHT);
		Window.setIcon(genTriwave(64, 64, 0x00000000, 0xffff0000, 0xff00ff00, 0xff0000ff, 0xff7f00ff, 0xff000000), 64, 64);
		this.sync(this.sync);
//		this.startSound(true);
		this.getVar("tic_target").setDefault();
		this.show(GuiMenu.INSTANCE);
		Log.SYSTEM.info("Client gestartet in " + String.format("%.1f", (double)(System.currentTimeMillis() - time) / 1000.0) + " Sekunden");
		
		while(!this.interrupted) {
			PerfSection.swap();
			PerfSection.TIMING.enter();
			this.tmr_current = Util.rtime();
			this.tmr_delta = this.tmr_current - this.tmr_last;
			this.tmr_last = this.tmr_current;
			this.fdelta = ((float)this.tmr_delta) / 1000000.0f;
			if((this.tmr_current - this.tmr_update) >= 1000000L) {
				this.framerate = ((float)this.tmr_frames) * 1000000.0f / ((float)(long)(this.tmr_current - this.tmr_update));
				this.tmr_frames = 0L;
				this.tmr_update = this.tmr_current;
			}
			this.start();
			PerfSection.INPUT.enter();
			Bind.updateBinds();
			this.input();
			Bind.enableInput();
			GlState.setActiveTexture(GL13.GL_TEXTURE0);
			GlState.bindTexture(0);
			this.inputGui();
			if(this.open != null)
				this.open.update();
			PerfSection.TICK.enter();
			this.doTicks();
			PerfSection.UPDATE.enter();
			PerfSection.RENDER.enter();
			this.render();
			PerfSection.GUI.enter();
			if(this.canRenderHud())
				this.renderHud();
			PerfSection.REST.enter();
			this.finish();
			PerfSection.SWAP.enter();
			if(this.glFlush)
				GL11.glFlush();
			Window.swapBuffers();
			PerfSection.EVENTS.enter();
			Log.flushLog();
			this.poll();
			PerfSection.WAIT.enter();
			long now = System.nanoTime();
	    	this.addFrame(now - this.startNanoTime);
	    	this.startNanoTime = now;
			while(this.syncLimited && (Util.rtime() - this.tmr_current) < (1000000L / this.syncLimit)) {
				;
			}
			this.tmr_frames += 1L;
		}

		Log.SYSTEM.info("Beende ...");
		unload(false);
        this.getSoundManager().unload();
        this.renderGlobal.stopChunkBuilders();
		if(audio.end())
		 	Log.SOUND.info("Audiogerät geschlossen");
		Log.flushLog();
		this.save();
		Font.unload();
		Window.destroyWindow();
		Log.SYSTEM.info("Beendet.");
	}



//	public void complete(String[] comp) {
//		
//	}
//	public void infolist(String[] list, int[] data) {
//		
//	}
	
	public void disconnected(String msg) {
		Log.NETWORK.info("Getrennt: %s", msg);
		this.unload(true);
		this.show(new GuiInfo("Von Server getrennt", TextColor.RED + "Verbindung zum Server wurde getrennt\n\n" + TextColor.RESET + msg));
	}
	
	

	private void input() {
		if(!this.saving && Bind.SCREENSHOT.isPressed()) {
			this.screenshot = true;
		}
		if(Bind.isWindowActive()) {
			if(Bind.FULLSCREEN.isPressed()) {
				this.full(!this.fullscreen);
			}
			if(!(this.open instanceof GuiLoading)) {
				if(!(this.open instanceof GuiConsole) && Bind.CONSOLE.isPressed()) {
					this.show(GuiConsole.INSTANCE.setFull(true));
				}
				else if(this.open == null && Bind.COMMAND.isPressed()) {
					this.show(GuiConsole.INSTANCE.setFull(false));
				}
	//			if(this.theWorld != null && this.open == null && Bind.COMMAND.isPressed()) {
	//				this.displayGuiScreen(GuiChat.INSTANCE);
	//			}
				if(this.world != null && Bind.MENU.isPressed()) {
					if(this.open != (this.charEditor ? GuiChar.INSTANCE : null))
						this.show(this.charEditor ? GuiChar.INSTANCE : null);
					else
						this.show(GuiMenu.INSTANCE);
				}
				else if(this.world == null && !(this.open instanceof GuiMenu) && Bind.MENU.isPressed()) {
					this.show(GuiMenu.INSTANCE);
				}
				if(this.world != null && !this.charEditor && Bind.INVENTORY.isPressed()) {
					if(this.open instanceof GuiContainer && !(this.open.selected instanceof client.gui.element.Field || this.open.selected instanceof Area)) {
						this.show(null);
					}
					else if(this.open == null) {
			            if(this.player.isRiding() && this.player.vehicle instanceof EntityHorse)
			            	this.player.sendHorseInventory();
			            else
			            	this.show(/* this.itemCheat ? new GuiCheat() : */ new GuiInventory(this.player));
					}
				}
			}
			if(Bind.SHOW.isPressed()) {
				if(this.drawDebug) {
					this.drawDebug = this.drawFps = false;
				}
				else {
					this.drawDebug = this.drawFps;
					this.drawFps = true;
				}
			}
		}
	}
	
	private void screenshot() {
		if(this.saving)
			return;
		this.saving = true;
		final int stride = ((this.fb_raw_x * 3) & 3) != 0 ? 4 + ((this.fb_raw_x * 3) & ~3) : (this.fb_raw_x * 3);
		final ByteBuffer data = ByteBuffer.allocateDirect(stride * this.fb_raw_y).order(ByteOrder.nativeOrder());
		GL11.glReadPixels(0, 0, this.fb_raw_x, this.fb_raw_y, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, data);
		new Thread(new Runnable() {
			public void run() {
				byte[] pixels = new byte[stride * Client.this.fb_raw_y];
				data.get(pixels);
				byte[] conv = new byte[Client.this.fb_raw_x * Client.this.fb_raw_y * 3];
				for(int l = 0; l < Client.this.fb_raw_y; l++) {
					System.arraycopy(pixels, l * stride, conv, (Client.this.fb_raw_y - 1 - l) * Client.this.fb_raw_x * 3, Client.this.fb_raw_x * 3);
				}
		    	BufferedImage image = new BufferedImage(Client.this.fb_raw_x, Client.this.fb_raw_y, BufferedImage.TYPE_INT_ARGB);
		    	int[] img = new int[Client.this.fb_raw_x * Client.this.fb_raw_y];
		    	for(int z = 0; z < img.length; z++) {
		    		img[z] = (int)(conv[(z * 3) + 2] & 0xff) | ((int)(conv[(z * 3) + 1] & 0xff) << 8) | ((int)(conv[(z * 3) + 0] & 0xff) << 16) | 0xff000000;
		    	}
		    	image.setRGB(0, 0, Client.this.fb_raw_x, Client.this.fb_raw_y, img, 0, Client.this.fb_raw_x);

				File dir = new File("screenshots");
				dir.mkdirs();
				int n = 0;
				File file;
				Date date = new Date();
				do {
					file = new File(dir, "screen_" + new SimpleDateFormat("dd-MM-yyyy_HH-mm-ss").format(date) + (n != 0 ? "_" + n : "") + ".png");
					n++;
				}
				while(file.exists());
				final File saved = file;
		    	try {
					ImageIO.write(image, "png", file);
				}
				catch(IOException e) {
					Client.this.schedule(new Runnable() {
						public void run() {
							Client.this.saving = false;
							Log.IO.error(e, "Konnte Textur '" + saved + "' nicht speichern");
						}
					});
					return;
				}
				Client.this.schedule(new Runnable() {
					public void run() {
						Client.this.saving = false;
						Client.this.logFeed("Bildschirmfoto als '%s' gespeichert", saved.getName());
					}
				});
			}
		}, "Screenshot writer").start();
	}
	
	public TextColor framecode() {
		return (this.framerate >= 59.0f) ? TextColor.GREEN : ((this.framerate >= 29.0f) ? TextColor.YELLOW : ((this.framerate >= 14.0f) ? TextColor.ORANGE : TextColor.RED));
	}
	
	public TextColor tpscode() {
		return (this.tickrate >= (((float)this.tickTarget / 1000.0f) - 1.0f)) ? TextColor.GREEN : ((this.tickrate >= (((float)this.tickTarget / 1000.0f) / 2.0f - 1.0f)) ? TextColor.YELLOW : 
			((this.tickrate >= (((float)this.tickTarget / 1000.0f) / 4.0f - 1.0f)) ? TextColor.ORANGE : TextColor.RED));
	}
	
	private void doTicks() {
		this.tick_stime = Util.rtime();
		if((this.tick_stime - this.tick_update) >= 1000000L) {
			this.tickrate = ((float)this.tick_done) * 1000000.0f / ((float)(long)(this.tick_stime - this.tick_update));
			this.tick_done = 0;
			this.tick_update = this.tick_stime;
		}
		this.tick_torun += (((long)this.tickTarget) * this.tmr_delta) / 1000L;
		this.tickFrame = 0;
		this.tick_torun = this.tick_torun > 2000000L ? 1000000L : this.tick_torun;
		while(this.tick_torun >= 1000000L) {
			this.tick();
			this.tick_done += 1L;
			this.tickFrame += 1;
			this.tick_total += 1L;
			this.tick_torun -= 1000000L;
			if((this.tick_ftime = (Util.rtime() - this.tick_stime)) >= (((long)this.tickTimeout) * 1000L)) {
				Log.TICK.warn("Ticks benötigten %d ms dieses Frame (maximal %d ms), überspringe %d Ticks", this.tick_ftime / 1000L, this.tickTimeout, this.tick_torun / 1000000L);
				this.tick_torun = 0L;
				break;
			}
		}
		this.tick_fraction = ((double)this.tick_torun) / 1000000.0;
		this.tick_ttime += this.tick_ftime;
		this.tick_time = this.tickFrame != 0 ? (this.tick_ftime / (long)this.tickFrame) : 0L;
	}
	
	public void tick_target(float tps) {
		this.tickTarget = (int)(tps * 1000.0f);
		this.tick_torun = 0L;
		this.tick_done = 0L;
		this.tick_update = Util.rtime();
	}
	
	public void unload(boolean loading) {
		if(this.world != null && this.getNetHandler() != null)
			this.getNetHandler().getConnection().closeChannel("Quitting");
        this.unloadWorld();
		this.show(GuiMenu.INSTANCE);
	}
	
	private void startSound(boolean load) {
		if(load)
			SoundManager.loadSounds();
    	audio = new AudioInterface(this.soundFrameSize, this.soundBufferSize, !this.soundEnabled);
    	boolean started = audio.start();
		Log.flushLog();
		if(started)
			Log.SOUND.info("Audiogerät geöffnet");
		else if(this.soundEnabled)
			Log.SOUND.info("Audiogerät konnte nicht geöffnet werden");
		for(Volume volume : Volume.values()) {
			volume.apply();
		}
	}
	
	public void restartSound(boolean load) {
		Client.this.logFeed("Lade Sound-System " + (load ? "und alle Sounds " : "") + "neu");
	    this.soundManager.unload();
    	if(audio.end())
			Log.SOUND.info("Audiogerät geschlossen");
    	this.startSound(load);
    	Client.this.logFeed("Das Sound-System " + (load ? "und alle Sounds wurden" : "wurde") + " neu geladen");
	}
	
	public AudioInterface getAudioInterface() {
	    return audio;
	}
    
    public void distance(int distance) {
        if(this.renderGlobal != null)
        	this.renderGlobal.setDisplayListEntitiesDirty();
    }
    
    private void registerDebug(Keysym key, String help, DebugRunner runner) {
    	if(this.debug.containsKey(key))
    		throw new IllegalStateException("Debug-Taste " + key.getDisplay() + " ist bereits registriert");
    	this.debug.put(key, new DebugFunction(key, runner, help));
    }
    
    private void registerDebug() {
    	this.registerDebug(Keysym.H, "Hilfe zu Tastenkombinationen anzeigen", new DebugRunner() {
			public void execute(Keysym key) {
				String bind = Bind.CHEAT.getInput() == null ? "n/a" : Bind.CHEAT.getInput().getDisplay();
				Client.this.show(new GuiInfo("Hilfe zu Tastenkombinationen", TextColor.DGREEN + "" + Client.this.debug.size() + " Tastenkombinationen stehen zur Verfügung:\n" + 
					Util.buildLines(new Function<DebugFunction, String>() {
					public String apply(DebugFunction func) {
						return TextColor.CYAN + bind + TextColor.RED + "+" + TextColor.GREEN + func.key.getDisplay() + TextColor.GRAY + " - " + TextColor.YELLOW + func.help;
					}
				}, Client.this.debug.values())));
			}
		});
    	this.registerDebug(Keysym.N, "NoClip umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.performAction(Action.NOCLIP);
			}
		});
    	this.registerDebug(Keysym.G, "Unsterblichkeit umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.performAction(Action.GOD);
			}
		});
    	this.registerDebug(Keysym.F, "Geschwindigkeit umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.performAction(Action.SPEED);
			}
		});
    	this.registerDebug(Keysym.R, "Gegenstand in Hand / Alle (+Shift) reparieren und auffüllen", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.performAction(Action.REPAIR, Client.this.shift() ? 1 : 0);
			}
		});
    	this.registerDebug(Keysym.E, "Gegenstands-Cheat-Menü umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.logFeed("Cheat-Menü: %s", (Client.this.itemCheat ^= true) ? "an" : "aus");
		    	if(Client.this.open instanceof GuiContainer)
		    		Client.this.open.init();
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.L, "Maximale Helligkeit umschalten", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.logFeed("Maximale Helligkeit: %s", (Client.this.setGamma ^= true) ? "an" : "aus");
				}
			});
    	this.registerDebug(Keysym.J, "JVM GC ausführen", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.logFeed("Führe JVM GC aus");
	            long mem = Runtime.getRuntime().freeMemory();
	            System.gc();
	            System.gc();
	            mem = Runtime.getRuntime().freeMemory() - mem;
	            mem = mem < 0L ? 0L : mem;
	            Client.this.logFeed("JVM GC ausgeführt: %d MB freigegeben", (int)(mem / 1024L / 1024L));
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.B, "Hitbox-Overlay umschalten", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.getRenderManager().setDebugBoundingBox(!Client.this.getRenderManager().isDebugBoundingBox());
					Client.this.logFeed("Objekt-Grenzen: %s", Client.this.getRenderManager().isDebugBoundingBox() ? "an" : "aus");
				}
			});
    	this.registerDebug(Keysym.K, "Debug-Kamera in 3. Person umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.logFeed("Debug-Kamera 3. Person: %s", (Client.this.debugCamEnable ^= true) ? "an" : "aus");
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.X, "Röntgenblick umschalten", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.xrayActive ^= true;
					Client.this.renderGlobal.loadRenderers();
					Client.this.logFeed("Röntgenblick: %s", Client.this.xrayActive ? "an" : "aus");
				}
			});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.O, "Objekt-Overlay umschalten", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.logFeed("Objekt-Umrahmung: %s", (Client.this.renderOutlines ^= true) ? "an" : "aus");
				}
			});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.I, "Block-Objekt-Overlay umschalten", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.logFeed("Block-Objekte anzeigen: %s", (Client.this.tileOverlay ^= true) ? "an" : "aus");
				}
			});
    	this.registerDebug(Keysym.Y, "Alle Chunks neu kompilieren", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.logFeed("Kompiliere alle Chunks neu");
				Client.this.renderGlobal.loadRenderers();
				Client.this.logFeed("Alle Chunks wurden neu kompiliert");
			}
		});
    	this.registerDebug(Keysym.T, "Alle Texturen neu laden", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.refreshResources();
			}
		});
    	this.registerDebug(Keysym.S, "Sound neu starten / Alle Sounds neu laden (+Shift)", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.restartSound(Client.this.shift());
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.W, "Server-Tick-Limit umschalten (Welt beschleunigen / Warpmodus)", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.performAction(Action.WARP_MODE);
				}
			});
    	this.registerDebug(Keysym.M, "Alle Gegenstände herbei ziehen (Magnetmodus)", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.performAction(Action.MAGNET);
			}
		});
    	this.registerDebug(Keysym.Z, "Den Spieler heilen", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.performAction(Action.HEAL);
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.P, "Server Performance-Anfrage senden", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.performAction(Action.PERF);
				}
			});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.C, "Debug-Crash auslösen (2x schnell hintereinander)", new DebugRunner() {
	    		long lastUsed;
	    		
				public void execute(Keysym key) {
					if(System.currentTimeMillis() - this.lastUsed <= 1000L) {
						throw new RuntimeException("Manuell herbeigerufener Debugging-Absturz");
					}
					else {
						this.lastUsed = System.currentTimeMillis();
						Client.this.logFeed(TextColor.RED + "VORSICHT: Debug-Absturz nach mehrmaligem Drücken innerhalb einer Sekunde");
					}
				}
			});
    	this.registerDebug(Keysym.V, "Alle Sounds stoppen", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.soundManager.stopSounds();
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.Q, "Programm sofort beenden und trennen", new DebugRunner() {
				public void execute(Keysym key) {
					Client.this.interrupted = true;
				}
			});
    	this.registerDebug(Keysym.A, "Bild-Synchonisation umschalten (VSync - begrenzt - unbegrenzt)", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.getVar("win_sync").parse("" + (Client.this.vsync ? Client.this.syncLimit : (Client.this.syncLimited ? -1 : 0)));
				Client.this.setDirty();
			}
		});
    	this.registerDebug(Keysym.D, "Konsole und Chat leeren", new DebugRunner() {
			public void execute(Keysym key) {
				GuiConsole.INSTANCE.reset();
				if(Client.this.open instanceof GuiConsole)
					((GuiConsole)Client.this.open).setLog(Client.this.buffer);
			}
		});
    	this.registerDebug(Keysym.U, "HUD umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.showHud ^= true;
			}
		});
    	this.registerDebug(Keysym.UE, "Spieler in Overlay umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				Client.this.logFeed("Spieler-Info in Overlay: %s", (Client.this.debugPlayer ^= true) ? "an" : "aus");
			}
		});
    	this.registerDebug(Keysym.OE, "Tick-Profiler umschalten", new DebugRunner() {
			public void execute(Keysym key) {
				if(Client.this.lastTickTime >= 0) {
					Client.this.performAction(Action.STOP_PROFILING);
					Client.this.lastTickTime = -1;
				}
				else {
					Client.this.performAction(Action.START_PROFILING);
					Client.this.lastTickTime = 0;
				}
			}
		});
    	if(Util.DEVMODE)
	    	this.registerDebug(Keysym.AE, "Programm sofort beenden und server beenden", new DebugRunner() {
				public void execute(Keysym key) {
					if(Client.this.getNetHandler() != null) {
						Client.this.getNetHandler().getConnection().sendPacket(new CPacketMessage(CPacketMessage.Type.COMMAND, "shutdown"), new GenericFutureListener<Future<? super Void>>() {
							public void operationComplete(Future<? super Void> u) throws Exception {
								try {
									Thread.sleep(1000L);
								}
								catch(InterruptedException e) {
								}
								Client.this.interrupted = true;
							}
						});
					}
					else {
						Client.this.interrupted = true;
					}
				}
			});
    }
    
    private boolean handleDebugKey(Keysym key) {
    	DebugFunction func = this.debug.get(key);
    	if(func != null) {
    		Bind.disableInput(key);
    		if(!(this.open instanceof GuiLoading)) {
    			this.soundManager.playSound(new PositionedSound(SoundEvent.CLICK, EventType.UI_INTERFACE));
    			func.runner.execute(key);
    		}
    	}
    	return func != null;
    }
	
	
	
	public static void main(String[] args) {
		long time = System.currentTimeMillis();
		Util.checkPlatform();
		Thread.currentThread().setName("Render thread");
		Locale.setDefault(Locale.ROOT);
		Util.setupHandlers();
		Log.init();
		Log.SYSTEM.info("Java " + System.getProperty("java.version"));
		Log.SYSTEM.info("Starte " + VERSION + " (Protokoll #" + Util.PROTOCOL + ")");
		if(Util.DEVMODE)
			Log.SYSTEM.warn("Entwicklungsmodus aktiv - Debugging-Features sind verfügbar");
		Window.init();
		ModelBlock.setAsProvider();
		Registry.register();
		UniverseRegistry.register();
		Log.setSync(CLIENT);
		CLIENT.run(time);
		Window.end();
	}
	


	private static float tri(float x, float p, float a) {
		return ((4.0f * a) / p) * ExtMath.absf(((x - p / 4.0f) % p) - p / 2.0f) - a;
	}
	
	private static void memcpy(byte[] dest, int doff, byte[] src, int soff, int len) {
		System.arraycopy(src, soff, dest, doff, len);
	}
	
	private static byte[] genTriwave(int w, int h, int color1, int color2, int color3, int color4, int color5, int color6) {
		byte[] data = new byte[w * h * 4];
		byte[] color = new byte[24];
		color1 = (color1 << 8) | (color1 >> 24);
		color2 = (color2 << 8) | (color2 >> 24);
		color3 = (color3 << 8) | (color3 >> 24);
		color4 = (color4 << 8) | (color4 >> 24);
		color5 = (color5 << 8) | (color5 >> 24);
		color6 = (color6 << 8) | (color6 >> 24);
		for(int z = 0; z < 4; z++) {
			color[z] = (byte)((color1 >> ((3 - z) * 8)) & 0xff);
			color[z+4] = (byte)((color2 >> ((3 - z) * 8)) & 0xff);
			color[z+8] = (byte)((color3 >> ((3 - z) * 8)) & 0xff);
			color[z+12] = (byte)((color4 >> ((3 - z) * 8)) & 0xff);
			color[z+16] = (byte)((color5 >> ((3 - z) * 8)) & 0xff);
			color[z+20] = (byte)((color6 >> ((3 - z) * 8)) & 0xff);
		}
		for(int y = 0; y < h; y++) {
			int offs = ((y / 2 == h / 16) || (y / 2 == (h / 2 - h / 16) - 1)) ? 16 : (((y / 2 == h / 16 + 1) || (y / 2 == (h / 2 - h / 16) - 2)) ? 20 : 0);
			for(int x = 0; x < w; x++) {
				memcpy(data, (y*w+x) << 2, color, offs, 4);
			}
		}
		for(int x = 0; x < w; x++) {
			int y = ((x < w / 8) || (x >= w - w / 8)) ? (h / 2) : (h / 2 + (int)tri((float)(w / 4 + x), (float)((w * 3) / 4), (float)(((h * 3) / 4) / 2)));
			// sys_assert(y - 8 >= 0 && y + 7 < h)
			memcpy(data, ((y-8)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y-7)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y-6)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y-5)*w+x) << 2, color, 4, 4);
			memcpy(data, ((y-4)*w+x) << 2, color, 4, 4);
			memcpy(data, ((y-3)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y-2)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y-1)*w+x) << 2, color, 8, 4);
			memcpy(data, ((y+0)*w+x) << 2, color, 8, 4);
			memcpy(data, ((y+1)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y+2)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y+3)*w+x) << 2, color, 12, 4);
			memcpy(data, ((y+4)*w+x) << 2, color, 12, 4);
			memcpy(data, ((y+5)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y+6)*w+x) << 2, color, 0, 4);
			memcpy(data, ((y+7)*w+x) << 2, color, 0, 4);
		}
		return data;
	}

	public <T extends CVar> T getVar(String name) {
		return (T)cvars.get(name);
	}

	public Iterable<String> getVars() {
		return this.cvars.keySet();
	}
	
	public void setDirty() {
		cfgDirty = true;
	}
	
	public boolean isDirty() {
		return cfgDirty;
	}
	
	public String getBuffer() {
		return buffer;
	}
	
	public float getTickFraction() {
		return (float)this.tick_fraction;
	}
	
	public long getPassedTime() {
		return this.tmr_delta;
	}
	
	private void regVar(CVar cv) {
		if(cvars.containsKey(cv.getCVarName()))
			throw new IllegalArgumentException("Variable " + cv.getCVarName() + " existiert bereits!");
		cvars.put(cv.getCVarName(), cv);
	}
	
	private void regVars(Class clazz, Object object) {
		for(Field field : clazz.getDeclaredFields()) {
			if(field.isAnnotationPresent(Variable.class)) {
				if(Modifier.isStatic(field.getModifiers()) != (object == null))
					continue;
				if(Modifier.isFinal(field.getModifiers()))
					throw new IllegalArgumentException("Feld für Variable " + field + " muss änderbar sein!");
				Variable value = field.getAnnotation(Variable.class);
				if(value.name().isEmpty())
					throw new IllegalArgumentException("Variablenname von " + field + " kann nicht leer sein!");
				field.setAccessible(true);
				CVar cv;
				VarFunction func = null;
				if(value.callback() != VarFunction.class) {
					try {
						func = value.callback().getConstructor().newInstance();
					}
					catch(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException 
							| NoSuchMethodException e) {
						throw new RuntimeException(e);
					}
				}
				CharValidator validator = null;
				if(value.validator() != CharValidator.class) {
					try {
						validator = value.validator().getConstructor().newInstance();
					}
					catch(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException 
							| NoSuchMethodException e) {
						throw new RuntimeException(e);
					}
				}
				if(field.getType() == int.class)
					cv = value.type() != IntType.INT ? new ColorVar(value.name(), value.display(), field, object, value.category(), value.type() == IntType.ALPHA, (IntFunction)func) : 
						new IntVar(value.name(), value.display(), field, object, value.category(), (int)value.min(), (int)value.max(), (IntFunction)func, value.unit(), value.precision());
				else if(field.getType() == float.class)
					cv = new FloatVar(value.name(), value.display(), field, object, value.category(), value.min(), value.max(), (FloatFunction)func, value.unit(), value.precision());
				else if(field.getType() == boolean.class)
					cv = new BoolVar(value.name(), value.display(), field, object, value.category(), (BoolFunction)func);
				else if(field.getType() == String.class)
					cv = new StringVar(value.name(), value.display(), field, object, value.category(), (int)value.max(), (StringFunction)func, validator, (int)value.min() <= 0);
				else if(field.getType().isEnum())
					cv = new EnumVar(value.name(), value.display(), field, object, value.category(), (EnumFunction)func, value.switched());
				else
					throw new IllegalArgumentException(value.name() + ": Unbekannter Variablen-Typ - " + field.getType());
				regVar(cv);
			}
		}
	}
	
	private void regVars(Object object) {
		regVars(object.getClass(), object);
	}
	
	private void regVars(Class clazz) {
		regVars(clazz, null);
	}
	
	public void initConsole() {
		reset();
		for(Bind bind : Bind.values()) {
			regVar(bind);
		}
		for(Volume volume : Volume.values()) {
			regVar(volume);
		}
		regVars(this);
		regVars(Style.CUSTOM);
		regVars(GuiServer.INSTANCE);
		regVars(GuiChar.INSTANCE);
		
		if(!config.exists())
			return;
		String data;
		try {
			data = FileUtils.read(config);
		}
		catch(IOException e) {
			Log.IO.error(e, "Konnte Konfigurationsdatei nicht laden");
			return;
		}
		if(data == null)
			return;
		for(String line : data.split("\n")) { // new String(data, CharsetUtil.UTF_8).split("\n")
			if(line.startsWith("#"))
				continue;
			String[] tok = line.split("=", 2);
			if(tok.length != 2)
				continue;
			CVar cv = getVar(tok[0]);
			if(cv == null) {
				Log.CONSOLE.error("CVAR '%s' existiert nicht", tok[0]);
				continue;
			}
			if(cv.parse(tok[1])) {
				Log.CONSOLE.trace("%s = %s", cv.getCVarName(), cv.format());
			}
			else {
				Log.CONSOLE.error("Kann CVAR '%s' nicht auf '%s' setzen", cv.getCVarName(), tok[1]);
			}
		}
	}
	
	public void save() {
		cfgDirty = false;
		StringBuilder sb = new StringBuilder();
		for(CVar cv : cvars.values()) {
			if(sb.length() > 0)
				sb.append('\n');
			sb.append(cv.getCVarName() + "=" + cv.format());
		}
		try {
			FileUtils.write(config, sb.toString());
		}
		catch(IOException e) {
			Log.IO.error(e, "Konnte Konfigurationsdatei nicht speichern");
		}
	}
	
	private void printVar(CVar cv) {
		String values = cv.getValues();
		this.logConsole("%s " + TextColor.NEON + "%s " + TextColor.DGRAY + "[%s" + TextColor.DGRAY + "]" + TextColor.GRAY + " = " + TextColor.WHITE + "%s " + TextColor.DGRAY + 
				"[" + TextColor.GRAY + "D " + TextColor.CRIMSON + "%s" + TextColor.DGRAY + "]%s", cv.getType(), cv.getCVarName(), cv.getCategory(), cv.format(), cv.getDefault(), 
				values != null ? " [" + TextColor.LGRAY + values + TextColor.DGRAY + "]" : "");
	}
	
	public void exec(String line) {
		if(line.equals("#")) {
			for(CVar cv : cvars.values()) {
				printVar(cv);
			}
			this.logConsole(TextColor.GREEN + "CVARs insgesamt registriert: %d", cvars.size());
//			this.command(line);
			return;
		}
		else if(line.startsWith("#")) {
			String tok = line.substring(1);
			int space = tok.indexOf(' ');
			CVar cv = getVar(space >= 0 ? tok.substring(0, space) : tok);
			if(cv != null) {
				if(space < 0 || space >= tok.trim().length()) {
					this.logConsole("%s = %s", cv.getCVarName(), cv.format());
					return;
				}
				String value = tok.substring(space + 1).trim();
				if(cv.parse(value)) {
					cfgDirty = true;
					this.logConsole("%s -> %s", cv.getCVarName(), cv.format());
				}
				else {
					this.logConsole(TextColor.RED + "Kann CVAR '%s' nicht auf '%s' setzen", cv.getCVarName(), value);
				}
				return;
			}
		}
    	if(this.player != null && this.getNetHandler() != null)
    		this.getNetHandler().addToSendQueue(new CPacketMessage(line.startsWith("/") ? CPacketMessage.Type.COMMAND : CPacketMessage.Type.CHAT, line.startsWith("/") ? line.substring(1) : line));
//		Log.CONSOLE.user("%s", line);
//		this.command(line);
	}
	
	public void reset() {
		this.buffer = TextColor.NEON + "*** " + VERSION + " ***";
		this.console.clear();
		this.chat.clear();
		this.feed.clear();
		this.hotbar.clear();
	}
	
	private void resize(List<Message> log, int size) {
		while(log.size() > size) {
			log.remove(log.size() - 1);
		}
	}
	
	public void resizeConsole() {
		this.resize(this.console, this.consoleSize);
	}
	
	public void resizeChat() {
		this.resize(this.chat, this.chatSize);
	}
	
	public void resizeFeed() {
		this.resize(this.feed, this.feedSize);
	}
	
	public void resizeHotbar() {
		this.resize(this.hotbar, this.hotbarSize);
	}
	
	public void log(String prefixed, String line) {
		String msg = this.conTimestamps ? prefixed : line;
//		this.addMessage(msg);
		if((buffer.length() + msg.length() + 1) > LOG_BUFFER) {
			int offset = (msg.length() + 1) > 1024 ? (msg.length() + 1) : 1024;
			int nl = buffer.indexOf('\n', offset);
			buffer = nl >= 0 ? buffer.substring(nl + 1) : "";
		}
		buffer = buffer + "\n" + msg;
		if(this.open instanceof GuiConsole) {
			((GuiConsole)this.open).setLog(buffer);
		}
	}
	
	private void addMessage(List<Message> log, int size, String msg) {
	    Log.CONSOLE.user(msg);
		if(size > 0) {
			for(String line : msg.split("\n")) {
				Message lmsg = new Message(line, this.tmr_current);
				while(log.size() >= size) {
					log.remove(log.size() - 1);
				}
				log.add(0, lmsg);
			}
		}
	}
	
	public void logConsole(String line) {
	    this.addMessage(this.console, this.consoleSize, line);
	}
	
	public void logConsole(String fmt, Object ... args) {
		this.logConsole(String.format(fmt, args));
	}
	
	public void logChat(String line) {
	    this.addMessage(this.chat, this.chatSize, line);
	}
	
	public void logChat(String fmt, Object ... args) {
		this.logChat(String.format(fmt, args));
	}
	
	public void logFeed(String line) {
	    this.addMessage(this.feed, this.feedSize, line);
	}
	
	public void logFeed(String fmt, Object ... args) {
		this.logFeed(String.format(fmt, args));
	}
	
	public void logHotbar(String line) {
	    this.addMessage(this.hotbar, this.hotbarSize, line);
	}
	
	public void logHotbar(String fmt, Object ... args) {
		this.logHotbar(String.format(fmt, args));
	}

	private void drawOverlay(List<Message> log, int size, boolean up, int align, int x, int y) {
		if(size > 0) {
			long fade = 1000000L * (long)this.hudFadeout;
			int bg = (this.hudOpacity << 24) | 0x000000;
			y = up ? y - Font.YGLYPH : y;
			for(Iterator<Message> iter = log.iterator(); iter.hasNext();) {
				Message msg = iter.next();
				if((this.tmr_current - msg.time()) <= fade || (log == this.chat && this.chatPermanent)) {
					if(align > 0)
						Drawing.drawTextbox(msg.message(), x, y, bg);
					else if(align < 0)
						Drawing.drawTextboxRight(msg.message(), x, y, bg);
					else
						Drawing.drawTextboxCentered(msg.message(), x, y, bg);
					y += up ? -(Font.YGLYPH) : Font.YGLYPH;
				}
				else {
					iter.remove();
				}
			}
		}
		else {
			log.clear();
		}
	}

	private static String formatPing(int ping) {
		TextColor base;
		if(ping < 0) {
			base = TextColor.GRAY;
		}
		else if(ping < 80) {
			base = TextColor.DGREEN;
		}
		else if(ping < 160) {
			base = TextColor.GREEN;
		}
		else if(ping < 350) {
			base = TextColor.YELLOW;
		}
		else if(ping < 700) {
			base = TextColor.RED;
		}
		else {
			base = TextColor.DRED;
		}
		return base + (ping < 10000 ? String.format("%d", ping) + "ms" : String.format("%.1f", ((float)ping) / 1000.0f) + "s");
	}

/*
	private void drawPing(int width, int xPos, int yPos, int ping) {
		GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
		this.gm.getTextureManager().bindTexture(icons);
		int rectX = 0;
		int rectWidth = 0;
		if(ping < 0 || ping >= 20000) {
			rectWidth = 5;
		}
		else if(ping < 150) {
			rectWidth = 0;
		}
		else if(ping < 300) {
			rectWidth = 1;
		}
		else if(ping < 600) {
			rectWidth = 2;
		}
		else if(ping < 1000) {
			rectWidth = 3;
		}
		else {
			rectWidth = 4;
		}

		String str = this.formatPing(ping);

		this.zLevel += 100.0F;
		this.drawTexturedModalRect(xPos + width - 11, yPos, 0 + rectX * 10, 176 + rectWidth * 8, 10, 8);
		SKC.drawString(str, xPos + width - (11 + 1 + SKC.getStringWidth(str)), yPos, -1);
		this.zLevel -= 100.0F;
	}
*/

	public void drawInfo() {
		ClientPlayer netHandler = this.getNetHandler();
		if(netHandler != null) {
			int size = this.playerList.size();
			int w = size > 80 ? 4 : (size > 40 ? 3 : (size > 10 ? 2 : 1));
			w = Math.min(w, Math.max(1, this.fb_x / 300));
			int bx = 0;
			int by = 0;
			for(Entry<String, Integer> elem : this.playerList.entrySet()) {
				int x = this.fb_x / 2 - (w * 300) / 2 + bx * 300;
				int y = 10 + by * (Font.YGLYPH + 2);
				Drawing.drawGradient(x, y, 300, Font.YGLYPH + 2, 0x7f404040, 0x7f000000);
				Drawing.drawText(elem.getKey(), x + 4, y + 1, 0xffffffff);
				Drawing.drawTextRight(formatPing(elem.getValue()), x + 300 - 4, y + 1, 0xffffffff);
				if(++bx >= w) {
					bx = 0;
					++by;
				}
			}
		}
	}
	
    private void renderWorldDirections(float partialTicks) {
        GlState.enableBlend();
        GlState.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
        GL11.glLineWidth(1.0F);
        GlState.disableTexture2D();
        GlState.depthMask(false);
//        GlState.color(1.0f, 1.0f, 1.0f, 1.0f);
        GL11.glPushMatrix();
//        WCF.glMatrixMode(5888);
//        WCF.glLoadIdentity();
    	GL11.glTranslatef((float)(this.fb_x / 2), (float)(this.fb_y / 2), 0.0F);
        this.entityRenderer.rotateCamera(this.viewEntity, partialTicks);
//        WCF.glScalef(-1.0f, -1.0f, -1.0f);
//    	Drawing.drawRectColor(0, 0, 10, 2, 0xff00ff00);
        RenderGlobal.drawOutlinedBoundingBox(new BoundingBox(0.0D, 0.0D, 0.0D, 24D, 1D, 1D), 255, 0, 0, 255);
        RenderGlobal.drawOutlinedBoundingBox(new BoundingBox(0.0D, 0.0D, 0.0D, 1D, 1D, 24D), 0, 0, 255, 255);
        RenderGlobal.drawOutlinedBoundingBox(new BoundingBox(0.0D, 0.0D, 0.0D, 1D, -20D, 1D), 0, 255, 0, 255);
        GL11.glPopMatrix();
        GlState.depthMask(true);
        GlState.enableTexture2D();
        GlState.disableBlend();
    }
    
    private int blendColors(int color1, int color2, float value) {
		int i = color1 >> 24 & 255;
		int j = color1 >> 16 & 255;
		int k = color1 >> 8 & 255;
		int l = color1 & 255;
		int i1 = color2 >> 24 & 255;
		int j1 = color2 >> 16 & 255;
		int k1 = color2 >> 8 & 255;
		int l1 = color2 & 255;
		int i2 = ExtMath.clampi((int)((float)i + (float)(i1 - i) * value), 0, 255);
		int j2 = ExtMath.clampi((int)((float)j + (float)(j1 - j) * value), 0, 255);
		int k2 = ExtMath.clampi((int)((float)k + (float)(k1 - k) * value), 0, 255);
		int l2 = ExtMath.clampi((int)((float)l + (float)(l1 - l) * value), 0, 255);
		return i2 << 24 | j2 << 16 | k2 << 8 | l2;
	}

    private int getFrameColor(int value, int base, int mid, int high) {
		value = ExtMath.clampi(value, base, high);
		return value < mid ? this.blendColors(-16711936, -256, (float)value / (float)mid)
				: this.blendColors(-256, -65536, (float)(value - mid) / (float)(high - mid));
	}
    
	private void renderLagometer() {
//		GlState.disableDepth();
		int w = this.fb_x;
		int h = this.fb_y;
		int shifted = this.lastIndex;
		int x = 0;
		Drawing.drawRect(0, h - 74, 240, 60, 0x90505050);

		Drawing.drawRect(0, h - 44, 240, 1, 0x5fffffff);
		Drawing.drawRect(0, h - 74, 240, 1, 0x5fffffff);

		while(shifted != this.frameIndex) {
			int value = (int) (this.frames[shifted] / 1000000L); // , 30);
			int color = this.getFrameColor(value, 0, 17, 67);
			if(value > 0)
				Drawing.drawRect(x, h - 14 - value, 1, value, color & 0xc0ffffff);
			++x;
			shifted = (shifted + 1) % 240;
		}
		
		Drawing.drawText("30ms", 2, h - 30 - 14, 0xffE0E0E0);
		Drawing.drawText("60ms", 2, h - 60 - 14, 0xffE0E0E0);
		Drawing.drawTextRight("ms/Frame", 238, h - 60 - 14, 0xffE0E0E0);
		
		if(this.lastTickTime >= 0) {
			this.updateTick();
			Drawing.drawRect(w - 240, h - 74, 240, 60, 0x90505050);
			x = w - 240;

			Drawing.drawRect(w - 240, h - 44, 240, 1, 0x5fffffff);
			Drawing.drawRect(w - 240, h - 74, 240, 1, 0x5fffffff);
			
			for (int n = 0; n < 240; ++n)
	        {
	            int value = this.tickTimes[(n + this.tickIndex) % 240];
				int color = this.getFrameColor(value, 0, 50, 250);
				if(value > 0)
					Drawing.drawRect(x, h - 14 - value, 1, value, color & 0xc0ffffff);
				++x;
				shifted = (shifted + 1) % 240;
			}

			Drawing.drawText("30ms", w - 240 + 2, h - 30 - 14, 0xffE0E0E0);
			Drawing.drawText("60ms", w - 240 + 2, h - 60 - 14, 0xffE0E0E0);
			Drawing.drawTextRight("ms/Tick", w - 2, h - 60 - 14, 0xffE0E0E0);
		}

//		GlState.enableDepth();
	}
	
	public static enum FileMode {
		DIRECTORY_LOAD,
		DIRECTORY_SAVE,
		FILE_LOAD,
		FILE_LOAD_MULTI,
		FILE_SAVE;
	}
	
	public void showFileDialog(final FileMode mode, final String title, final File def, final FileCallback callback) {
		if(this.waitingForFile)
			return;
		this.waitingForFile = true;
		new Thread(new Runnable() {
			public void run() {
				String output = null;
				JFileChooser chooser = null;
				try {
					if(Util.WINDOWS)
						throw new RuntimeException("Windows wird von Zenity nicht unterstützt");
					List<String> list = Lists.newArrayList("zenity", "--file-selection");
					switch(mode) {
						case DIRECTORY_SAVE:
							list.add("--save");
						case DIRECTORY_LOAD:
							list.add("--directory");
							break;
						case FILE_SAVE:
							list.add("--save");
							break;
						case FILE_LOAD_MULTI:
							list.add("--multiple");
							list.add("--separator");
							list.add(":");
							break;
					}
					list.add("--title");
					list.add(title);
					if(def != null) {
						list.add("--filename");
						list.add(def.isDirectory() ? def.getAbsolutePath() + File.separator + "__THISFILEDOESNOTEXIST__" : def.getAbsolutePath());
					}
					Process proc = Runtime.getRuntime().exec(list.toArray(new String[list.size()]));
					BufferedReader buf = new BufferedReader(new InputStreamReader(new BufferedInputStream(proc.getInputStream())));
					proc.waitFor();
					output = buf.readLine();
					try {
						buf.close();
					}
					catch(Throwable e) {
					}
				}
				catch(Throwable e) {
					Log.SYSTEM.error(e, "Konnte Dateibrowser nicht starten");
					chooser = new JFileChooser(def.isDirectory() ? def : def.getParentFile());
					chooser.setDialogTitle(title);
					chooser.setMultiSelectionEnabled(mode == FileMode.FILE_LOAD_MULTI);
					chooser.setFileSelectionMode(mode == FileMode.DIRECTORY_LOAD || mode == FileMode.DIRECTORY_SAVE ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_ONLY);
					int result;
					if(mode == FileMode.FILE_SAVE || mode == FileMode.DIRECTORY_SAVE)
						result = chooser.showSaveDialog(null);
					else
						result = chooser.showOpenDialog(null);
					if(result != JFileChooser.APPROVE_OPTION) {
//						Client.this.logFeed(TextColor.RED + "Konnte Dateibrowser nicht öffnen");
						Client.this.waitingForFile = false;
						return;
					}
				}
				if(output == null && chooser == null) {
					Client.this.waitingForFile = false;
					return;
				}
				if(mode == FileMode.FILE_LOAD_MULTI) {
					final List<File> files = Lists.newArrayList();
					if(chooser != null) {
						for(File file : chooser.getSelectedFiles()) {
							if(file.isFile())
								files.add(file);
						}
					}
					else {
						for(String out : output.split(":")) {
							File file = new File(out);
							if(file.isFile())
								files.add(file);
						}
					}
					if(files.isEmpty()) {
						Client.this.waitingForFile = false;
						return;
					}
					Client.this.schedule(new Runnable() {
						public void run() {
							if(Client.this.waitingForFile) {
								for(File file : files) {
									callback.selected(file);
								}
							}
							Client.this.waitingForFile = false;
						}
					});
				}
				else {
					File file = chooser != null ? chooser.getSelectedFile() : new File(output);
					switch(mode) {
						case DIRECTORY_LOAD:
							if(!file.isDirectory()) {
								Client.this.waitingForFile = false;
								return;
							}
							break;
						case DIRECTORY_SAVE:
							if(file.exists() && !file.isDirectory()) {
								Client.this.waitingForFile = false;
								return;
							}
							break;
						case FILE_LOAD:
							if(!file.isFile()) {
								Client.this.waitingForFile = false;
								return;
							}
							break;
						case FILE_SAVE:
							if(file.exists() && !file.isFile()) {
								Client.this.waitingForFile = false;
								return;
							}
							break;
					}
					Client.this.schedule(new Runnable() {
						public void run() {
							if(Client.this.waitingForFile)
								callback.selected(file);
							Client.this.waitingForFile = false;
						}
					});
				}
			}
		}, "File Browser listener").start();
	}
}
