Java tutorial
/** * Copyright (c) 2012 Martin Geisse * * This file is distributed under the terms of the MIT license. */ package name.martingeisse.miner.ingame; import static org.lwjgl.opengl.GL11.GL_BLEND; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.GL_LESS; import static org.lwjgl.opengl.GL11.GL_LINEAR; import static org.lwjgl.opengl.GL11.GL_LINES; import static org.lwjgl.opengl.GL11.GL_MODELVIEW; import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA; import static org.lwjgl.opengl.GL11.GL_PROJECTION; import static org.lwjgl.opengl.GL11.GL_QUADS; import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA; import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL11.GL_TEXTURE_GEN_Q; import static org.lwjgl.opengl.GL11.GL_TEXTURE_GEN_R; import static org.lwjgl.opengl.GL11.GL_TEXTURE_GEN_S; import static org.lwjgl.opengl.GL11.GL_TEXTURE_GEN_T; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; import static org.lwjgl.opengl.GL11.glBegin; import static org.lwjgl.opengl.GL11.glBindTexture; import static org.lwjgl.opengl.GL11.glBlendFunc; import static org.lwjgl.opengl.GL11.glClear; import static org.lwjgl.opengl.GL11.glClearColor; import static org.lwjgl.opengl.GL11.glColor3f; import static org.lwjgl.opengl.GL11.glDepthFunc; import static org.lwjgl.opengl.GL11.glDepthMask; import static org.lwjgl.opengl.GL11.glDisable; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glEnd; import static org.lwjgl.opengl.GL11.glLineWidth; import static org.lwjgl.opengl.GL11.glLoadIdentity; import static org.lwjgl.opengl.GL11.glMatrixMode; import static org.lwjgl.opengl.GL11.glPopMatrix; import static org.lwjgl.opengl.GL11.glPushMatrix; import static org.lwjgl.opengl.GL11.glRotatef; import static org.lwjgl.opengl.GL11.glScalef; import static org.lwjgl.opengl.GL11.glTexCoord2f; import static org.lwjgl.opengl.GL11.glTexParameteri; import static org.lwjgl.opengl.GL11.glTranslated; import static org.lwjgl.opengl.GL11.glVertex2f; import static org.lwjgl.opengl.GL11.glVertex3f; import static org.lwjgl.opengl.GL11.glVertex3i; import static org.lwjgl.opengl.GL14.glWindowPos2i; import static org.lwjgl.util.glu.GLU.gluPerspective; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import name.martingeisse.common.util.ThreadUtil; import name.martingeisse.miner.common.MinerCommonConstants; import name.martingeisse.miner.common.MinerCubeTypes; import name.martingeisse.miner.ingame.player.Player; import name.martingeisse.miner.ingame.player.PlayerProxy; import name.martingeisse.miner.ingame.visual.OtherPlayerVisualTemplate; import name.martingeisse.stackd.client.engine.EngineParameters; import name.martingeisse.stackd.client.engine.FrameRenderParameters; import name.martingeisse.stackd.client.engine.WorldWorkingSet; import name.martingeisse.stackd.client.engine.renderer.DefaultSectionRenderer; import name.martingeisse.stackd.client.frame.AbstractIntervalFrameHandler; import name.martingeisse.stackd.client.frame.FrameDurationSensor; import name.martingeisse.stackd.client.glworker.GlWorkUnit; import name.martingeisse.stackd.client.glworker.GlWorkerLoop; import name.martingeisse.stackd.client.network.CubeModificationPacketBuilder; import name.martingeisse.stackd.client.network.SectionGridLoader; import name.martingeisse.stackd.client.sound.RegularSound; import name.martingeisse.stackd.client.system.Font; import name.martingeisse.stackd.client.util.MouseUtil; import name.martingeisse.stackd.client.util.RayAction; import name.martingeisse.stackd.client.util.RayActionSupport; import name.martingeisse.stackd.common.StackdConstants; import name.martingeisse.stackd.common.geometry.AxisAlignedDirection; import name.martingeisse.stackd.common.geometry.RectangularRegion; import name.martingeisse.stackd.common.util.ProfilingHelper; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.joda.time.Duration; import org.joda.time.Instant; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; /** * TODO: document me * */ public class CubeWorldHandler { /** * the logger */ private static Logger logger = Logger.getLogger(CubeWorldHandler.class); /** * the MAX_STAIRS_HEIGHT */ public static final double MAX_STAIRS_HEIGHT = 0.8; /** * the player */ private final Player player; /** * the resources */ private final MinerResources resources; /** * the workingSet */ private final WorldWorkingSet workingSet; /** * the infoButtonPressed */ private boolean infoButtonPressed; /** * the rayActionSupport */ private final RayActionSupport rayActionSupport; /** * the captureRayActionSupport */ private boolean captureRayActionSupport; /** * the wireframe */ private boolean wireframe; /** * the grid */ private boolean grid; /** * the screenWidth */ private final int screenWidth; /** * the screenHeight */ private final int screenHeight; /** * the aspectRatio */ private final float aspectRatio; /** * the currentCubeType */ private byte currentCubeType = 1; /** * the frameDurationSensor */ private final FrameDurationSensor frameDurationSensor; /** * the playerProxies */ private List<PlayerProxy> playerProxies; /** * the playerNames */ private Map<Integer, String> playerNames; /** * the minusPressed */ private boolean minusPressed; /** * the flashMessageCounter */ private int flashMessageCounter = 0; /** * the footstepSound */ private RegularSound footstepSound; /** * the walking */ private boolean walking; /** * the cooldownFinishTime */ private long cooldownFinishTime; /** * the previousConnectionProblemInstant */ private Instant previousConnectionProblemInstant = new Instant(); /** * the otherPlayerVisualTemplate */ private final OtherPlayerVisualTemplate otherPlayerVisualTemplate; /** * The sectionLoadHandler -- checks often (100 ms), but doesn't re-request frequently (5 sec) * to avoid re-requesting a section again and again while the server is loading it. * * TODO should be resettable for edge cases where frequent reloading is needed, such as * falling down from high places. * This handler checks if sections must be loaded. */ private AbstractIntervalFrameHandler sectionLoadHandler = new AbstractIntervalFrameHandler(100) { private int requestCooldown = 0; @Override protected void onIntervalTimerExpired() { if (requestCooldown == 0) { IngameHandler.protocolClient.getSectionGridLoader().setViewerPosition(player.getSectionId()); if (IngameHandler.protocolClient.getSectionGridLoader().update()) { requestCooldown = 50; } } else { requestCooldown--; } } }; /** * Constructor. * @param width the width of the framebuffer * @param height the height of the framebuffer * @param resources the resources * @throws IOException on I/O errors while loading the textures */ public CubeWorldHandler(final int width, final int height, final MinerResources resources) throws IOException { // the resources (textures) this.resources = resources; // the world final DefaultSectionRenderer sectionRenderer = new DefaultSectionRenderer(); sectionRenderer.prepareForTextures(resources.getCubeTextures()); final EngineParameters engineParameters = new EngineParameters(sectionRenderer, resources.getCubeTextures(), MinerCubeTypes.CUBE_TYPES); workingSet = new WorldWorkingSet(engineParameters, MinerCommonConstants.CLUSTER_SIZE); // the player player = new Player(workingSet); player.getPosition().setX(0); player.getPosition().setY(10); player.getPosition().setZ(0); // other stuff rayActionSupport = new RayActionSupport(width, height); screenWidth = width; screenHeight = height; aspectRatio = (float) width / (float) height; frameDurationSensor = new FrameDurationSensor(); playerProxies = new ArrayList<PlayerProxy>(); playerNames = new HashMap<Integer, String>(); footstepSound = new RegularSound(resources.getFootstep(), 500); cooldownFinishTime = System.currentTimeMillis(); otherPlayerVisualTemplate = new OtherPlayerVisualTemplate(resources, player, this); // TODO: implement better checking for connection problems: only stall when surrounding sections // are missing AND the player is in that half of the current section. currently using collider // radius 2 to avoid "connection problems" when crossing a section boundary IngameHandler.protocolClient .setSectionGridLoader(new SectionGridLoader(workingSet, IngameHandler.protocolClient, 3, 2)); } /** * Getter method for the resources. * @return the resources */ public MinerResources getResources() { return resources; } /** * Getter method for the currentCubeType. * @return the currentCubeType */ public byte getCurrentCubeType() { return currentCubeType; } /** * Getter method for the workingSet. * @return the workingSet */ public WorldWorkingSet getWorkingSet() { return workingSet; } /** * Getter method for the player. * @return the player */ public Player getPlayer() { return player; } /** * Getter method for the playerProxies. * @return the playerProxies */ public List<PlayerProxy> getPlayerProxies() { return playerProxies; } /** * Setter method for the playerProxies. * @param playerProxies the playerProxies to set */ public void setPlayerProxies(final List<PlayerProxy> playerProxies) { this.playerProxies = playerProxies; } /** * Getter method for the playerNames. * @return the playerNames */ public Map<Integer, String> getPlayerNames() { return playerNames; } /** * Setter method for the playerNames. * @param playerNames the playerNames to set */ public void setPlayerNames(final Map<Integer, String> playerNames) { this.playerNames = playerNames; } /** * */ public void step() { final boolean keysEnabled = IngameHandler.gameMenuHandlerWrapper.getWrappedHandler() == null; final boolean mouseMovementEnabled = IngameHandler.gameMenuHandlerWrapper.getWrappedHandler() == null; // first, handle the stuff that already works without the world being loaded "enough" frameDurationSensor.tick(); if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_I)) { if (!infoButtonPressed) { player.dump(); } infoButtonPressed = true; } else { infoButtonPressed = false; } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_P)) { player.setObserverMode(false); } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_O)) { player.setObserverMode(true); } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_G)) { grid = true; } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_H)) { grid = false; } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_K)) { MouseUtil.ungrab(); } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_L)) { MouseUtil.grab(); } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_SLASH) && !minusPressed) { IngameHandler.flashMessageHandler.addMessage("foobar! " + flashMessageCounter); flashMessageCounter++; } minusPressed = keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_SLASH); wireframe = keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_F); player.setWantsToJump(keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_SPACE)); if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_1)) { currentCubeType = 1; } else if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_2)) { currentCubeType = 2; } else if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_3)) { currentCubeType = 3; } else if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_4)) { currentCubeType = 50; } if (mouseMovementEnabled) { player.getOrientation() .setHorizontalAngle(player.getOrientation().getHorizontalAngle() - Mouse.getDX() * 0.5); double newUpAngle = player.getOrientation().getVerticalAngle() + Mouse.getDY() * 0.5; newUpAngle = (newUpAngle > 90) ? 90 : (newUpAngle < -90) ? -90 : newUpAngle; player.getOrientation().setVerticalAngle(newUpAngle); } sectionLoadHandler.handleStep(); // process keyboard events -- needed for flawless GUI toggling // TODO properly disable all keyboard / mouse handling in the CubeWorldHandler when the GUI is active if (keysEnabled) { while (Keyboard.next()) { if (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE && Keyboard.getEventKeyState()) { IngameHandler.gameMenuHandlerWrapper.setWrappedHandler(IngameHandler.gameMenuHandler); MouseUtil.ungrab(); } } } // check if the world is loaded "enough" workingSet.acceptLoadedSections(); if (!workingSet.hasAllRenderModels(player.getSectionId(), 1) || !workingSet.hasAllColliders(player.getSectionId(), 1)) { final Instant now = new Instant(); if (new Duration(previousConnectionProblemInstant, now).getMillis() >= 1000) { logger.warn("connection problems"); ThreadUtil.dumpThreads(Level.INFO); previousConnectionProblemInstant = now; } return; } // --------------------------------------------------------------------------------------------------- // now, handle the stuff that only works with enough information from the world // --------------------------------------------------------------------------------------------------- // normal movement: If on the ground, we move the player step-up, then front/side, then step-down. // This way the player can climb stairs while walking. In the air, this boils down to front/side movement. // We also keep track if the player is walking (front/side) for a "walking" sound effect. double speed = keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_TAB) ? 10.0 : keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) ? 3.0 : 1.5; speed *= frameDurationSensor.getMultiplier(); walking = false; double forward = 0, right = 0; if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_A)) { right = -speed; walking = true; } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_D)) { right = speed; walking = true; } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_W)) { forward = speed; walking = true; } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_S)) { forward = -speed; walking = true; } player.moveHorizontal(forward, right, player.isOnGround() ? MAX_STAIRS_HEIGHT : 0); // special movement if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_C)) { player.moveUp(-speed); } if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_E)) { player.moveUp(speed); } // cube placement captureRayActionSupport = false; final long now = System.currentTimeMillis(); if (now >= cooldownFinishTime) { if (mouseMovementEnabled && Mouse.isButtonDown(0)) { captureRayActionSupport = true; rayActionSupport.execute(player.getPosition().getX(), player.getPosition().getY(), player.getPosition().getZ(), new RayAction(false) { @Override public void handleImpact(final int x, final int y, final int z, final double distance) { if (distance < 3.0) { final byte effectiveCubeType; if (currentCubeType == 50) { final int angle = ((int) player.getOrientation().getHorizontalAngle() % 360 + 360) % 360; if (angle < 45) { effectiveCubeType = 52; } else if (angle < 45 + 90) { effectiveCubeType = 50; } else if (angle < 45 + 180) { effectiveCubeType = 53; } else if (angle < 45 + 270) { effectiveCubeType = 51; } else { effectiveCubeType = 52; } } else { effectiveCubeType = currentCubeType; } /* TODO: The call to breakFree() will remove a stairs cube if the player is standing * on the lower step, because the player's bounding box intersects with the cube's * bounding box. Solution 1: Remove breakFree(), don't place a cube if the player then * collides. Solution 2: Make breakFree() more accurate. */ final CubeModificationPacketBuilder builder = new CubeModificationPacketBuilder(); builder.addModification(x, y, z, effectiveCubeType); breakFree(builder); IngameHandler.protocolClient.send(builder.getPacket()); // cooldownFinishTime = now + 1000; cooldownFinishTime = now + 200; } } }); } else if (mouseMovementEnabled && Mouse.isButtonDown(1)) { captureRayActionSupport = true; rayActionSupport.execute(player.getPosition().getX(), player.getPosition().getY(), player.getPosition().getZ(), new RayAction(true) { @Override public void handleImpact(final int x, final int y, final int z, final double distance) { if (distance < 2.0) { IngameHandler.protocolClient.sendDigNotification(x, y, z); resources.getHitCube().playAsSoundEffect(1.0f, 1.0f, false); // cooldownFinishTime = now + 1000; cooldownFinishTime = now + 200; } } }); } } // special actions if (keysEnabled && Keyboard.isKeyDown(Keyboard.KEY_B)) { final CubeModificationPacketBuilder builder = new CubeModificationPacketBuilder(); breakFree(builder); IngameHandler.protocolClient.send(builder.getPacket()); } // handle player logic player.step(frameDurationSensor.getMultiplier()); // handle sound effects if (player.isOnGround() && walking) { footstepSound.handleActiveTime(); } else { footstepSound.reset(); } if (player.isJustLanded()) { resources.getLandOnGround().playAsSoundEffect(1.0f, 1.0f, false); } } /** * */ private void breakFree(final CubeModificationPacketBuilder builder) { final RectangularRegion region = player.createCollisionRegion(); for (int x = region.getStartX(); x < region.getEndX(); x++) { for (int y = region.getStartY(); y < region.getEndY(); y++) { for (int z = region.getStartZ(); z < region.getEndZ(); z++) { builder.addModification(x, y, z, (byte) 0); } } } } /** * */ public void purge() { } /** * @param glWorkerLoop the OpenGL worker loop */ public void draw(final GlWorkerLoop glWorkerLoop) { // determine player's position as integers final int playerX = (int) (Math.floor(player.getPosition().getX())); final int playerY = (int) (Math.floor(player.getPosition().getY())); final int playerZ = (int) (Math.floor(player.getPosition().getZ())); // set the GL worker loop for the section renderer ((DefaultSectionRenderer) workingSet.getEngineParameters().getSectionRenderer()) .setGlWorkerLoop(glWorkerLoop); // run preparation code in the OpenGL worker thread glWorkerLoop.schedule(new GlWorkUnit() { @Override public void execute() { // profiling ProfilingHelper.start(); // set up projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, aspectRatio, 0.1f, 10000.0f); // set up modelview matrix glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // model transformation (direct) glRotatef((float) player.getOrientation().getVerticalAngle(), -1, 0, 0); // view transformation (reversed) glRotatef((float) player.getOrientation().getHorizontalAngle(), 0, -1, 0); // ... glTranslated(-player.getPosition().getX(), -player.getPosition().getY(), -player.getPosition().getZ()); // ... // clear the screen glDepthMask(true); glClearColor(0.5f, 0.5f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // some more preparation glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); ((DefaultSectionRenderer) workingSet.getEngineParameters().getSectionRenderer()) .setWireframe(wireframe); ((DefaultSectionRenderer) workingSet.getEngineParameters().getSectionRenderer()) .setTexturing(IngameHandler.enableTexturing); ((DefaultSectionRenderer) workingSet.getEngineParameters().getSectionRenderer()) .setTextureCoordinateGeneration(IngameHandler.enableTexGen); // scale by the inverse detail factor for drawing the cubes, but prepare for scaling back glPushMatrix(); float inverseFactor = 1.0f / StackdConstants.GEOMETRY_DETAIL_FACTOR; glScalef(inverseFactor, inverseFactor, inverseFactor); } }); // actually draw the world TODO pass the GL worker workingSet.draw(new FrameRenderParameters(playerX, playerY, playerZ)); // post-draw code, again in the GL worker thread glWorkerLoop.schedule(new GlWorkUnit() { @Override public void execute() { // scale back for the remaining operations glPopMatrix(); // Measure visible distance in the center of the crosshair, with only the world visible (no HUD or similar). // Only call if needed, this stalls the rendering pipeline --> 2x frame rate possible! if (captureRayActionSupport) { rayActionSupport.capture(); } else { rayActionSupport.release(); } // draw the sky glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_Q); glDisable(GL_TEXTURE_GEN_R); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); resources.getClouds().glBindTexture(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); final float tex = 10.0f; glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex3i(-100000, 1000, -100000); glTexCoord2f(tex, 0); glVertex3i(+100000, 1000, -100000); glTexCoord2f(tex, tex); glVertex3i(+100000, 1000, +100000); glTexCoord2f(0, tex); glVertex3i(-100000, 1000, +100000); glEnd(); glDisable(GL_BLEND); // draw the grid if (grid) { glDisable(GL_TEXTURE_2D); glColor3f(1.0f, 1.0f, 1.0f); final int sectionX = playerX >> MinerCommonConstants.CLUSTER_SIZE.getShiftBits(); final int sectionY = playerY >> MinerCommonConstants.CLUSTER_SIZE.getShiftBits(); final int sectionZ = playerZ >> MinerCommonConstants.CLUSTER_SIZE.getShiftBits(); final int distance = 48; glLineWidth(2.0f); glBegin(GL_LINES); for (int u = -3; u <= 4; u++) { for (int v = -3; v <= 4; v++) { for (final AxisAlignedDirection direction : AxisAlignedDirection.values()) { if (direction.isNegative()) { continue; } final int x = MinerCommonConstants.CLUSTER_SIZE.getSize() * (sectionX + direction.selectByAxis(0, u, v)); final int dx = direction.selectByAxis(distance, 0, 0); final int y = MinerCommonConstants.CLUSTER_SIZE.getSize() * (sectionY + direction.selectByAxis(v, 0, u)); final int dy = direction.selectByAxis(0, distance, 0); final int z = MinerCommonConstants.CLUSTER_SIZE.getSize() * (sectionZ + direction.selectByAxis(u, v, 0)); final int dz = direction.selectByAxis(0, 0, distance); glVertex3f(x + dx, y + dy, z + dz); glVertex3f(x - dx, y - dy, z - dz); } } } glEnd(); } // draw player proxies (i.e. other players) glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_BLEND); glMatrixMode(GL_MODELVIEW); for (final PlayerProxy playerProxy : playerProxies) { if (playerProxy.getId() != IngameHandler.protocolClient.getSessionId()) { otherPlayerVisualTemplate.renderEmbedded(playerProxy); } } glDisable(GL_BLEND); // draw the crosshair glLineWidth(1.0f); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_LINES); glVertex2f(-0.1f, 0.0f); glVertex2f(+0.1f, 0.0f); glVertex2f(0.0f, -0.1f); glVertex2f(0.0f, +0.1f); glEnd(); // draw the HUD glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glWindowPos2i(screenWidth, screenHeight - 30); GL11.glPixelTransferf(GL11.GL_RED_BIAS, 1.0f); GL11.glPixelTransferf(GL11.GL_GREEN_BIAS, 1.0f); GL11.glPixelTransferf(GL11.GL_BLUE_BIAS, 1.0f); resources.getFont().drawText("coins: " + IngameHandler.protocolClient.getCoins(), 2, Font.ALIGN_RIGHT, Font.ALIGN_TOP); GL11.glPixelTransferf(GL11.GL_RED_BIAS, 0.0f); GL11.glPixelTransferf(GL11.GL_GREEN_BIAS, 0.0f); GL11.glPixelTransferf(GL11.GL_BLUE_BIAS, 0.0f); // profiling ProfilingHelper.checkRelevant("draw", 50); } }); } }