/**
   * Entry point to our test
   *
   * @param argv The arguments passed to the test
   */
  public static void main(String[] argv) {
    try {
      Renderer.setLineStripRenderer(Renderer.QUAD_BASED_LINE_STRIP_RENDERER);
      Renderer.getLineStripRenderer().setLineCaps(true);

      AppGameContainer container = new AppGameContainer(new LineRenderTest());
      container.setDisplayMode(800, 600, false);
      container.start();
    } catch (SlickException e) {
      e.printStackTrace();
    }
  }
示例#2
0
 /**
  * Uploads the pixel data to the given texture at the specified position. This only needs to be
  * called once, after the pixel data has changed.
  *
  * @param texture the texture to modify
  * @param x the x position to place the pixel data on the texture
  * @param y the y position to place the pixel data on the texture
  */
 public void apply(Texture texture, int x, int y) {
   if (x + width > texture.getTextureWidth() || y + height > texture.getTextureHeight())
     throw new IndexOutOfBoundsException("pixel data won't fit in given texture");
   position(length());
   pixels.flip();
   int glFmt = format.getOGLType();
   final SGL GL = Renderer.get();
   texture.bind();
   GL.glTexSubImage2D(
       SGL.GL_TEXTURE_2D, 0, x, y, width, height, glFmt, SGL.GL_UNSIGNED_BYTE, pixels);
 }
示例#3
0
  /**
   * Entry point to our test
   *
   * @param argv The arguments to pass into the test
   */
  public static void main(String[] argv) {
    try {
      Renderer.setRenderer(Renderer.VERTEX_ARRAY_RENDERER);

      AppGameContainer container = new AppGameContainer(new GradientTest());
      container.setDisplayMode(800, 600, false);
      container.start();
    } catch (SlickException e) {
      e.printStackTrace();
    }
  }
示例#4
0
/**
 * A particle syste responsible for maintaining a set of data about individual particles which are
 * created and controlled by assigned emitters. This pseudo chaotic nature hopes to give more
 * organic looking effects
 *
 * @author kevin
 */
public class ParticleSystem {
  /** The renderer to use for all GL operations */
  protected static SGL GL = Renderer.get();

  /** The blending mode for the glowy style */
  public static final int BLEND_ADDITIVE = 1;
  /** The blending mode for the normal style */
  public static final int BLEND_COMBINE = 2;

  /** The default number of particles in the system */
  private static final int DEFAULT_PARTICLES = 100;

  /**
   * Set the path from which images should be loaded
   *
   * @param path The path from which images should be loaded
   */
  public static void setRelativePath(String path) {
    ConfigurableEmitter.setRelativePath(path);
  }

  /**
   * A pool of particles being used by a specific emitter
   *
   * @author void
   */
  private class ParticlePool {
    /** The particles being rendered and maintained */
    public Particle[] particles;
    /**
     * The list of particles left to be used, if this size() == 0 then the particle engine was too
     * small for the effect
     */
    public ArrayList available;

    /**
     * Create a new particle pool contiaining a set of particles
     *
     * @param system The system that owns the particles over all
     * @param maxParticles The maximum number of particles in the pool
     */
    public ParticlePool(ParticleSystem system, int maxParticles) {
      particles = new Particle[maxParticles];
      available = new ArrayList();

      for (int i = 0; i < particles.length; i++) {
        particles[i] = createParticle(system);
      }

      reset(system);
    }

    /**
     * Rest the list of particles
     *
     * @param system The system in which the particle belong
     */
    public void reset(ParticleSystem system) {
      available.clear();

      for (int i = 0; i < particles.length; i++) {
        available.add(particles[i]);
      }
    }
  }

  /**
   * A map from emitter to a the particle pool holding the particles it uses void: this is now
   * sorted by emitters to allow emitter specfic state to be set for each emitter. actually this is
   * used to allow setting an individual blend mode for each emitter
   */
  protected HashMap particlesByEmitter = new HashMap();
  /** The maximum number of particles allows per emitter */
  protected int maxParticlesPerEmitter;

  /** The list of emittered producing and controlling particles */
  protected ArrayList emitters = new ArrayList();

  /** The dummy particle to return should no more particles be available */
  protected Particle dummy;
  /** The blending mode */
  private int blendingMode = BLEND_COMBINE;
  /** The number of particles in use */
  private int pCount;
  /** True if we're going to use points to render the particles */
  private boolean usePoints;
  /** The x coordinate at which this system should be rendered */
  private float x;
  /** The x coordinate at which this system should be rendered */
  private float y;
  /** True if we should remove completed emitters */
  private boolean removeCompletedEmitters = true;

  /** The default image for the particles */
  private Image sprite;
  /** True if the particle system is visible */
  private boolean visible = true;
  /** The name of the default image */
  private String defaultImageName;
  /** The mask used to make the particle image background transparent if any */
  private Color mask;

  /**
   * Create a new particle system
   *
   * @param defaultSprite The sprite to render for each particle
   */
  public ParticleSystem(Image defaultSprite) {
    this(defaultSprite, DEFAULT_PARTICLES);
  }

  /**
   * Create a new particle system
   *
   * @param defaultSpriteRef The sprite to render for each particle
   */
  public ParticleSystem(String defaultSpriteRef) {
    this(defaultSpriteRef, DEFAULT_PARTICLES);
  }

  /** Reset the state of the system */
  public void reset() {
    Iterator pools = particlesByEmitter.values().iterator();
    while (pools.hasNext()) {
      ParticlePool pool = (ParticlePool) pools.next();
      pool.reset(this);
    }

    for (int i = 0; i < emitters.size(); i++) {
      ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);
      emitter.resetState();
    }
  }

  /**
   * Check if this system is currently visible, i.e. it's actually rendered
   *
   * @return True if the particle system is rendered
   */
  public boolean isVisible() {
    return visible;
  }

  /**
   * Indicate whether the particle system should be visible, i.e. whether it'll actually render
   *
   * @param visible True if the particle system should render
   */
  public void setVisible(boolean visible) {
    this.visible = visible;
  }

  /**
   * Indicate if completed emitters should be removed
   *
   * @param remove True if completed emitters should be removed
   */
  public void setRemoveCompletedEmitters(boolean remove) {
    removeCompletedEmitters = remove;
  }

  /**
   * Indicate if this engine should use points to render the particles
   *
   * @param usePoints True if points should be used to render the particles
   */
  public void setUsePoints(boolean usePoints) {
    this.usePoints = usePoints;
  }

  /**
   * Check if this engine should use points to render the particles
   *
   * @return True if the engine should use points to render the particles
   */
  public boolean usePoints() {
    return usePoints;
  }

  /**
   * Create a new particle system
   *
   * @param defaultSpriteRef The sprite to render for each particle
   * @param maxParticles The number of particles available in the system
   */
  public ParticleSystem(String defaultSpriteRef, int maxParticles) {
    this(defaultSpriteRef, maxParticles, null);
  }

  /**
   * Create a new particle system
   *
   * @param defaultSpriteRef The sprite to render for each particle
   * @param maxParticles The number of particles available in the system
   * @param mask The mask used to make the sprite image transparent
   */
  public ParticleSystem(String defaultSpriteRef, int maxParticles, Color mask) {
    this.maxParticlesPerEmitter = maxParticles;
    this.mask = mask;

    setDefaultImageName(defaultSpriteRef);
    dummy = createParticle(this);
  }

  /**
   * Create a new particle system
   *
   * @param defaultSprite The sprite to render for each particle
   * @param maxParticles The number of particles available in the system
   */
  public ParticleSystem(Image defaultSprite, int maxParticles) {
    this.maxParticlesPerEmitter = maxParticles;

    sprite = defaultSprite;
    dummy = createParticle(this);
  }

  /**
   * Set the default image name
   *
   * @param ref The default image name
   */
  public void setDefaultImageName(String ref) {
    defaultImageName = ref;
    sprite = null;
  }

  /**
   * Get the blending mode in use
   *
   * @see #BLEND_COMBINE
   * @see #BLEND_ADDITIVE
   * @return The blending mode in use
   */
  public int getBlendingMode() {
    return blendingMode;
  }

  /**
   * Create a particle specific to this system, override for your own implementations. These
   * particles will be cached and reused within this system.
   *
   * @param system The system owning this particle
   * @return The newly created particle.
   */
  protected Particle createParticle(ParticleSystem system) {
    return new Particle(system);
  }

  /**
   * Set the blending mode for the particles
   *
   * @param mode The mode for blending particles together
   */
  public void setBlendingMode(int mode) {
    this.blendingMode = mode;
  }

  /**
   * Get the number of emitters applied to the system
   *
   * @return The number of emitters applied to the system
   */
  public int getEmitterCount() {
    return emitters.size();
  }

  /**
   * Get an emitter a specified index int he list contained within this system
   *
   * @param index The index of the emitter to retrieve
   * @return The particle emitter
   */
  public ParticleEmitter getEmitter(int index) {
    return (ParticleEmitter) emitters.get(index);
  }

  /**
   * Add a particle emitter to be used on this system
   *
   * @param emitter The emitter to be added
   */
  public void addEmitter(ParticleEmitter emitter) {
    emitters.add(emitter);

    ParticlePool pool = new ParticlePool(this, maxParticlesPerEmitter);
    particlesByEmitter.put(emitter, pool);
  }

  /**
   * Remove a particle emitter that is currently used in the system
   *
   * @param emitter The emitter to be removed
   */
  public void removeEmitter(ParticleEmitter emitter) {
    emitters.remove(emitter);
    particlesByEmitter.remove(emitter);
  }

  /** Remove all the emitters from the system */
  public void removeAllEmitters() {
    for (int i = 0; i < emitters.size(); i++) {
      removeEmitter((ParticleEmitter) emitters.get(i));
      i--;
    }
  }

  /**
   * Get the x coordiante of the position of the system
   *
   * @return The x coordinate of the position of the system
   */
  public float getPositionX() {
    return x;
  }

  /**
   * Get the y coordiante of the position of the system
   *
   * @return The y coordinate of the position of the system
   */
  public float getPositionY() {
    return y;
  }

  /**
   * Set the position at which this system should render relative to the current graphics context
   * setup
   *
   * @param x The x coordinate at which this system should be centered
   * @param y The y coordinate at which this system should be centered
   */
  public void setPosition(float x, float y) {
    this.x = x;
    this.y = y;
  }

  /** Render the particles in the system */
  public void render() {
    render(x, y);
  }

  /**
   * Render the particles in the system
   *
   * @param x The x coordinate to render the particle system at (in the current coordinate space)
   * @param y The y coordinate to render the particle system at (in the current coordiante space)
   */
  public void render(float x, float y) {
    if ((sprite == null) && (defaultImageName != null)) {
      loadSystemParticleImage();
    }

    if (!visible) {
      return;
    }

    GL.glTranslatef(x, y, 0);

    if (blendingMode == BLEND_ADDITIVE) {
      GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);
    }
    if (usePoints()) {
      GL.glEnable(SGL.GL_POINT_SMOOTH);
      TextureImpl.bindNone();
    }

    // iterate over all emitters
    for (int emitterIdx = 0; emitterIdx < emitters.size(); emitterIdx++) {
      // get emitter
      ParticleEmitter emitter = (ParticleEmitter) emitters.get(emitterIdx);

      // check for additive override and enable when set
      if (emitter.useAdditive()) {
        GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);
      }

      // now get the particle pool for this emitter and render all particles that are in use
      ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
      Image image = emitter.getImage();
      if (image == null) {
        image = this.sprite;
      }

      if (!emitter.isOriented() && !emitter.usePoints(this)) {
        image.startUse();
      }

      for (int i = 0; i < pool.particles.length; i++) {
        if (pool.particles[i].inUse()) pool.particles[i].render();
      }

      if (!emitter.isOriented() && !emitter.usePoints(this)) {
        image.endUse();
      }

      // reset additive blend mode
      if (emitter.useAdditive()) {
        GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);
      }
    }

    if (usePoints()) {
      GL.glDisable(SGL.GL_POINT_SMOOTH);
    }
    if (blendingMode == BLEND_ADDITIVE) {
      GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);
    }

    Color.white.bind();
    GL.glTranslatef(-x, -y, 0);
  }

  /** Load the system particle image as the extension permissions */
  private void loadSystemParticleImage() {
    AccessController.doPrivileged(
        new PrivilegedAction() {
          public Object run() {
            try {
              if (mask != null) {
                sprite = new Image(defaultImageName, mask);
              } else {
                sprite = new Image(defaultImageName);
              }
            } catch (SlickException e) {
              Log.error(e);
              defaultImageName = null;
            }
            return null; // nothing to return
          }
        });
  }

  /**
   * Update the system, request the assigned emitters update the particles
   *
   * @param delta The amount of time thats passed since last update in milliseconds
   */
  public void update(int delta) {
    if ((sprite == null) && (defaultImageName != null)) {
      loadSystemParticleImage();
    }

    ArrayList removeMe = new ArrayList();
    for (int i = 0; i < emitters.size(); i++) {
      ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);
      if (emitter.isEnabled()) {
        emitter.update(this, delta);
        if (removeCompletedEmitters) {
          if (emitter.completed()) {
            removeMe.add(emitter);
            particlesByEmitter.remove(emitter);
          }
        }
      }
    }
    emitters.removeAll(removeMe);

    pCount = 0;

    if (!particlesByEmitter.isEmpty()) {
      Iterator it = particlesByEmitter.values().iterator();
      while (it.hasNext()) {
        ParticlePool pool = (ParticlePool) it.next();
        for (int i = 0; i < pool.particles.length; i++) {
          if (pool.particles[i].life > 0) {
            pool.particles[i].update(delta);
            pCount++;
          }
        }
      }
    }
  }

  /**
   * Get the number of particles in use in this system
   *
   * @return The number of particles in use in this system
   */
  public int getParticleCount() {
    return pCount;
  }

  /**
   * Get a new particle from the system. This should be used by emitters to request particles
   *
   * @param emitter The emitter requesting the particle
   * @param life The time the new particle should live for
   * @return A particle from the system
   */
  public Particle getNewParticle(ParticleEmitter emitter, float life) {
    ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
    ArrayList available = pool.available;
    if (available.size() > 0) {
      Particle p = (Particle) available.remove(available.size() - 1);
      p.init(emitter, life);
      p.setImage(sprite);

      return p;
    }

    Log.warn("Ran out of particles (increase the limit)!");
    return dummy;
  }

  /**
   * Release a particle back to the system once it has expired
   *
   * @param particle The particle to be released
   */
  public void release(Particle particle) {
    if (particle != dummy) {
      ParticlePool pool = (ParticlePool) particlesByEmitter.get(particle.getEmitter());
      pool.available.add(particle);
    }
  }

  /**
   * Release all the particles owned by the specified emitter
   *
   * @param emitter The emitter owning the particles that should be released
   */
  public void releaseAll(ParticleEmitter emitter) {
    if (!particlesByEmitter.isEmpty()) {
      Iterator it = particlesByEmitter.values().iterator();
      while (it.hasNext()) {
        ParticlePool pool = (ParticlePool) it.next();
        for (int i = 0; i < pool.particles.length; i++) {
          if (pool.particles[i].inUse()) {
            if (pool.particles[i].getEmitter() == emitter) {
              pool.particles[i].setLife(-1);
              release(pool.particles[i]);
            }
          }
        }
      }
    }
  }

  /**
   * Move all the particles owned by the specified emitter
   *
   * @param emitter The emitter owning the particles that should be released
   * @param x The amount on the x axis to move the particles
   * @param y The amount on the y axis to move the particles
   */
  public void moveAll(ParticleEmitter emitter, int x, int y) {
    if (!particlesByEmitter.isEmpty()) {
      Iterator it = particlesByEmitter.values().iterator();
      while (it.hasNext()) {
        ParticlePool pool = (ParticlePool) it.next();
        for (int i = 0; i < pool.particles.length; i++) {
          if (pool.particles[i].inUse()) {
            pool.particles[i].move(x, y);
          }
        }
      }
    }
  }

  /**
   * Create a duplicate of this system. This would have been nicer as a different interface but may
   * cause to much API change headache. Maybe next full version release it should be rethought.
   *
   * <p>TODO: Consider refactor at next point release
   *
   * @return A copy of this particle system
   * @throws SlickException Indicates a failure during copy or a invalid particle system to be
   *     duplicated
   */
  public ParticleSystem duplicate() throws SlickException {
    for (int i = 0; i < emitters.size(); i++) {
      if (!(emitters.get(i) instanceof ConfigurableEmitter)) {
        throw new SlickException("Only systems contianing configurable emitters can be duplicated");
      }
    }

    ParticleSystem theCopy = null;
    try {
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      ParticleIO.saveConfiguredSystem(bout, this);
      ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
      theCopy = ParticleIO.loadConfiguredSystem(bin);
    } catch (IOException e) {
      Log.error("Failed to duplicate particle system");
      throw new SlickException("Unable to duplicated particle system", e);
    }

    return theCopy;
  }
}
示例#5
0
/**
 * A font implementation that will parse BMFont format font files. The font files can be output by
 * Hiero, which is included with Slick, and also the AngelCode font tool available at:
 *
 * <p><a
 * href="http://www.angelcode.com/products/bmfont/">http://www.angelcode.com/products/bmfont/</a>
 *
 * <p>This implementation copes with both the font display and kerning information allowing nicer
 * looking paragraphs of text. Note that this utility only supports the text BMFont format
 * definition file.
 *
 * @author kevin
 * @author Nathan Sweet <*****@*****.**>
 */
public class AngelCodeFont implements Font, java.io.Serializable {
  /** The renderer to use for all GL operations */
  private static SGL GL = Renderer.get();

  /**
   * The line cache size, this is how many lines we can render before starting to regenerate lists
   */
  private static final int DISPLAY_LIST_CACHE_SIZE = 200;

  /** The highest character that AngelCodeFont will support. */
  private static final int MAX_CHAR = 255;

  /** True if this font should use display list caching */
  private boolean displayListCaching = true;

  /** The image containing the bitmap font */
  private Image fontImage;
  /** The characters building up the font */
  private CharDef[] chars;
  /** The height of a line */
  private int lineHeight;
  /** The first display list ID */
  private int baseDisplayListID = -1;
  /** The eldest display list ID */
  private int eldestDisplayListID;
  /** The eldest display list */
  private DisplayList eldestDisplayList;

  /** The display list cache for rendered lines */
  private final LinkedHashMap displayLists =
      new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {
        protected boolean removeEldestEntry(Entry eldest) {
          eldestDisplayList = (DisplayList) eldest.getValue();
          eldestDisplayListID = eldestDisplayList.id;

          return false;
        }
      };

  /**
   * Create a new font based on a font definition from AngelCode's tool and the font image generated
   * from the tool.
   *
   * @param fntFile The location of the font defnition file
   * @param image The image to use for the font
   * @throws SlickException Indicates a failure to load either file
   */
  public AngelCodeFont(String fntFile, Image image) throws SlickException {
    fontImage = image;

    parseFnt(ResourceLoader.getResourceAsStream(fntFile));
  }

  /**
   * Create a new font based on a font definition from AngelCode's tool and the font image generated
   * from the tool.
   *
   * @param fntFile The location of the font defnition file
   * @param imgFile The location of the font image
   * @throws SlickException Indicates a failure to load either file
   */
  public AngelCodeFont(String fntFile, String imgFile) throws SlickException {
    fontImage = new Image(imgFile);

    parseFnt(ResourceLoader.getResourceAsStream(fntFile));
  }

  /**
   * Create a new font based on a font definition from AngelCode's tool and the font image generated
   * from the tool.
   *
   * @param fntFile The location of the font defnition file
   * @param image The image to use for the font
   * @param caching True if this font should use display list caching
   * @throws SlickException Indicates a failure to load either file
   */
  public AngelCodeFont(String fntFile, Image image, boolean caching) throws SlickException {
    fontImage = image;
    displayListCaching = caching;
    parseFnt(ResourceLoader.getResourceAsStream(fntFile));
  }

  /**
   * Create a new font based on a font definition from AngelCode's tool and the font image generated
   * from the tool.
   *
   * @param fntFile The location of the font defnition file
   * @param imgFile The location of the font image
   * @param caching True if this font should use display list caching
   * @throws SlickException Indicates a failure to load either file
   */
  public AngelCodeFont(String fntFile, String imgFile, boolean caching) throws SlickException {
    fontImage = new Image(imgFile);
    displayListCaching = caching;
    parseFnt(ResourceLoader.getResourceAsStream(fntFile));
  }

  /**
   * Create a new font based on a font definition from AngelCode's tool and the font image generated
   * from the tool.
   *
   * @param name The name to assign to the font image in the image store
   * @param fntFile The stream of the font defnition file
   * @param imgFile The stream of the font image
   * @throws SlickException Indicates a failure to load either file
   */
  public AngelCodeFont(String name, InputStream fntFile, InputStream imgFile)
      throws SlickException {
    fontImage = new Image(imgFile, name, false);

    parseFnt(fntFile);
  }

  /**
   * Create a new font based on a font definition from AngelCode's tool and the font image generated
   * from the tool.
   *
   * @param name The name to assign to the font image in the image store
   * @param fntFile The stream of the font defnition file
   * @param imgFile The stream of the font image
   * @param caching True if this font should use display list caching
   * @throws SlickException Indicates a failure to load either file
   */
  public AngelCodeFont(String name, InputStream fntFile, InputStream imgFile, boolean caching)
      throws SlickException {
    fontImage = new Image(imgFile, name, false);

    displayListCaching = caching;
    parseFnt(fntFile);
  }

  /**
   * Parse the font definition file
   *
   * @param fntFile The stream from which the font file can be read
   * @throws SlickException
   */
  private void parseFnt(InputStream fntFile) throws SlickException {
    if (displayListCaching) {
      baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE);
      if (baseDisplayListID == 0) displayListCaching = false;
    }

    try {
      // now parse the font file
      BufferedReader in = new BufferedReader(new InputStreamReader(fntFile));
      String info = in.readLine();
      String common = in.readLine();
      String page = in.readLine();

      Map kerning = new HashMap(64);
      List charDefs = new ArrayList(MAX_CHAR);
      int maxChar = 0;
      boolean done = false;
      while (!done) {
        String line = in.readLine();
        if (line == null) {
          done = true;
        } else {
          if (line.startsWith("chars c")) {
            // ignore
          } else if (line.startsWith("char")) {
            CharDef def = parseChar(line);
            if (def != null) {
              maxChar = Math.max(maxChar, def.id);
              charDefs.add(def);
            }
          }
          if (line.startsWith("kernings c")) {
            // ignore
          } else if (line.startsWith("kerning")) {
            StringTokenizer tokens = new StringTokenizer(line, " =");
            tokens.nextToken(); // kerning
            tokens.nextToken(); // first
            short first = Short.parseShort(tokens.nextToken()); // first value
            tokens.nextToken(); // second
            int second = Integer.parseInt(tokens.nextToken()); // second value
            tokens.nextToken(); // offset
            int offset = Integer.parseInt(tokens.nextToken()); // offset value
            List values = (List) kerning.get(new Short(first));
            if (values == null) {
              values = new ArrayList();
              kerning.put(new Short(first), values);
            }
            // Pack the character and kerning offset into a short.
            values.add(new Short((short) ((offset << 8) | second)));
          }
        }
      }

      chars = new CharDef[maxChar + 1];
      for (Iterator iter = charDefs.iterator(); iter.hasNext(); ) {
        CharDef def = (CharDef) iter.next();
        chars[def.id] = def;
      }

      // Turn each list of kerning values into a short[] and set on the chardef.
      for (Iterator iter = kerning.entrySet().iterator(); iter.hasNext(); ) {
        Entry entry = (Entry) iter.next();
        short first = ((Short) entry.getKey()).shortValue();
        List valueList = (List) entry.getValue();
        short[] valueArray = new short[valueList.size()];
        int i = 0;
        for (Iterator valueIter = valueList.iterator(); valueIter.hasNext(); i++)
          valueArray[i] = ((Short) valueIter.next()).shortValue();
        chars[first].kerning = valueArray;
      }
    } catch (IOException e) {
      Log.error(e);
      throw new SlickException("Failed to parse font file: " + fntFile);
    }
  }

  /**
   * Parse a single character line from the definition
   *
   * @param line The line to be parsed
   * @return The character definition from the line
   * @throws SlickException Indicates a given character is not valid in an angel code font
   */
  private CharDef parseChar(String line) throws SlickException {
    CharDef def = new CharDef();
    StringTokenizer tokens = new StringTokenizer(line, " =");

    tokens.nextToken(); // char
    tokens.nextToken(); // id
    def.id = Short.parseShort(tokens.nextToken()); // id value
    if (def.id < 0) {
      return null;
    }
    if (def.id > MAX_CHAR) {
      throw new SlickException(
          "Invalid character '"
              + def.id
              + "': AngelCodeFont does not support characters above "
              + MAX_CHAR);
    }

    tokens.nextToken(); // x
    def.x = Short.parseShort(tokens.nextToken()); // x value
    tokens.nextToken(); // y
    def.y = Short.parseShort(tokens.nextToken()); // y value
    tokens.nextToken(); // width
    def.width = Short.parseShort(tokens.nextToken()); // width value
    tokens.nextToken(); // height
    def.height = Short.parseShort(tokens.nextToken()); // height value
    tokens.nextToken(); // x offset
    def.xoffset = Short.parseShort(tokens.nextToken()); // xoffset value
    tokens.nextToken(); // y offset
    def.yoffset = Short.parseShort(tokens.nextToken()); // yoffset value
    tokens.nextToken(); // xadvance
    def.xadvance = Short.parseShort(tokens.nextToken()); // xadvance

    def.init();

    if (def.id != ' ') {
      lineHeight = Math.max(def.height + def.yoffset, lineHeight);
    }

    return def;
  }

  /** @see org.newdawn.slick.Font#drawString(float, float, java.lang.String) */
  public void drawString(float x, float y, String text) {
    drawString(x, y, text, Color.white);
  }

  /**
   * @see org.newdawn.slick.Font#drawString(float, float, java.lang.String, org.newdawn.slick.Color)
   */
  public void drawString(float x, float y, String text, Color col) {
    drawString(x, y, text, col, 0, text.length() - 1);
  }

  /** @see Font#drawString(float, float, String, Color, int, int) */
  public void drawString(float x, float y, String text, Color col, int startIndex, int endIndex) {
    fontImage.bind();
    col.bind();

    GL.glTranslatef(x, y, 0);
    if (displayListCaching && startIndex == 0 && endIndex == text.length() - 1) {
      DisplayList displayList =
          (DisplayList) displayLists.get(text + "" + startIndex + "" + endIndex);
      if (displayList != null) {
        GL.glCallList(displayList.id);
      } else {
        // Compile a new display list.
        displayList = new DisplayList();
        displayList.text = text;
        int displayListCount = displayLists.size();
        if (displayListCount < DISPLAY_LIST_CACHE_SIZE) {
          displayList.id = baseDisplayListID + displayListCount;
        } else {
          displayList.id = eldestDisplayListID;
          displayLists.remove(eldestDisplayList.text);
        }

        displayLists.put(text + "" + startIndex + "" + endIndex, displayList);

        GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE);
        render(text, startIndex, endIndex);
        GL.glEndList();
      }
    } else {
      render(text, startIndex, endIndex);
    }
    GL.glTranslatef(-x, -y, 0);
  }

  /**
   * Render based on immediate rendering
   *
   * @param text The text to be rendered
   * @param start The index of the first character in the string to render
   * @param end The index of the last character in the string to render
   */
  private void render(String text, int start, int end) {
    GL.glBegin(SGL.GL_QUADS);

    int x = 0, y = 0;
    CharDef lastCharDef = null;
    char[] data = text.toCharArray();
    for (int i = 0; i < data.length; i++) {
      int id = data[i];
      if (id == '\n') {
        x = 0;
        y += getLineHeight();
        continue;
      }
      if (id >= chars.length) {
        continue;
      }
      CharDef charDef = chars[id];
      if (charDef == null) {
        continue;
      }

      if (lastCharDef != null) x += lastCharDef.getKerning(id);
      lastCharDef = charDef;

      if ((i >= start) && (i <= end)) {
        charDef.draw(x, y);
      }

      x += charDef.xadvance;
    }
    GL.glEnd();
  }

  /**
   * Returns the distance from the y drawing location to the top most pixel of the specified text.
   *
   * @param text The text that is to be tested
   * @return The yoffset from the y draw location at which text will start
   */
  public int getYOffset(String text) {
    DisplayList displayList = null;
    if (displayListCaching) {
      displayList = (DisplayList) displayLists.get(text);
      if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();
    }

    int stopIndex = text.indexOf('\n');
    if (stopIndex == -1) stopIndex = text.length();

    int minYOffset = 10000;
    for (int i = 0; i < stopIndex; i++) {
      int id = text.charAt(i);
      CharDef charDef = chars[id];
      if (charDef == null) {
        continue;
      }
      minYOffset = Math.min(charDef.yoffset, minYOffset);
    }

    if (displayList != null) displayList.yOffset = new Short((short) minYOffset);

    return minYOffset;
  }

  /** @see org.newdawn.slick.Font#getHeight(java.lang.String) */
  public int getHeight(String text) {
    DisplayList displayList = null;
    if (displayListCaching) {
      displayList = (DisplayList) displayLists.get(text);
      if (displayList != null && displayList.height != null) return displayList.height.intValue();
    }

    int lines = 0;
    int maxHeight = 0;
    for (int i = 0; i < text.length(); i++) {
      int id = text.charAt(i);
      if (id == '\n') {
        lines++;
        maxHeight = 0;
        continue;
      }
      // ignore space, it doesn't contribute to height
      if (id == ' ') {
        continue;
      }
      CharDef charDef = chars[id];
      if (charDef == null) {
        continue;
      }

      maxHeight = Math.max(charDef.height + charDef.yoffset, maxHeight);
    }

    maxHeight += lines * getLineHeight();

    if (displayList != null) displayList.height = new Short((short) maxHeight);

    return maxHeight;
  }

  /** @see org.newdawn.slick.Font#getWidth(java.lang.String) */
  public int getWidth(String text) {
    DisplayList displayList = null;
    if (displayListCaching) {
      displayList = (DisplayList) displayLists.get(text);
      if (displayList != null && displayList.width != null) return displayList.width.intValue();
    }

    int maxWidth = 0;
    int width = 0;
    CharDef lastCharDef = null;
    for (int i = 0, n = text.length(); i < n; i++) {
      int id = text.charAt(i);
      if (id == '\n') {
        width = 0;
        continue;
      }
      if (id >= chars.length) {
        continue;
      }
      CharDef charDef = chars[id];
      if (charDef == null) {
        continue;
      }

      if (lastCharDef != null) width += lastCharDef.getKerning(id);
      lastCharDef = charDef;

      if (i < n - 1) {
        width += charDef.xadvance;
      } else {
        width += charDef.width;
      }
      maxWidth = Math.max(maxWidth, width);
    }

    if (displayList != null) displayList.width = new Short((short) maxWidth);

    return maxWidth;
  }

  /**
   * The definition of a single character as defined in the AngelCode file format
   *
   * @author kevin
   */
  private class CharDef implements java.io.Serializable {
    /** The id of the character */
    public short id;
    /** The x location on the sprite sheet */
    public short x;
    /** The y location on the sprite sheet */
    public short y;
    /** The width of the character image */
    public short width;
    /** The height of the character image */
    public short height;
    /** The amount the x position should be offset when drawing the image */
    public short xoffset;
    /** The amount the y position should be offset when drawing the image */
    public short yoffset;

    /** The amount to move the current position after drawing the character */
    public short xadvance;
    /** The image containing the character */
    public Image image;
    /** The display list index for this character */
    public short dlIndex;
    /** The kerning info for this character */
    public short[] kerning;

    /**
     * Initialise the image by cutting the right section from the map produced by the AngelCode
     * tool.
     */
    public void init() {
      image = fontImage.getSubImage(x, y, width, height);
    }

    /** @see java.lang.Object#toString() */
    public String toString() {
      return "[CharDef id=" + id + " x=" + x + " y=" + y + "]";
    }

    /**
     * Draw this character embedded in a image draw
     *
     * @param x The x position at which to draw the text
     * @param y The y position at which to draw the text
     */
    public void draw(float x, float y) {
      image.drawEmbedded(x + xoffset, y + yoffset, width, height);
    }

    /**
     * Get the kerning offset between this character and the specified character.
     *
     * @param otherCodePoint The other code point
     * @return the kerning offset
     */
    public int getKerning(int otherCodePoint) {
      if (kerning == null) return 0;
      int low = 0;
      int high = kerning.length - 1;
      while (low <= high) {
        int midIndex = (low + high) >>> 1;
        int value = kerning[midIndex];
        int foundCodePoint = value & 0xff;
        if (foundCodePoint < otherCodePoint) low = midIndex + 1;
        else if (foundCodePoint > otherCodePoint) high = midIndex - 1;
        else return value >> 8;
      }
      return 0;
    }
  }

  /** @see org.newdawn.slick.Font#getLineHeight() */
  public int getLineHeight() {
    return lineHeight;
  }

  /**
   * A descriptor for a single display list
   *
   * @author Nathan Sweet <*****@*****.**>
   */
  private static class DisplayList {
    /** The if of the distance list */
    int id;
    /** The offset of the line rendered */
    Short yOffset;
    /** The width of the line rendered */
    Short width;
    /** The height of the line rendered */
    Short height;
    /** The text that the display list holds */
    String text;
  }
}
/**
 * A generic game container that handles the game loop, fps recording and managing the input system
 *
 * @author kevin
 */
public abstract class GameContainer implements GUIContext {
  /** The renderer to use for all GL operations */
  protected static SGL GL = Renderer.get();
  /** The shared drawable if any */
  protected static Drawable SHARED_DRAWABLE;

  /** The time the last frame was rendered */
  protected long lastFrame;
  /** The last time the FPS recorded */
  protected long lastFPS;
  /** The last recorded FPS */
  protected int recordedFPS;
  /** The current count of FPS */
  protected int fps;
  /** True if we're currently running the game loop */
  protected boolean running = true;

  /** The width of the display */
  protected int width;
  /** The height of the display */
  protected int height;
  /** The game being managed */
  protected Game game;

  /** The default font to use in the graphics context */
  private Font defaultFont;
  /** The graphics context to be passed to the game */
  private Graphics graphics;

  /** The input system to pass to the game */
  protected Input input;
  /** The FPS we want to lock to */
  protected int targetFPS = -1;
  /** True if we should show the fps */
  private boolean showFPS = true;
  /** The minimum logic update interval */
  protected long minimumLogicInterval = 1;
  /** The stored delta */
  protected long storedDelta;
  /** The maximum logic update interval */
  protected long maximumLogicInterval = 0;
  /** The last game started */
  protected Game lastGame;
  /** True if we should clear the screen each frame */
  protected boolean clearEachFrame = true;

  /** True if the game is paused */
  protected boolean paused;
  /** True if we should force exit */
  protected boolean forceExit = true;
  /** True if vsync has been requested */
  protected boolean vsync;
  /** Smoothed deltas requested */
  protected boolean smoothDeltas;
  /** The number of samples we'll attempt through hardware */
  protected int samples;

  /** True if this context supports multisample */
  protected boolean supportsMultiSample;

  /** True if we should render when not focused */
  protected boolean alwaysRender;
  /** True if we require stencil bits */
  protected static boolean stencil;

  /**
   * Create a new game container wrapping a given game
   *
   * @param game The game to be wrapped
   */
  protected GameContainer(Game game) {
    this.game = game;
    lastFrame = getTime();

    getBuildVersion();
    Log.checkVerboseLogSetting();
  }

  public static void enableStencil() {
    stencil = true;
  }

  /**
   * Set the default font that will be intialised in the graphics held in this container
   *
   * @param font The font to use as default
   */
  public void setDefaultFont(Font font) {
    if (font != null) {
      this.defaultFont = font;
    } else {
      Log.warn("Please provide a non null font");
    }
  }

  /**
   * Indicate whether we want to try to use fullscreen multisampling. This will give antialiasing
   * across the whole scene using a hardware feature.
   *
   * @param samples The number of samples to attempt (2 is safe)
   */
  public void setMultiSample(int samples) {
    this.samples = samples;
  }

  /**
   * Check if this hardware can support multi-sampling
   *
   * @return True if the hardware supports multi-sampling
   */
  public boolean supportsMultiSample() {
    return supportsMultiSample;
  }

  /**
   * The number of samples we're attempting to performing using hardware multisampling
   *
   * @return The number of samples requested
   */
  public int getSamples() {
    return samples;
  }

  /**
   * Indicate if we should force exitting the VM at the end of the game (default = true)
   *
   * @param forceExit True if we should force the VM exit
   */
  public void setForceExit(boolean forceExit) {
    this.forceExit = forceExit;
  }

  /**
   * Indicate if we want to smooth deltas. This feature will report a delta based on the FPS not the
   * time passed. This works well with vsync.
   *
   * @param smoothDeltas True if we should report smooth deltas
   */
  public void setSmoothDeltas(boolean smoothDeltas) {
    this.smoothDeltas = smoothDeltas;
  }

  /**
   * Check if the display is in fullscreen mode
   *
   * @return True if the display is in fullscreen mode
   */
  public boolean isFullscreen() {
    return false;
  }

  /**
   * Get the aspect ratio of the screen
   *
   * @return The aspect ratio of the display
   */
  public float getAspectRatio() {
    return getWidth() / getHeight();
  }

  /**
   * Indicate whether we want to be in fullscreen mode. Note that the current display mode must be
   * valid as a fullscreen mode for this to work
   *
   * @param fullscreen True if we want to be in fullscreen mode
   * @throws SlickException Indicates we failed to change the display mode
   */
  public void setFullscreen(boolean fullscreen) throws SlickException {}

  /**
   * Enable shared OpenGL context. After calling this all containers created will shared a single
   * parent context
   *
   * @throws SlickException Indicates a failure to create the shared drawable
   */
  public static void enableSharedContext() throws SlickException {
    try {
      SHARED_DRAWABLE = new Pbuffer(64, 64, new PixelFormat(8, 0, 0), null);
    } catch (LWJGLException e) {
      throw new SlickException(
          "Unable to create the pbuffer used for shard context, buffers not supported", e);
    }
  }

  /**
   * Get the context shared by all containers
   *
   * @return The context shared by all the containers or null if shared context isn't enabled
   */
  public static Drawable getSharedContext() {
    return SHARED_DRAWABLE;
  }

  /**
   * Indicate if we should clear the screen at the beginning of each frame. If you're rendering to
   * the whole screen each frame then setting this to false can give some performance improvements
   *
   * @param clear True if the the screen should be cleared each frame
   */
  public void setClearEachFrame(boolean clear) {
    this.clearEachFrame = clear;
  }

  /**
   * Renitialise the game and the context in which it's being rendered
   *
   * @throws SlickException Indicates a failure rerun initialisation routines
   */
  public void reinit() throws SlickException {}

  /** Pause the game - i.e. suspend updates */
  public void pause() {
    setPaused(true);
  }

  /** Resumt the game - i.e. continue updates */
  public void resume() {
    setPaused(false);
  }

  /**
   * Check if the container is currently paused.
   *
   * @return True if the container is paused
   */
  public boolean isPaused() {
    return paused;
  }

  /**
   * Indicates if the game should be paused, i.e. if updates should be propogated through to the
   * game.
   *
   * @param paused True if the game should be paused
   */
  public void setPaused(boolean paused) {
    this.paused = paused;
  }

  /**
   * True if this container should render when it has focus
   *
   * @return True if this container should render when it has focus
   */
  public boolean getAlwaysRender() {
    return alwaysRender;
  }

  /**
   * Indicate whether we want this container to render when it has focus
   *
   * @param alwaysRender True if this container should render when it has focus
   */
  public void setAlwaysRender(boolean alwaysRender) {
    this.alwaysRender = alwaysRender;
  }

  /**
   * Get the build number of slick
   *
   * @return The build number of slick
   */
  public static int getBuildVersion() {
    try {
      Properties props = new Properties();
      props.load(ResourceLoader.getResourceAsStream("version"));

      int build = Integer.parseInt(props.getProperty("build"));
      Log.info("Slick Build #" + build);

      return build;
    } catch (Exception e) {
      Log.info("Unable to determine Slick build number");
      return -1;
    }
  }

  /**
   * Get the default system font
   *
   * @return The default system font
   */
  @Override
  public Font getDefaultFont() {
    return defaultFont;
  }

  /**
   * Check if sound effects are enabled
   *
   * @return True if sound effects are enabled
   */
  public boolean isSoundOn() {
    return SoundStore.get().soundsOn();
  }

  /**
   * Check if music is enabled
   *
   * @return True if music is enabled
   */
  public boolean isMusicOn() {
    return SoundStore.get().musicOn();
  }

  /**
   * Indicate whether music should be enabled
   *
   * @param on True if music should be enabled
   */
  public void setMusicOn(boolean on) {
    SoundStore.get().setMusicOn(on);
  }

  /**
   * Indicate whether sound effects should be enabled
   *
   * @param on True if sound effects should be enabled
   */
  public void setSoundOn(boolean on) {
    SoundStore.get().setSoundsOn(on);
  }

  /**
   * Retrieve the current default volume for music
   *
   * @return the current default volume for music
   */
  public float getMusicVolume() {
    return SoundStore.get().getMusicVolume();
  }

  /**
   * Retrieve the current default volume for sound fx
   *
   * @return the current default volume for sound fx
   */
  public float getSoundVolume() {
    return SoundStore.get().getSoundVolume();
  }

  /**
   * Set the default volume for sound fx
   *
   * @param volume the new default value for sound fx volume
   */
  public void setSoundVolume(float volume) {
    SoundStore.get().setSoundVolume(volume);
  }

  /**
   * Set the default volume for music
   *
   * @param volume the new default value for music volume
   */
  public void setMusicVolume(float volume) {
    SoundStore.get().setMusicVolume(volume);
  }

  /**
   * Get the width of the standard screen resolution
   *
   * @return The screen width
   */
  @Override
  public abstract int getScreenWidth();

  /**
   * Get the height of the standard screen resolution
   *
   * @return The screen height
   */
  @Override
  public abstract int getScreenHeight();

  /**
   * Get the width of the game canvas
   *
   * @return The width of the game canvas
   */
  @Override
  public int getWidth() {
    return width;
  }

  /**
   * Get the height of the game canvas
   *
   * @return The height of the game canvas
   */
  @Override
  public int getHeight() {
    return height;
  }

  /**
   * Set the icon to be displayed if possible in this type of container
   *
   * @param ref The reference to the icon to be displayed
   * @throws SlickException Indicates a failure to load the icon
   */
  public abstract void setIcon(String ref) throws SlickException;

  /**
   * Set the icons to be used for this application. Note that the size of the icon defines how it
   * will be used. Important ones to note
   *
   * <p>Windows window icon must be 16x16 Windows alt-tab icon must be 24x24 or 32x32 depending on
   * Windows version (XP=32)
   *
   * @param refs The reference to the icon to be displayed
   * @throws SlickException Indicates a failure to load the icon
   */
  public abstract void setIcons(String[] refs) throws SlickException;

  /**
   * Get the accurate system time
   *
   * @return The system time in milliseconds
   */
  @Override
  public long getTime() {
    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
  }

  /**
   * Sleep for a given period
   *
   * @param milliseconds The period to sleep for in milliseconds
   */
  public void sleep(int milliseconds) {
    long target = getTime() + milliseconds;
    while (getTime() < target) {
      try {
        Thread.sleep(1);
      } catch (Exception e) {
      }
    }
  }

  /**
   * Set the mouse cursor to be displayed - this is a hardware cursor and hence shouldn't have any
   * impact on FPS.
   *
   * @param ref The location of the image to be loaded for the cursor
   * @param hotSpotX The x coordinate of the hotspot within the cursor image
   * @param hotSpotY The y coordinate of the hotspot within the cursor image
   * @throws SlickException Indicates a failure to load the cursor image or create the hardware
   *     cursor
   */
  @Override
  public abstract void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException;

  /**
   * Set the mouse cursor to be displayed - this is a hardware cursor and hence shouldn't have any
   * impact on FPS.
   *
   * @param data The image data from which the cursor can be construted
   * @param hotSpotX The x coordinate of the hotspot within the cursor image
   * @param hotSpotY The y coordinate of the hotspot within the cursor image
   * @throws SlickException Indicates a failure to load the cursor image or create the hardware
   *     cursor
   */
  @Override
  public abstract void setMouseCursor(ImageData data, int hotSpotX, int hotSpotY)
      throws SlickException;

  /**
   * Set the mouse cursor based on the contents of the image. Note that this will not take account
   * of render state type changes to images (rotation and such). If these effects are required it is
   * recommended that an offscreen buffer be used to produce an appropriate image. An offscreen
   * buffer will always be used to produce the new cursor and as such this operation an be very
   * expensive
   *
   * @param image The image to use as the cursor
   * @param hotSpotX The x coordinate of the hotspot within the cursor image
   * @param hotSpotY The y coordinate of the hotspot within the cursor image
   * @throws SlickException Indicates a failure to load the cursor image or create the hardware
   *     cursor
   */
  public abstract void setMouseCursor(Image image, int hotSpotX, int hotSpotY)
      throws SlickException;

  /**
   * Set the mouse cursor to be displayed - this is a hardware cursor and hence shouldn't have any
   * impact on FPS.
   *
   * @param cursor The cursor to use
   * @param hotSpotX The x coordinate of the hotspot within the cursor image
   * @param hotSpotY The y coordinate of the hotspot within the cursor image
   * @throws SlickException Indicates a failure to load the cursor image or create the hardware
   *     cursor
   */
  @Override
  public abstract void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY)
      throws SlickException;

  /**
   * Get a cursor based on a image reference on the classpath. The image is assumed to be a
   * set/strip of cursor animation frames running from top to bottom.
   *
   * @param ref The reference to the image to be loaded
   * @param x The x-coordinate of the cursor hotspot (left {@literal ->} right)
   * @param y The y-coordinate of the cursor hotspot (bottom {@literal ->} top)
   * @param width The x width of the cursor
   * @param height The y height of the cursor
   * @param cursorDelays image delays between changing frames in animation
   * @throws SlickException Indicates a failure to load the image or a failure to create the
   *     hardware cursor
   */
  public void setAnimatedMouseCursor(
      String ref, int x, int y, int width, int height, int[] cursorDelays) throws SlickException {
    try {
      Cursor cursor;
      cursor = CursorLoader.get().getAnimatedCursor(ref, x, y, width, height, cursorDelays);
      setMouseCursor(cursor, x, y);
    } catch (IOException e) {
      throw new SlickException("Failed to set mouse cursor", e);
    } catch (LWJGLException e) {
      throw new SlickException("Failed to set mouse cursor", e);
    }
  }

  /** Set the default mouse cursor - i.e. the original cursor before any native cursor was set */
  @Override
  public abstract void setDefaultMouseCursor();

  /**
   * Get the input system
   *
   * @return The input system available to this game container
   */
  @Override
  public Input getInput() {
    return input;
  }

  /**
   * Get the current recorded FPS (frames per second)
   *
   * @return The current FPS
   */
  public int getFPS() {
    return recordedFPS;
  }

  /**
   * Indicate whether mouse cursor should be grabbed or not
   *
   * @param grabbed True if mouse cursor should be grabbed
   */
  public abstract void setMouseGrabbed(boolean grabbed);

  /**
   * Check if the mouse cursor is current grabbed. This will cause it not to be seen.
   *
   * @return True if the mouse is currently grabbed
   */
  public abstract boolean isMouseGrabbed();

  /**
   * Retrieve the time taken to render the last frame, i.e. the change in time - delta.
   *
   * @return The time taken to render the last frame
   */
  protected int getDelta() {
    long time = getTime();
    int delta = (int) (time - lastFrame);
    lastFrame = time;

    return delta;
  }

  /** Updated the FPS counter */
  protected void updateFPS() {
    if (getTime() - lastFPS > 1000) {
      lastFPS = getTime();
      recordedFPS = fps;
      fps = 0;
    }
    fps++;
  }

  /**
   * Set the minimum amount of time in milliseonds that has to pass before update() is called on the
   * container game. This gives a way to limit logic updates compared to renders.
   *
   * @param interval The minimum interval between logic updates
   */
  public void setMinimumLogicUpdateInterval(int interval) {
    minimumLogicInterval = interval;
  }

  /**
   * Set the maximum amount of time in milliseconds that can passed into the update method. Useful
   * for collision detection without sweeping.
   *
   * @param interval The maximum interval between logic updates
   */
  public void setMaximumLogicUpdateInterval(int interval) {
    maximumLogicInterval = interval;
  }

  /**
   * Update and render the game
   *
   * @param delta The change in time since last update and render
   * @throws SlickException Indicates an internal fault to the game.
   */
  protected void updateAndRender(int delta) throws SlickException {
    if (smoothDeltas) {
      if (getFPS() != 0) {
        delta = 1000 / getFPS();
      }
    }

    input.poll(width, height);

    Music.poll(delta);
    if (!paused) {
      storedDelta += delta;

      if (storedDelta >= minimumLogicInterval) {
        try {
          if (maximumLogicInterval != 0) {
            long cycles = storedDelta / maximumLogicInterval;
            for (int i = 0; i < cycles; i++) {
              game.update(this, (int) maximumLogicInterval);
            }

            int remainder = (int) (storedDelta % maximumLogicInterval);
            if (remainder > minimumLogicInterval) {
              game.update(this, (int) (remainder % maximumLogicInterval));
              storedDelta = 0;
            } else {
              storedDelta = remainder;
            }
          } else {
            game.update(this, (int) storedDelta);
            storedDelta = 0;
          }

        } catch (Throwable e) {
          //					Log.error(e);
          throw new SlickException("Game.update() failure.", e);
        }
      }
    } else {
      game.update(this, 0);
    }

    if (hasFocus() || getAlwaysRender()) {
      if (clearEachFrame) {
        GL.glClear(SGL.GL_COLOR_BUFFER_BIT | SGL.GL_DEPTH_BUFFER_BIT);
      }

      GL.glLoadIdentity();

      graphics.resetTransform();
      graphics.resetFont();
      graphics.resetLineWidth();
      graphics.setAntiAlias(false);
      try {
        game.render(this, graphics);
      } catch (Throwable e) {
        //				Log.error(e);
        throw new SlickException("Game.render() failure.", e);
      }
      graphics.resetTransform();

      if (showFPS) {
        defaultFont.drawString(10, 10, "FPS: " + recordedFPS);
      }

      GL.flush();
    }

    if (targetFPS != -1) {
      Display.sync(targetFPS);
    }
  }

  /**
   * Indicate if the display should update only when the game is visible (the default is true)
   *
   * @param updateOnlyWhenVisible True if we should updated only when the display is visible
   */
  public void setUpdateOnlyWhenVisible(boolean updateOnlyWhenVisible) {}

  /**
   * Check if this game is only updating when visible to the user (default = true)
   *
   * @return True if the game is only updated when the display is visible
   */
  public boolean isUpdatingOnlyWhenVisible() {
    return true;
  }

  /** Initialise the GL context */
  protected void initGL() {
    Log.info("Starting display " + width + "x" + height);
    GL.initDisplay(width, height);

    if (input == null) {
      input = new Input(height);
    }
    input.init(height);
    // no need to remove listeners?
    // input.removeAllListeners();
    if (game instanceof InputListener) {
      input.removeListener((InputListener) game);
      input.addListener((InputListener) game);
    }

    if (graphics != null) {
      graphics.setDimensions(getWidth(), getHeight());
    }
    lastGame = game;
  }

  /**
   * Initialise the system components, OpenGL and OpenAL.
   *
   * @throws SlickException Indicates a failure to create a native handler
   */
  protected void initSystem() throws SlickException {
    initGL();
    setMusicVolume(1.0f);
    setSoundVolume(1.0f);

    graphics = new Graphics(width, height);
    defaultFont = graphics.getFont();
  }

  /** Enter the orthographic mode */
  protected void enterOrtho() {
    enterOrtho(width, height);
  }

  /**
   * Indicate whether the container should show the FPS
   *
   * @param show True if the container should show the FPS
   */
  public void setShowFPS(boolean show) {
    showFPS = show;
  }

  /**
   * Check if the FPS is currently showing
   *
   * @return True if the FPS is showing
   */
  public boolean isShowingFPS() {
    return showFPS;
  }

  /**
   * Set the target fps we're hoping to get
   *
   * @param fps The target fps we're hoping to get
   */
  public void setTargetFrameRate(int fps) {
    targetFPS = fps;
  }

  /**
   * Indicate whether the display should be synced to the vertical refresh (stops tearing)
   *
   * @param vsync True if we want to sync to vertical refresh
   */
  public void setVSync(boolean vsync) {
    this.vsync = vsync;
    Display.setVSyncEnabled(vsync);
  }

  /**
   * True if vsync is requested
   *
   * @return True if vsync is requested
   */
  public boolean isVSyncRequested() {
    return vsync;
  }

  /**
   * True if the game is running
   *
   * @return True if the game is running
   */
  protected boolean running() {
    return running;
  }

  /**
   * Inidcate we want verbose logging
   *
   * @param verbose True if we want verbose logging (INFO and DEBUG)
   */
  public void setVerbose(boolean verbose) {
    Log.setVerbose(verbose);
  }

  /** Cause the game to exit and shutdown cleanly */
  public void exit() {
    running = false;
  }

  /**
   * Check if the game currently has focus
   *
   * @return True if the game currently has focus
   */
  public abstract boolean hasFocus();

  /**
   * Get the graphics context used by this container. Note that this value may vary over the life
   * time of the game.
   *
   * @return The graphics context used by this container
   */
  public Graphics getGraphics() {
    return graphics;
  }

  /**
   * Enter the orthographic mode
   *
   * @param xsize The size of the panel being used
   * @param ysize The size of the panel being used
   */
  protected void enterOrtho(int xsize, int ysize) {
    GL.enterOrtho(xsize, ysize);
  }
}
/**
 * @author Mark Bernard
 *     <p>Use this class to render shpaes directly to OpenGL. Allows you to bypass the Graphics
 *     class.
 */
public final class ShapeRenderer {
  /** The renderer to use for all GL operations */
  private static SGL GL = Renderer.get();
  /** The renderer to use line strips */
  private static LineStripRenderer LSR = Renderer.getLineStripRenderer();

  /**
   * Draw the outline of the given shape. Only the vertices are set. The colour has to be set
   * independently of this method.
   *
   * @param shape The shape to draw.
   */
  public static final void draw(Shape shape) {
    Texture t = TextureImpl.getLastBind();
    TextureImpl.bindNone();

    float points[] = shape.getPoints();

    LSR.start();
    for (int i = 0; i < points.length; i += 2) {
      LSR.vertex(points[i], points[i + 1]);
    }

    if (shape.closed()) {
      LSR.vertex(points[0], points[1]);
    }

    LSR.end();

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Draw the outline of the given shape. Only the vertices are set. The colour has to be set
   * independently of this method.
   *
   * @param shape The shape to draw.
   * @param fill The fill to apply
   */
  public static final void draw(Shape shape, ShapeFill fill) {
    float points[] = shape.getPoints();

    Texture t = TextureImpl.getLastBind();
    TextureImpl.bindNone();

    float center[] = shape.getCenter();
    GL.glBegin(SGL.GL_LINE_STRIP);
    for (int i = 0; i < points.length; i += 2) {
      fill.colorAt(shape, points[i], points[i + 1]).bind();
      Vector2f offset = fill.getOffsetAt(shape, points[i], points[i + 1]);
      GL.glVertex2f(points[i] + offset.x, points[i + 1] + offset.y);
    }

    if (shape.closed()) {
      fill.colorAt(shape, points[0], points[1]).bind();
      Vector2f offset = fill.getOffsetAt(shape, points[0], points[1]);
      GL.glVertex2f(points[0] + offset.x, points[1] + offset.y);
    }
    GL.glEnd();

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Check there are enough points to fill
   *
   * @param shape THe shape we're drawing
   * @return True if the fill is valid
   */
  public static boolean validFill(Shape shape) {
    if (shape.getTriangles() == null) {
      return false;
    }
    return shape.getTriangles().getTriangleCount() != 0;
  }

  /**
   * Draw the the given shape filled in. Only the vertices are set. The colour has to be set
   * independently of this method.
   *
   * @param shape The shape to fill.
   */
  public static final void fill(Shape shape) {
    if (!validFill(shape)) {
      return;
    }

    Texture t = TextureImpl.getLastBind();
    TextureImpl.bindNone();

    fill(
        shape,
        new PointCallback() {
          public float[] preRenderPoint(Shape shape, float x, float y) {
            // do nothing, we're just filling the shape this time
            return null;
          }
        });

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Draw the the given shape filled in. Only the vertices are set. The colour has to be set
   * independently of this method.
   *
   * @param shape The shape to fill.
   * @param callback The callback that will be invoked for each shape point
   */
  private static final void fill(Shape shape, PointCallback callback) {
    Triangulator tris = shape.getTriangles();

    GL.glBegin(SGL.GL_TRIANGLES);
    for (int i = 0; i < tris.getTriangleCount(); i++) {
      for (int p = 0; p < 3; p++) {
        float[] pt = tris.getTrianglePoint(i, p);
        float[] np = callback.preRenderPoint(shape, pt[0], pt[1]);

        if (np == null) {
          GL.glVertex2f(pt[0], pt[1]);
        } else {
          GL.glVertex2f(np[0], np[1]);
        }
      }
    }
    GL.glEnd();
  }

  /**
   * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to
   * be set independently of this method.
   *
   * @param shape The shape to texture.
   * @param image The image to tile across the shape
   */
  public static final void texture(Shape shape, Image image) {
    texture(shape, image, 0.01f, 0.01f);
  }

  /**
   * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to
   * be set independently of this method. This method is required to fit the texture once across the
   * shape.
   *
   * @param shape The shape to texture.
   * @param image The image to tile across the shape
   */
  public static final void textureFit(Shape shape, Image image) {
    textureFit(shape, image, 1f, 1f);
  }

  /**
   * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to
   * be set independently of this method.
   *
   * @param shape The shape to texture.
   * @param image The image to tile across the shape
   * @param scaleX The scale to apply on the x axis for texturing
   * @param scaleY The scale to apply on the y axis for texturing
   */
  public static final void texture(
      Shape shape, final Image image, final float scaleX, final float scaleY) {
    if (!validFill(shape)) {
      return;
    }

    final Texture t = TextureImpl.getLastBind();
    image.getTexture().bind();

    fill(
        shape,
        new PointCallback() {
          public float[] preRenderPoint(Shape shape, float x, float y) {
            float tx = x * scaleX;
            float ty = y * scaleY;

            tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx);
            ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty);

            GL.glTexCoord2f(tx, ty);
            return null;
          }
        });

    float points[] = shape.getPoints();

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to
   * be set independently of this method. This method is required to fit the texture scaleX times
   * across the shape and scaleY times down the shape.
   *
   * @param shape The shape to texture.
   * @param image The image to tile across the shape
   * @param scaleX The scale to apply on the x axis for texturing
   * @param scaleY The scale to apply on the y axis for texturing
   */
  public static final void textureFit(
      Shape shape, final Image image, final float scaleX, final float scaleY) {
    if (!validFill(shape)) {
      return;
    }

    float points[] = shape.getPoints();

    Texture t = TextureImpl.getLastBind();
    image.getTexture().bind();

    final float minX = shape.getX();
    final float minY = shape.getY();
    final float maxX = shape.getMaxX() - minX;
    final float maxY = shape.getMaxY() - minY;

    fill(
        shape,
        new PointCallback() {
          public float[] preRenderPoint(Shape shape, float x, float y) {
            x -= shape.getMinX();
            y -= shape.getMinY();

            x /= (shape.getMaxX() - shape.getMinX());
            y /= (shape.getMaxY() - shape.getMinY());

            float tx = x * scaleX;
            float ty = y * scaleY;

            tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx);
            ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty);

            GL.glTexCoord2f(tx, ty);
            return null;
          }
        });

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Draw the the given shape filled in. Only the vertices are set. The colour has to be set
   * independently of this method.
   *
   * @param shape The shape to fill.
   * @param fill The fill to apply
   */
  public static final void fill(final Shape shape, final ShapeFill fill) {
    if (!validFill(shape)) {
      return;
    }

    Texture t = TextureImpl.getLastBind();
    TextureImpl.bindNone();

    final float center[] = shape.getCenter();
    fill(
        shape,
        new PointCallback() {
          public float[] preRenderPoint(Shape shape, float x, float y) {
            fill.colorAt(shape, x, y).bind();
            Vector2f offset = fill.getOffsetAt(shape, x, y);

            return new float[] {offset.x + x, offset.y + y};
          }
        });

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to
   * be set independently of this method.
   *
   * @param shape The shape to texture.
   * @param image The image to tile across the shape
   * @param scaleX The scale to apply on the x axis for texturing
   * @param scaleY The scale to apply on the y axis for texturing
   * @param fill The fill to apply
   */
  public static final void texture(
      final Shape shape,
      final Image image,
      final float scaleX,
      final float scaleY,
      final ShapeFill fill) {
    if (!validFill(shape)) {
      return;
    }

    Texture t = TextureImpl.getLastBind();
    image.getTexture().bind();

    final float center[] = shape.getCenter();
    fill(
        shape,
        new PointCallback() {
          public float[] preRenderPoint(Shape shape, float x, float y) {
            fill.colorAt(shape, x - center[0], y - center[1]).bind();
            Vector2f offset = fill.getOffsetAt(shape, x, y);

            x += offset.x;
            y += offset.y;

            float tx = x * scaleX;
            float ty = y * scaleY;

            tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx);
            ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty);

            GL.glTexCoord2f(tx, ty);

            return new float[] {offset.x + x, offset.y + y};
          }
        });

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }
  /**
   * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to
   * be set independently of this method.
   *
   * @param shape The shape to texture.
   * @param image The image to tile across the shape
   * @param gen The texture coordinate generator to create coordiantes for the shape
   */
  public static final void texture(final Shape shape, Image image, final TexCoordGenerator gen) {
    Texture t = TextureImpl.getLastBind();

    image.getTexture().bind();

    final float center[] = shape.getCenter();
    fill(
        shape,
        new PointCallback() {
          public float[] preRenderPoint(Shape shape, float x, float y) {
            Vector2f tex = gen.getCoordFor(x, y);
            GL.glTexCoord2f(tex.x, tex.y);

            return new float[] {x, y};
          }
        });

    if (t == null) {
      TextureImpl.bindNone();
    } else {
      t.bind();
    }
  }

  /**
   * Description of some feature that will be applied to each point render
   *
   * @author kevin
   */
  private static interface PointCallback {
    /**
     * Apply feature before the call to glVertex
     *
     * @param shape The shape the point belongs to
     * @param x The x poisiton the vertex will be at
     * @param y The y position the vertex will be at
     * @return The new coordinates of null
     */
    float[] preRenderPoint(Shape shape, float x, float y);
  }
}
  /**
   * Load a texture into OpenGL from a BufferedImage
   *
   * @param resourceName The location of the resource to load
   * @param resourceimage The BufferedImage we are converting
   * @param target The GL target to load the texture against
   * @param dstPixelFormat The pixel format of the screen
   * @param minFilter The minimising filter
   * @param magFilter The magnification filter
   * @return The loaded texture
   * @throws IOException Indicates a failure to access the resource
   */
  public static Texture getTexture(
      String resourceName,
      BufferedImage resourceimage,
      int target,
      int dstPixelFormat,
      int minFilter,
      int magFilter)
      throws IOException {
    ImageIOImageData data = new ImageIOImageData();
    int srcPixelFormat = 0;

    // create the texture ID for this texture
    int textureID = InternalTextureLoader.createTextureID();
    TextureImpl texture = new TextureImpl(resourceName, target, textureID);

    // Enable texturing
    Renderer.get().glEnable(SGL.GL_TEXTURE_2D);

    // bind this texture
    Renderer.get().glBindTexture(target, textureID);

    BufferedImage bufferedImage = resourceimage;
    texture.setWidth(bufferedImage.getWidth());
    texture.setHeight(bufferedImage.getHeight());

    if (bufferedImage.getColorModel().hasAlpha()) {
      srcPixelFormat = SGL.GL_RGBA;
    } else {
      srcPixelFormat = SGL.GL_RGB;
    }

    // convert that image into a byte buffer of texture data
    ByteBuffer textureBuffer = data.imageToByteBuffer(bufferedImage, false, false, null);
    texture.setTextureHeight(data.getTexHeight());
    texture.setTextureWidth(data.getTexWidth());
    texture.setAlpha(data.getDepth() == 32);

    if (target == SGL.GL_TEXTURE_2D) {
      Renderer.get().glTexParameteri(target, SGL.GL_TEXTURE_MIN_FILTER, minFilter);
      Renderer.get().glTexParameteri(target, SGL.GL_TEXTURE_MAG_FILTER, magFilter);

      if (Renderer.get().canTextureMirrorClamp()) {
        Renderer.get()
            .glTexParameteri(
                SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT);
        Renderer.get()
            .glTexParameteri(
                SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT);
      } else {
        Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_CLAMP);
        Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_CLAMP);
      }
    }

    Renderer.get()
        .glTexImage2D(
            target,
            0,
            dstPixelFormat,
            texture.getTextureWidth(),
            texture.getTextureHeight(),
            0,
            srcPixelFormat,
            SGL.GL_UNSIGNED_BYTE,
            textureBuffer);

    return texture;
  }
/**
 * A wrapper to allow any game to be scalable. This relies on knowing the normal width/height of the
 * game - i.e. the dimensions that the game is expecting to be run at. The wrapper then takes the
 * size of the container and scales rendering and input based on the ratio.
 *
 * <p>Note: Using OpenGL directly within a ScalableGame can break it
 *
 * @author kevin
 */
public class ScalableGame implements Game {
  /** The renderer to use for all GL operations */
  private static SGL GL = Renderer.get();

  /** The normal or native width of the game */
  private float normalWidth;
  /** The normal or native height of the game */
  private float normalHeight;
  /** The game that is being wrapped */
  private Game held;
  /** True if we should maintain the aspect ratio */
  private boolean maintainAspect;
  /** The target width */
  private int targetWidth;
  /** The target height */
  private int targetHeight;
  /** The game container wrapped */
  private GameContainer container;

  /**
   * Create a new scalable game wrapper
   *
   * @param held The game to be wrapper and displayed at a different resolution
   * @param normalWidth The normal width of the game
   * @param normalHeight The noral height of the game
   */
  public ScalableGame(Game held, int normalWidth, int normalHeight) {
    this(held, normalWidth, normalHeight, false);
  }

  /**
   * Create a new scalable game wrapper
   *
   * @param held The game to be wrapper and displayed at a different resolution
   * @param normalWidth The normal width of the game
   * @param normalHeight The noral height of the game
   * @param maintainAspect True if we should maintain the aspect ratio
   */
  public ScalableGame(Game held, int normalWidth, int normalHeight, boolean maintainAspect) {
    this.held = held;
    this.normalWidth = normalWidth;
    this.normalHeight = normalHeight;
    this.maintainAspect = maintainAspect;
  }

  /** @see org.newdawn.slick.BasicGame#init(org.newdawn.slick.GameContainer) */
  public void init(GameContainer container) throws SlickException {
    this.container = container;

    recalculateScale();
    held.init(container);
  }

  /**
   * Returns the horizontal scale factor; e.g. target width divided by native width.
   *
   * @return the x-axis scaling
   */
  public float getScaleX() {
    return targetWidth / normalWidth;
  }

  /**
   * Returns the vertical scale factor; e.g. target height divided by native height.
   *
   * @return the y-axis scaling
   */
  public float getScaleY() {
    return targetHeight / normalHeight;
  }

  /**
   * Recalculate the scale of the game
   *
   * @throws SlickException Indicates a failure to reinit the game
   */
  public void recalculateScale() throws SlickException {
    targetWidth = container.getWidth();
    targetHeight = container.getHeight();

    if (maintainAspect) {
      boolean normalIsWide = (normalWidth / normalHeight > 1.6 ? true : false);
      boolean containerIsWide = ((float) targetWidth / (float) targetHeight > 1.6 ? true : false);
      float wScale = targetWidth / normalWidth;
      float hScale = targetHeight / normalHeight;

      if (normalIsWide & containerIsWide) {
        float scale = (wScale < hScale ? wScale : hScale);
        targetWidth = (int) (normalWidth * scale);
        targetHeight = (int) (normalHeight * scale);
      } else if (normalIsWide & !containerIsWide) {
        targetWidth = (int) (normalWidth * wScale);
        targetHeight = (int) (normalHeight * wScale);
      } else if (!normalIsWide & containerIsWide) {
        targetWidth = (int) (normalWidth * hScale);
        targetHeight = (int) (normalHeight * hScale);
      } else {
        float scale = (wScale < hScale ? wScale : hScale);
        targetWidth = (int) (normalWidth * scale);
        targetHeight = (int) (normalHeight * scale);
      }
    }

    if (held instanceof InputListener) {
      container.getInput().addListener((InputListener) held);
    }
    container.getInput().setScale(normalWidth / targetWidth, normalHeight / targetHeight);

    int yoffset = 0;
    int xoffset = 0;

    if (targetHeight < container.getHeight()) {
      yoffset = (container.getHeight() - targetHeight) / 2;
    }
    if (targetWidth < container.getWidth()) {
      xoffset = (container.getWidth() - targetWidth) / 2;
    }
    container
        .getInput()
        .setOffset(
            -xoffset / (targetWidth / normalWidth), -yoffset / (targetHeight / normalHeight));
  }

  /** @see org.newdawn.slick.BasicGame#update(org.newdawn.slick.GameContainer, int) */
  public void update(GameContainer container, int delta) throws SlickException {
    if ((targetHeight != container.getHeight()) || (targetWidth != container.getWidth())) {
      recalculateScale();
    }

    held.update(container, delta);
  }

  /**
   * @see org.newdawn.slick.Game#render(org.newdawn.slick.GameContainer, org.newdawn.slick.Graphics)
   */
  public final void render(GameContainer container, Graphics g) throws SlickException {
    int yoffset = 0;
    int xoffset = 0;

    if (targetHeight < container.getHeight()) {
      yoffset = (container.getHeight() - targetHeight) / 2;
    }
    if (targetWidth < container.getWidth()) {
      xoffset = (container.getWidth() - targetWidth) / 2;
    }

    SlickCallable.enterSafeBlock();
    g.setClip(xoffset, yoffset, targetWidth, targetHeight);
    GL.glTranslatef(xoffset, yoffset, 0);
    g.scale(targetWidth / normalWidth, targetHeight / normalHeight);
    GL.glPushMatrix();
    held.render(container, g);
    GL.glPopMatrix();
    g.clearClip();
    SlickCallable.leaveSafeBlock();

    renderOverlay(container, g);
  }

  /**
   * Render the overlay that will sit over the scaled screen
   *
   * @param container The container holding the game being render
   * @param g Graphics context on which to render
   */
  protected void renderOverlay(GameContainer container, Graphics g) {}

  /** @see org.newdawn.slick.Game#closeRequested() */
  public boolean closeRequested() {
    return held.closeRequested();
  }

  /** @see org.newdawn.slick.Game#getTitle() */
  public String getTitle() {
    return held.getTitle();
  }
}