From d3080ffb78b6102f62d3708f725aa7e1d8d43828 Mon Sep 17 00:00:00 2001 From: Song Minjae Date: Fri, 30 Dec 2016 23:29:12 +0900 Subject: [PATCH] added sources for Slick Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054 Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d --- .idea/libraries/lib.xml | 1 + .../org/newdawn/slick/AngelCodeFont.java | 635 ++++++ .../org/newdawn/slick/Animation.java | 724 +++++++ .../org/newdawn/slick/AppGameContainer.java | 547 +++++ .../newdawn/slick/AppletGameContainer.java | 576 ++++++ .../org/newdawn/slick/BasicGame.java | 205 ++ .../org/newdawn/slick/BigImage.java | 768 +++++++ .../org/newdawn/slick/CachedRender.java | 76 + .../newdawn/slick/CanvasGameContainer.java | 184 ++ lib/slick-source/org/newdawn/slick/Color.java | 392 ++++ .../slick/ControlledInputReciever.java | 42 + .../org/newdawn/slick/ControllerListener.java | 102 + lib/slick-source/org/newdawn/slick/Font.java | 65 + lib/slick-source/org/newdawn/slick/Game.java | 55 + .../org/newdawn/slick/GameContainer.java | 868 ++++++++ .../org/newdawn/slick/Graphics.java | 1780 +++++++++++++++++ lib/slick-source/org/newdawn/slick/Image.java | 1403 +++++++++++++ .../org/newdawn/slick/ImageBuffer.java | 167 ++ lib/slick-source/org/newdawn/slick/Input.java | 1533 ++++++++++++++ .../org/newdawn/slick/InputListener.java | 9 + .../org/newdawn/slick/KeyListener.java | 25 + .../org/newdawn/slick/MouseListener.java | 69 + lib/slick-source/org/newdawn/slick/Music.java | 412 ++++ .../org/newdawn/slick/MusicListener.java | 26 + .../org/newdawn/slick/PackedSpriteSheet.java | 193 ++ .../org/newdawn/slick/Renderable.java | 18 + .../org/newdawn/slick/SavedState.java | 176 ++ .../org/newdawn/slick/ScalableGame.java | 188 ++ .../org/newdawn/slick/ShapeFill.java | 33 + .../org/newdawn/slick/SlickException.java | 27 + lib/slick-source/org/newdawn/slick/Sound.java | 175 ++ .../org/newdawn/slick/SpriteSheet.java | 303 +++ .../org/newdawn/slick/SpriteSheetFont.java | 111 + .../org/newdawn/slick/TrueTypeFont.java | 409 ++++ .../org/newdawn/slick/UnicodeFont.java | 980 +++++++++ .../org/newdawn/slick/XMLPackedSheet.java | 65 + .../newdawn/slick/command/BasicCommand.java | 54 + .../org/newdawn/slick/command/Command.java | 12 + .../org/newdawn/slick/command/Control.java | 9 + .../command/ControllerButtonControl.java | 20 + .../slick/command/ControllerControl.java | 61 + .../command/ControllerDirectionControl.java | 47 + .../newdawn/slick/command/InputProvider.java | 464 +++++ .../slick/command/InputProviderListener.java | 24 + .../org/newdawn/slick/command/KeyControl.java | 39 + .../slick/command/MouseButtonControl.java | 39 + .../org/newdawn/slick/command/package.html | 4 + .../org/newdawn/slick/data/.gitattributes | 1 + .../org/newdawn/slick/data/defaultfont.fnt | 195 ++ .../org/newdawn/slick/data/defaultfont.png | Bin 0 -> 18648 bytes .../org/newdawn/slick/data/helvetica_svg | 131 ++ .../org/newdawn/slick/data/package.html | 4 + .../org/newdawn/slick/fills/GradientFill.java | 253 +++ .../org/newdawn/slick/fills/package.html | 3 + .../org/newdawn/slick/font/Glyph.java | 152 ++ .../org/newdawn/slick/font/GlyphPage.java | 260 +++ .../org/newdawn/slick/font/HieroSettings.java | 380 ++++ .../slick/font/effects/ColorEffect.java | 92 + .../font/effects/ConfigurableEffect.java | 53 + .../newdawn/slick/font/effects/Effect.java | 25 + .../slick/font/effects/EffectUtil.java | 368 ++++ .../slick/font/effects/FilterEffect.java | 62 + .../slick/font/effects/GradientEffect.java | 195 ++ .../slick/font/effects/OutlineEffect.java | 178 ++ .../font/effects/OutlineWobbleEffect.java | 200 ++ .../font/effects/OutlineZigzagEffect.java | 194 ++ .../slick/font/effects/ShadowEffect.java | 321 +++ .../newdawn/slick/geom/BasicTriangulator.java | 437 ++++ .../org/newdawn/slick/geom/Circle.java | 234 +++ .../org/newdawn/slick/geom/Curve.java | 113 ++ .../org/newdawn/slick/geom/Ellipse.java | 196 ++ .../org/newdawn/slick/geom/GeomUtil.java | 449 +++++ .../newdawn/slick/geom/GeomUtilListener.java | 32 + .../org/newdawn/slick/geom/Line.java | 482 +++++ .../newdawn/slick/geom/MannTriangulator.java | 617 ++++++ .../org/newdawn/slick/geom/MorphShape.java | 193 ++ .../newdawn/slick/geom/NeatTriangulator.java | 617 ++++++ .../newdawn/slick/geom/OverTriangulator.java | 113 ++ .../org/newdawn/slick/geom/Path.java | 241 +++ .../org/newdawn/slick/geom/Point.java | 72 + .../org/newdawn/slick/geom/Polygon.java | 200 ++ .../org/newdawn/slick/geom/Rectangle.java | 269 +++ .../newdawn/slick/geom/RoundedRectangle.java | 284 +++ .../org/newdawn/slick/geom/Shape.java | 767 +++++++ .../org/newdawn/slick/geom/ShapeRenderer.java | 391 ++++ .../newdawn/slick/geom/TexCoordGenerator.java | 19 + .../org/newdawn/slick/geom/Transform.java | 230 +++ .../org/newdawn/slick/geom/Triangulator.java | 48 + .../org/newdawn/slick/geom/Vector2f.java | 396 ++++ .../org/newdawn/slick/geom/package.html | 3 + .../newdawn/slick/gui/AbstractComponent.java | 182 ++ .../org/newdawn/slick/gui/BasicComponent.java | 83 + .../newdawn/slick/gui/ComponentListener.java | 17 + .../org/newdawn/slick/gui/GUIContext.java | 103 + .../org/newdawn/slick/gui/MouseOverArea.java | 433 ++++ .../org/newdawn/slick/gui/TextField.java | 476 +++++ .../org/newdawn/slick/gui/package.html | 3 + .../newdawn/slick/imageout/ImageIOWriter.java | 94 + .../org/newdawn/slick/imageout/ImageOut.java | 131 ++ .../newdawn/slick/imageout/ImageWriter.java | 25 + .../slick/imageout/ImageWriterFactory.java | 76 + .../org/newdawn/slick/imageout/TGAWriter.java | 86 + .../org/newdawn/slick/imageout/package.html | 3 + .../slick/loading/DeferredResource.java | 26 + .../newdawn/slick/loading/LoadingList.java | 112 ++ .../org/newdawn/slick/loading/package.html | 4 + .../org/newdawn/slick/muffin/FileMuffin.java | 74 + .../org/newdawn/slick/muffin/Muffin.java | 29 + .../newdawn/slick/muffin/WebstartMuffin.java | 138 ++ .../org/newdawn/slick/muffin/package.html | 3 + .../org/newdawn/slick/openal/AiffData.java | 268 +++ .../org/newdawn/slick/openal/Audio.java | 79 + .../org/newdawn/slick/openal/AudioImpl.java | 139 ++ .../slick/openal/AudioInputStream.java | 71 + .../org/newdawn/slick/openal/AudioLoader.java | 96 + .../newdawn/slick/openal/DeferredSound.java | 164 ++ .../org/newdawn/slick/openal/MODSound.java | 105 + .../org/newdawn/slick/openal/NullAudio.java | 66 + .../org/newdawn/slick/openal/OggData.java | 17 + .../org/newdawn/slick/openal/OggDecoder.java | 329 +++ .../newdawn/slick/openal/OggInputStream.java | 506 +++++ .../slick/openal/OpenALStreamPlayer.java | 322 +++ .../org/newdawn/slick/openal/SoundStore.java | 974 +++++++++ .../org/newdawn/slick/openal/StreamSound.java | 108 + .../org/newdawn/slick/openal/WaveData.java | 265 +++ .../org/newdawn/slick/openal/package.html | 4 + .../slick/opengl/CompositeIOException.java | 42 + .../slick/opengl/CompositeImageData.java | 151 ++ .../newdawn/slick/opengl/CursorLoader.java | 183 ++ .../newdawn/slick/opengl/DeferredTexture.java | 234 +++ .../newdawn/slick/opengl/EmptyImageData.java | 71 + .../org/newdawn/slick/opengl/GLUtils.java | 22 + .../org/newdawn/slick/opengl/ImageData.java | 55 + .../slick/opengl/ImageDataFactory.java | 77 + .../slick/opengl/ImageIOImageData.java | 244 +++ .../slick/opengl/InternalTextureLoader.java | 530 +++++ .../slick/opengl/LoadableImageData.java | 54 + .../org/newdawn/slick/opengl/PNGDecoder.java | 762 +++++++ .../newdawn/slick/opengl/PNGImageData.java | 202 ++ .../newdawn/slick/opengl/SlickCallable.java | 122 ++ .../newdawn/slick/opengl/TGAImageData.java | 319 +++ .../org/newdawn/slick/opengl/Texture.java | 97 + .../org/newdawn/slick/opengl/TextureImpl.java | 377 ++++ .../newdawn/slick/opengl/TextureLoader.java | 66 + .../org/newdawn/slick/opengl/package.html | 4 + .../slick/opengl/pbuffer/FBOGraphics.java | 228 +++ .../slick/opengl/pbuffer/GraphicsFactory.java | 141 ++ .../slick/opengl/pbuffer/PBufferGraphics.java | 172 ++ .../opengl/pbuffer/PBufferUniqueGraphics.java | 167 ++ .../renderer/DefaultLineStripRenderer.java | 72 + .../renderer/ImmediateModeOGLRenderer.java | 426 ++++ .../opengl/renderer/LineStripRenderer.java | 66 + .../renderer/QuadBasedLineStripRenderer.java | 293 +++ .../slick/opengl/renderer/Renderer.java | 99 + .../newdawn/slick/opengl/renderer/SGL.java | 535 +++++ .../slick/opengl/renderer/VAOGLRenderer.java | 417 ++++ .../slick/particles/ConfigurableEmitter.java | 911 +++++++++ .../particles/ConfigurableEmitterFactory.java | 18 + .../org/newdawn/slick/particles/Particle.java | 502 +++++ .../slick/particles/ParticleEmitter.java | 91 + .../newdawn/slick/particles/ParticleIO.java | 817 ++++++++ .../slick/particles/ParticleSystem.java | 646 ++++++ .../slick/particles/effects/FireEmitter.java | 145 ++ .../slick/particles/effects/package.html | 3 + .../org/newdawn/slick/particles/package.html | 4 + .../newdawn/slick/state/BasicGameState.java | 166 ++ .../org/newdawn/slick/state/GameState.java | 71 + .../newdawn/slick/state/StateBasedGame.java | 541 +++++ .../org/newdawn/slick/state/package.html | 5 + .../state/transition/BlobbyTransition.java | 164 ++ .../state/transition/CombinedTransition.java | 86 + .../transition/CrossStateTransition.java | 105 + .../state/transition/EmptyTransition.java | 51 + .../state/transition/FadeInTransition.java | 87 + .../state/transition/FadeOutTransition.java | 86 + .../transition/HorizontalSplitTransition.java | 112 ++ .../state/transition/RotateTransition.java | 103 + .../state/transition/SelectTransition.java | 168 ++ .../slick/state/transition/Transition.java | 59 + .../transition/VerticalSplitTransition.java | 114 ++ .../org/newdawn/slick/svg/Diagram.java | 162 ++ .../org/newdawn/slick/svg/Figure.java | 84 + .../org/newdawn/slick/svg/Gradient.java | 289 +++ .../org/newdawn/slick/svg/InkscapeLoader.java | 226 +++ .../newdawn/slick/svg/LinearGradientFill.java | 69 + .../org/newdawn/slick/svg/Loader.java | 20 + .../newdawn/slick/svg/NonGeometricData.java | 210 ++ .../newdawn/slick/svg/ParsingException.java | 54 + .../newdawn/slick/svg/RadialGradientFill.java | 63 + .../org/newdawn/slick/svg/SVGMorph.java | 118 ++ .../slick/svg/SimpleDiagramRenderer.java | 111 + .../slick/svg/inkscape/DefsProcessor.java | 171 ++ .../slick/svg/inkscape/ElementProcessor.java | 36 + .../slick/svg/inkscape/EllipseProcessor.java | 60 + .../slick/svg/inkscape/GroupProcessor.java | 36 + .../inkscape/InkscapeNonGeometricData.java | 49 + .../slick/svg/inkscape/LineProcessor.java | 127 ++ .../slick/svg/inkscape/PathProcessor.java | 127 ++ .../slick/svg/inkscape/PolygonProcessor.java | 119 ++ .../slick/svg/inkscape/RectProcessor.java | 54 + .../slick/svg/inkscape/UseProcessor.java | 55 + .../org/newdawn/slick/svg/inkscape/Util.java | 198 ++ .../org/newdawn/slick/svg/package.html | 4 + .../org/newdawn/slick/tests/AlphaMapTest.java | 84 + .../newdawn/slick/tests/AnimationTest.java | 121 ++ .../newdawn/slick/tests/AntiAliasTest.java | 65 + .../org/newdawn/slick/tests/BigImageTest.java | 115 ++ .../slick/tests/BigSpriteSheetTest.java | 89 + .../newdawn/slick/tests/CachedRenderTest.java | 88 + .../slick/tests/CanvasContainerTest.java | 130 ++ .../newdawn/slick/tests/CanvasSizeTest.java | 76 + .../org/newdawn/slick/tests/ClipTest.java | 111 + .../slick/tests/CopyAreaAlphaTest.java | 82 + .../org/newdawn/slick/tests/CurveTest.java | 103 + .../slick/tests/DeferredLoadingTest.java | 140 ++ .../slick/tests/DistanceFieldTest.java | 106 + .../newdawn/slick/tests/DoubleClickTest.java | 71 + .../slick/tests/DuplicateEmitterTest.java | 111 + .../org/newdawn/slick/tests/FlashTest.java | 85 + .../slick/tests/FontPerformanceTest.java | 101 + .../org/newdawn/slick/tests/FontTest.java | 106 + .../org/newdawn/slick/tests/GUITest.java | 159 ++ .../newdawn/slick/tests/GeomAccuracyTest.java | 305 +++ .../org/newdawn/slick/tests/GeomTest.java | 143 ++ .../org/newdawn/slick/tests/GeomUtilTest.java | 243 +++ .../newdawn/slick/tests/GeomUtilTileTest.java | 426 ++++ .../slick/tests/GradientImageTest.java | 122 ++ .../org/newdawn/slick/tests/GradientTest.java | 130 ++ .../org/newdawn/slick/tests/GraphicsTest.java | 163 ++ .../slick/tests/ImageBufferEndianTest.java | 113 ++ .../newdawn/slick/tests/ImageBufferTest.java | 81 + .../newdawn/slick/tests/ImageCornerTest.java | 97 + .../slick/tests/ImageGraphicsTest.java | 167 ++ .../org/newdawn/slick/tests/ImageMemTest.java | 65 + .../org/newdawn/slick/tests/ImageOutTest.java | 134 ++ .../newdawn/slick/tests/ImageReadTest.java | 99 + .../org/newdawn/slick/tests/ImageTest.java | 153 ++ .../org/newdawn/slick/tests/InkscapeTest.java | 128 ++ .../slick/tests/InputProviderTest.java | 102 + .../org/newdawn/slick/tests/InputTest.java | 254 +++ .../org/newdawn/slick/tests/IsoTiledTest.java | 59 + .../newdawn/slick/tests/KeyRepeatTest.java | 79 + .../org/newdawn/slick/tests/LameTest.java | 70 + .../newdawn/slick/tests/LineRenderTest.java | 102 + .../org/newdawn/slick/tests/MorphSVGTest.java | 86 + .../newdawn/slick/tests/MorphShapeTest.java | 96 + .../slick/tests/MusicListenerTest.java | 109 + .../org/newdawn/slick/tests/NavMeshTest.java | 258 +++ .../newdawn/slick/tests/PackedSheetTest.java | 110 + .../org/newdawn/slick/tests/ParticleTest.java | 92 + .../org/newdawn/slick/tests/PedigreeTest.java | 124 ++ .../org/newdawn/slick/tests/PolygonTest.java | 81 + .../org/newdawn/slick/tests/PureFontTest.java | 80 + .../newdawn/slick/tests/SavedStateTest.java | 120 ++ .../org/newdawn/slick/tests/ScalableTest.java | 102 + .../org/newdawn/slick/tests/ShapeTest.java | 291 +++ .../slick/tests/SlickCallableTest.java | 244 +++ .../slick/tests/SoundPositionTest.java | 91 + .../org/newdawn/slick/tests/SoundTest.java | 225 +++ .../org/newdawn/slick/tests/SoundURLTest.java | 168 ++ .../slick/tests/SpriteSheetFontTest.java | 82 + .../newdawn/slick/tests/StateBasedTest.java | 48 + .../org/newdawn/slick/tests/TestBox.java | 274 +++ .../org/newdawn/slick/tests/TestUtils.java | 223 +++ .../newdawn/slick/tests/TexturePaintTest.java | 90 + .../org/newdawn/slick/tests/TileMapTest.java | 119 ++ .../newdawn/slick/tests/TransformTest.java | 111 + .../newdawn/slick/tests/TransformTest2.java | 170 ++ .../newdawn/slick/tests/TransitionTest.java | 161 ++ .../slick/tests/TransparentColorTest.java | 72 + .../tests/TrueTypeFontPerformanceTest.java | 112 ++ .../newdawn/slick/tests/UnicodeFontTest.java | 99 + .../org/newdawn/slick/tests/package.html | 3 + .../slick/tests/states/TestState1.java | 88 + .../slick/tests/states/TestState2.java | 79 + .../slick/tests/states/TestState3.java | 94 + .../newdawn/slick/tests/states/package.html | 3 + .../org/newdawn/slick/tests/xml/Entity.java | 46 + .../org/newdawn/slick/tests/xml/GameData.java | 34 + .../newdawn/slick/tests/xml/Inventory.java | 34 + .../org/newdawn/slick/tests/xml/Item.java | 23 + .../slick/tests/xml/ItemContainer.java | 56 + .../slick/tests/xml/ObjectParserTest.java | 28 + .../org/newdawn/slick/tests/xml/Stats.java | 26 + .../org/newdawn/slick/tests/xml/XMLTest.java | 117 ++ .../org/newdawn/slick/tests/xml/package.html | 3 + .../org/newdawn/slick/tiled/Layer.java | 293 +++ .../org/newdawn/slick/tiled/TileSet.java | 265 +++ .../org/newdawn/slick/tiled/TiledMap.java | 1108 ++++++++++ .../org/newdawn/slick/tiled/package.html | 5 + .../org/newdawn/slick/util/Bootstrap.java | 29 + .../newdawn/slick/util/BufferedImageUtil.java | 162 ++ .../newdawn/slick/util/ClasspathLocation.java | 28 + .../newdawn/slick/util/DefaultLogSystem.java | 82 + .../org/newdawn/slick/util/FastTrig.java | 56 + .../slick/util/FileSystemLocation.java | 61 + .../org/newdawn/slick/util/FontUtils.java | 220 ++ .../org/newdawn/slick/util/InputAdapter.java | 154 ++ .../org/newdawn/slick/util/LocatedImage.java | 137 ++ .../org/newdawn/slick/util/Log.java | 161 ++ .../org/newdawn/slick/util/LogSystem.java | 60 + .../org/newdawn/slick/util/MaskUtil.java | 62 + .../util/OperationNotSupportedException.java | 18 + .../newdawn/slick/util/ResourceLoader.java | 120 ++ .../newdawn/slick/util/ResourceLocation.java | 29 + .../org/newdawn/slick/util/package.html | 3 + .../util/pathfinding/AStarHeuristic.java | 27 + .../util/pathfinding/AStarPathFinder.java | 590 ++++++ .../newdawn/slick/util/pathfinding/Mover.java | 17 + .../newdawn/slick/util/pathfinding/Path.java | 158 ++ .../slick/util/pathfinding/PathFinder.java | 29 + .../util/pathfinding/PathFindingContext.java | 36 + .../slick/util/pathfinding/TileBasedMap.java | 58 + .../heuristics/ClosestHeuristic.java | 26 + .../heuristics/ClosestSquaredHeuristic.java | 26 + .../heuristics/ManhattanHeuristic.java | 34 + .../slick/util/pathfinding/navmesh/Link.java | 69 + .../util/pathfinding/navmesh/NavMesh.java | 176 ++ .../pathfinding/navmesh/NavMeshBuilder.java | 222 ++ .../util/pathfinding/navmesh/NavPath.java | 75 + .../slick/util/pathfinding/navmesh/Space.java | 319 +++ .../slick/util/pathfinding/package.html | 3 + .../slick/util/xml/ObjectTreeParser.java | 483 +++++ .../slick/util/xml/SlickXMLException.java | 32 + .../newdawn/slick/util/xml/XMLElement.java | 260 +++ .../slick/util/xml/XMLElementList.java | 68 + .../org/newdawn/slick/util/xml/XMLParser.java | 63 + .../org/newdawn/slick/util/xml/package.html | 3 + .../terrarum/tileproperties/TilePropUtil.kt | 9 +- 329 files changed, 58400 insertions(+), 7 deletions(-) create mode 100644 lib/slick-source/org/newdawn/slick/AngelCodeFont.java create mode 100644 lib/slick-source/org/newdawn/slick/Animation.java create mode 100644 lib/slick-source/org/newdawn/slick/AppGameContainer.java create mode 100644 lib/slick-source/org/newdawn/slick/AppletGameContainer.java create mode 100644 lib/slick-source/org/newdawn/slick/BasicGame.java create mode 100644 lib/slick-source/org/newdawn/slick/BigImage.java create mode 100644 lib/slick-source/org/newdawn/slick/CachedRender.java create mode 100644 lib/slick-source/org/newdawn/slick/CanvasGameContainer.java create mode 100644 lib/slick-source/org/newdawn/slick/Color.java create mode 100644 lib/slick-source/org/newdawn/slick/ControlledInputReciever.java create mode 100644 lib/slick-source/org/newdawn/slick/ControllerListener.java create mode 100644 lib/slick-source/org/newdawn/slick/Font.java create mode 100644 lib/slick-source/org/newdawn/slick/Game.java create mode 100644 lib/slick-source/org/newdawn/slick/GameContainer.java create mode 100644 lib/slick-source/org/newdawn/slick/Graphics.java create mode 100644 lib/slick-source/org/newdawn/slick/Image.java create mode 100644 lib/slick-source/org/newdawn/slick/ImageBuffer.java create mode 100644 lib/slick-source/org/newdawn/slick/Input.java create mode 100644 lib/slick-source/org/newdawn/slick/InputListener.java create mode 100644 lib/slick-source/org/newdawn/slick/KeyListener.java create mode 100644 lib/slick-source/org/newdawn/slick/MouseListener.java create mode 100644 lib/slick-source/org/newdawn/slick/Music.java create mode 100644 lib/slick-source/org/newdawn/slick/MusicListener.java create mode 100644 lib/slick-source/org/newdawn/slick/PackedSpriteSheet.java create mode 100644 lib/slick-source/org/newdawn/slick/Renderable.java create mode 100644 lib/slick-source/org/newdawn/slick/SavedState.java create mode 100644 lib/slick-source/org/newdawn/slick/ScalableGame.java create mode 100644 lib/slick-source/org/newdawn/slick/ShapeFill.java create mode 100644 lib/slick-source/org/newdawn/slick/SlickException.java create mode 100644 lib/slick-source/org/newdawn/slick/Sound.java create mode 100644 lib/slick-source/org/newdawn/slick/SpriteSheet.java create mode 100644 lib/slick-source/org/newdawn/slick/SpriteSheetFont.java create mode 100644 lib/slick-source/org/newdawn/slick/TrueTypeFont.java create mode 100644 lib/slick-source/org/newdawn/slick/UnicodeFont.java create mode 100644 lib/slick-source/org/newdawn/slick/XMLPackedSheet.java create mode 100644 lib/slick-source/org/newdawn/slick/command/BasicCommand.java create mode 100644 lib/slick-source/org/newdawn/slick/command/Command.java create mode 100644 lib/slick-source/org/newdawn/slick/command/Control.java create mode 100644 lib/slick-source/org/newdawn/slick/command/ControllerButtonControl.java create mode 100644 lib/slick-source/org/newdawn/slick/command/ControllerControl.java create mode 100644 lib/slick-source/org/newdawn/slick/command/ControllerDirectionControl.java create mode 100644 lib/slick-source/org/newdawn/slick/command/InputProvider.java create mode 100644 lib/slick-source/org/newdawn/slick/command/InputProviderListener.java create mode 100644 lib/slick-source/org/newdawn/slick/command/KeyControl.java create mode 100644 lib/slick-source/org/newdawn/slick/command/MouseButtonControl.java create mode 100644 lib/slick-source/org/newdawn/slick/command/package.html create mode 100644 lib/slick-source/org/newdawn/slick/data/.gitattributes create mode 100644 lib/slick-source/org/newdawn/slick/data/defaultfont.fnt create mode 100644 lib/slick-source/org/newdawn/slick/data/defaultfont.png create mode 100644 lib/slick-source/org/newdawn/slick/data/helvetica_svg create mode 100644 lib/slick-source/org/newdawn/slick/data/package.html create mode 100644 lib/slick-source/org/newdawn/slick/fills/GradientFill.java create mode 100644 lib/slick-source/org/newdawn/slick/fills/package.html create mode 100644 lib/slick-source/org/newdawn/slick/font/Glyph.java create mode 100644 lib/slick-source/org/newdawn/slick/font/GlyphPage.java create mode 100644 lib/slick-source/org/newdawn/slick/font/HieroSettings.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/ColorEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/ConfigurableEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/Effect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/EffectUtil.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/FilterEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/GradientEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/OutlineEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/OutlineWobbleEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/OutlineZigzagEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/font/effects/ShadowEffect.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/BasicTriangulator.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Circle.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Curve.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Ellipse.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/GeomUtil.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/GeomUtilListener.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Line.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/MannTriangulator.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/MorphShape.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/NeatTriangulator.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/OverTriangulator.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Path.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Point.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Polygon.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Rectangle.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/RoundedRectangle.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Shape.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/ShapeRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/TexCoordGenerator.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Transform.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Triangulator.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/Vector2f.java create mode 100644 lib/slick-source/org/newdawn/slick/geom/package.html create mode 100644 lib/slick-source/org/newdawn/slick/gui/AbstractComponent.java create mode 100644 lib/slick-source/org/newdawn/slick/gui/BasicComponent.java create mode 100644 lib/slick-source/org/newdawn/slick/gui/ComponentListener.java create mode 100644 lib/slick-source/org/newdawn/slick/gui/GUIContext.java create mode 100644 lib/slick-source/org/newdawn/slick/gui/MouseOverArea.java create mode 100644 lib/slick-source/org/newdawn/slick/gui/TextField.java create mode 100644 lib/slick-source/org/newdawn/slick/gui/package.html create mode 100644 lib/slick-source/org/newdawn/slick/imageout/ImageIOWriter.java create mode 100644 lib/slick-source/org/newdawn/slick/imageout/ImageOut.java create mode 100644 lib/slick-source/org/newdawn/slick/imageout/ImageWriter.java create mode 100644 lib/slick-source/org/newdawn/slick/imageout/ImageWriterFactory.java create mode 100644 lib/slick-source/org/newdawn/slick/imageout/TGAWriter.java create mode 100644 lib/slick-source/org/newdawn/slick/imageout/package.html create mode 100644 lib/slick-source/org/newdawn/slick/loading/DeferredResource.java create mode 100644 lib/slick-source/org/newdawn/slick/loading/LoadingList.java create mode 100644 lib/slick-source/org/newdawn/slick/loading/package.html create mode 100644 lib/slick-source/org/newdawn/slick/muffin/FileMuffin.java create mode 100644 lib/slick-source/org/newdawn/slick/muffin/Muffin.java create mode 100644 lib/slick-source/org/newdawn/slick/muffin/WebstartMuffin.java create mode 100644 lib/slick-source/org/newdawn/slick/muffin/package.html create mode 100644 lib/slick-source/org/newdawn/slick/openal/AiffData.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/Audio.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/AudioImpl.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/AudioInputStream.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/AudioLoader.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/DeferredSound.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/MODSound.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/NullAudio.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/OggData.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/OggDecoder.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/OggInputStream.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/OpenALStreamPlayer.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/SoundStore.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/StreamSound.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/WaveData.java create mode 100644 lib/slick-source/org/newdawn/slick/openal/package.html create mode 100644 lib/slick-source/org/newdawn/slick/opengl/CompositeIOException.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/CompositeImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/CursorLoader.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/DeferredTexture.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/EmptyImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/GLUtils.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/ImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/ImageDataFactory.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/ImageIOImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/InternalTextureLoader.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/LoadableImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/PNGDecoder.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/PNGImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/SlickCallable.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/TGAImageData.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/Texture.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/TextureImpl.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/TextureLoader.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/package.html create mode 100644 lib/slick-source/org/newdawn/slick/opengl/pbuffer/FBOGraphics.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/pbuffer/GraphicsFactory.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/pbuffer/PBufferGraphics.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/pbuffer/PBufferUniqueGraphics.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/DefaultLineStripRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/ImmediateModeOGLRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/LineStripRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/QuadBasedLineStripRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/Renderer.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/SGL.java create mode 100644 lib/slick-source/org/newdawn/slick/opengl/renderer/VAOGLRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/ConfigurableEmitter.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/ConfigurableEmitterFactory.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/Particle.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/ParticleEmitter.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/ParticleIO.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/ParticleSystem.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/effects/FireEmitter.java create mode 100644 lib/slick-source/org/newdawn/slick/particles/effects/package.html create mode 100644 lib/slick-source/org/newdawn/slick/particles/package.html create mode 100644 lib/slick-source/org/newdawn/slick/state/BasicGameState.java create mode 100644 lib/slick-source/org/newdawn/slick/state/GameState.java create mode 100644 lib/slick-source/org/newdawn/slick/state/StateBasedGame.java create mode 100644 lib/slick-source/org/newdawn/slick/state/package.html create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/BlobbyTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/CombinedTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/CrossStateTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/EmptyTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/FadeInTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/FadeOutTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/HorizontalSplitTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/RotateTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/SelectTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/Transition.java create mode 100644 lib/slick-source/org/newdawn/slick/state/transition/VerticalSplitTransition.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/Diagram.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/Figure.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/Gradient.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/InkscapeLoader.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/LinearGradientFill.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/Loader.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/NonGeometricData.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/ParsingException.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/RadialGradientFill.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/SVGMorph.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/SimpleDiagramRenderer.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/DefsProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/ElementProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/EllipseProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/GroupProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/InkscapeNonGeometricData.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/LineProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/PathProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/PolygonProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/RectProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/UseProcessor.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/inkscape/Util.java create mode 100644 lib/slick-source/org/newdawn/slick/svg/package.html create mode 100644 lib/slick-source/org/newdawn/slick/tests/AlphaMapTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/AnimationTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/AntiAliasTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/BigImageTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/BigSpriteSheetTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/CachedRenderTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/CanvasContainerTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/CanvasSizeTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ClipTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/CopyAreaAlphaTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/CurveTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/DeferredLoadingTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/DistanceFieldTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/DoubleClickTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/DuplicateEmitterTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/FlashTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/FontPerformanceTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/FontTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GUITest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GeomAccuracyTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GeomTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GeomUtilTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GeomUtilTileTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GradientImageTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GradientTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/GraphicsTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageBufferEndianTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageBufferTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageCornerTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageGraphicsTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageMemTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageOutTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageReadTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ImageTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/InkscapeTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/InputProviderTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/InputTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/IsoTiledTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/KeyRepeatTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/LameTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/LineRenderTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/MorphSVGTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/MorphShapeTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/MusicListenerTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/NavMeshTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/PackedSheetTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ParticleTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/PedigreeTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/PolygonTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/PureFontTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/SavedStateTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ScalableTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/ShapeTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/SlickCallableTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/SoundPositionTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/SoundTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/SoundURLTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/SpriteSheetFontTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/StateBasedTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TestBox.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TestUtils.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TexturePaintTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TileMapTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TransformTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TransformTest2.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TransitionTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TransparentColorTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/TrueTypeFontPerformanceTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/UnicodeFontTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/package.html create mode 100644 lib/slick-source/org/newdawn/slick/tests/states/TestState1.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/states/TestState2.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/states/TestState3.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/states/package.html create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/Entity.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/GameData.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/Inventory.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/Item.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/ItemContainer.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/ObjectParserTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/Stats.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/XMLTest.java create mode 100644 lib/slick-source/org/newdawn/slick/tests/xml/package.html create mode 100644 lib/slick-source/org/newdawn/slick/tiled/Layer.java create mode 100644 lib/slick-source/org/newdawn/slick/tiled/TileSet.java create mode 100644 lib/slick-source/org/newdawn/slick/tiled/TiledMap.java create mode 100644 lib/slick-source/org/newdawn/slick/tiled/package.html create mode 100644 lib/slick-source/org/newdawn/slick/util/Bootstrap.java create mode 100644 lib/slick-source/org/newdawn/slick/util/BufferedImageUtil.java create mode 100644 lib/slick-source/org/newdawn/slick/util/ClasspathLocation.java create mode 100644 lib/slick-source/org/newdawn/slick/util/DefaultLogSystem.java create mode 100644 lib/slick-source/org/newdawn/slick/util/FastTrig.java create mode 100644 lib/slick-source/org/newdawn/slick/util/FileSystemLocation.java create mode 100644 lib/slick-source/org/newdawn/slick/util/FontUtils.java create mode 100644 lib/slick-source/org/newdawn/slick/util/InputAdapter.java create mode 100644 lib/slick-source/org/newdawn/slick/util/LocatedImage.java create mode 100644 lib/slick-source/org/newdawn/slick/util/Log.java create mode 100644 lib/slick-source/org/newdawn/slick/util/LogSystem.java create mode 100644 lib/slick-source/org/newdawn/slick/util/MaskUtil.java create mode 100644 lib/slick-source/org/newdawn/slick/util/OperationNotSupportedException.java create mode 100644 lib/slick-source/org/newdawn/slick/util/ResourceLoader.java create mode 100644 lib/slick-source/org/newdawn/slick/util/ResourceLocation.java create mode 100644 lib/slick-source/org/newdawn/slick/util/package.html create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/AStarHeuristic.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/AStarPathFinder.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/Mover.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/Path.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/PathFinder.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/PathFindingContext.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/TileBasedMap.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/heuristics/ClosestHeuristic.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/heuristics/ClosestSquaredHeuristic.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/heuristics/ManhattanHeuristic.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/navmesh/Link.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/navmesh/NavMesh.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/navmesh/NavMeshBuilder.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/navmesh/NavPath.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/navmesh/Space.java create mode 100644 lib/slick-source/org/newdawn/slick/util/pathfinding/package.html create mode 100644 lib/slick-source/org/newdawn/slick/util/xml/ObjectTreeParser.java create mode 100644 lib/slick-source/org/newdawn/slick/util/xml/SlickXMLException.java create mode 100644 lib/slick-source/org/newdawn/slick/util/xml/XMLElement.java create mode 100644 lib/slick-source/org/newdawn/slick/util/xml/XMLElementList.java create mode 100644 lib/slick-source/org/newdawn/slick/util/xml/XMLParser.java create mode 100644 lib/slick-source/org/newdawn/slick/util/xml/package.html diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml index 5a7878cf7..fee5556f2 100644 --- a/.idea/libraries/lib.xml +++ b/.idea/libraries/lib.xml @@ -28,6 +28,7 @@ + diff --git a/lib/slick-source/org/newdawn/slick/AngelCodeFont.java b/lib/slick-source/org/newdawn/slick/AngelCodeFont.java new file mode 100644 index 000000000..83ddfa42e --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/AngelCodeFont.java @@ -0,0 +1,635 @@ +package org.newdawn.slick; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Map.Entry; + +import org.newdawn.slick.opengl.renderer.Renderer; +import org.newdawn.slick.opengl.renderer.SGL; +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +/** + * 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: + * + * http://www.angelcode.com/products/bmfont/ + * + * 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 { + /** 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); + 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, 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 { + /** 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 + */ + static private 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; + } +} diff --git a/lib/slick-source/org/newdawn/slick/Animation.java b/lib/slick-source/org/newdawn/slick/Animation.java new file mode 100644 index 000000000..235a343d4 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Animation.java @@ -0,0 +1,724 @@ +package org.newdawn.slick; + +import java.util.ArrayList; + +import org.lwjgl.Sys; +import org.newdawn.slick.util.Log; + +/** + * A utility to hold and render animations + * + * @author kevin + * @author DeX (speed updates) + */ +public class Animation implements Renderable { + /** The list of frames to render in this animation */ + private ArrayList frames = new ArrayList(); + /** The frame currently being displayed */ + private int currentFrame = -1; + /** The time the next frame change should take place */ + private long nextChange = 0; + /** True if the animation is stopped */ + private boolean stopped = false; + /** The time left til the next frame */ + private long timeLeft; + /** The current speed of the animation */ + private float speed = 1.0f; + /** The frame to stop at */ + private int stopAt = -2; + /** The last time the frame was automagically updated */ + private long lastUpdate; + /** True if this is the first update */ + private boolean firstUpdate = true; + /** True if we should auto update the animation - default true */ + private boolean autoUpdate = true; + /** The direction the animation is running */ + private int direction = 1; + /** True if the animation in ping ponging back and forth */ + private boolean pingPong; + /** True if the animation should loop (default) */ + private boolean loop = true; + /** The spriteSheet backing this animation */ + private SpriteSheet spriteSheet = null; + + /** + * Create an empty animation + */ + public Animation() { + this(true); + } + + /** + * Create a new animation from a set of images + * + * @param frames The images for the animation frames + * @param duration The duration to show each frame + */ + public Animation(Image[] frames, int duration) { + this(frames, duration, true); + } + + /** + * Create a new animation from a set of images + * + * @param frames The images for the animation frames + * @param durations The duration to show each frame + */ + public Animation(Image[] frames, int[] durations) { + this(frames, durations, true); + } + + /** + * Create an empty animation + * + * @param autoUpdate True if this animation should automatically update. This means that the + * current frame will be caculated based on the time between renders + */ + public Animation(boolean autoUpdate) { + currentFrame = 0; + this.autoUpdate = autoUpdate; + } + + /** + * Create a new animation from a set of images + * + * @param frames The images for the animation frames + * @param duration The duration to show each frame + * @param autoUpdate True if this animation should automatically update. This means that the + * current frame will be caculated based on the time between renders + */ + public Animation(Image[] frames, int duration, boolean autoUpdate) { + for (int i=0;iSpriteSheet ss. + * @param ss The SpriteSheet backing this animation + * @param frames An array of coordinates of sub-image locations for each frame + * @param duration The duration each frame should be displayed for + */ + public Animation(SpriteSheet ss, int[] frames, int[] duration){ + spriteSheet = ss; + int x = -1; + int y = -1; + + for(int i = 0; i < frames.length/2; i++){ + x = frames[i*2]; + y = frames[i*2 + 1]; + addFrame(duration[i], x, y); + } + } + + /** + * Add animation frame to the animation. + * @param duration The duration to display the frame for + * @param x The x location of the frame on the SpriteSheet + * @param y The y location of the frame on the spriteSheet + */ + public void addFrame(int duration, int x, int y){ + if (duration == 0) { + Log.error("Invalid duration: "+duration); + throw new RuntimeException("Invalid duration: "+duration); + } + + if (frames.isEmpty()) { + nextChange = (int) (duration / speed); + } + + frames.add(new Frame(duration, x, y)); + currentFrame = 0; + } + + /** + * Indicate if this animation should automatically update based on the + * time between renders or if it should need updating via the update() + * method. + * + * @param auto True if this animation should automatically update + */ + public void setAutoUpdate(boolean auto) { + this.autoUpdate = auto; + } + + /** + * Indicate if this animation should ping pong back and forth + * + * @param pingPong True if the animation should ping pong + */ + public void setPingPong(boolean pingPong) { + this.pingPong = pingPong; + } + + /** + * Check if this animation has stopped (either explictly or because it's reached its target frame) + * + * @see #stopAt + * @return True if the animation has stopped + */ + public boolean isStopped() { + return stopped; + } + + /** + * Adjust the overall speed of the animation. + * + * @param spd The speed to run the animation. Default: 1.0 + */ + public void setSpeed(float spd) { + if (spd > 0) { + // Adjust nextChange + nextChange = (long) (nextChange * speed / spd); + + speed = spd; + } + } + + /** + * Returns the current speed of the animation. + * + * @return The speed this animation is being played back at + */ + public float getSpeed() { + return speed; + } + + + /** + * Stop the animation + */ + public void stop() { + if (frames.size() == 0) { + return; + } + timeLeft = nextChange; + stopped = true; + } + + /** + * Start the animation playing again + */ + public void start() { + if (!stopped) { + return; + } + if (frames.size() == 0) { + return; + } + stopped = false; + nextChange = timeLeft; + } + + /** + * Restart the animation from the beginning + */ + public void restart() { + if (frames.size() == 0) { + return; + } + stopped = false; + currentFrame = 0; + nextChange = (int) (((Frame) frames.get(0)).duration / speed); + firstUpdate = true; + lastUpdate = 0; + } + + /** + * Add animation frame to the animation + * + * @param frame The image to display for the frame + * @param duration The duration to display the frame for + */ + public void addFrame(Image frame, int duration) { + if (duration == 0) { + Log.error("Invalid duration: "+duration); + throw new RuntimeException("Invalid duration: "+duration); + } + + if (frames.isEmpty()) { + nextChange = (int) (duration / speed); + } + + frames.add(new Frame(frame, duration)); + currentFrame = 0; + } + + /** + * Draw the animation to the screen + */ + public void draw() { + draw(0,0); + } + + /** + * Draw the animation at a specific location + * + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + */ + public void draw(float x,float y) { + draw(x,y,getWidth(),getHeight()); + } + + /** + * Draw the animation at a specific location + * + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + * @param filter The filter to apply + */ + public void draw(float x,float y, Color filter) { + draw(x,y,getWidth(),getHeight(), filter); + } + + /** + * Draw the animation + * + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + * @param width The width to draw the animation at + * @param height The height to draw the animation at + */ + public void draw(float x,float y,float width,float height) { + draw(x,y,width,height,Color.white); + } + + /** + * Draw the animation + * + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + * @param width The width to draw the animation at + * @param height The height to draw the animation at + * @param col The colour filter to use + */ + public void draw(float x,float y,float width,float height, Color col) { + if (frames.size() == 0) { + return; + } + + if (autoUpdate) { + long now = getTime(); + long delta = now - lastUpdate; + if (firstUpdate) { + delta = 0; + firstUpdate = false; + } + lastUpdate = now; + nextFrame(delta); + } + + Frame frame = (Frame) frames.get(currentFrame); + frame.image.draw(x,y,width,height, col); + } + + /** + * Render the appropriate frame when the spriteSheet backing this Animation is in use. + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + */ + public void renderInUse(int x, int y){ + if (frames.size() == 0) { + return; + } + + if (autoUpdate) { + long now = getTime(); + long delta = now - lastUpdate; + if (firstUpdate) { + delta = 0; + firstUpdate = false; + } + lastUpdate = now; + nextFrame(delta); + } + + Frame frame = (Frame) frames.get(currentFrame); + spriteSheet.renderInUse(x, y, frame.x, frame.y); + } + + /** + * Get the width of the current frame + * + * @return The width of the current frame + */ + public int getWidth() { + return ((Frame) frames.get(currentFrame)).image.getWidth(); + } + + /** + * Get the height of the current frame + * + * @return The height of the current frame + */ + public int getHeight() { + return ((Frame) frames.get(currentFrame)).image.getHeight(); + } + + /** + * Draw the animation + * + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + * @param width The width to draw the animation at + * @param height The height to draw the animation at + */ + public void drawFlash(float x,float y,float width,float height) { + drawFlash(x,y,width,height, Color.white); + } + + /** + * Draw the animation + * + * @param x The x position to draw the animation at + * @param y The y position to draw the animation at + * @param width The width to draw the animation at + * @param height The height to draw the animation at + * @param col The colour for the flash + */ + public void drawFlash(float x,float y,float width,float height, Color col) { + if (frames.size() == 0) { + return; + } + + if (autoUpdate) { + long now = getTime(); + long delta = now - lastUpdate; + if (firstUpdate) { + delta = 0; + firstUpdate = false; + } + lastUpdate = now; + nextFrame(delta); + } + + Frame frame = (Frame) frames.get(currentFrame); + frame.image.drawFlash(x,y,width,height,col); + } + + /** + * Update the animation cycle without draw the image, useful + * for keeping two animations in sync + * + * @deprecated + */ + public void updateNoDraw() { + if (autoUpdate) { + long now = getTime(); + long delta = now - lastUpdate; + if (firstUpdate) { + delta = 0; + firstUpdate = false; + } + lastUpdate = now; + nextFrame(delta); + } + } + + /** + * Update the animation, note that this will have odd effects if auto update + * is also turned on + * + * @see #autoUpdate + * @param delta The amount of time thats passed since last update + */ + public void update(long delta) { + nextFrame(delta); + } + + /** + * Get the index of the current frame + * + * @return The index of the current frame + */ + public int getFrame() { + return currentFrame; + } + + /** + * Set the current frame to be rendered + * + * @param index The index of the frame to rendered + */ + public void setCurrentFrame(int index) { + currentFrame = index; + } + + /** + * Get the image assocaited with a given frame index + * + * @param index The index of the frame image to retrieve + * @return The image of the specified animation frame + */ + public Image getImage(int index) { + Frame frame = (Frame) frames.get(index); + return frame.image; + } + + /** + * Get the number of frames that are in the animation + * + * @return The number of frames that are in the animation + */ + public int getFrameCount() { + return frames.size(); + } + + /** + * Get the image associated with the current animation frame + * + * @return The image associated with the current animation frame + */ + public Image getCurrentFrame() { + Frame frame = (Frame) frames.get(currentFrame); + return frame.image; + } + + /** + * Check if we need to move to the next frame + * + * @param delta The amount of time thats passed since last update + */ + private void nextFrame(long delta) { + if (stopped) { + return; + } + if (frames.size() == 0) { + return; + } + + nextChange -= delta; + + while (nextChange < 0 && (!stopped)) { + if (currentFrame == stopAt) { + stopped = true; + break; + } + if ((currentFrame == frames.size() - 1) && (!loop) && (!pingPong)) { + stopped = true; + break; + } + currentFrame = (currentFrame + direction) % frames.size(); + + if (pingPong) { + if (currentFrame <= 0) { + currentFrame = 0; + direction = 1; + if (!loop) { + stopped = true; + break; + } + } + else if (currentFrame >= frames.size()-1) { + currentFrame = frames.size()-1; + direction = -1; + } + } + int realDuration = (int) (((Frame) frames.get(currentFrame)).duration / speed); + nextChange = nextChange + realDuration; + } + } + + /** + * Indicate if this animation should loop or stop at the last frame + * + * @param loop True if this animation should loop (true = default) + */ + public void setLooping(boolean loop) { + this.loop = loop; + } + + /** + * Get the accurate system time + * + * @return The system time in milliseconds + */ + private long getTime() { + return (Sys.getTime() * 1000) / Sys.getTimerResolution(); + } + + /** + * Indicate the animation should stop when it reaches the specified + * frame index (note, not frame number but index in the animation + * + * @param frameIndex The index of the frame to stop at + */ + public void stopAt(int frameIndex) { + stopAt = frameIndex; + } + + /** + * Get the duration of a particular frame + * + * @param index The index of the given frame + * @return The duration in (ms) of the given frame + */ + public int getDuration(int index) { + return ((Frame) frames.get(index)).duration; + } + + /** + * Set the duration of the given frame + * + * @param index The index of the given frame + * @param duration The duration in (ms) for the given frame + */ + public void setDuration(int index, int duration) { + ((Frame) frames.get(index)).duration = duration; + } + + /** + * Get the durations of all the frames in this animation + * + * @return The durations of all the frames in this animation + */ + public int[] getDurations() { + int[] durations = new int[frames.size()]; + for (int i=0;iSpriteSheet + * @param y the y location of the frame on the SpriteSheet + */ + public Frame(int duration, int x, int y) { + this.image = spriteSheet.getSubImage(x, y); + this.duration = duration; + this.x = x; + this.y = y; + } + } +} diff --git a/lib/slick-source/org/newdawn/slick/AppGameContainer.java b/lib/slick-source/org/newdawn/slick/AppGameContainer.java new file mode 100644 index 000000000..ed3ceab4e --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/AppGameContainer.java @@ -0,0 +1,547 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.input.Cursor; +import org.lwjgl.input.Mouse; +import org.lwjgl.openal.AL; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.DisplayMode; +import org.lwjgl.opengl.PixelFormat; +import org.newdawn.slick.openal.SoundStore; +import org.newdawn.slick.opengl.CursorLoader; +import org.newdawn.slick.opengl.ImageData; +import org.newdawn.slick.opengl.ImageIOImageData; +import org.newdawn.slick.opengl.InternalTextureLoader; +import org.newdawn.slick.opengl.LoadableImageData; +import org.newdawn.slick.opengl.TGAImageData; +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +/** + * A game container that will display the game as an stand alone + * application. + * + * @author kevin + */ +public class AppGameContainer extends GameContainer { + static { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Display.getDisplayMode(); + } catch (Exception e) { + Log.error(e); + } + return null; + }}); + } + + /** The original display mode before we tampered with things */ + protected DisplayMode originalDisplayMode; + /** The display mode we're going to try and use */ + protected DisplayMode targetDisplayMode; + /** True if we should update the game only when the display is visible */ + protected boolean updateOnlyOnVisible = true; + /** Alpha background supported */ + protected boolean alphaSupport = false; + + /** + * Create a new container wrapping a game + * + * @param game The game to be wrapped + * @throws SlickException Indicates a failure to initialise the display + */ + public AppGameContainer(Game game) throws SlickException { + this(game,640,480,false); + } + + /** + * Create a new container wrapping a game + * + * @param game The game to be wrapped + * @param width The width of the display required + * @param height The height of the display required + * @param fullscreen True if we want fullscreen mode + * @throws SlickException Indicates a failure to initialise the display + */ + public AppGameContainer(Game game,int width,int height,boolean fullscreen) throws SlickException { + super(game); + + originalDisplayMode = Display.getDisplayMode(); + + setDisplayMode(width,height,fullscreen); + } + + /** + * Check if the display created supported alpha in the back buffer + * + * @return True if the back buffer supported alpha + */ + public boolean supportsAlphaInBackBuffer() { + return alphaSupport; + } + + /** + * Set the title of the window + * + * @param title The title to set on the window + */ + public void setTitle(String title) { + Display.setTitle(title); + } + + /** + * Set the display mode to be used + * + * @param width The width of the display required + * @param height The height of the display required + * @param fullscreen True if we want fullscreen mode + * @throws SlickException Indicates a failure to initialise the display + */ + public void setDisplayMode(int width, int height, boolean fullscreen) throws SlickException { + if ((this.width == width) && (this.height == height) && (isFullscreen() == fullscreen)) { + return; + } + + try { + targetDisplayMode = null; + if (fullscreen) { + DisplayMode[] modes = Display.getAvailableDisplayModes(); + int freq = 0; + + for (int i=0;i= freq)) { + if ((targetDisplayMode == null) || (current.getBitsPerPixel() > targetDisplayMode.getBitsPerPixel())) { + targetDisplayMode = current; + freq = targetDisplayMode.getFrequency(); + } + } + + // if we've found a match for bpp and frequence against the + // original display mode then it's probably best to go for this one + // since it's most likely compatible with the monitor + if ((current.getBitsPerPixel() == originalDisplayMode.getBitsPerPixel()) && + (current.getFrequency() == originalDisplayMode.getFrequency())) { + targetDisplayMode = current; + break; + } + } + } + } else { + targetDisplayMode = new DisplayMode(width,height); + } + + if (targetDisplayMode == null) { + throw new SlickException("Failed to find value mode: "+width+"x"+height+" fs="+fullscreen); + } + + this.width = width; + this.height = height; + + Display.setDisplayMode(targetDisplayMode); + Display.setFullscreen(fullscreen); + + if (Display.isCreated()) { + initGL(); + enterOrtho(); + } + + if (targetDisplayMode.getBitsPerPixel() == 16) { + InternalTextureLoader.get().set16BitMode(); + } + } catch (LWJGLException e) { + throw new SlickException("Unable to setup mode "+width+"x"+height+" fullscreen="+fullscreen, e); + } + + getDelta(); + } + + /** + * Check if the display is in fullscreen mode + * + * @return True if the display is in fullscreen mode + */ + public boolean isFullscreen() { + return Display.isFullscreen(); + } + + /** + * 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 { + if (isFullscreen() == fullscreen) { + return; + } + + if (!fullscreen) { + try { + Display.setFullscreen(fullscreen); + } catch (LWJGLException e) { + throw new SlickException("Unable to set fullscreen="+fullscreen, e); + } + } else { + setDisplayMode(width, height, fullscreen); + } + getDelta(); + } + + /** + * @see org.newdawn.slick.GameContainer#setMouseCursor(java.lang.String, int, int) + */ + public void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException { + try { + Cursor cursor = CursorLoader.get().getCursor(ref, hotSpotX, hotSpotY); + Mouse.setNativeCursor(cursor); + } catch (Throwable e) { + Log.error("Failed to load and apply cursor.", e); + throw new SlickException("Failed to set mouse cursor", e); + } + } + + /** + * @see org.newdawn.slick.GameContainer#setMouseCursor(org.newdawn.slick.opengl.ImageData, int, int) + */ + public void setMouseCursor(ImageData data, int hotSpotX, int hotSpotY) throws SlickException { + try { + Cursor cursor = CursorLoader.get().getCursor(data, hotSpotX, hotSpotY); + Mouse.setNativeCursor(cursor); + } catch (Throwable e) { + Log.error("Failed to load and apply cursor.", e); + throw new SlickException("Failed to set mouse cursor", e); + } + } + + /** + * @see org.newdawn.slick.GameContainer#setMouseCursor(org.lwjgl.input.Cursor, int, int) + */ + public void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY) throws SlickException { + try { + Mouse.setNativeCursor(cursor); + } catch (Throwable e) { + Log.error("Failed to load and apply cursor.", e); + throw new SlickException("Failed to set mouse cursor", e); + } + } + + /** + * Get the closest greater power of 2 to the fold number + * + * @param fold The target number + * @return The power of 2 + */ + private int get2Fold(int fold) { + int ret = 2; + while (ret < fold) { + ret *= 2; + } + return ret; + } + + /** + * @see org.newdawn.slick.GameContainer#setMouseCursor(org.newdawn.slick.Image, int, int) + */ + public void setMouseCursor(Image image, int hotSpotX, int hotSpotY) throws SlickException { + try { + Image temp = new Image(get2Fold(image.getWidth()), get2Fold(image.getHeight())); + Graphics g = temp.getGraphics(); + + ByteBuffer buffer = BufferUtils.createByteBuffer(temp.getWidth() * temp.getHeight() * 4); + g.drawImage(image.getFlippedCopy(false, true), 0, 0); + g.flush(); + g.getArea(0,0,temp.getWidth(),temp.getHeight(),buffer); + + Cursor cursor = CursorLoader.get().getCursor(buffer, hotSpotX, hotSpotY,temp.getWidth(),image.getHeight()); + Mouse.setNativeCursor(cursor); + } catch (Throwable e) { + Log.error("Failed to load and apply cursor.", e); + throw new SlickException("Failed to set mouse cursor", e); + } + } + + /** + * @see org.newdawn.slick.GameContainer#reinit() + */ + public void reinit() throws SlickException { + InternalTextureLoader.get().clear(); + SoundStore.get().clear(); + initSystem(); + enterOrtho(); + + try { + game.init(this); + } catch (SlickException e) { + Log.error(e); + running = false; + } + } + + /** + * Try creating a display with the given format + * + * @param format The format to attempt + * @throws LWJGLException Indicates a failure to support the given format + */ + private void tryCreateDisplay(PixelFormat format) throws LWJGLException { + if (SHARED_DRAWABLE == null) + { + Display.create(format); + } + else + { + Display.create(format, SHARED_DRAWABLE); + } + } + + /** + * Start running the game + * + * @throws SlickException Indicates a failure to initialise the system + */ + public void start() throws SlickException { + try { + setup(); + + getDelta(); + while (running()) { + gameLoop(); + } + } finally { + destroy(); + } + + if (forceExit) { + System.exit(0); + } + } + + /** + * Setup the environment + * + * @throws SlickException Indicates a failure + */ + protected void setup() throws SlickException { + if (targetDisplayMode == null) { + setDisplayMode(640,480,false); + } + + Display.setTitle(game.getTitle()); + + Log.info("LWJGL Version: "+Sys.getVersion()); + Log.info("OriginalDisplayMode: "+originalDisplayMode); + Log.info("TargetDisplayMode: "+targetDisplayMode); + + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + PixelFormat format = new PixelFormat(8,8,stencil ? 8 : 0,samples); + + tryCreateDisplay(format); + supportsMultiSample = true; + } catch (Exception e) { + Display.destroy(); + + try { + PixelFormat format = new PixelFormat(8,8,stencil ? 8 : 0); + + tryCreateDisplay(format); + alphaSupport = false; + } catch (Exception e2) { + Display.destroy(); + // if we couldn't get alpha, let us know + try { + tryCreateDisplay(new PixelFormat()); + } catch (Exception e3) { + Log.error(e3); + } + } + } + + return null; + }}); + + if (!Display.isCreated()) { + throw new SlickException("Failed to initialise the LWJGL display"); + } + + initSystem(); + enterOrtho(); + + try { + getInput().initControllers(); + } catch (SlickException e) { + Log.info("Controllers not available"); + } catch (Throwable e) { + Log.info("Controllers not available"); + } + + try { + game.init(this); + } catch (SlickException e) { + Log.error(e); + running = false; + } + } + + /** + * Strategy for overloading game loop context handling + * + * @throws SlickException Indicates a game failure + */ + protected void gameLoop() throws SlickException { + int delta = getDelta(); + if (!Display.isVisible() && updateOnlyOnVisible) { + try { Thread.sleep(100); } catch (Exception e) {} + } else { + try { + updateAndRender(delta); + } catch (SlickException e) { + Log.error(e); + running = false; + return; + } + } + + updateFPS(); + + Display.update(); + + if (Display.isCloseRequested()) { + if (game.closeRequested()) { + running = false; + } + } + } + + /** + * @see org.newdawn.slick.GameContainer#setUpdateOnlyWhenVisible(boolean) + */ + public void setUpdateOnlyWhenVisible(boolean updateOnlyWhenVisible) { + updateOnlyOnVisible = updateOnlyWhenVisible; + } + + /** + * @see org.newdawn.slick.GameContainer#isUpdatingOnlyWhenVisible() + */ + public boolean isUpdatingOnlyWhenVisible() { + return updateOnlyOnVisible; + } + + /** + * @see org.newdawn.slick.GameContainer#setIcon(java.lang.String) + */ + public void setIcon(String ref) throws SlickException { + setIcons(new String[] {ref}); + } + + /** + * @see org.newdawn.slick.GameContainer#setMouseGrabbed(boolean) + */ + public void setMouseGrabbed(boolean grabbed) { + Mouse.setGrabbed(grabbed); + } + + /** + * @see org.newdawn.slick.GameContainer#isMouseGrabbed() + */ + public boolean isMouseGrabbed() { + return Mouse.isGrabbed(); + } + + /** + * @see org.newdawn.slick.GameContainer#hasFocus() + */ + public boolean hasFocus() { + // hmm, not really the right thing, talk to the LWJGL guys + return Display.isActive(); + } + + /** + * @see org.newdawn.slick.GameContainer#getScreenHeight() + */ + public int getScreenHeight() { + return originalDisplayMode.getHeight(); + } + + /** + * @see org.newdawn.slick.GameContainer#getScreenWidth() + */ + public int getScreenWidth() { + return originalDisplayMode.getWidth(); + } + + /** + * Destroy the app game container + */ + public void destroy() { + Display.destroy(); + AL.destroy(); + } + + /** + * A null stream to clear out those horrid errors + * + * @author kevin + */ + private class NullOutputStream extends OutputStream { + /** + * @see java.io.OutputStream#write(int) + */ + public void write(int b) throws IOException { + // null implemetnation + } + + } + + /** + * @see org.newdawn.slick.GameContainer#setIcons(java.lang.String[]) + */ + public void setIcons(String[] refs) throws SlickException { + ByteBuffer[] bufs = new ByteBuffer[refs.length]; + for (int i=0;i= screenAspectRatio) { + newWidth = screenWidth; + newHeight = (int) (height / ((float) width / screenWidth)); + } else { + newWidth = (int) (width / ((float) height / screenHeight)); + newHeight = screenHeight; + } + + // center new screen + int xoffset = (screenWidth - newWidth) / 2; + int yoffset = (screenHeight - newHeight) / 2; + + // scale game to match new resolution + GL11.glViewport(xoffset, yoffset, newWidth, newHeight); + + enterOrtho(); + + // fix input to match new resolution + this.getInput().setOffset( + -xoffset * (float) width / newWidth, + -yoffset * (float) height / newHeight); + + this.getInput().setScale((float) width / newWidth, + (float) height / newHeight); + + width = screenWidth; + height = screenHeight; + Display.setFullscreen(true); + } else { + // restore input + this.getInput().setOffset(0, 0); + this.getInput().setScale(1, 1); + width = AppletGameContainer.this.getWidth(); + height = AppletGameContainer.this.getHeight(); + GL11.glViewport(0, 0, width, height); + + enterOrtho(); + + Display.setFullscreen(false); + } + } catch (LWJGLException e) { + Log.error(e); + } + + } + + /** + * The running game loop + * + * @throws Exception Indicates a failure within the game's loop rather than the framework + */ + public void runloop() throws Exception { + while (running) { + int delta = getDelta(); + + updateAndRender(delta); + + updateFPS(); + Display.update(); + } + + Display.destroy(); + } + } + + /** + * A basic console to display an error message if the applet crashes. + * This will prevent the applet from just freezing in the browser + * and give the end user an a nice gui where the error message can easily + * be viewed and copied. + */ + public class ConsolePanel extends Panel { + /** The area display the console output */ + TextArea textArea = new TextArea(); + + /** + * Create a new panel to display the console output + * + * @param e The exception causing the console to be displayed + */ + public ConsolePanel(Exception e) { + setLayout(new BorderLayout()); + setBackground(Color.black); + setForeground(Color.white); + + Font consoleFont = new Font("Arial", Font.BOLD, 14); + + Label slickLabel = new Label("SLICK CONSOLE", Label.CENTER); + slickLabel.setFont(consoleFont); + add(slickLabel, BorderLayout.PAGE_START); + + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + + textArea.setText(sw.toString()); + textArea.setEditable(false); + add(textArea, BorderLayout.CENTER); + + // add a border on both sides of the console + add(new Panel(), BorderLayout.LINE_START); + add(new Panel(), BorderLayout.LINE_END); + + Panel bottomPanel = new Panel(); + bottomPanel.setLayout(new GridLayout(0, 1)); + Label infoLabel1 = new Label("An error occured while running the applet.", Label.CENTER); + Label infoLabel2 = new Label("Plese contact support to resolve this issue.", Label.CENTER); + infoLabel1.setFont(consoleFont); + infoLabel2.setFont(consoleFont); + bottomPanel.add(infoLabel1); + bottomPanel.add(infoLabel2); + add(bottomPanel, BorderLayout.PAGE_END); + } + } +} \ No newline at end of file diff --git a/lib/slick-source/org/newdawn/slick/BasicGame.java b/lib/slick-source/org/newdawn/slick/BasicGame.java new file mode 100644 index 000000000..65a269f1e --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/BasicGame.java @@ -0,0 +1,205 @@ +package org.newdawn.slick; + + +/** + * A basic implementation of a game to take out the boring bits + * + * @author kevin + */ +public abstract class BasicGame implements Game, InputListener { + /** The maximum number of controllers supported by the basic game */ + private static final int MAX_CONTROLLERS = 20; + /** The maximum number of controller buttons supported by the basic game */ + private static final int MAX_CONTROLLER_BUTTONS = 100; + /** The title of the game */ + private String title; + /** The state of the left control */ + protected boolean[] controllerLeft = new boolean[MAX_CONTROLLERS]; + /** The state of the right control */ + protected boolean[] controllerRight = new boolean[MAX_CONTROLLERS]; + /** The state of the up control */ + protected boolean[] controllerUp = new boolean[MAX_CONTROLLERS]; + /** The state of the down control */ + protected boolean[] controllerDown = new boolean[MAX_CONTROLLERS]; + /** The state of the button controlls */ + protected boolean[][] controllerButton = new boolean[MAX_CONTROLLERS][MAX_CONTROLLER_BUTTONS]; + + /** + * Create a new basic game + * + * @param title The title for the game + */ + public BasicGame(String title) { + this.title = title; + } + + /** + * @see org.newdawn.slick.InputListener#setInput(org.newdawn.slick.Input) + */ + public void setInput(Input input) { + } + + /** + * @see org.newdawn.slick.Game#closeRequested() + */ + public boolean closeRequested() { + return true; + } + + /** + * @see org.newdawn.slick.Game#getTitle() + */ + public String getTitle() { + return title; + } + + /** + * @see org.newdawn.slick.Game#init(org.newdawn.slick.GameContainer) + */ + public abstract void init(GameContainer container) throws SlickException; + + /** + * @see org.newdawn.slick.InputListener#keyPressed(int, char) + */ + public void keyPressed(int key, char c) { + } + + /** + * @see org.newdawn.slick.InputListener#keyReleased(int, char) + */ + public void keyReleased(int key, char c) { + } + + /** + * @see org.newdawn.slick.InputListener#mouseMoved(int, int, int, int) + */ + public void mouseMoved(int oldx, int oldy, int newx, int newy) { + } + + /** + * @see org.newdawn.slick.InputListener#mouseDragged(int, int, int, int) + */ + public void mouseDragged(int oldx, int oldy, int newx, int newy) { + } + + /** + * @see org.newdawn.slick.InputListener#mouseClicked(int, int, int, int) + */ + public void mouseClicked(int button, int x, int y, int clickCount) { + } + + /** + * @see org.newdawn.slick.InputListener#mousePressed(int, int, int) + */ + public void mousePressed(int button, int x, int y) { + + } + + /** + * @see org.newdawn.slick.InputListener#controllerButtonPressed(int, int) + */ + public void controllerButtonPressed(int controller, int button) { + controllerButton[controller][button] = true; + } + + /** + * @see org.newdawn.slick.InputListener#controllerButtonReleased(int, int) + */ + public void controllerButtonReleased(int controller, int button) { + controllerButton[controller][button] = false; + } + + /** + * @see org.newdawn.slick.InputListener#controllerDownPressed(int) + */ + public void controllerDownPressed(int controller) { + controllerDown[controller] = true; + } + + /** + * @see org.newdawn.slick.InputListener#controllerDownReleased(int) + */ + public void controllerDownReleased(int controller) { + controllerDown[controller] = false; + } + + /** + * @see org.newdawn.slick.InputListener#controllerLeftPressed(int) + */ + public void controllerLeftPressed(int controller) { + controllerLeft[controller] = true; + } + + /** + * @see org.newdawn.slick.InputListener#controllerLeftReleased(int) + */ + public void controllerLeftReleased(int controller) { + controllerLeft[controller] = false; + } + + /** + * @see org.newdawn.slick.InputListener#controllerRightPressed(int) + */ + public void controllerRightPressed(int controller) { + controllerRight[controller] = true; + } + + /** + * @see org.newdawn.slick.InputListener#controllerRightReleased(int) + */ + public void controllerRightReleased(int controller) { + controllerRight[controller] = false; + } + + /** + * @see org.newdawn.slick.InputListener#controllerUpPressed(int) + */ + public void controllerUpPressed(int controller) { + controllerUp[controller] = true; + } + + /** + * @see org.newdawn.slick.InputListener#controllerUpReleased(int) + */ + public void controllerUpReleased(int controller) { + controllerUp[controller] = false; + } + + /** + * @see org.newdawn.slick.InputListener#mouseReleased(int, int, int) + */ + public void mouseReleased(int button, int x, int y) { + } + + /** + * @see org.newdawn.slick.Game#update(org.newdawn.slick.GameContainer, int) + */ + public abstract void update(GameContainer container, int delta) throws SlickException; + + /** + * @see org.newdawn.slick.InputListener#mouseWheelMoved(int) + */ + public void mouseWheelMoved(int change) { + } + + /** + * @see org.newdawn.slick.InputListener#isAcceptingInput() + */ + public boolean isAcceptingInput() { + return true; + } + + /** + * @see org.newdawn.slick.InputListener#inputEnded() + */ + public void inputEnded() { + + } + + /** + * @see org.newdawn.slick.ControlledInputReciever#inputStarted() + */ + public void inputStarted() { + + } +} diff --git a/lib/slick-source/org/newdawn/slick/BigImage.java b/lib/slick-source/org/newdawn/slick/BigImage.java new file mode 100644 index 000000000..8da20d23c --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/BigImage.java @@ -0,0 +1,768 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import org.lwjgl.BufferUtils; +import org.newdawn.slick.opengl.ImageData; +import org.newdawn.slick.opengl.ImageDataFactory; +import org.newdawn.slick.opengl.LoadableImageData; +import org.newdawn.slick.opengl.Texture; +import org.newdawn.slick.opengl.renderer.SGL; +import org.newdawn.slick.opengl.renderer.Renderer; +import org.newdawn.slick.util.OperationNotSupportedException; +import org.newdawn.slick.util.ResourceLoader; + +/** + * An image implementation that handles loaded images that are larger than the + * maximum texture size supported by the card. In most cases it makes sense + * to make sure all of your image resources are less than 512x512 in size when + * using OpenGL. However, in the rare circumstances where this isn't possible + * this implementation can be used to draw a tiled version of the image built + * from several smaller textures. + * + * This implementation does come with limitations and some performance impact + * however - so use only when absolutely required. + * + * TODO: The code in here isn't pretty, really needs revisiting with a comment stick. + * + * @author kevin + */ +public class BigImage extends Image { + /** The renderer to use for all GL operations */ + protected static SGL GL = Renderer.get(); + + /** + * Get the maximum size of an image supported by the underlying + * hardware. + * + * @return The maximum size of the textures supported by the underlying + * hardware. + */ + public static final int getMaxSingleImageSize() { + IntBuffer buffer = BufferUtils.createIntBuffer(16); + GL.glGetInteger(SGL.GL_MAX_TEXTURE_SIZE, buffer); + + return buffer.get(0); + } + + /** The last image that we put into "in use" mode */ + private static Image lastBind; + + /** The images building up this sub-image */ + private Image[][] images; + /** The number of images on the xaxis */ + private int xcount; + /** The number of images on the yaxis */ + private int ycount; + /** The real width of the whole image - maintained even when scaled */ + private int realWidth; + /** The real hieght of the whole image - maintained even when scaled */ + private int realHeight; + + /** + * Create a new big image. Empty contructor for cloning only + */ + private BigImage() { + inited = true; + } + + /** + * Create a new big image by loading it from the specified reference + * + * @param ref The reference to the image to load + * @throws SlickException Indicates we were unable to locate the resource + */ + public BigImage(String ref) throws SlickException { + this(ref, Image.FILTER_NEAREST); + } + + /** + * Create a new big image by loading it from the specified reference + * + * @param ref The reference to the image to load + * @param filter The image filter to apply (@see #Image.FILTER_NEAREST) + * @throws SlickException Indicates we were unable to locate the resource + */ + public BigImage(String ref,int filter) throws SlickException { + + build(ref, filter, getMaxSingleImageSize()); + } + + /** + * Create a new big image by loading it from the specified reference + * + * @param ref The reference to the image to load + * @param filter The image filter to apply (@see #Image.FILTER_NEAREST) + * @param tileSize The maximum size of the tiles to use to build the bigger image + * @throws SlickException Indicates we were unable to locate the resource + */ + public BigImage(String ref, int filter, int tileSize) throws SlickException { + build(ref, filter, tileSize); + } + + /** + * Create a new big image by loading it from the specified image data + * + * @param data The pixelData to use to create the image + * @param imageBuffer The buffer containing texture data + * @param filter The image filter to apply (@see #Image.FILTER_NEAREST) + */ + public BigImage(LoadableImageData data, ByteBuffer imageBuffer, int filter) { + build(data, imageBuffer, filter, getMaxSingleImageSize()); + } + + /** + * Create a new big image by loading it from the specified image data + * + * @param data The pixelData to use to create the image + * @param imageBuffer The buffer containing texture data + * @param filter The image filter to apply (@see #Image.FILTER_NEAREST) + * @param tileSize The maximum size of the tiles to use to build the bigger image + */ + public BigImage(LoadableImageData data, ByteBuffer imageBuffer, int filter, int tileSize) { + build(data, imageBuffer, filter, tileSize); + } + + /** + * Get a sub tile of this big image. Useful for debugging + * + * @param x The x tile index + * @param y The y tile index + * @return The image used for this tile + */ + public Image getTile(int x, int y) { + return images[x][y]; + } + + /** + * Create a new big image by loading it from the specified reference + * + * @param ref The reference to the image to load + * @param filter The image filter to apply (@see #Image.FILTER_NEAREST) + * @param tileSize The maximum size of the tiles to use to build the bigger image + * @throws SlickException Indicates we were unable to locate the resource + */ + private void build(String ref, int filter, int tileSize) throws SlickException { + try { + final LoadableImageData data = ImageDataFactory.getImageDataFor(ref); + final ByteBuffer imageBuffer = data.loadImage(ResourceLoader.getResourceAsStream(ref), false, null); + build(data, imageBuffer, filter, tileSize); + } catch (IOException e) { + throw new SlickException("Failed to load: "+ref, e); + } + } + + /** + * Create an big image from a image data source. + * + * @param data The pixelData to use to create the image + * @param imageBuffer The buffer containing texture data + * @param filter The filter to use when scaling this image + * @param tileSize The maximum size of the tiles to use to build the bigger image + */ + private void build(final LoadableImageData data, final ByteBuffer imageBuffer, int filter, int tileSize) { + final int dataWidth = data.getTexWidth(); + final int dataHeight = data.getTexHeight(); + + realWidth = width = data.getWidth(); + realHeight = height = data.getHeight(); + + if ((dataWidth <= tileSize) && (dataHeight <= tileSize)) { + images = new Image[1][1]; + ImageData tempData = new ImageData() { + public int getDepth() { + return data.getDepth(); + } + + public int getHeight() { + return dataHeight; + } + + public ByteBuffer getImageBufferData() { + return imageBuffer; + } + + public int getTexHeight() { + return dataHeight; + } + + public int getTexWidth() { + return dataWidth; + } + + public int getWidth() { + return dataWidth; + } + }; + images[0][0] = new Image(tempData, filter); + xcount = 1; + ycount = 1; + inited = true; + return; + } + + xcount = ((realWidth-1) / tileSize) + 1; + ycount = ((realHeight-1) / tileSize) + 1; + + images = new Image[xcount][ycount]; + int components = data.getDepth() / 8; + + for (int x=0;x 0) && (targetHeight > 0)) { + Image subImage = current.getSubImage((int) (targetX1 - xp), (int) (targetY1 - yp), + (targetX2 - targetX1), + (targetY2 - targetY1)); + foundStart = true; + image.images[startx][starty] = subImage; + starty++; + image.ycount = Math.max(image.ycount, starty); + } + + yp += current.getHeight(); + if (yt == ycount - 1) { + xp += current.getWidth(); + } + } + if (foundStart) { + startx++; + image.xcount++; + } + } + + return image; + } + + /** + * Not supported in BigImage + * + * @see org.newdawn.slick.Image#getTexture() + */ + public Texture getTexture() { + throw new OperationNotSupportedException("Can't use big images as offscreen buffers"); + } + + /** + * @see org.newdawn.slick.Image#initImpl() + */ + protected void initImpl() { + throw new OperationNotSupportedException("Can't use big images as offscreen buffers"); + } + + /** + * @see org.newdawn.slick.Image#reinit() + */ + protected void reinit() { + throw new OperationNotSupportedException("Can't use big images as offscreen buffers"); + } + + /** + * Not supported in BigImage + * + * @see org.newdawn.slick.Image#setTexture(org.newdawn.slick.opengl.Texture) + */ + public void setTexture(Texture texture) { + throw new OperationNotSupportedException("Can't use big images as offscreen buffers"); + } + + /** + * Get a sub-image that builds up this image. Note that the offsets + * used will depend on the maximum texture size on the OpenGL hardware + * + * @param offsetX The x position of the image to return + * @param offsetY The y position of the image to return + * @return The image at the specified offset into the big image + */ + public Image getSubImage(int offsetX, int offsetY) { + return images[offsetX][offsetY]; + } + + /** + * Get a count of the number images that build this image up horizontally + * + * @return The number of sub-images across the big image + */ + public int getHorizontalImageCount() { + return xcount; + } + + /** + * Get a count of the number images that build this image up vertically + * + * @return The number of sub-images down the big image + */ + public int getVerticalImageCount() { + return ycount; + } + + /** + * @see org.newdawn.slick.Image#toString() + */ + public String toString() { + return "[BIG IMAGE]"; + } + + /** + * Destroy the image and release any native resources. + * Calls on a destroyed image have undefined results + */ + public void destroy() throws SlickException { + for (int tx=0;tx 1.0) + * @param g The green component of the colour (0.0 -> 1.0) + * @param b The blue component of the colour (0.0 -> 1.0) + */ + public Color(float r,float g,float b) { + this.r = r; + this.g = g; + this.b = b; + this.a = 1; + } + + /** + * Create a 4 component colour + * + * @param r The red component of the colour (0.0 -> 1.0) + * @param g The green component of the colour (0.0 -> 1.0) + * @param b The blue component of the colour (0.0 -> 1.0) + * @param a The alpha component of the colour (0.0 -> 1.0) + */ + public Color(float r,float g,float b,float a) { + this.r = Math.min(r, 1); + this.g = Math.min(g, 1); + this.b = Math.min(b, 1); + this.a = Math.min(a, 1); + } + + /** + * Create a 3 component colour + * + * @param r The red component of the colour (0 -> 255) + * @param g The green component of the colour (0 -> 255) + * @param b The blue component of the colour (0 -> 255) + */ + public Color(int r,int g,int b) { + this.r = r / 255.0f; + this.g = g / 255.0f; + this.b = b / 255.0f; + this.a = 1; + } + + /** + * Create a 4 component colour + * + * @param r The red component of the colour (0 -> 255) + * @param g The green component of the colour (0 -> 255) + * @param b The blue component of the colour (0 -> 255) + * @param a The alpha component of the colour (0 -> 255) + */ + public Color(int r,int g,int b,int a) { + this.r = r / 255.0f; + this.g = g / 255.0f; + this.b = b / 255.0f; + this.a = a / 255.0f; + } + + /** + * Create a colour from an evil integer packed 0xAARRGGBB. If AA + * is specified as zero then it will be interpreted as unspecified + * and hence a value of 255 will be recorded. + * + * @param value The value to interpret for the colour + */ + public Color(int value) { + int r = (value & 0x00FF0000) >> 16; + int g = (value & 0x0000FF00) >> 8; + int b = (value & 0x000000FF); + int a = (value & 0xFF000000) >> 24; + + if (a < 0) { + a += 256; + } + if (a == 0) { + a = 255; + } + + this.r = r / 255.0f; + this.g = g / 255.0f; + this.b = b / 255.0f; + this.a = a / 255.0f; + } + + /** + * Decode a number in a string and process it as a colour + * reference. + * + * @param nm The number string to decode + * @return The color generated from the number read + */ + public static Color decode(String nm) { + return new Color(Integer.decode(nm).intValue()); + } + + /** + * Bind this colour to the GL context + */ + public void bind() { + GL.glColor4f(r,g,b,a); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return ((int) (r+g+b+a)*255); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object other) { + if (other instanceof Color) { + Color o = (Color) other; + return ((o.r == r) && (o.g == g) && (o.b == b) && (o.a == a)); + } + + return false; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "Color ("+r+","+g+","+b+","+a+")"; + } + + /** + * Make a darker instance of this colour + * + * @return The darker version of this colour + */ + public Color darker() { + return darker(0.5f); + } + + /** + * Make a darker instance of this colour + * + * @param scale The scale down of RGB (i.e. if you supply 0.03 the colour will be darkened by 3%) + * @return The darker version of this colour + */ + public Color darker(float scale) { + scale = 1 - scale; + Color temp = new Color(r * scale,g * scale,b * scale,a); + + return temp; + } + + /** + * Make a brighter instance of this colour + * + * @return The brighter version of this colour + */ + public Color brighter() { + return brighter(0.2f); + } + + /** + * Get the red byte component of this colour + * + * @return The red component (range 0-255) + */ + public int getRed() { + return (int) (r * 255); + } + + /** + * Get the green byte component of this colour + * + * @return The green component (range 0-255) + */ + public int getGreen() { + return (int) (g * 255); + } + + /** + * Get the blue byte component of this colour + * + * @return The blue component (range 0-255) + */ + public int getBlue() { + return (int) (b * 255); + } + + /** + * Get the alpha byte component of this colour + * + * @return The alpha component (range 0-255) + */ + public int getAlpha() { + return (int) (a * 255); + } + + /** + * Get the red byte component of this colour + * + * @return The red component (range 0-255) + */ + public int getRedByte() { + return (int) (r * 255); + } + + /** + * Get the green byte component of this colour + * + * @return The green component (range 0-255) + */ + public int getGreenByte() { + return (int) (g * 255); + } + + /** + * Get the blue byte component of this colour + * + * @return The blue component (range 0-255) + */ + public int getBlueByte() { + return (int) (b * 255); + } + + /** + * Get the alpha byte component of this colour + * + * @return The alpha component (range 0-255) + */ + public int getAlphaByte() { + return (int) (a * 255); + } + + /** + * Make a brighter instance of this colour + * + * @param scale The scale up of RGB (i.e. if you supply 0.03 the colour will be brightened by 3%) + * @return The brighter version of this colour + */ + public Color brighter(float scale) { + scale += 1; + Color temp = new Color(r * scale,g * scale,b * scale,a); + + return temp; + } + + /** + * Multiply this color by another + * + * @param c the other color + * @return product of the two colors + */ + public Color multiply(Color c) { + return new Color(r * c.r, g * c.g, b * c.b, a * c.a); + } + + /** + * Add another colour to this one + * + * @param c The colour to add + */ + public void add(Color c) { + r += c.r; + g += c.g; + b += c.b; + a += c.a; + } + + /** + * Scale the components of the colour by the given value + * + * @param value The value to scale by + */ + public void scale(float value) { + r *= value; + g *= value; + b *= value; + a *= value; + } + + /** + * Add another colour to this one + * + * @param c The colour to add + * @return The copy which has had the color added to it + */ + public Color addToCopy(Color c) { + Color copy = new Color(r,g,b,a); + copy.r += c.r; + copy.g += c.g; + copy.b += c.b; + copy.a += c.a; + + return copy; + } + + /** + * Scale the components of the colour by the given value + * + * @param value The value to scale by + * @return The copy which has been scaled + */ + public Color scaleCopy(float value) { + Color copy = new Color(r,g,b,a); + copy.r *= value; + copy.g *= value; + copy.b *= value; + copy.a *= value; + + return copy; + } +} diff --git a/lib/slick-source/org/newdawn/slick/ControlledInputReciever.java b/lib/slick-source/org/newdawn/slick/ControlledInputReciever.java new file mode 100644 index 000000000..f4fc32ebd --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/ControlledInputReciever.java @@ -0,0 +1,42 @@ +package org.newdawn.slick; + +/** + * Description of any class capable of recieving and controlling it's own + * reception of input + * + * You'll shouldn't really need to implement this one for your self, use one of the sub-interfaces: + * + * {@link InputListener} + * {@link MouseListener} + * {@link KeyListener} + * {@link ControllerListener} + * + * @author kevin + */ +public interface ControlledInputReciever { + + /** + * Set the input that events are being sent from + * + * @param input The input instance sending events + */ + public abstract void setInput(Input input); + + /** + * Check if this input listener is accepting input + * + * @return True if the input listener should recieve events + */ + public abstract boolean isAcceptingInput(); + + /** + * Notification that all input events have been sent for this frame + */ + public abstract void inputEnded(); + + /** + * Notification that input is about to be processed + */ + public abstract void inputStarted(); + +} \ No newline at end of file diff --git a/lib/slick-source/org/newdawn/slick/ControllerListener.java b/lib/slick-source/org/newdawn/slick/ControllerListener.java new file mode 100644 index 000000000..44d7cec69 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/ControllerListener.java @@ -0,0 +1,102 @@ +package org.newdawn.slick; + +/** + * Description of classes capable of responding to controller events + * + * @author kevin + */ +public interface ControllerListener extends ControlledInputReciever { + + /** + * Notification that the left control has been pressed on + * the controller. + * + * @param controller The index of the controller on which the control + * was pressed. + */ + public abstract void controllerLeftPressed(int controller); + + /** + * Notification that the left control has been released on + * the controller. + * + * @param controller The index of the controller on which the control + * was released. + */ + public abstract void controllerLeftReleased(int controller); + + /** + * Notification that the right control has been pressed on + * the controller. + * + * @param controller The index of the controller on which the control + * was pressed. + */ + public abstract void controllerRightPressed(int controller); + + /** + * Notification that the right control has been released on + * the controller. + * + * @param controller The index of the controller on which the control + * was released. + */ + public abstract void controllerRightReleased(int controller); + + /** + * Notification that the up control has been pressed on + * the controller. + * + * @param controller The index of the controller on which the control + * was pressed. + */ + public abstract void controllerUpPressed(int controller); + + /** + * Notification that the up control has been released on + * the controller. + * + * @param controller The index of the controller on which the control + * was released. + */ + public abstract void controllerUpReleased(int controller); + + /** + * Notification that the down control has been pressed on + * the controller. + * + * @param controller The index of the controller on which the control + * was pressed. + */ + public abstract void controllerDownPressed(int controller); + + /** + * Notification that the down control has been released on + * the controller. + * + * @param controller The index of the controller on which the control + * was released. + */ + public abstract void controllerDownReleased(int controller); + + /** + * Notification that a button control has been pressed on + * the controller. + * + * @param controller The index of the controller on which the control + * was pressed. + * @param button The index of the button pressed (starting at 1) + */ + public abstract void controllerButtonPressed(int controller, int button); + + /** + * Notification that a button control has been released on + * the controller. + * + * @param controller The index of the controller on which the control + * was released. + * @param button The index of the button released (starting at 1) + */ + public abstract void controllerButtonReleased(int controller, int button); + +} \ No newline at end of file diff --git a/lib/slick-source/org/newdawn/slick/Font.java b/lib/slick-source/org/newdawn/slick/Font.java new file mode 100644 index 000000000..bf6781ae9 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Font.java @@ -0,0 +1,65 @@ +package org.newdawn.slick; + + +/** + * The proprites of any font implementation + * + * @author Kevin Glass + */ +public interface Font { + /** + * Get the width of the given string + * + * @param str The string to obtain the rendered with of + * @return The width of the given string + */ + public abstract int getWidth(String str); + + /** + * Get the height of the given string + * + * @param str The string to obtain the rendered with of + * @return The width of the given string + */ + public abstract int getHeight(String str); + + /** + * Get the maximum height of any line drawn by this font + * + * @return The maxium height of any line drawn by this font + */ + public int getLineHeight(); + + /** + * Draw a string to the screen + * + * @param x The x location at which to draw the string + * @param y The y location at which to draw the string + * @param text The text to be displayed + */ + public abstract void drawString(float x, float y, String text); + + /** + * Draw a string to the screen + * + * @param x The x location at which to draw the string + * @param y The y location at which to draw the string + * @param text The text to be displayed + * @param col The colour to draw with + */ + public abstract void drawString(float x, float y, String text, Color col); + + + /** + * Draw part of a string to the screen. Note that this will + * still position the text as though it's part of the bigger string. + * + * @param x The x location at which to draw the string + * @param y The y location at which to draw the string + * @param text The text to be displayed + * @param col The colour to draw with + * @param startIndex The index of the first character to draw + * @param endIndex The index of the last character from the string to draw + */ + public abstract void drawString(float x, float y, String text, Color col, int startIndex, int endIndex); +} \ No newline at end of file diff --git a/lib/slick-source/org/newdawn/slick/Game.java b/lib/slick-source/org/newdawn/slick/Game.java new file mode 100644 index 000000000..758839685 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Game.java @@ -0,0 +1,55 @@ +package org.newdawn.slick; + +/** + * The main game interface that should be implemented by any game being developed + * using the container system. There will be some utility type sub-classes as development + * continues. + * + * @see org.newdawn.slick.BasicGame + * + * @author kevin + */ +public interface Game { + /** + * Initialise the game. This can be used to load static resources. It's called + * before the game loop starts + * + * @param container The container holding the game + * @throws SlickException Throw to indicate an internal error + */ + public void init(GameContainer container) throws SlickException; + + /** + * Update the game logic here. No rendering should take place in this method + * though it won't do any harm. + * + * @param container The container holing this game + * @param delta The amount of time thats passed since last update in milliseconds + * @throws SlickException Throw to indicate an internal error + */ + public void update(GameContainer container, int delta) throws SlickException; + + /** + * Render the game's screen here. + * + * @param container The container holing this game + * @param g The graphics context that can be used to render. However, normal rendering + * routines can also be used. + * @throws SlickException Throw to indicate a internal error + */ + public void render(GameContainer container, Graphics g) throws SlickException; + + /** + * Notification that a game close has been requested + * + * @return True if the game should close + */ + public boolean closeRequested(); + + /** + * Get the title of this game + * + * @return The title of the game + */ + public String getTitle(); +} diff --git a/lib/slick-source/org/newdawn/slick/GameContainer.java b/lib/slick-source/org/newdawn/slick/GameContainer.java new file mode 100644 index 000000000..27e720824 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/GameContainer.java @@ -0,0 +1,868 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.util.Properties; + +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.input.Cursor; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.Drawable; +import org.lwjgl.opengl.Pbuffer; +import org.lwjgl.opengl.PixelFormat; +import org.newdawn.slick.gui.GUIContext; +import org.newdawn.slick.openal.SoundStore; +import org.newdawn.slick.opengl.CursorLoader; +import org.newdawn.slick.opengl.ImageData; +import org.newdawn.slick.opengl.renderer.Renderer; +import org.newdawn.slick.opengl.renderer.SGL; +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +/** + * 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.error("Unable to determine Slick build number"); + return -1; + } + } + + /** + * Get the default system font + * + * @return The default system font + */ + 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 + */ + public abstract int getScreenWidth(); + + /** + * Get the height of the standard screen resolution + * + * @return The screen height + */ + public abstract int getScreenHeight(); + + /** + * Get the width of the game canvas + * + * @return The width of the game canvas + */ + public int getWidth() { + return width; + } + + /** + * Get the height of the game canvas + * + * @return The height of the game canvas + */ + 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 + * + * 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 -> right) + * @param y The y-coordinate of the cursor hotspot (bottom -> 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 + */ + public abstract void setDefaultMouseCursor(); + + /** + * Get the input system + * + * @return The input system available to this game container + */ + 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 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 - check the game code."); + } + } + } 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 - check the game code."); + } + 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); + } +} diff --git a/lib/slick-source/org/newdawn/slick/Graphics.java b/lib/slick-source/org/newdawn/slick/Graphics.java new file mode 100644 index 000000000..5f3244f66 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Graphics.java @@ -0,0 +1,1780 @@ +package org.newdawn.slick; + +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; + +import org.lwjgl.BufferUtils; +import org.newdawn.slick.geom.Rectangle; +import org.newdawn.slick.geom.Shape; +import org.newdawn.slick.geom.ShapeRenderer; +import org.newdawn.slick.opengl.TextureImpl; +import org.newdawn.slick.opengl.renderer.LineStripRenderer; +import org.newdawn.slick.opengl.renderer.Renderer; +import org.newdawn.slick.opengl.renderer.SGL; +import org.newdawn.slick.util.FastTrig; +import org.newdawn.slick.util.Log; + +/** + * A graphics context that can be used to render primatives to the accelerated + * canvas provided by LWJGL. + * + * @author kevin + */ +public class Graphics { + /** The renderer to use for all GL operations */ + protected static SGL GL = Renderer.get(); + /** The renderer to use line strips */ + private static LineStripRenderer LSR = Renderer.getLineStripRenderer(); + + /** The normal drawing mode */ + public static int MODE_NORMAL = 1; + + /** Draw to the alpha map */ + public static int MODE_ALPHA_MAP = 2; + + /** Draw using the alpha blending */ + public static int MODE_ALPHA_BLEND = 3; + + /** Draw multiplying the source and destination colours */ + public static int MODE_COLOR_MULTIPLY = 4; + + /** Draw adding the existing colour to the new colour */ + public static int MODE_ADD = 5; + + /** Draw blending the new image into the old one by a factor of it's colour */ + public static int MODE_SCREEN = 6; + + /** The default number of segments that will be used when drawing an oval */ + private static final int DEFAULT_SEGMENTS = 50; + + /** The last graphics context in use */ + protected static Graphics currentGraphics = null; + + /** The default font to use */ + protected static Font DEFAULT_FONT; + + /** The last set scale */ + private float sx = 1; + /** The last set scale */ + private float sy = 1; + + /** + * Set the current graphics context in use + * + * @param current The graphics context that should be considered current + */ + public static void setCurrent(Graphics current) { + if (currentGraphics != current) { + if (currentGraphics != null) { + currentGraphics.disable(); + } + currentGraphics = current; + currentGraphics.enable(); + } + } + + /** The font in use */ + private Font font; + + /** The current color */ + private Color currentColor = Color.white; + + /** The width of the screen */ + protected int screenWidth; + + /** The height of the screen */ + protected int screenHeight; + + /** True if the matrix has been pushed to the stack */ + private boolean pushed; + + /** The graphics context clipping */ + private Rectangle clip; + + /** Buffer used for setting the world clip */ + private DoubleBuffer worldClip = BufferUtils.createDoubleBuffer(4); + + /** The buffer used to read a screen pixel */ + private ByteBuffer readBuffer = BufferUtils.createByteBuffer(4); + + /** True if we're antialias */ + private boolean antialias; + + /** The world clip recorded since last set */ + private Rectangle worldClipRecord; + + /** The current drawing mode */ + private int currentDrawingMode = MODE_NORMAL; + + /** The current line width */ + private float lineWidth = 1; + + /** The matrix stack */ + private ArrayList stack = new ArrayList(); + /** The index into the stack we're using */ + private int stackIndex; + + /** + * Default constructor for sub-classes + */ + public Graphics() { + } + + /** + * Create a new graphics context. Only the container should be doing this + * really + * + * @param width + * The width of the screen for this context + * @param height + * The height of the screen for this context + */ + public Graphics(int width, int height) { + if (DEFAULT_FONT == null) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + DEFAULT_FONT = new AngelCodeFont( + "org/newdawn/slick/data/defaultfont.fnt", + "org/newdawn/slick/data/defaultfont.png"); + } catch (SlickException e) { + Log.error(e); + } + return null; // nothing to return + } + }); + } + + this.font = DEFAULT_FONT; + screenWidth = width; + screenHeight = height; + } + + /** + * Set the dimensions considered by the graphics context + * + * @param width The width of the graphics context + * @param height The height of the graphics context + */ + void setDimensions(int width, int height) { + screenWidth = width; + screenHeight = height; + } + + /** + * Set the drawing mode to use. This mode defines how pixels are drawn to + * the graphics context. It can be used to draw into the alpha map. + * + * The mode supplied should be one of {@link Graphics#MODE_NORMAL} or + * {@link Graphics#MODE_ALPHA_MAP} or {@link Graphics#MODE_ALPHA_BLEND} + * + * @param mode + * The mode to apply. + */ + public void setDrawMode(int mode) { + predraw(); + currentDrawingMode = mode; + if (currentDrawingMode == MODE_NORMAL) { + GL.glEnable(SGL.GL_BLEND); + GL.glColorMask(true, true, true, true); + GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA); + } + if (currentDrawingMode == MODE_ALPHA_MAP) { + GL.glDisable(SGL.GL_BLEND); + GL.glColorMask(false, false, false, true); + } + if (currentDrawingMode == MODE_ALPHA_BLEND) { + GL.glEnable(SGL.GL_BLEND); + GL.glColorMask(true, true, true, false); + GL.glBlendFunc(SGL.GL_DST_ALPHA, SGL.GL_ONE_MINUS_DST_ALPHA); + } + if (currentDrawingMode == MODE_COLOR_MULTIPLY) { + GL.glEnable(SGL.GL_BLEND); + GL.glColorMask(true, true, true, true); + GL.glBlendFunc(SGL.GL_ONE_MINUS_SRC_COLOR, SGL.GL_SRC_COLOR); + } + if (currentDrawingMode == MODE_ADD) { + GL.glEnable(SGL.GL_BLEND); + GL.glColorMask(true, true, true, true); + GL.glBlendFunc(SGL.GL_ONE, SGL.GL_ONE); + } + if (currentDrawingMode == MODE_SCREEN) { + GL.glEnable(SGL.GL_BLEND); + GL.glColorMask(true, true, true, true); + GL.glBlendFunc(SGL.GL_ONE, SGL.GL_ONE_MINUS_SRC_COLOR); + } + postdraw(); + } + + /** + * Clear the state of the alpha map across the entire screen. This sets + * alpha to 0 everywhere, meaning in {@link Graphics#MODE_ALPHA_BLEND} + * nothing will be drawn. + */ + public void clearAlphaMap() { + pushTransform(); + GL.glLoadIdentity(); + + int originalMode = currentDrawingMode; + setDrawMode(MODE_ALPHA_MAP); + setColor(new Color(0,0,0,0)); + fillRect(0, 0, screenWidth, screenHeight); + setColor(currentColor); + setDrawMode(originalMode); + + popTransform(); + } + + /** + * Must be called before all OpenGL operations to maintain context for + * dynamic images + */ + private void predraw() { + setCurrent(this); + } + + /** + * Must be called after all OpenGL operations to maintain context for + * dynamic images + */ + private void postdraw() { + } + + /** + * Enable rendering to this graphics context + */ + protected void enable() { + } + + /** + * Flush this graphics context to the underlying rendering context + */ + public void flush() { + if (currentGraphics == this) { + currentGraphics.disable(); + currentGraphics = null; + } + } + + /** + * Disable rendering to this graphics context + */ + protected void disable() { + } + + /** + * Get the current font + * + * @return The current font + */ + public Font getFont() { + return font; + } + + /** + * Set the background colour of the graphics context. This colour + * is used when clearing the context. Note that calling this method + * alone does not cause the context to be cleared. + * + * @param color + * The background color of the graphics context + */ + public void setBackground(Color color) { + predraw(); + GL.glClearColor(color.r, color.g, color.b, color.a); + postdraw(); + } + + /** + * Get the current graphics context background color + * + * @return The background color of this graphics context + */ + public Color getBackground() { + predraw(); + FloatBuffer buffer = BufferUtils.createFloatBuffer(16); + GL.glGetFloat(SGL.GL_COLOR_CLEAR_VALUE, buffer); + postdraw(); + + return new Color(buffer); + } + + /** + * Clear the graphics context + */ + public void clear() { + predraw(); + GL.glClear(SGL.GL_COLOR_BUFFER_BIT); + postdraw(); + } + + /** + * Reset the transformation on this graphics context + */ + public void resetTransform() { + sx = 1; + sy = 1; + + if (pushed) { + predraw(); + GL.glPopMatrix(); + pushed = false; + postdraw(); + } + } + + /** + * Check if we've pushed the previous matrix, if not then push it now. + */ + private void checkPush() { + if (!pushed) { + predraw(); + GL.glPushMatrix(); + pushed = true; + postdraw(); + } + } + + /** + * Apply a scaling factor to everything drawn on the graphics context + * + * @param sx + * The scaling factor to apply to the x axis + * @param sy + * The scaling factor to apply to the y axis + */ + public void scale(float sx, float sy) { + this.sx = this.sx * sx; + this.sy = this.sy * sy; + + checkPush(); + + predraw(); + GL.glScalef(sx, sy, 1); + postdraw(); + } + + /** + * Apply a rotation to everything draw on the graphics context + * + * @param rx + * The x coordinate of the center of rotation + * @param ry + * The y coordinate of the center of rotation + * @param ang + * The angle (in degrees) to rotate by + */ + public void rotate(float rx, float ry, float ang) { + checkPush(); + + predraw(); + translate(rx, ry); + GL.glRotatef(ang, 0, 0, 1); + translate(-rx, -ry); + postdraw(); + } + + /** + * Apply a translation to everything drawn to the context + * + * @param x + * The amount to translate on the x-axis + * @param y + * The amount of translate on the y-axis + */ + public void translate(float x, float y) { + checkPush(); + + predraw(); + GL.glTranslatef(x, y, 0); + postdraw(); + } + + /** + * Set the font to be used when rendering text + * + * @param font + * The font to be used when rendering text + */ + public void setFont(Font font) { + this.font = font; + } + + /** + * Reset to using the default font for this context + */ + public void resetFont() { + font = DEFAULT_FONT; + } + + /** + * Set the color to use when rendering to this context + * + * @param color + * The color to use when rendering to this context + */ + public void setColor(Color color) { + if (color == null) { + return; + } + + currentColor = new Color(color); + predraw(); + currentColor.bind(); + postdraw(); + } + + /** + * Get the color in use by this graphics context + * + * @return The color in use by this graphics context + */ + public Color getColor() { + return new Color(currentColor); + } + + /** + * Draw a line on the canvas in the current colour + * + * @param x1 + * The x coordinate of the start point + * @param y1 + * The y coordinate of the start point + * @param x2 + * The x coordinate of the end point + * @param y2 + * The y coordinate of the end point + */ + public void drawLine(float x1, float y1, float x2, float y2) { + float lineWidth = this.lineWidth - 1; + + if (LSR.applyGLLineFixes()) { + if (x1 == x2) { + if (y1 > y2) { + float temp = y2; + y2 = y1; + y1 = temp; + } + float step = 1 / sy; + lineWidth = lineWidth / sy; + fillRect(x1-(lineWidth/2.0f),y1-(lineWidth/2.0f),lineWidth+step,(y2-y1)+lineWidth+step); + return; + } else if (y1 == y2) { + if (x1 > x2) { + float temp = x2; + x2 = x1; + x1 = temp; + } + float step = 1 / sx; + lineWidth = lineWidth / sx; + fillRect(x1-(lineWidth/2.0f),y1-(lineWidth/2.0f),(x2-x1)+lineWidth+step,lineWidth+step); + return; + } + } + + predraw(); + currentColor.bind(); + TextureImpl.bindNone(); + + LSR.start(); + LSR.vertex(x1,y1); + LSR.vertex(x2,y2); + LSR.end(); + + postdraw(); + } + + /** + * Draw the outline of the given shape. + * + * @param shape + * The shape to draw. + * @param fill + * The fill type to apply + */ + public void draw(Shape shape, ShapeFill fill) { + predraw(); + TextureImpl.bindNone(); + + ShapeRenderer.draw(shape, fill); + + currentColor.bind(); + postdraw(); + } + + /** + * Draw the the given shape filled in. + * + * @param shape + * The shape to fill. + * @param fill + * The fill type to apply + */ + public void fill(Shape shape, ShapeFill fill) { + predraw(); + TextureImpl.bindNone(); + + ShapeRenderer.fill(shape, fill); + + currentColor.bind(); + postdraw(); + } + + /** + * Draw the outline of the given shape. + * + * @param shape + * The shape to draw. + */ + public void draw(Shape shape) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + ShapeRenderer.draw(shape); + + postdraw(); + } + + /** + * Draw the the given shape filled in. + * + * @param shape + * The shape to fill. + */ + public void fill(Shape shape) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + ShapeRenderer.fill(shape); + + postdraw(); + } + + /** + * Draw the the given shape filled in with a texture + * + * @param shape + * The shape to texture. + * @param image + * The image to tile across the shape + */ + public void texture(Shape shape, Image image) { + texture(shape, image, 0.01f, 0.01f, false); + } + + /** + * Draw the the given shape filled in with a texture + * + * @param shape + * The shape to texture. + * @param image + * The image to tile across the shape + * @param fill + * The shape fill to apply + */ + public void texture(Shape shape, Image image, ShapeFill fill) { + texture(shape, image, 0.01f, 0.01f, fill); + } + + /** + * Draw the the given shape filled in with a texture + * + * @param shape + * The shape to texture. + * @param image + * The image to tile across the shape + * @param fit + * True if we want to fit the image on to the shape + */ + public void texture(Shape shape, Image image, boolean fit) { + if (fit) { + texture(shape, image, 1, 1, true); + } else { + texture(shape, image, 0.01f, 0.01f, false); + } + } + + /** + * Draw the the given shape filled in with a texture + * + * @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 void texture(Shape shape, Image image, float scaleX, float scaleY) { + texture(shape, image, scaleX, scaleY, false); + } + + /** + * Draw the the given shape filled in with a texture + * + * @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 fit + * True if we want to fit the image on to the shape + */ + public void texture(Shape shape, Image image, float scaleX, float scaleY, + boolean fit) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + if (fit) { + ShapeRenderer.textureFit(shape, image, scaleX, scaleY); + } else { + ShapeRenderer.texture(shape, image, scaleX, scaleY); + } + + postdraw(); + } + + /** + * Draw the the given shape filled in with a texture + * + * @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 shape fill to apply + */ + public void texture(Shape shape, Image image, float scaleX, float scaleY, + ShapeFill fill) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + ShapeRenderer.texture(shape, image, scaleX, scaleY, fill); + + postdraw(); + } + + /** + * Draw a rectangle to the canvas in the current colour + * + * @param x1 + * The x coordinate of the top left corner + * @param y1 + * The y coordinate of the top left corner + * @param width + * The width of the rectangle to draw + * @param height + * The height of the rectangle to draw + */ + public void drawRect(float x1, float y1, float width, float height) { + float lineWidth = getLineWidth(); + + drawLine(x1,y1,x1+width,y1); + drawLine(x1+width,y1,x1+width,y1+height); + drawLine(x1+width,y1+height,x1,y1+height); + drawLine(x1,y1+height,x1,y1); + } + + /** + * Clear the clipping being applied. This will allow graphics to be drawn + * anywhere on the screen + */ + public void clearClip() { + clip = null; + predraw(); + GL.glDisable(SGL.GL_SCISSOR_TEST); + postdraw(); + } + + /** + * Set clipping that controls which areas of the world will be drawn to. + * Note that world clip is different from standard screen clip in that it's + * defined in the space of the current world coordinate - i.e. it's affected + * by translate, rotate, scale etc. + * + * @param x + * The x coordinate of the top left corner of the allowed area + * @param y + * The y coordinate of the top left corner of the allowed area + * @param width + * The width of the allowed area + * @param height + * The height of the allowed area + */ + public void setWorldClip(float x, float y, float width, float height) { + predraw(); + worldClipRecord = new Rectangle(x, y, width, height); + + GL.glEnable(SGL.GL_CLIP_PLANE0); + worldClip.put(1).put(0).put(0).put(-x).flip(); + GL.glClipPlane(SGL.GL_CLIP_PLANE0, worldClip); + GL.glEnable(SGL.GL_CLIP_PLANE1); + worldClip.put(-1).put(0).put(0).put(x + width).flip(); + GL.glClipPlane(SGL.GL_CLIP_PLANE1, worldClip); + + GL.glEnable(SGL.GL_CLIP_PLANE2); + worldClip.put(0).put(1).put(0).put(-y).flip(); + GL.glClipPlane(SGL.GL_CLIP_PLANE2, worldClip); + GL.glEnable(SGL.GL_CLIP_PLANE3); + worldClip.put(0).put(-1).put(0).put(y + height).flip(); + GL.glClipPlane(SGL.GL_CLIP_PLANE3, worldClip); + postdraw(); + } + + /** + * Clear world clipping setup. This does not effect screen clipping + */ + public void clearWorldClip() { + predraw(); + worldClipRecord = null; + GL.glDisable(SGL.GL_CLIP_PLANE0); + GL.glDisable(SGL.GL_CLIP_PLANE1); + GL.glDisable(SGL.GL_CLIP_PLANE2); + GL.glDisable(SGL.GL_CLIP_PLANE3); + postdraw(); + } + + /** + * Set the world clip to be applied + * + * @see #setWorldClip(float, float, float, float) + * @param clip + * The area still visible + */ + public void setWorldClip(Rectangle clip) { + if (clip == null) { + clearWorldClip(); + } else { + setWorldClip(clip.getX(), clip.getY(), clip.getWidth(), clip + .getHeight()); + } + } + + /** + * Get the last set world clip or null of the world clip isn't set + * + * @return The last set world clip rectangle + */ + public Rectangle getWorldClip() { + return worldClipRecord; + } + + /** + * Set the clipping to apply to the drawing. Note that this clipping takes + * no note of the transforms that have been applied to the context and is + * always in absolute screen space coordinates. + * + * @param x + * The x coordinate of the top left corner of the allowed area + * @param y + * The y coordinate of the top left corner of the allowed area + * @param width + * The width of the allowed area + * @param height + * The height of the allowed area + */ + public void setClip(int x, int y, int width, int height) { + predraw(); + + if (clip == null) { + GL.glEnable(SGL.GL_SCISSOR_TEST); + clip = new Rectangle(x, y, width, height); + } else { + clip.setBounds(x,y,width,height); + } + + GL.glScissor(x, screenHeight - y - height, width, height); + postdraw(); + } + + /** + * Set the clipping to apply to the drawing. Note that this clipping takes + * no note of the transforms that have been applied to the context and is + * always in absolute screen space coordinates. + * + * @param rect + * The rectangle describing the clipped area in screen + * coordinates + */ + public void setClip(Rectangle rect) { + if (rect == null) { + clearClip(); + return; + } + + setClip((int) rect.getX(), (int) rect.getY(), (int) rect.getWidth(), + (int) rect.getHeight()); + } + + /** + * Return the currently applied clipping rectangle + * + * @return The current applied clipping rectangle or null if no clipping is + * applied + */ + public Rectangle getClip() { + return clip; + } + + /** + * Tile a rectangle with a pattern specifing the offset from the top corner + * that one tile should match + * + * @param x + * The x coordinate of the rectangle + * @param y + * The y coordinate of the rectangle + * @param width + * The width of the rectangle + * @param height + * The height of the rectangle + * @param pattern + * The image to tile across the rectangle + * @param offX + * The offset on the x axis from the top left corner + * @param offY + * The offset on the y axis from the top left corner + */ + public void fillRect(float x, float y, float width, float height, + Image pattern, float offX, float offY) { + int cols = ((int) Math.ceil(width / pattern.getWidth())) + 2; + int rows = ((int) Math.ceil(height / pattern.getHeight())) + 2; + + Rectangle preClip = getWorldClip(); + setWorldClip(x, y, width, height); + + predraw(); + // Draw all the quads we need + for (int c = 0; c < cols; c++) { + for (int r = 0; r < rows; r++) { + pattern.draw(c * pattern.getWidth() + x - offX, r + * pattern.getHeight() + y - offY); + } + } + postdraw(); + + setWorldClip(preClip); + } + + /** + * Fill a rectangle on the canvas in the current color + * + * @param x1 + * The x coordinate of the top left corner + * @param y1 + * The y coordinate of the top left corner + * @param width + * The width of the rectangle to fill + * @param height + * The height of the rectangle to fill + */ + public void fillRect(float x1, float y1, float width, float height) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + GL.glBegin(SGL.GL_QUADS); + GL.glVertex2f(x1, y1); + GL.glVertex2f(x1 + width, y1); + GL.glVertex2f(x1 + width, y1 + height); + GL.glVertex2f(x1, y1 + height); + GL.glEnd(); + postdraw(); + } + + /** + * Draw an oval to the canvas + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the oval + * @param y1 + * The y coordinate of the top left corner of a box containing + * the oval + * @param width + * The width of the oval + * @param height + * The height of the oval + */ + public void drawOval(float x1, float y1, float width, float height) { + drawOval(x1, y1, width, height, DEFAULT_SEGMENTS); + } + + /** + * Draw an oval to the canvas + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the oval + * @param y1 + * The y coordinate of the top left corner of a box containing + * the oval + * @param width + * The width of the oval + * @param height + * The height of the oval + * @param segments + * The number of line segments to use when drawing the oval + */ + public void drawOval(float x1, float y1, float width, float height, + int segments) { + drawArc(x1, y1, width, height, segments, 0, 360); + } + + /** + * Draw an oval to the canvas + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the arc + * @param y1 + * The y coordinate of the top left corner of a box containing + * the arc + * @param width + * The width of the arc + * @param height + * The height of the arc + * @param start + * The angle the arc starts at + * @param end + * The angle the arc ends at + */ + public void drawArc(float x1, float y1, float width, float height, + float start, float end) { + drawArc(x1, y1, width, height, DEFAULT_SEGMENTS, start, end); + } + + /** + * Draw an oval to the canvas + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the arc + * @param y1 + * The y coordinate of the top left corner of a box containing + * the arc + * @param width + * The width of the arc + * @param height + * The height of the arc + * @param segments + * The number of line segments to use when drawing the arc + * @param start + * The angle the arc starts at + * @param end + * The angle the arc ends at + */ + public void drawArc(float x1, float y1, float width, float height, + int segments, float start, float end) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + while (end < start) { + end += 360; + } + + float cx = x1 + (width / 2.0f); + float cy = y1 + (height / 2.0f); + + LSR.start(); + int step = 360 / segments; + + for (int a = (int) start; a < (int) (end + step); a += step) { + float ang = a; + if (ang > end) { + ang = end; + } + float x = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * width / 2.0f)); + float y = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * height / 2.0f)); + + LSR.vertex(x,y); + } + LSR.end(); + postdraw(); + } + + /** + * Fill an oval to the canvas + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the oval + * @param y1 + * The y coordinate of the top left corner of a box containing + * the oval + * @param width + * The width of the oval + * @param height + * The height of the oval + */ + public void fillOval(float x1, float y1, float width, float height) { + fillOval(x1, y1, width, height, DEFAULT_SEGMENTS); + } + + /** + * Fill an oval to the canvas + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the oval + * @param y1 + * The y coordinate of the top left corner of a box containing + * the oval + * @param width + * The width of the oval + * @param height + * The height of the oval + * @param segments + * The number of line segments to use when filling the oval + */ + public void fillOval(float x1, float y1, float width, float height, + int segments) { + fillArc(x1, y1, width, height, segments, 0, 360); + } + + /** + * Fill an arc to the canvas (a wedge) + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the arc + * @param y1 + * The y coordinate of the top left corner of a box containing + * the arc + * @param width + * The width of the arc + * @param height + * The height of the arc + * @param start + * The angle the arc starts at + * @param end + * The angle the arc ends at + */ + public void fillArc(float x1, float y1, float width, float height, + float start, float end) { + fillArc(x1, y1, width, height, DEFAULT_SEGMENTS, start, end); + } + + /** + * Fill an arc to the canvas (a wedge) + * + * @param x1 + * The x coordinate of the top left corner of a box containing + * the arc + * @param y1 + * The y coordinate of the top left corner of a box containing + * the arc + * @param width + * The width of the arc + * @param height + * The height of the arc + * @param segments + * The number of line segments to use when filling the arc + * @param start + * The angle the arc starts at + * @param end + * The angle the arc ends at + */ + public void fillArc(float x1, float y1, float width, float height, + int segments, float start, float end) { + predraw(); + TextureImpl.bindNone(); + currentColor.bind(); + + while (end < start) { + end += 360; + } + + float cx = x1 + (width / 2.0f); + float cy = y1 + (height / 2.0f); + + GL.glBegin(SGL.GL_TRIANGLE_FAN); + int step = 360 / segments; + + GL.glVertex2f(cx, cy); + + for (int a = (int) start; a < (int) (end + step); a += step) { + float ang = a; + if (ang > end) { + ang = end; + } + + float x = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * width / 2.0f)); + float y = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * height / 2.0f)); + + GL.glVertex2f(x, y); + } + GL.glEnd(); + + if (antialias) { + GL.glBegin(SGL.GL_TRIANGLE_FAN); + GL.glVertex2f(cx, cy); + if (end != 360) { + end -= 10; + } + + for (int a = (int) start; a < (int) (end + step); a += step) { + float ang = a; + if (ang > end) { + ang = end; + } + + float x = (float) (cx + (FastTrig.cos(Math.toRadians(ang + 10)) + * width / 2.0f)); + float y = (float) (cy + (FastTrig.sin(Math.toRadians(ang + 10)) + * height / 2.0f)); + + GL.glVertex2f(x, y); + } + GL.glEnd(); + } + + postdraw(); + } + + /** + * Draw a rounded rectangle + * + * @param x + * The x coordinate of the top left corner of the rectangle + * @param y + * The y coordinate of the top left corner of the rectangle + * @param width + * The width of the rectangle + * @param height + * The height of the rectangle + * @param cornerRadius + * The radius of the rounded edges on the corners + */ + public void drawRoundRect(float x, float y, float width, float height, + int cornerRadius) { + drawRoundRect(x, y, width, height, cornerRadius, DEFAULT_SEGMENTS); + } + + /** + * Draw a rounded rectangle + * + * @param x + * The x coordinate of the top left corner of the rectangle + * @param y + * The y coordinate of the top left corner of the rectangle + * @param width + * The width of the rectangle + * @param height + * The height of the rectangle + * @param cornerRadius + * The radius of the rounded edges on the corners + * @param segs + * The number of segments to make the corners out of + */ + public void drawRoundRect(float x, float y, float width, float height, + int cornerRadius, int segs) { + if (cornerRadius < 0) + throw new IllegalArgumentException("corner radius must be > 0"); + if (cornerRadius == 0) { + drawRect(x, y, width, height); + return; + } + + int mr = (int) Math.min(width, height) / 2; + // make sure that w & h are larger than 2*cornerRadius + if (cornerRadius > mr) { + cornerRadius = mr; + } + + drawLine(x + cornerRadius, y, x + width - cornerRadius, y); + drawLine(x, y + cornerRadius, x, y + height - cornerRadius); + drawLine(x + width, y + cornerRadius, x + width, y + height + - cornerRadius); + drawLine(x + cornerRadius, y + height, x + width - cornerRadius, y + + height); + + float d = cornerRadius * 2; + // bottom right - 0, 90 + drawArc(x + width - d, y + height - d, d, d, segs, 0, 90); + // bottom left - 90, 180 + drawArc(x, y + height - d, d, d, segs, 90, 180); + // top right - 270, 360 + drawArc(x + width - d, y, d, d, segs, 270, 360); + // top left - 180, 270 + drawArc(x, y, d, d, segs, 180, 270); + } + + /** + * Fill a rounded rectangle + * + * @param x + * The x coordinate of the top left corner of the rectangle + * @param y + * The y coordinate of the top left corner of the rectangle + * @param width + * The width of the rectangle + * @param height + * The height of the rectangle + * @param cornerRadius + * The radius of the rounded edges on the corners + */ + public void fillRoundRect(float x, float y, float width, float height, + int cornerRadius) { + fillRoundRect(x, y, width, height, cornerRadius, DEFAULT_SEGMENTS); + } + + /** + * Fill a rounded rectangle + * + * @param x + * The x coordinate of the top left corner of the rectangle + * @param y + * The y coordinate of the top left corner of the rectangle + * @param width + * The width of the rectangle + * @param height + * The height of the rectangle + * @param cornerRadius + * The radius of the rounded edges on the corners + * @param segs + * The number of segments to make the corners out of + */ + public void fillRoundRect(float x, float y, float width, float height, + int cornerRadius, int segs) { + if (cornerRadius < 0) + throw new IllegalArgumentException("corner radius must be > 0"); + if (cornerRadius == 0) { + fillRect(x, y, width, height); + return; + } + + int mr = (int) Math.min(width, height) / 2; + // make sure that w & h are larger than 2*cornerRadius + if (cornerRadius > mr) { + cornerRadius = mr; + } + + float d = cornerRadius * 2; + + fillRect(x + cornerRadius, y, width - d, cornerRadius); + fillRect(x, y + cornerRadius, cornerRadius, height - d); + fillRect(x + width - cornerRadius, y + cornerRadius, cornerRadius, + height - d); + fillRect(x + cornerRadius, y + height - cornerRadius, width - d, + cornerRadius); + fillRect(x + cornerRadius, y + cornerRadius, width - d, height - d); + + // bottom right - 0, 90 + fillArc(x + width - d, y + height - d, d, d, segs, 0, 90); + // bottom left - 90, 180 + fillArc(x, y + height - d, d, d, segs, 90, 180); + // top right - 270, 360 + fillArc(x + width - d, y, d, d, segs, 270, 360); + // top left - 180, 270 + fillArc(x, y, d, d, segs, 180, 270); + } + + /** + * Set the with of the line to be used when drawing line based primitives + * + * @param width + * The width of the line to be used when drawing line based + * primitives + */ + public void setLineWidth(float width) { + predraw(); + this.lineWidth = width; + LSR.setWidth(width); + GL.glPointSize(width); + postdraw(); + } + + /** + * Get the width of lines being drawn in this context + * + * @return The width of lines being draw in this context + */ + public float getLineWidth() { + return lineWidth; + } + + /** + * Reset the line width in use to the default for this graphics context + */ + public void resetLineWidth() { + predraw(); + + Renderer.getLineStripRenderer().setWidth(1.0f); + GL.glLineWidth(1.0f); + GL.glPointSize(1.0f); + + postdraw(); + } + + /** + * Indicate if we should antialias as we draw primitives + * + * @param anti + * True if we should antialias + */ + public void setAntiAlias(boolean anti) { + predraw(); + antialias = anti; + LSR.setAntiAlias(anti); + if (anti) { + GL.glEnable(SGL.GL_POLYGON_SMOOTH); + } else { + GL.glDisable(SGL.GL_POLYGON_SMOOTH); + } + postdraw(); + } + + /** + * True if antialiasing has been turned on for this graphics context + * + * @return True if antialiasing has been turned on for this graphics context + */ + public boolean isAntiAlias() { + return antialias; + } + + /** + * Draw a string to the screen using the current font + * + * @param str + * The string to draw + * @param x + * The x coordinate to draw the string at + * @param y + * The y coordinate to draw the string at + */ + public void drawString(String str, float x, float y) { + predraw(); + font.drawString(x, y, str, currentColor); + postdraw(); + } + + /** + * Draw an image to the screen + * + * @param image + * The image to draw to the screen + * @param x + * The x location at which to draw the image + * @param y + * The y location at which to draw the image + * @param col + * The color to apply to the image as a filter + */ + public void drawImage(Image image, float x, float y, Color col) { + predraw(); + image.draw(x, y, col); + currentColor.bind(); + postdraw(); + } + + /** + * Draw an animation to this graphics context + * + * @param anim + * The animation to be drawn + * @param x + * The x position to draw the animation at + * @param y + * The y position to draw the animation at + */ + public void drawAnimation(Animation anim, float x, float y) { + drawAnimation(anim, x, y, Color.white); + } + + /** + * Draw an animation to this graphics context + * + * @param anim + * The animation to be drawn + * @param x + * The x position to draw the animation at + * @param y + * The y position to draw the animation at + * @param col + * The color to apply to the animation as a filter + */ + public void drawAnimation(Animation anim, float x, float y, Color col) { + predraw(); + anim.draw(x, y, col); + currentColor.bind(); + postdraw(); + } + + /** + * Draw an image to the screen + * + * @param image + * The image to draw to the screen + * @param x + * The x location at which to draw the image + * @param y + * The y location at which to draw the image + */ + public void drawImage(Image image, float x, float y) { + drawImage(image, x, y, Color.white); + } + + /** + * Draw a section of an image at a particular location and scale on the + * screen + * + * @param image + * The image to draw a section of + * @param x + * The x position to draw the image + * @param y + * The y position to draw the image + * @param x2 + * The x position of the bottom right corner of the drawn image + * @param y2 + * The y position of the bottom right corner of the drawn image + * @param srcx + * The x position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcy + * The y position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcx2 + * The x position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + * @param srcy2 + * The t position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + */ + public void drawImage(Image image, float x, float y, float x2, float y2, + float srcx, float srcy, float srcx2, float srcy2) { + predraw(); + image.draw(x, y, x2, y2, srcx, srcy, srcx2, srcy2); + currentColor.bind(); + postdraw(); + } + + /** + * Draw a section of an image at a particular location and scale on the + * screen + * + * @param image + * The image to draw a section of + * @param x + * The x position to draw the image + * @param y + * The y position to draw the image + * @param srcx + * The x position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcy + * The y position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcx2 + * The x position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + * @param srcy2 + * The t position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + */ + public void drawImage(Image image, float x, float y, float srcx, + float srcy, float srcx2, float srcy2) { + drawImage(image, x, y, x + image.getWidth(), y + image.getHeight(), + srcx, srcy, srcx2, srcy2); + } + + /** + * Copy an area of the rendered screen into an image. The width and height + * of the area are assumed to match that of the image + * + * @param target + * The target image + * @param x + * The x position to copy from + * @param y + * The y position to copy from + */ + public void copyArea(Image target, int x, int y) { + int format = target.getTexture().hasAlpha() ? SGL.GL_RGBA : SGL.GL_RGB; + target.bind(); + GL.glCopyTexImage2D(SGL.GL_TEXTURE_2D, 0, format, x, screenHeight + - (y + target.getHeight()), target.getTexture() + .getTextureWidth(), target.getTexture().getTextureHeight(), 0); + target.ensureInverted(); + } + + /** + * Translate an unsigned int into a signed integer + * + * @param b + * The byte to convert + * @return The integer value represented by the byte + */ + private int translate(byte b) { + if (b < 0) { + return 256 + b; + } + + return b; + } + + /** + * Get the colour of a single pixel in this graphics context + * + * @param x + * The x coordinate of the pixel to read + * @param y + * The y coordinate of the pixel to read + * @return The colour of the pixel at the specified location + */ + public Color getPixel(int x, int y) { + predraw(); + GL.glReadPixels(x, screenHeight - y, 1, 1, SGL.GL_RGBA, + SGL.GL_UNSIGNED_BYTE, readBuffer); + postdraw(); + + return new Color(translate(readBuffer.get(0)), translate(readBuffer + .get(1)), translate(readBuffer.get(2)), translate(readBuffer + .get(3))); + } + + /** + * Get an ara of pixels as RGBA values into a buffer + * + * @param x The x position in the context to grab from + * @param y The y position in the context to grab from + * @param width The width of the area to grab from + * @param height The hiehgt of the area to grab from + * @param target The target buffer to grab into + */ + public void getArea(int x, int y, int width, int height, ByteBuffer target) + { + if (target.capacity() < width * height * 4) + { + throw new IllegalArgumentException("Byte buffer provided to get area is not big enough"); + } + + predraw(); + GL.glReadPixels(x, screenHeight - y - height, width, height, SGL.GL_RGBA, + SGL.GL_UNSIGNED_BYTE, target); + postdraw(); + } + + /** + * Draw a section of an image at a particular location and scale on the + * screen + * + * @param image + * The image to draw a section of + * @param x + * The x position to draw the image + * @param y + * The y position to draw the image + * @param x2 + * The x position of the bottom right corner of the drawn image + * @param y2 + * The y position of the bottom right corner of the drawn image + * @param srcx + * The x position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcy + * The y position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcx2 + * The x position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + * @param srcy2 + * The t position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + * @param col + * The color to apply to the image as a filter + */ + public void drawImage(Image image, float x, float y, float x2, float y2, + float srcx, float srcy, float srcx2, float srcy2, Color col) { + predraw(); + image.draw(x, y, x2, y2, srcx, srcy, srcx2, srcy2, col); + currentColor.bind(); + postdraw(); + } + + /** + * Draw a section of an image at a particular location and scale on the + * screen + * + * @param image + * The image to draw a section of + * @param x + * The x position to draw the image + * @param y + * The y position to draw the image + * @param srcx + * The x position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcy + * The y position of the rectangle to draw from this image (i.e. + * relative to the image) + * @param srcx2 + * The x position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + * @param srcy2 + * The t position of the bottom right cornder of rectangle to + * draw from this image (i.e. relative to the image) + * @param col + * The color to apply to the image as a filter + */ + public void drawImage(Image image, float x, float y, float srcx, + float srcy, float srcx2, float srcy2, Color col) { + drawImage(image, x, y, x + image.getWidth(), y + image.getHeight(), + srcx, srcy, srcx2, srcy2, col); + } + + /** + * Draw a line with a gradient between the two points. + * + * @param x1 + * The starting x position to draw the line + * @param y1 + * The starting y position to draw the line + * @param red1 + * The starting position's shade of red + * @param green1 + * The starting position's shade of green + * @param blue1 + * The starting position's shade of blue + * @param alpha1 + * The starting position's alpha value + * @param x2 + * The ending x position to draw the line + * @param y2 + * The ending y position to draw the line + * @param red2 + * The ending position's shade of red + * @param green2 + * The ending position's shade of green + * @param blue2 + * The ending position's shade of blue + * @param alpha2 + * The ending position's alpha value + */ + public void drawGradientLine(float x1, float y1, float red1, float green1, + float blue1, float alpha1, float x2, float y2, float red2, + float green2, float blue2, float alpha2) { + predraw(); + + TextureImpl.bindNone(); + + GL.glBegin(SGL.GL_LINES); + + GL.glColor4f(red1, green1, blue1, alpha1); + GL.glVertex2f(x1, y1); + + GL.glColor4f(red2, green2, blue2, alpha2); + GL.glVertex2f(x2, y2); + + GL.glEnd(); + + postdraw(); + } + + /** + * Draw a line with a gradient between the two points. + * + * @param x1 + * The starting x position to draw the line + * @param y1 + * The starting y position to draw the line + * @param Color1 + * The starting position's color + * @param x2 + * The ending x position to draw the line + * @param y2 + * The ending y position to draw the line + * @param Color2 + * The ending position's color + */ + public void drawGradientLine(float x1, float y1, Color Color1, float x2, + float y2, Color Color2) { + predraw(); + + TextureImpl.bindNone(); + + GL.glBegin(SGL.GL_LINES); + + Color1.bind(); + GL.glVertex2f(x1, y1); + + Color2.bind(); + GL.glVertex2f(x2, y2); + + GL.glEnd(); + + postdraw(); + } + + /** + * Push the current state of the transform from this graphics contexts + * onto the underlying graphics stack's transform stack. An associated + * popTransform() must be performed to restore the state before the end + * of the rendering loop. + */ + public void pushTransform() { + predraw(); + + FloatBuffer buffer; + if (stackIndex >= stack.size()) { + buffer = BufferUtils.createFloatBuffer(18); + stack.add(buffer); + } else { + buffer = (FloatBuffer) stack.get(stackIndex); + } + + GL.glGetFloat(SGL.GL_MODELVIEW_MATRIX, buffer); + buffer.put(16, sx); + buffer.put(17, sy); + stackIndex++; + + postdraw(); + } + + /** + * Pop a previously pushed transform from the stack to the current. This should + * only be called if a transform has been previously pushed. + */ + public void popTransform() { + if (stackIndex == 0) { + throw new RuntimeException("Attempt to pop a transform that hasn't be pushed"); + } + + predraw(); + + stackIndex--; + FloatBuffer oldBuffer = (FloatBuffer) stack.get(stackIndex); + GL.glLoadMatrix(oldBuffer); + sx = oldBuffer.get(16); + sy = oldBuffer.get(17); + + postdraw(); + } + + /** + * Dispose this graphics context, this will release any underlying resourses. However + * this will also invalidate it's use + */ + public void destroy() { + + } +} diff --git a/lib/slick-source/org/newdawn/slick/Image.java b/lib/slick-source/org/newdawn/slick/Image.java new file mode 100644 index 000000000..4e5f042e4 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Image.java @@ -0,0 +1,1403 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.io.InputStream; + +import org.newdawn.slick.opengl.ImageData; +import org.newdawn.slick.opengl.InternalTextureLoader; +import org.newdawn.slick.opengl.Texture; +import org.newdawn.slick.opengl.TextureImpl; +import org.newdawn.slick.opengl.pbuffer.GraphicsFactory; +import org.newdawn.slick.opengl.renderer.Renderer; +import org.newdawn.slick.opengl.renderer.SGL; +import org.newdawn.slick.util.Log; + +/** + * An image loaded from a file and renderable to the canvas + * + * @author kevin + */ +public class Image implements Renderable { + /** The top left corner identifier */ + public static final int TOP_LEFT = 0; + /** The top right corner identifier */ + public static final int TOP_RIGHT = 1; + /** The bottom right corner identifier */ + public static final int BOTTOM_RIGHT = 2; + /** The bottom left corner identifier */ + public static final int BOTTOM_LEFT = 3; + + /** The renderer to use for all GL operations */ + protected static SGL GL = Renderer.get(); + + /** The sprite sheet currently in use */ + protected static Image inUse; + /** Use Linear Filtering */ + public static final int FILTER_LINEAR = 1; + /** Use Nearest Filtering */ + public static final int FILTER_NEAREST = 2; + + /** The OpenGL texture for this image */ + protected Texture texture; + /** The width of the image */ + protected int width; + /** The height of the image */ + protected int height; + /** The texture coordinate width to use to find our image */ + protected float textureWidth; + /** The texture coordinate height to use to find our image */ + protected float textureHeight; + /** The x texture offset to use to find our image */ + protected float textureOffsetX; + /** The y texture offset to use to find our image */ + protected float textureOffsetY; + /** Angle to rotate the image to. */ + protected float angle; + /** The alpha to draw the image at */ + protected float alpha = 1.0f; + /** The name given for the image */ + protected String ref; + /** True if this image's state has been initialised */ + protected boolean inited = false; + /** A pixelData holding the pixel data if it's been read for this texture */ + protected byte[] pixelData; + /** True if the image has been destroyed */ + protected boolean destroyed; + + /** The x coordinate of the centre of rotation */ + protected float centerX; + /** The y coordinate of the centre of rotation */ + protected float centerY; + + /** A meaningful name provided by the user of the image to tag it */ + protected String name; + + /** The colours for each of the corners */ + protected Color[] corners; + /** The OpenGL max filter */ + private int filter = SGL.GL_LINEAR; + + /** True if the image should be flipped vertically */ + private boolean flipped; + /** The transparent colour set if any */ + private Color transparent; + + /** + * Create a texture as a copy of another + * + * @param other The other texture to copy + */ + protected Image(Image other) { + this.width = other.getWidth(); + this.height = other.getHeight(); + this.texture = other.texture; + this.textureWidth = other.textureWidth; + this.textureHeight = other.textureHeight; + this.ref = other.ref; + this.textureOffsetX = other.textureOffsetX; + this.textureOffsetY = other.textureOffsetY; + + centerX = width / 2; + centerY = height / 2; + inited = true; + } + + /** + * Cloning constructor - only used internally. + */ + protected Image() { + } + + /** + * Creates an image using the specified texture + * + * @param texture + * The texture to use + */ + public Image(Texture texture) { + this.texture = texture; + ref = texture.toString(); + clampTexture(); + } + + /** + * Create an image based on a file at the specified location + * + * @param ref + * The location of the image file to load + * @throws SlickException + * Indicates a failure to load the image + */ + public Image(String ref) throws SlickException { + this(ref, false); + } + + /** + * Create an image based on a file at the specified location + * + * @param ref The location of the image file to load + * @param trans The color to be treated as transparent + * @throws SlickException Indicates a failure to load the image + */ + public Image(String ref, Color trans) throws SlickException { + this(ref, false, FILTER_LINEAR, trans); + } + + /** + * Create an image based on a file at the specified location + * + * @param ref The location of the image file to load + * @param flipped True if the image should be flipped on the y-axis on load + * @throws SlickException Indicates a failure to load the image + */ + public Image(String ref, boolean flipped) throws SlickException { + this(ref, flipped, FILTER_LINEAR); + } + + /** + * Create an image based on a file at the specified location + * + * @param ref The location of the image file to load + * @param flipped True if the image should be flipped on the y-axis on load + * @param filter The filtering method to use when scaling this image + * @throws SlickException Indicates a failure to load the image + */ + public Image(String ref, boolean flipped, int filter) throws SlickException { + this(ref, flipped, filter, null); + } + + /** + * Create an image based on a file at the specified location + * + * @param ref The location of the image file to load + * @param flipped True if the image should be flipped on the y-axis on load + * @param f The filtering method to use when scaling this image + * @param transparent The color to treat as transparent + * @throws SlickException Indicates a failure to load the image + */ + public Image(String ref, boolean flipped, int f, Color transparent) throws SlickException { + this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; + this.transparent = transparent; + this.flipped = flipped; + + try { + this.ref = ref; + int[] trans = null; + if (transparent != null) { + trans = new int[3]; + trans[0] = (int) (transparent.r * 255); + trans[1] = (int) (transparent.g * 255); + trans[2] = (int) (transparent.b * 255); + } + texture = InternalTextureLoader.get().getTexture(ref, flipped, filter, trans); + } catch (IOException e) { + Log.error(e); + throw new SlickException("Failed to load image from: "+ref, e); + } + } + + /** + * Set the image filtering to be used. Note that this will also affect any + * image that was derived from this one (i.e. sub-images etc) + * + * @param f The filtering mode to use + */ + public void setFilter(int f) { + this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; + + texture.bind(); + GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_MIN_FILTER, filter); + GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_MAG_FILTER, filter); + } + + /** + * Create an empty image + * + * @param width The width of the image + * @param height The height of the image + * @throws SlickException Indicates a failure to create the underlying resource + */ + public Image(int width, int height) throws SlickException { + this(width, height, FILTER_NEAREST); + } + + /** + * Create an empty image + * + * @param width The width of the image + * @param height The height of the image + * @param f The filter to apply to scaling the new image + * @throws SlickException Indicates a failure to create the underlying resource + */ + public Image(int width, int height, int f) throws SlickException { + ref = super.toString(); + this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; + + try { + texture = InternalTextureLoader.get().createTexture(width, height, this.filter); + } catch (IOException e) { + Log.error(e); + throw new SlickException("Failed to create empty image "+width+"x"+height); + } + + init(); + } + + /** + * Create an image based on a file at the specified location + * + * @param in The input stream to read the image from + * @param ref The name that should be assigned to the image + * @param flipped True if the image should be flipped on the y-axis on load + * @throws SlickException Indicates a failure to load the image + */ + public Image(InputStream in, String ref, boolean flipped) throws SlickException { + this(in, ref, flipped, FILTER_LINEAR); + } + + /** + * Create an image based on a file at the specified location + * + * @param in The input stream to read the image from + * @param ref The name that should be assigned to the image + * @param flipped True if the image should be flipped on the y-axis on load + * @param filter The filter to use when scaling this image + * @throws SlickException Indicates a failure to load the image + */ + public Image(InputStream in, String ref, boolean flipped,int filter) throws SlickException { + load(in, ref, flipped, filter, null); + } + + /** + * Create an image from a pixelData of pixels + * + * @param buffer The pixelData to use to create the image + */ + Image(ImageBuffer buffer) { + this(buffer, FILTER_LINEAR); + TextureImpl.bindNone(); + } + + /** + * Create an image from a pixelData of pixels + * + * @param buffer The pixelData to use to create the image + * @param filter The filter to use when scaling this image + */ + Image(ImageBuffer buffer, int filter) { + this((ImageData) buffer, filter); + TextureImpl.bindNone(); + } + + /** + * Create an image from a image data source + * + * @param data The pixelData to use to create the image + */ + public Image(ImageData data) { + this(data, FILTER_LINEAR); + } + + /** + * Create an image from a image data source. Note that this method uses + * + * @param data The pixelData to use to create the image + * @param f The filter to use when scaling this image + */ + public Image(ImageData data, int f) { + try { + this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; + texture = InternalTextureLoader.get().getTexture(data, this.filter); + ref = texture.toString(); + } catch (IOException e) { + Log.error(e); + } + } + + /** + * Get the OpenGL image filter in use + * + * @return The filter for magnification + */ + public int getFilter() { + return filter; + } + + /** + * Get the reference to the resource this image was loaded from, if any. Note that + * this can be null in the cases where an image was programatically generated. + * + * @return The reference to the resource the reference was loaded from + */ + public String getResourceReference() { + return ref; + } + + /** + * Set the filter to apply when drawing this image + * + * @param r The red component of the filter colour + * @param g The green component of the filter colour + * @param b The blue component of the filter colour + * @param a The alpha component of the filter colour + */ + public void setImageColor(float r, float g, float b, float a) { + setColor(TOP_LEFT, r, g, b, a); + setColor(TOP_RIGHT, r, g, b, a); + setColor(BOTTOM_LEFT, r, g, b, a); + setColor(BOTTOM_RIGHT, r, g, b, a); + } + + /** + * Set the filter to apply when drawing this image + * + * @param r The red component of the filter colour + * @param g The green component of the filter colour + * @param b The blue component of the filter colour + */ + public void setImageColor(float r, float g, float b) { + setColor(TOP_LEFT, r, g, b); + setColor(TOP_RIGHT, r, g, b); + setColor(BOTTOM_LEFT, r, g, b); + setColor(BOTTOM_RIGHT, r, g, b); + } + + /** + * Set the color of the given corner when this image is rendered. This is + * useful lots of visual effect but especially light maps + * + * @param corner The corner identifier for the corner to be set + * @param r The red component value to set (between 0 and 1) + * @param g The green component value to set (between 0 and 1) + * @param b The blue component value to set (between 0 and 1) + * @param a The alpha component value to set (between 0 and 1) + */ + public void setColor(int corner, float r, float g, float b, float a) { + if (corners == null) { + corners = new Color[] {new Color(1,1,1,1f),new Color(1,1,1,1f), new Color(1,1,1,1f), new Color(1,1,1,1f)}; + } + + corners[corner].r = r; + corners[corner].g = g; + corners[corner].b = b; + corners[corner].a = a; + } + + /** + * Set the color of the given corner when this image is rendered. This is + * useful lots of visual effect but especially light maps + * + * @param corner The corner identifier for the corner to be set + * @param r The red component value to set (between 0 and 1) + * @param g The green component value to set (between 0 and 1) + * @param b The blue component value to set (between 0 and 1) + */ + public void setColor(int corner, float r, float g, float b) { + if (corners == null) { + corners = new Color[] {new Color(1,1,1,1f),new Color(1,1,1,1f), new Color(1,1,1,1f), new Color(1,1,1,1f)}; + } + + corners[corner].r = r; + corners[corner].g = g; + corners[corner].b = b; + } + + /** + * Clamp the loaded texture to it's edges + */ + public void clampTexture() { + if (GL.canTextureMirrorClamp()) { + GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT); + GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT); + } else { + GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_CLAMP); + GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_CLAMP); + } + } + + /** + * Give this image a meaningful tagging name. Can be used as user data/identifier + * for the image. + * + * @param name The name to assign the image + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return a meaningful tagging name that has been assigned to this image. + * + * @return A name or null if the name hasn't been set + */ + public String getName() { + return name; + } + + /** + * Get a graphics context that can be used to draw to this image + * + * @return The graphics context used to render to this image + * @throws SlickException Indicates a failure to create a graphics context + */ + public Graphics getGraphics() throws SlickException { + return GraphicsFactory.getGraphicsForImage(this); + } + + /** + * Load the image + * + * @param in The input stream to read the image from + * @param ref The name that should be assigned to the image + * @param flipped True if the image should be flipped on the y-axis on load + * @param f The filter to use when scaling this image + * @param transparent The color to treat as transparent + * @throws SlickException Indicates a failure to load the image + */ + private void load(InputStream in, String ref, boolean flipped, int f, Color transparent) throws SlickException { + this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; + + try { + this.ref = ref; + int[] trans = null; + if (transparent != null) { + trans = new int[3]; + trans[0] = (int) (transparent.r * 255); + trans[1] = (int) (transparent.g * 255); + trans[2] = (int) (transparent.b * 255); + } + texture = InternalTextureLoader.get().getTexture(in, ref, flipped, filter, trans); + } catch (IOException e) { + Log.error(e); + throw new SlickException("Failed to load image from: "+ref, e); + } + } + + /** + * Bind to the texture of this image + */ + public void bind() { + texture.bind(); + } + + /** + * Reinitialise internal data + */ + protected void reinit() { + inited = false; + init(); + } + + /** + * Initialise internal data + */ + protected final void init() { + if (inited) { + return; + } + + inited = true; + if (texture != null) { + width = texture.getImageWidth(); + height = texture.getImageHeight(); + textureOffsetX = 0; + textureOffsetY = 0; + textureWidth = texture.getWidth(); + textureHeight = texture.getHeight(); + } + + initImpl(); + + centerX = width / 2; + centerY = height / 2; + } + + /** + * Hook for subclasses to perform initialisation + */ + protected void initImpl() { + + } + + /** + * Draw this image at the current location + */ + public void draw() { + draw(0,0); + } + + /** + * Draw the image based on it's center + * + * @param x The x coordinate to place the image's center at + * @param y The y coordinate to place the image's center at + */ + public void drawCentered(float x, float y) { + draw(x-(getWidth()/2),y-(getHeight()/2)); + } + + /** + * Draw this image at the specified location + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + */ + public void draw(float x, float y) { + init(); + draw(x,y,width,height); + } + + /** + * Draw this image at the specified location + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param filter The color to filter with when drawing + */ + public void draw(float x, float y, Color filter) { + init(); + draw(x,y,width,height, filter); + } + + /** + * Draw this image as part of a collection of images + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param width The width to render the image at + * @param height The height to render the image at + */ + public void drawEmbedded(float x,float y,float width,float height) { + init(); + + if (corners == null) { + GL.glTexCoord2f(textureOffsetX, textureOffsetY); + GL.glVertex3f(x, y, 0); + GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); + GL.glVertex3f(x, y + height, 0); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + + textureHeight); + GL.glVertex3f(x + width, y + height, 0); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); + GL.glVertex3f(x + width, y, 0); + } else { + corners[TOP_LEFT].bind(); + GL.glTexCoord2f(textureOffsetX, textureOffsetY); + GL.glVertex3f(x, y, 0); + corners[BOTTOM_LEFT].bind(); + GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); + GL.glVertex3f(x, y + height, 0); + corners[BOTTOM_RIGHT].bind(); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + + textureHeight); + GL.glVertex3f(x + width, y + height, 0); + corners[TOP_RIGHT].bind(); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); + GL.glVertex3f(x + width, y, 0); + } + } + + /** + * Get the x offset in texels into the source texture + * + * @return The x offset + */ + public float getTextureOffsetX() { + init(); + + return textureOffsetX; + } + + /** + * Get the y offset in texels into the source texture + * + * @return The y offset + */ + public float getTextureOffsetY() { + init(); + + return textureOffsetY; + } + + /** + * Get the width in texels into the source texture + * + * @return The width + */ + public float getTextureWidth() { + init(); + + return textureWidth; + } + + /** + * Get the height in texels into the source texture + * + * @return The height + */ + public float getTextureHeight() { + init(); + + return textureHeight; + } + + /** + * Draw the image with a given scale + * + * @param x The x position to draw the image at + * @param y The y position to draw the image at + * @param scale The scaling to apply + */ + public void draw(float x,float y,float scale) { + init(); + draw(x,y,width*scale,height*scale,Color.white); + } + + /** + * Draw the image with a given scale + * + * @param x The x position to draw the image at + * @param y The y position to draw the image at + * @param scale The scaling to apply + * @param filter The colour filter to adapt the image with + */ + public void draw(float x,float y,float scale,Color filter) { + init(); + draw(x,y,width*scale,height*scale,filter); + } + + /** + * Draw this image at a specified location and size + * + * @param x + * The x location to draw the image at + * @param y + * The y location to draw the image at + * @param width + * The width to render the image at + * @param height + * The height to render the image at + */ + public void draw(float x,float y,float width,float height) { + init(); + draw(x,y,width,height,Color.white); + } + + /** + * Draw this image at a specified location and size + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param hshear The amount to shear the bottom points by horizontally + * @param vshear The amount to shear the right points by vertically + */ + public void drawSheared(float x,float y, float hshear, float vshear) { + this.drawSheared(x, y, hshear, vshear, Color.white); + } + /** + * Draw this image at a specified location and size + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param hshear The amount to shear the bottom points by horizontally + * @param vshear The amount to shear the right points by vertically + * @param filter The colour filter to apply + */ + public void drawSheared(float x,float y, float hshear, float vshear, Color filter) { + if (alpha != 1) { + if (filter == null) { + filter = Color.white; + } + + filter = new Color(filter); + filter.a *= alpha; + } + if (filter != null) { + filter.bind(); + } + + texture.bind(); + + GL.glTranslatef(x, y, 0); + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + + GL.glBegin(SGL.GL_QUADS); + init(); + + GL.glTexCoord2f(textureOffsetX, textureOffsetY); + GL.glVertex3f(0, 0, 0); + GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); + GL.glVertex3f(hshear, height, 0); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + + textureHeight); + GL.glVertex3f(width + hshear, height + vshear, 0); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); + GL.glVertex3f(width, vshear, 0); + GL.glEnd(); + + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + GL.glTranslatef(-x, -y, 0); + } + + /** + * Draw this image at a specified location and size + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param width The width to render the image at + * @param height The height to render the image at + * @param filter The color to filter with while drawing + */ + public void draw(float x,float y,float width,float height,Color filter) { + if (alpha != 1) { + if (filter == null) { + filter = Color.white; + } + + filter = new Color(filter); + filter.a *= alpha; + } + if (filter != null) { + filter.bind(); + } + + texture.bind(); + + GL.glTranslatef(x, y, 0); + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + + GL.glBegin(SGL.GL_QUADS); + drawEmbedded(0,0,width,height); + GL.glEnd(); + + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + GL.glTranslatef(-x, -y, 0); + } + + /** + * Draw this image at a specified location and size as a silohette + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param width The width to render the image at + * @param height The height to render the image at + */ + public void drawFlash(float x,float y,float width,float height) { + drawFlash(x,y,width,height,Color.white); + } + + /** + * Set the centre of the rotation when applied to this image + * + * @param x The x coordinate of center of rotation relative to the top left corner of the image + * @param y The y coordinate of center of rotation relative to the top left corner of the image + */ + public void setCenterOfRotation(float x, float y) { + centerX = x; + centerY = y; + } + + /** + * Get the x component of the center of rotation of this image + * + * @return The x component of the center of rotation + */ + public float getCenterOfRotationX() { + init(); + + return centerX; + } + + /** + * Get the y component of the center of rotation of this image + * + * @return The y component of the center of rotation + */ + public float getCenterOfRotationY() { + init(); + + return centerY; + } + + /** + * Draw this image at a specified location and size as a silohette + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + * @param width The width to render the image at + * @param height The height to render the image at + * @param col The color for the sillohette + */ + public void drawFlash(float x,float y,float width,float height, Color col) { + init(); + + col.bind(); + texture.bind(); + + if (GL.canSecondaryColor()) { + GL.glEnable(SGL.GL_COLOR_SUM_EXT); + GL.glSecondaryColor3ubEXT((byte)(col.r * 255), + (byte)(col.g * 255), + (byte)(col.b * 255)); + } + + GL.glTexEnvi(SGL.GL_TEXTURE_ENV, SGL.GL_TEXTURE_ENV_MODE, SGL.GL_MODULATE); + + GL.glTranslatef(x, y, 0); + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + + GL.glBegin(SGL.GL_QUADS); + drawEmbedded(0,0,width,height); + GL.glEnd(); + + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + GL.glTranslatef(-x, -y, 0); + + if (GL.canSecondaryColor()) { + GL.glDisable(SGL.GL_COLOR_SUM_EXT); + } + } + + /** + * Draw this image at a specified location and size in a white silohette + * + * @param x The x location to draw the image at + * @param y The y location to draw the image at + */ + public void drawFlash(float x,float y) { + drawFlash(x,y,getWidth(),getHeight()); + } + + /** + * Set the angle to rotate this image to. The angle will be normalized to + * be 0 <= angle < 360. The image will be rotated around its center. + * + * @param angle The angle to be set + */ + public void setRotation(float angle) { + this.angle = angle % 360.0f; + } + + /** + * Get the current angle of rotation for this image. + * The image will be rotated around its center. + * + * @return The current angle. + */ + public float getRotation() { + return angle; + } + + /** + * Get the alpha value to use when rendering this image + * + * @return The alpha value to use when rendering this image + */ + public float getAlpha() { + return alpha; + } + + /** + * Set the alpha value to use when rendering this image + * + * @param alpha The alpha value to use when rendering this image + */ + public void setAlpha(float alpha) { + this.alpha = alpha; + } + + /** + * Add the angle provided to the current rotation. The angle will be normalized to + * be 0 <= angle < 360. The image will be rotated around its center. + * + * @param angle The angle to add. + */ + public void rotate(float angle) { + this.angle += angle; + this.angle = this.angle % 360; + } + + /** + * Get a sub-part of this image. Note that the create image retains a reference to the + * image data so should anything change it will affect sub-images too. + * + * @param x The x coordinate of the sub-image + * @param y The y coordinate of the sub-image + * @param width The width of the sub-image + * @param height The height of the sub-image + * @return The image represent the sub-part of this image + */ + public Image getSubImage(int x,int y,int width,int height) { + init(); + + float newTextureOffsetX = ((x / (float) this.width) * textureWidth) + textureOffsetX; + float newTextureOffsetY = ((y / (float) this.height) * textureHeight) + textureOffsetY; + float newTextureWidth = ((width / (float) this.width) * textureWidth); + float newTextureHeight = ((height / (float) this.height) * textureHeight); + + Image sub = new Image(); + sub.inited = true; + sub.texture = this.texture; + sub.textureOffsetX = newTextureOffsetX; + sub.textureOffsetY = newTextureOffsetY; + sub.textureWidth = newTextureWidth; + sub.textureHeight = newTextureHeight; + + sub.width = width; + sub.height = height; + sub.ref = ref; + sub.centerX = width / 2; + sub.centerY = height / 2; + + return sub; + } + + /** + * Draw a section of this image at a particular location and scale on the screen + * + * @param x The x position to draw the image + * @param y The y position to draw the image + * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + */ + public void draw(float x, float y, float srcx, float srcy, float srcx2, float srcy2) { + draw(x,y,x+width,y+height,srcx,srcy,srcx2,srcy2); + } + + /** + * Draw a section of this image at a particular location and scale on the screen + * + * @param x The x position to draw the image + * @param y The y position to draw the image + * @param x2 The x position of the bottom right corner of the drawn image + * @param y2 The y position of the bottom right corner of the drawn image + * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + */ + public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) { + draw(x,y,x2,y2,srcx,srcy,srcx2,srcy2,Color.white); + } + + /** + * Draw a section of this image at a particular location and scale on the screen + * + * @param x The x position to draw the image + * @param y The y position to draw the image + * @param x2 The x position of the bottom right corner of the drawn image + * @param y2 The y position of the bottom right corner of the drawn image + * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param filter The colour filter to apply when drawing + */ + public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, Color filter) { + init(); + + if (alpha != 1) { + if (filter == null) { + filter = Color.white; + } + + filter = new Color(filter); + filter.a *= alpha; + } + filter.bind(); + texture.bind(); + + GL.glTranslatef(x, y, 0); + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + + GL.glBegin(SGL.GL_QUADS); + drawEmbedded(0,0,x2-x,y2-y,srcx,srcy,srcx2,srcy2); + GL.glEnd(); + + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + GL.glTranslatef(-x, -y, 0); + +// GL.glBegin(SGL.GL_QUADS); +// drawEmbedded(x,y,x2,y2,srcx,srcy,srcx2,srcy2); +// GL.glEnd(); + } + + /** + * Draw a section of this image at a particular location and scale on the screen, while this + * is image is "in use", i.e. between calls to startUse and endUse. + * + * @param x The x position to draw the image + * @param y The y position to draw the image + * @param x2 The x position of the bottom right corner of the drawn image + * @param y2 The y position of the bottom right corner of the drawn image + * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + */ + public void drawEmbedded(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) { + drawEmbedded(x,y,x2,y2,srcx,srcy,srcx2,srcy2,null); + } + + /** + * Draw a section of this image at a particular location and scale on the screen, while this + * is image is "in use", i.e. between calls to startUse and endUse. + * + * @param x The x position to draw the image + * @param y The y position to draw the image + * @param x2 The x position of the bottom right corner of the drawn image + * @param y2 The y position of the bottom right corner of the drawn image + * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) + * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) + * @param filter The colour filter to apply when drawing + */ + public void drawEmbedded(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, Color filter) { + if (filter != null) { + filter.bind(); + } + + float mywidth = x2 - x; + float myheight = y2 - y; + float texwidth = srcx2 - srcx; + float texheight = srcy2 - srcy; + + float newTextureOffsetX = (((srcx) / (width)) * textureWidth) + + textureOffsetX; + float newTextureOffsetY = (((srcy) / (height)) * textureHeight) + + textureOffsetY; + float newTextureWidth = ((texwidth) / (width)) + * textureWidth; + float newTextureHeight = ((texheight) / (height)) + * textureHeight; + + GL.glTexCoord2f(newTextureOffsetX, newTextureOffsetY); + GL.glVertex3f(x,y, 0.0f); + GL.glTexCoord2f(newTextureOffsetX, newTextureOffsetY + + newTextureHeight); + GL.glVertex3f(x,(y + myheight), 0.0f); + GL.glTexCoord2f(newTextureOffsetX + newTextureWidth, + newTextureOffsetY + newTextureHeight); + GL.glVertex3f((x + mywidth),(y + myheight), 0.0f); + GL.glTexCoord2f(newTextureOffsetX + newTextureWidth, + newTextureOffsetY); + GL.glVertex3f((x + mywidth),y, 0.0f); + } + + /** + * Draw the image in a warper rectangle. The effects this can + * have are many and varied, might be interesting though. + * + * @param x1 The top left corner x coordinate + * @param y1 The top left corner y coordinate + * @param x2 The top right corner x coordinate + * @param y2 The top right corner y coordinate + * @param x3 The bottom right corner x coordinate + * @param y3 The bottom right corner y coordinate + * @param x4 The bottom left corner x coordinate + * @param y4 The bottom left corner y coordinate + */ + public void drawWarped(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { + Color.white.bind(); + texture.bind(); + + GL.glTranslatef(x1, y1, 0); + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + + GL.glBegin(SGL.GL_QUADS); + init(); + + GL.glTexCoord2f(textureOffsetX, textureOffsetY); + GL.glVertex3f(0, 0, 0); + GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); + GL.glVertex3f(x2 - x1, y2 - y1, 0); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + + textureHeight); + GL.glVertex3f(x3 - x1, y3 - y1, 0); + GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); + GL.glVertex3f(x4 - x1, y4 - y1, 0); + GL.glEnd(); + + if (angle != 0) { + GL.glTranslatef(centerX, centerY, 0.0f); + GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); + GL.glTranslatef(-centerX, -centerY, 0.0f); + } + GL.glTranslatef(-x1, -y1, 0); + } + + /** + * Get the width of this image + * + * @return The width of this image + */ + public int getWidth() { + init(); + return width; + } + + /** + * Get the height of this image + * + * @return The height of this image + */ + public int getHeight() { + init(); + return height; + } + + /** + * Get a copy of this image. This is a shallow copy and does not + * duplicate image adata. + * + * @return The copy of this image + */ + public Image copy() { + init(); + return getSubImage(0,0,width,height); + } + + /** + * Get a scaled copy of this image with a uniform scale + * + * @param scale The scale to apply + * @return The new scaled image + */ + public Image getScaledCopy(float scale) { + init(); + return getScaledCopy((int) (width*scale),(int) (height*scale)); + } + + /** + * Get a scaled copy of this image + * + * @param width The width of the copy + * @param height The height of the copy + * @return The new scaled image + */ + public Image getScaledCopy(int width, int height) { + init(); + Image image = copy(); + image.width = width; + image.height = height; + image.centerX = width / 2; + image.centerY = height / 2; + return image; + } + + /** + * Make sure the texture cordinates are inverse on the y axis + */ + public void ensureInverted() { + if (textureHeight > 0) { + textureOffsetY = textureOffsetY + textureHeight; + textureHeight = -textureHeight; + } + } + + /** + * Get a copy image flipped on potentially two axis + * + * @param flipHorizontal True if we want to flip the image horizontally + * @param flipVertical True if we want to flip the image vertically + * @return The flipped image instance + */ + public Image getFlippedCopy(boolean flipHorizontal, boolean flipVertical) { + init(); + Image image = copy(); + + if (flipHorizontal) { + image.textureOffsetX = textureOffsetX + textureWidth; + image.textureWidth = -textureWidth; + } + if (flipVertical) { + image.textureOffsetY = textureOffsetY + textureHeight; + image.textureHeight = -textureHeight; + } + + return image; + } + + /** + * End the use of this sprite sheet and release the lock. + * + * @see #startUse + */ + public void endUse() { + if (inUse != this) { + throw new RuntimeException("The sprite sheet is not currently in use"); + } + inUse = null; + GL.glEnd(); + } + + /** + * Start using this sheet. This method can be used for optimal rendering of a collection + * of sprites from a single sprite sheet. First, startUse(). Then render each sprite by + * calling renderInUse(). Finally, endUse(). Between start and end there can be no rendering + * of other sprites since the rendering is locked for this sprite sheet. + */ + public void startUse() { + if (inUse != null) { + throw new RuntimeException("Attempt to start use of a sprite sheet before ending use with another - see endUse()"); + } + inUse = this; + init(); + + Color.white.bind(); + texture.bind(); + GL.glBegin(SGL.GL_QUADS); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + init(); + + return "[Image "+ref+" "+width+"x"+height+" "+textureOffsetX+","+textureOffsetY+","+textureWidth+","+textureHeight+"]"; + } + + /** + * Get the OpenGL texture holding this image + * + * @return The OpenGL texture holding this image + */ + public Texture getTexture() { + return texture; + } + + /** + * Set the texture used by this image + * + * @param texture The texture used by this image + */ + public void setTexture(Texture texture) { + this.texture = texture; + reinit(); + } + + /** + * Translate an unsigned int into a signed integer + * + * @param b The byte to convert + * @return The integer value represented by the byte + */ + private int translate(byte b) { + if (b < 0) { + return 256 + b; + } + + return b; + } + + /** + * Get the colour of a pixel at a specified location in this image + * + * @param x The x coordinate of the pixel + * @param y The y coordinate of the pixel + * @return The Color of the pixel at the specified location + */ + public Color getColor(int x, int y) { + if (pixelData == null) { + pixelData = texture.getTextureData(); + } + + int xo = (int) (textureOffsetX * texture.getTextureWidth()); + int yo = (int) (textureOffsetY * texture.getTextureHeight()); + + if (textureWidth < 0) { + x = xo - x; + } else { + x = xo + x; + } + + if (textureHeight < 0) { + y = yo - y; + } else { + y = yo + y; + } + + int offset = x + (y * texture.getTextureWidth()); + offset *= texture.hasAlpha() ? 4 : 3; + + if (texture.hasAlpha()) { + return new Color(translate(pixelData[offset]),translate(pixelData[offset+1]), + translate(pixelData[offset+2]),translate(pixelData[offset+3])); + } else { + return new Color(translate(pixelData[offset]),translate(pixelData[offset+1]), + translate(pixelData[offset+2])); + } + } + + /** + * Check if this image has been destroyed + * + * @return True if this image has been destroyed + */ + public boolean isDestroyed() { + return destroyed; + } + + /** + * Destroy the image and release any native resources. + * Calls on a destroyed image have undefined results + * + * @throws SlickException Indicates a failure to release resources on the graphics card + */ + public void destroy() throws SlickException { + if (isDestroyed()) { + return; + } + + destroyed = true; + texture.release(); + GraphicsFactory.releaseGraphicsForImage(this); + } + + /** + * Flush the current pixel data to force a re-read next update + */ + public void flushPixelData() { + pixelData = null; + } +} diff --git a/lib/slick-source/org/newdawn/slick/ImageBuffer.java b/lib/slick-source/org/newdawn/slick/ImageBuffer.java new file mode 100644 index 000000000..5151bdd60 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/ImageBuffer.java @@ -0,0 +1,167 @@ +package org.newdawn.slick; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.lwjgl.BufferUtils; +import org.newdawn.slick.opengl.ImageData; + +/** + * A utility for creating images from pixel operations + * + * Expected usage is: + * + * ImageBuffer buffer = new ImageBuffer(320,200); + * buffer.setRGBA(100,100,50,50,20,255); + * .. + * Image image = buffer.getImage(); + * + * + * @author kevin + */ +public class ImageBuffer implements ImageData { + /** The width of the image */ + private int width; + /** The height of the image */ + private int height; + /** The width of the texture */ + private int texWidth; + /** The height of the texture */ + private int texHeight; + /** The raw data generated for the image */ + private byte[] rawData; + + /** + * + * @param width + * @param height + */ + public ImageBuffer(int width, int height) { + this.width = width; + this.height = height; + + texWidth = get2Fold(width); + texHeight = get2Fold(height); + + rawData = new byte[texWidth * texHeight * 4]; + } + + /** + * Retrieve the raw data stored within the image buffer + * + * @return The raw data in RGBA packed format from within the image buffer + */ + public byte[] getRGBA() { + return rawData; + } + + /** + * @see org.newdawn.slick.opengl.ImageData#getDepth() + */ + public int getDepth() { + return 32; + } + + /** + * @see org.newdawn.slick.opengl.ImageData#getHeight() + */ + public int getHeight() { + return height; + } + + /** + * @see org.newdawn.slick.opengl.ImageData#getTexHeight() + */ + public int getTexHeight() { + return texHeight; + } + + /** + * @see org.newdawn.slick.opengl.ImageData#getTexWidth() + */ + public int getTexWidth() { + return texWidth; + } + + /** + * @see org.newdawn.slick.opengl.ImageData#getWidth() + */ + public int getWidth() { + return width; + } + + /** + * @see org.newdawn.slick.opengl.ImageData#getImageBufferData() + */ + public ByteBuffer getImageBufferData() { + ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length); + scratch.put(rawData); + scratch.flip(); + + return scratch; + } + + /** + * Set a pixel in the image buffer + * + * @param x The x position of the pixel to set + * @param y The y position of the pixel to set + * @param r The red component to set (0->255) + * @param g The green component to set (0->255) + * @param b The blue component to set (0->255) + * @param a The alpha component to set (0->255) + */ + public void setRGBA(int x, int y, int r, int g, int b, int a) { + if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) { + throw new RuntimeException("Specified location: "+x+","+y+" outside of image"); + } + + int ofs = ((x + (y * texWidth)) * 4); + + if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { + rawData[ofs] = (byte) b; + rawData[ofs + 1] = (byte) g; + rawData[ofs + 2] = (byte) r; + rawData[ofs + 3] = (byte) a; + } else { + rawData[ofs] = (byte) r; + rawData[ofs + 1] = (byte) g; + rawData[ofs + 2] = (byte) b; + rawData[ofs + 3] = (byte) a; + } + } + + /** + * Get an image generated based on this buffer + * + * @return The image generated from this buffer + */ + public Image getImage() { + return new Image(this); + } + + /** + * Get an image generated based on this buffer + * + * @param filter The filtering method to use when scaling this image + * @return The image generated from this buffer + */ + public Image getImage(int filter) { + return new Image(this, filter); + } + + /** + * Get the closest greater power of 2 to the fold number + * + * @param fold The target number + * @return The power of 2 + */ + private int get2Fold(int fold) { + int ret = 2; + while (ret < fold) { + ret *= 2; + } + return ret; + } + +} diff --git a/lib/slick-source/org/newdawn/slick/Input.java b/lib/slick-source/org/newdawn/slick/Input.java new file mode 100644 index 000000000..59a2691a4 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Input.java @@ -0,0 +1,1533 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; + +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Controller; +import org.lwjgl.input.Controllers; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.newdawn.slick.util.Log; + +/** + * A wrapped for all keyboard, mouse and controller input + * + * @author kevin + */ +public class Input { + /** The controller index to pass to check all controllers */ + public static final int ANY_CONTROLLER = -1; + + /** The maximum number of buttons on controllers */ + private static final int MAX_BUTTONS = 100; + + /** */ + public static final int KEY_ESCAPE = 0x01; + /** */ + public static final int KEY_1 = 0x02; + /** */ + public static final int KEY_2 = 0x03; + /** */ + public static final int KEY_3 = 0x04; + /** */ + public static final int KEY_4 = 0x05; + /** */ + public static final int KEY_5 = 0x06; + /** */ + public static final int KEY_6 = 0x07; + /** */ + public static final int KEY_7 = 0x08; + /** */ + public static final int KEY_8 = 0x09; + /** */ + public static final int KEY_9 = 0x0A; + /** */ + public static final int KEY_0 = 0x0B; + /** */ + public static final int KEY_MINUS = 0x0C; /* - on main keyboard */ + /** */ + public static final int KEY_EQUALS = 0x0D; + /** */ + public static final int KEY_BACK = 0x0E; /* backspace */ + /** */ + public static final int KEY_TAB = 0x0F; + /** */ + public static final int KEY_Q = 0x10; + /** */ + public static final int KEY_W = 0x11; + /** */ + public static final int KEY_E = 0x12; + /** */ + public static final int KEY_R = 0x13; + /** */ + public static final int KEY_T = 0x14; + /** */ + public static final int KEY_Y = 0x15; + /** */ + public static final int KEY_U = 0x16; + /** */ + public static final int KEY_I = 0x17; + /** */ + public static final int KEY_O = 0x18; + /** */ + public static final int KEY_P = 0x19; + /** */ + public static final int KEY_LBRACKET = 0x1A; + /** */ + public static final int KEY_RBRACKET = 0x1B; + /** */ + public static final int KEY_RETURN = 0x1C; /* Enter on main keyboard */ + /** */ + public static final int KEY_ENTER = 0x1C; /* Enter on main keyboard */ + /** */ + public static final int KEY_LCONTROL = 0x1D; + /** */ + public static final int KEY_A = 0x1E; + /** */ + public static final int KEY_S = 0x1F; + /** */ + public static final int KEY_D = 0x20; + /** */ + public static final int KEY_F = 0x21; + /** */ + public static final int KEY_G = 0x22; + /** */ + public static final int KEY_H = 0x23; + /** */ + public static final int KEY_J = 0x24; + /** */ + public static final int KEY_K = 0x25; + /** */ + public static final int KEY_L = 0x26; + /** */ + public static final int KEY_SEMICOLON = 0x27; + /** */ + public static final int KEY_APOSTROPHE = 0x28; + /** */ + public static final int KEY_GRAVE = 0x29; /* accent grave */ + /** */ + public static final int KEY_LSHIFT = 0x2A; + /** */ + public static final int KEY_BACKSLASH = 0x2B; + /** */ + public static final int KEY_Z = 0x2C; + /** */ + public static final int KEY_X = 0x2D; + /** */ + public static final int KEY_C = 0x2E; + /** */ + public static final int KEY_V = 0x2F; + /** */ + public static final int KEY_B = 0x30; + /** */ + public static final int KEY_N = 0x31; + /** */ + public static final int KEY_M = 0x32; + /** */ + public static final int KEY_COMMA = 0x33; + /** */ + public static final int KEY_PERIOD = 0x34; /* . on main keyboard */ + /** */ + public static final int KEY_SLASH = 0x35; /* / on main keyboard */ + /** */ + public static final int KEY_RSHIFT = 0x36; + /** */ + public static final int KEY_MULTIPLY = 0x37; /* * on numeric keypad */ + /** */ + public static final int KEY_LMENU = 0x38; /* left Alt */ + /** */ + public static final int KEY_SPACE = 0x39; + /** */ + public static final int KEY_CAPITAL = 0x3A; + /** */ + public static final int KEY_F1 = 0x3B; + /** */ + public static final int KEY_F2 = 0x3C; + /** */ + public static final int KEY_F3 = 0x3D; + /** */ + public static final int KEY_F4 = 0x3E; + /** */ + public static final int KEY_F5 = 0x3F; + /** */ + public static final int KEY_F6 = 0x40; + /** */ + public static final int KEY_F7 = 0x41; + /** */ + public static final int KEY_F8 = 0x42; + /** */ + public static final int KEY_F9 = 0x43; + /** */ + public static final int KEY_F10 = 0x44; + /** */ + public static final int KEY_NUMLOCK = 0x45; + /** */ + public static final int KEY_SCROLL = 0x46; /* Scroll Lock */ + /** */ + public static final int KEY_NUMPAD7 = 0x47; + /** */ + public static final int KEY_NUMPAD8 = 0x48; + /** */ + public static final int KEY_NUMPAD9 = 0x49; + /** */ + public static final int KEY_SUBTRACT = 0x4A; /* - on numeric keypad */ + /** */ + public static final int KEY_NUMPAD4 = 0x4B; + /** */ + public static final int KEY_NUMPAD5 = 0x4C; + /** */ + public static final int KEY_NUMPAD6 = 0x4D; + /** */ + public static final int KEY_ADD = 0x4E; /* + on numeric keypad */ + /** */ + public static final int KEY_NUMPAD1 = 0x4F; + /** */ + public static final int KEY_NUMPAD2 = 0x50; + /** */ + public static final int KEY_NUMPAD3 = 0x51; + /** */ + public static final int KEY_NUMPAD0 = 0x52; + /** */ + public static final int KEY_DECIMAL = 0x53; /* . on numeric keypad */ + /** */ + public static final int KEY_F11 = 0x57; + /** */ + public static final int KEY_F12 = 0x58; + /** */ + public static final int KEY_F13 = 0x64; /* (NEC PC98) */ + /** */ + public static final int KEY_F14 = 0x65; /* (NEC PC98) */ + /** */ + public static final int KEY_F15 = 0x66; /* (NEC PC98) */ + /** */ + public static final int KEY_KANA = 0x70; /* (Japanese keyboard) */ + /** */ + public static final int KEY_CONVERT = 0x79; /* (Japanese keyboard) */ + /** */ + public static final int KEY_NOCONVERT = 0x7B; /* (Japanese keyboard) */ + /** */ + public static final int KEY_YEN = 0x7D; /* (Japanese keyboard) */ + /** */ + public static final int KEY_NUMPADEQUALS = 0x8D; /* = on numeric keypad (NEC PC98) */ + /** */ + public static final int KEY_CIRCUMFLEX = 0x90; /* (Japanese keyboard) */ + /** */ + public static final int KEY_AT = 0x91; /* (NEC PC98) */ + /** */ + public static final int KEY_COLON = 0x92; /* (NEC PC98) */ + /** */ + public static final int KEY_UNDERLINE = 0x93; /* (NEC PC98) */ + /** */ + public static final int KEY_KANJI = 0x94; /* (Japanese keyboard) */ + /** */ + public static final int KEY_STOP = 0x95; /* (NEC PC98) */ + /** */ + public static final int KEY_AX = 0x96; /* (Japan AX) */ + /** */ + public static final int KEY_UNLABELED = 0x97; /* (J3100) */ + /** */ + public static final int KEY_NUMPADENTER = 0x9C; /* Enter on numeric keypad */ + /** */ + public static final int KEY_RCONTROL = 0x9D; + /** */ + public static final int KEY_NUMPADCOMMA = 0xB3; /* , on numeric keypad (NEC PC98) */ + /** */ + public static final int KEY_DIVIDE = 0xB5; /* / on numeric keypad */ + /** */ + public static final int KEY_SYSRQ = 0xB7; + /** */ + public static final int KEY_RMENU = 0xB8; /* right Alt */ + /** */ + public static final int KEY_PAUSE = 0xC5; /* Pause */ + /** */ + public static final int KEY_HOME = 0xC7; /* Home on arrow keypad */ + /** */ + public static final int KEY_UP = 0xC8; /* UpArrow on arrow keypad */ + /** */ + public static final int KEY_PRIOR = 0xC9; /* PgUp on arrow keypad */ + /** */ + public static final int KEY_LEFT = 0xCB; /* LeftArrow on arrow keypad */ + /** */ + public static final int KEY_RIGHT = 0xCD; /* RightArrow on arrow keypad */ + /** */ + public static final int KEY_END = 0xCF; /* End on arrow keypad */ + /** */ + public static final int KEY_DOWN = 0xD0; /* DownArrow on arrow keypad */ + /** */ + public static final int KEY_NEXT = 0xD1; /* PgDn on arrow keypad */ + /** */ + public static final int KEY_INSERT = 0xD2; /* Insert on arrow keypad */ + /** */ + public static final int KEY_DELETE = 0xD3; /* Delete on arrow keypad */ + /** */ + public static final int KEY_LWIN = 0xDB; /* Left Windows key */ + /** */ + public static final int KEY_RWIN = 0xDC; /* Right Windows key */ + /** */ + public static final int KEY_APPS = 0xDD; /* AppMenu key */ + /** */ + public static final int KEY_POWER = 0xDE; + /** */ + public static final int KEY_SLEEP = 0xDF; + + /** A helper for left ALT */ + public static final int KEY_LALT = KEY_LMENU; + /** A helper for right ALT */ + public static final int KEY_RALT = KEY_RMENU; + + /** Control index */ + private static final int LEFT = 0; + /** Control index */ + private static final int RIGHT = 1; + /** Control index */ + private static final int UP = 2; + /** Control index */ + private static final int DOWN = 3; + /** Control index */ + private static final int BUTTON1 = 4; + /** Control index */ + private static final int BUTTON2 = 5; + /** Control index */ + private static final int BUTTON3 = 6; + /** Control index */ + private static final int BUTTON4 = 7; + /** Control index */ + private static final int BUTTON5 = 8; + /** Control index */ + private static final int BUTTON6 = 9; + /** Control index */ + private static final int BUTTON7 = 10; + /** Control index */ + private static final int BUTTON8 = 11; + /** Control index */ + private static final int BUTTON9 = 12; + /** Control index */ + private static final int BUTTON10 = 13; + + /** The left mouse button indicator */ + public static final int MOUSE_LEFT_BUTTON = 0; + /** The right mouse button indicator */ + public static final int MOUSE_RIGHT_BUTTON = 1; + /** The middle mouse button indicator */ + public static final int MOUSE_MIDDLE_BUTTON = 2; + + /** True if the controllers system has been initialised */ + private static boolean controllersInited = false; + /** The list of controllers */ + private static ArrayList controllers = new ArrayList(); + + /** The last recorded mouse x position */ + private int lastMouseX; + /** The last recorded mouse y position */ + private int lastMouseY; + /** THe state of the mouse buttons */ + protected boolean[] mousePressed = new boolean[10]; + /** THe state of the controller buttons */ + private boolean[][] controllerPressed = new boolean[100][MAX_BUTTONS]; + + /** The character values representing the pressed keys */ + protected char[] keys = new char[1024]; + /** True if the key has been pressed since last queries */ + protected boolean[] pressed = new boolean[1024]; + /** The time since the next key repeat to be fired for the key */ + protected long[] nextRepeat = new long[1024]; + + /** The control states from the controllers */ + private boolean[][] controls = new boolean[10][MAX_BUTTONS+10]; + /** True if the event has been consumed */ + protected boolean consumed = false; + /** A list of listeners to be notified of input events */ + protected HashSet allListeners = new HashSet(); + /** The listeners to notify of key events */ + protected ArrayList keyListeners = new ArrayList(); + /** The listener to add */ + protected ArrayList keyListenersToAdd = new ArrayList(); + /** The listeners to notify of mouse events */ + protected ArrayList mouseListeners = new ArrayList(); + /** The listener to add */ + protected ArrayList mouseListenersToAdd = new ArrayList(); + /** The listener to nofiy of controller events */ + protected ArrayList controllerListeners = new ArrayList(); + /** The current value of the wheel */ + private int wheel; + /** The height of the display */ + private int height; + + /** True if the display is active */ + private boolean displayActive = true; + + /** True if key repeat is enabled */ + private boolean keyRepeat; + /** The initial delay for key repeat starts */ + private int keyRepeatInitial; + /** The interval of key repeat */ + private int keyRepeatInterval; + + /** True if the input is currently paused */ + private boolean paused; + /** The scale to apply to screen coordinates */ + private float scaleX = 1; + /** The scale to apply to screen coordinates */ + private float scaleY = 1; + /** The offset to apply to screen coordinates */ + private float xoffset = 0; + /** The offset to apply to screen coordinates */ + private float yoffset = 0; + + /** The delay before determining a single or double click */ + private int doubleClickDelay = 250; + /** The timer running out for a single click */ + private long doubleClickTimeout = 0; + + /** The clicked x position */ + private int clickX; + /** The clicked y position */ + private int clickY; + /** The clicked button */ + private int clickButton; + + /** The x position location the mouse was pressed */ + private int pressedX = -1; + + /** The x position location the mouse was pressed */ + private int pressedY = -1; + + /** The pixel distance the mouse can move to accept a mouse click */ + private int mouseClickTolerance = 5; + + /** + * Disables support for controllers. This means the jinput JAR and native libs + * are not required. + */ + public static void disableControllers() { + controllersInited = true; + } + + /** + * Create a new input with the height of the screen + * + * @param height The height of the screen + */ + public Input(int height) { + init(height); + } + + /** + * Set the double click interval, the time between the first + * and second clicks that should be interpreted as a + * double click. + * + * @param delay The delay between clicks + */ + public void setDoubleClickInterval(int delay) { + doubleClickDelay = delay; + } + + /** + * Set the pixel distance the mouse can move to accept a mouse click. + * Default is 5. + * + * @param mouseClickTolerance The number of pixels. + */ + public void setMouseClickTolerance (int mouseClickTolerance) { + this.mouseClickTolerance = mouseClickTolerance; + } + + /** + * Set the scaling to apply to screen coordinates + * + * @param scaleX The scaling to apply to the horizontal axis + * @param scaleY The scaling to apply to the vertical axis + */ + public void setScale(float scaleX, float scaleY) { + this.scaleX = scaleX; + this.scaleY = scaleY; + } + + /** + * Set the offset to apply to the screen coodinates + * + * @param xoffset The offset on the x-axis + * @param yoffset The offset on the y-axis + */ + public void setOffset(float xoffset, float yoffset) { + this.xoffset = xoffset; + this.yoffset = yoffset; + } + + /** + * Reset the transformation being applied to the input to the default + */ + public void resetInputTransform() { + setOffset(0, 0); + setScale(1, 1); + } + + /** + * Add a listener to be notified of input events + * + * @param listener The listener to be notified + */ + public void addListener(InputListener listener) { + addKeyListener(listener); + addMouseListener(listener); + addControllerListener(listener); + } + + /** + * Add a key listener to be notified of key input events + * + * @param listener The listener to be notified + */ + public void addKeyListener(KeyListener listener) { + keyListenersToAdd.add(listener); + } + + /** + * Add a key listener to be notified of key input events + * + * @param listener The listener to be notified + */ + private void addKeyListenerImpl(KeyListener listener) { + if (keyListeners.contains(listener)) { + return; + } + keyListeners.add(listener); + allListeners.add(listener); + } + + /** + * Add a mouse listener to be notified of mouse input events + * + * @param listener The listener to be notified + */ + public void addMouseListener(MouseListener listener) { + mouseListenersToAdd.add(listener); + } + + /** + * Add a mouse listener to be notified of mouse input events + * + * @param listener The listener to be notified + */ + private void addMouseListenerImpl(MouseListener listener) { + if (mouseListeners.contains(listener)) { + return; + } + mouseListeners.add(listener); + allListeners.add(listener); + } + + /** + * Add a controller listener to be notified of controller input events + * + * @param listener The listener to be notified + */ + public void addControllerListener(ControllerListener listener) { + if (controllerListeners.contains(listener)) { + return; + } + controllerListeners.add(listener); + allListeners.add(listener); + } + + /** + * Remove all the listeners from this input + */ + public void removeAllListeners() { + removeAllKeyListeners(); + removeAllMouseListeners(); + removeAllControllerListeners(); + } + + /** + * Remove all the key listeners from this input + */ + public void removeAllKeyListeners() { + allListeners.removeAll(keyListeners); + keyListeners.clear(); + } + + /** + * Remove all the mouse listeners from this input + */ + public void removeAllMouseListeners() { + allListeners.removeAll(mouseListeners); + mouseListeners.clear(); + } + + /** + * Remove all the controller listeners from this input + */ + public void removeAllControllerListeners() { + allListeners.removeAll(controllerListeners); + controllerListeners.clear(); + } + + /** + * Add a listener to be notified of input events. This listener + * will get events before others that are currently registered + * + * @param listener The listener to be notified + */ + public void addPrimaryListener(InputListener listener) { + removeListener(listener); + + keyListeners.add(0, listener); + mouseListeners.add(0, listener); + controllerListeners.add(0, listener); + + allListeners.add(listener); + } + + /** + * Remove a listener that will no longer be notified + * + * @param listener The listen to be removed + */ + public void removeListener(InputListener listener) { + removeKeyListener(listener); + removeMouseListener(listener); + removeControllerListener(listener); + } + + /** + * Remove a key listener that will no longer be notified + * + * @param listener The listen to be removed + */ + public void removeKeyListener(KeyListener listener) { + keyListeners.remove(listener); + + if (!mouseListeners.contains(listener) && !controllerListeners.contains(listener)) { + allListeners.remove(listener); + } + } + + /** + * Remove a controller listener that will no longer be notified + * + * @param listener The listen to be removed + */ + public void removeControllerListener(ControllerListener listener) { + controllerListeners.remove(listener); + + if (!mouseListeners.contains(listener) && !keyListeners.contains(listener)) { + allListeners.remove(listener); + } + } + + /** + * Remove a mouse listener that will no longer be notified + * + * @param listener The listen to be removed + */ + public void removeMouseListener(MouseListener listener) { + mouseListeners.remove(listener); + + if (!controllerListeners.contains(listener) && !keyListeners.contains(listener)) { + allListeners.remove(listener); + } + } + + /** + * Initialise the input system + * + * @param height The height of the window + */ + void init(int height) { + this.height = height; + lastMouseX = getMouseX(); + lastMouseY = getMouseY(); + } + + /** + * Get the character representation of the key identified by the specified code + * + * @param code The key code of the key to retrieve the name of + * @return The name or character representation of the key requested + */ + public static String getKeyName(int code) { + return Keyboard.getKeyName(code); + } + + /** + * Check if a particular key has been pressed since this method + * was last called for the specified key + * + * @param code The key code of the key to check + * @return True if the key has been pressed + */ + public boolean isKeyPressed(int code) { + if (pressed[code]) { + pressed[code] = false; + return true; + } + + return false; + } + + /** + * Check if a mouse button has been pressed since last call + * + * @param button The button to check + * @return True if the button has been pressed since last call + */ + public boolean isMousePressed(int button) { + if (mousePressed[button]) { + mousePressed[button] = false; + return true; + } + + return false; + } + + /** + * Check if a controller button has been pressed since last + * time + * + * @param button The button to check for (note that this includes directional controls first) + * @return True if the button has been pressed since last time + */ + public boolean isControlPressed(int button) { + return isControlPressed(button, 0); + } + + /** + * Check if a controller button has been pressed since last + * time + * + * @param controller The index of the controller to check + * @param button The button to check for (note that this includes directional controls first) + * @return True if the button has been pressed since last time + */ + public boolean isControlPressed(int button, int controller) { + if (controllerPressed[controller][button]) { + controllerPressed[controller][button] = false; + return true; + } + + return false; + } + + /** + * Clear the state for isControlPressed method. This will reset all + * controls to not pressed + */ + public void clearControlPressedRecord() { + for (int i=0;iisKeyPressed method. This will + * resort in all keys returning that they haven't been pressed, until + * they are pressed again + */ + public void clearKeyPressedRecord() { + Arrays.fill(pressed, false); + } + + /** + * Clear the state for the isMousePressed method. This will + * resort in all mouse buttons returning that they haven't been pressed, until + * they are pressed again + */ + public void clearMousePressedRecord() { + Arrays.fill(mousePressed, false); + } + + /** + * Check if a particular key is down + * + * @param code The key code of the key to check + * @return True if the key is down + */ + public boolean isKeyDown(int code) { + return Keyboard.isKeyDown(code); + } + + /** + * Get the absolute x position of the mouse cursor within the container + * + * @return The absolute x position of the mouse cursor + */ + public int getAbsoluteMouseX() { + return Mouse.getX(); + } + + /** + * Get the absolute y position of the mouse cursor within the container + * + * @return The absolute y position of the mouse cursor + */ + public int getAbsoluteMouseY() { + return height - Mouse.getY(); + } + + /** + * Get the x position of the mouse cursor + * + * @return The x position of the mouse cursor + */ + public int getMouseX() { + return (int) ((Mouse.getX() * scaleX)+xoffset); + } + + /** + * Get the y position of the mouse cursor + * + * @return The y position of the mouse cursor + */ + public int getMouseY() { + return (int) (((height-Mouse.getY()) * scaleY)+yoffset); + } + + /** + * Check if a given mouse button is down + * + * @param button The index of the button to check (starting at 0) + * @return True if the mouse button is down + */ + public boolean isMouseButtonDown(int button) { + return Mouse.isButtonDown(button); + } + + /** + * Check if any mouse button is down + * + * @return True if any mouse button is down + */ + private boolean anyMouseDown() { + for (int i=0;i<3;i++) { + if (Mouse.isButtonDown(i)) { + return true; + } + } + + return false; + } + + /** + * Get a count of the number of controlles available + * + * @return The number of controllers available + */ + public int getControllerCount() { + try { + initControllers(); + } catch (SlickException e) { + throw new RuntimeException("Failed to initialise controllers"); + } + + return controllers.size(); + } + + /** + * Get the number of axis that are avaiable on a given controller + * + * @param controller The index of the controller to check + * @return The number of axis available on the controller + */ + public int getAxisCount(int controller) { + return ((Controller) controllers.get(controller)).getAxisCount(); + } + + /** + * Get the value of the axis with the given index + * + * @param controller The index of the controller to check + * @param axis The index of the axis to read + * @return The axis value at time of reading + */ + public float getAxisValue(int controller, int axis) { + return ((Controller) controllers.get(controller)).getAxisValue(axis); + } + + /** + * Get the name of the axis with the given index + * + * @param controller The index of the controller to check + * @param axis The index of the axis to read + * @return The name of the specified axis + */ + public String getAxisName(int controller, int axis) { + return ((Controller) controllers.get(controller)).getAxisName(axis); + } + + /** + * Check if the controller has the left direction pressed + * + * @param controller The index of the controller to check + * @return True if the controller is pressed to the left + */ + public boolean isControllerLeft(int controller) { + if (controller >= getControllerCount()) { + return false; + } + + if (controller == ANY_CONTROLLER) { + for (int i=0;i= getControllerCount()) { + return false; + } + + if (controller == ANY_CONTROLLER) { + for (int i=0;i 0.5f + || ((Controller) controllers.get(controller)).getPovX() > 0.5f; + } + + /** + * Check if the controller has the up direction pressed + * + * @param controller The index of the controller to check + * @return True if the controller is pressed to the up + */ + public boolean isControllerUp(int controller) { + if (controller >= getControllerCount()) { + return false; + } + + if (controller == ANY_CONTROLLER) { + for (int i=0;i= getControllerCount()) { + return false; + } + + if (controller == ANY_CONTROLLER) { + for (int i=0;i 0.5f + || ((Controller) controllers.get(controller)).getPovY() > 0.5f; + + } + + /** + * Check if controller button is pressed + * + * @param controller The index of the controller to check + * @param index The index of the button to check + * @return True if the button is pressed + */ + public boolean isButtonPressed(int index, int controller) { + if (controller >= getControllerCount()) { + return false; + } + + if (controller == ANY_CONTROLLER) { + for (int i=0;i= 3) && (controller.getButtonCount() < MAX_BUTTONS)) { + controllers.add(controller); + } + } + + Log.info("Found "+controllers.size()+" controllers"); + for (int i=0;i doubleClickTimeout) { + doubleClickTimeout = 0; + } + } + + this.height = height; + + Iterator allStarts = allListeners.iterator(); + while (allStarts.hasNext()) { + ControlledInputReciever listener = (ControlledInputReciever) allStarts.next(); + listener.inputStarted(); + } + + while (Keyboard.next()) { + if (Keyboard.getEventKeyState()) { + int eventKey = resolveEventKey(Keyboard.getEventKey(), Keyboard.getEventCharacter()); + + keys[eventKey] = Keyboard.getEventCharacter(); + pressed[eventKey] = true; + nextRepeat[eventKey] = System.currentTimeMillis() + keyRepeatInitial; + + consumed = false; + for (int i=0;i= 0) { + if (Mouse.getEventButtonState()) { + consumed = false; + mousePressed[Mouse.getEventButton()] = true; + + pressedX = (int) (xoffset + (Mouse.getEventX() * scaleX)); + pressedY = (int) (yoffset + ((height-Mouse.getEventY()) * scaleY)); + + for (int i=0;i nextRepeat[i]) { + nextRepeat[i] = System.currentTimeMillis() + keyRepeatInterval; + consumed = false; + for (int j=0;j= BUTTON1) { + return isButtonPressed((index-BUTTON1), controllerIndex); + } + + throw new RuntimeException("Unknown control index"); + } + + + /** + * Pauses the polling and sending of input events. + */ + public void pause() { + paused = true; + + // Reset all polling arrays + clearKeyPressedRecord(); + clearMousePressedRecord(); + clearControlPressedRecord(); + } + + /** + * Resumes the polling and sending of input events. + */ + public void resume() { + paused = false; + } + + /** + * Notify listeners that the mouse button has been clicked + * + * @param button The button that has been clicked + * @param x The location at which the button was clicked + * @param y The location at which the button was clicked + * @param clickCount The number of times the button was clicked (single or double click) + */ + private void fireMouseClicked(int button, int x, int y, int clickCount) { + consumed = false; + for (int i=0;i + */ +public class Music { + /** The music currently being played or null if none */ + private static Music currentMusic; + + /** + * Poll the state of the current music. This causes streaming music + * to stream and checks listeners. Note that if you're using a game container + * this will be auto-magically called for you. + * + * @param delta The amount of time since last poll + */ + public static void poll(int delta) { + if (currentMusic != null) { + SoundStore.get().poll(delta); + if (!SoundStore.get().isMusicPlaying()) { + if (!currentMusic.positioning) { + Music oldMusic = currentMusic; + currentMusic = null; + oldMusic.fireMusicEnded(); + } + } else { + currentMusic.update(delta); + } + } + } + + /** The sound from FECK representing this music */ + private Audio sound; + /** True if the music is playing */ + private boolean playing; + /** The list of listeners waiting for notification that the music ended */ + private ArrayList listeners = new ArrayList(); + /** The volume of this music */ + private float volume = 1.0f; + /** Start gain for fading in/out */ + private float fadeStartGain; + /** End gain for fading in/out */ + private float fadeEndGain; + /** Countdown for fading in/out */ + private int fadeTime; + /** Duration for fading in/out */ + private int fadeDuration; + /** True if music should be stopped after fading in/out */ + private boolean stopAfterFade; + /** True if the music is being repositioned and it is therefore normal that it's not playing */ + private boolean positioning; + /** The position that was requested */ + private float requiredPosition = -1; + + /** + * Create and load a piece of music (either OGG or MOD/XM) + * + * @param ref The location of the music + * @throws SlickException + */ + public Music(String ref) throws SlickException { + this(ref, false); + } + + /** + * Create and load a piece of music (either OGG or MOD/XM) + * + * @param ref The location of the music + * @throws SlickException + */ + public Music(URL ref) throws SlickException { + this(ref, false); + } + + /** + * Create and load a piece of music (either OGG or MOD/XM) + * @param in The stream to read the music from + * @param ref The symbolic name of this music + * @throws SlickException Indicates a failure to read the music from the stream + */ + public Music(InputStream in, String ref) throws SlickException { + SoundStore.get().init(); + + try { + if (ref.toLowerCase().endsWith(".ogg")) { + sound = SoundStore.get().getOgg(in); + } else if (ref.toLowerCase().endsWith(".wav")) { + sound = SoundStore.get().getWAV(in); + } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { + sound = SoundStore.get().getMOD(in); + } else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) { + sound = SoundStore.get().getAIF(in); + } else { + throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported."); + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to load music: "+ref); + } + } + + /** + * Create and load a piece of music (either OGG or MOD/XM) + * + * @param url The location of the music + * @param streamingHint A hint to indicate whether streaming should be used if possible + * @throws SlickException + */ + public Music(URL url, boolean streamingHint) throws SlickException { + SoundStore.get().init(); + String ref = url.getFile(); + + try { + if (ref.toLowerCase().endsWith(".ogg")) { + if (streamingHint) { + sound = SoundStore.get().getOggStream(url); + } else { + sound = SoundStore.get().getOgg(url.openStream()); + } + } else if (ref.toLowerCase().endsWith(".wav")) { + sound = SoundStore.get().getWAV(url.openStream()); + } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { + sound = SoundStore.get().getMOD(url.openStream()); + } else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) { + sound = SoundStore.get().getAIF(url.openStream()); + } else { + throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported."); + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to load sound: "+url); + } + } + + /** + * Create and load a piece of music (either OGG or MOD/XM) + * + * @param ref The location of the music + * @param streamingHint A hint to indicate whether streaming should be used if possible + * @throws SlickException + */ + public Music(String ref, boolean streamingHint) throws SlickException { + SoundStore.get().init(); + + try { + if (ref.toLowerCase().endsWith(".ogg")) { + if (streamingHint) { + sound = SoundStore.get().getOggStream(ref); + } else { + sound = SoundStore.get().getOgg(ref); + } + } else if (ref.toLowerCase().endsWith(".wav")) { + sound = SoundStore.get().getWAV(ref); + } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { + sound = SoundStore.get().getMOD(ref); + } else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) { + sound = SoundStore.get().getAIF(ref); + } else { + throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported."); + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to load sound: "+ref); + } + } + + /** + * Add a listener to this music + * + * @param listener The listener to add + */ + public void addListener(MusicListener listener) { + listeners.add(listener); + } + + /** + * Remove a listener from this music + * + * @param listener The listener to remove + */ + public void removeListener(MusicListener listener) { + listeners.remove(listener); + } + + /** + * Fire notifications that this music ended + */ + private void fireMusicEnded() { + playing = false; + for (int i=0;i 1.0f) + volume = 1.0f; + + sound.playAsMusic(pitch, volume, loop); + playing = true; + setVolume(volume); + if (requiredPosition != -1) { + setPosition(requiredPosition); + } + } + + /** + * Pause the music playback + */ + public void pause() { + playing = false; + AudioImpl.pauseMusic(); + } + + /** + * Stop the music playing + */ + public void stop() { + sound.stop(); + } + + /** + * Resume the music playback + */ + public void resume() { + playing = true; + AudioImpl.restartMusic(); + } + + /** + * Check if the music is being played + * + * @return True if the music is being played + */ + public boolean playing() { + return (currentMusic == this) && (playing); + } + + /** + * Set the volume of the music as a factor of the global volume setting + * + * @param volume The volume to play music at. 0 - 1, 1 is Max + */ + public void setVolume(float volume) { + // Bounds check + if(volume > 1) { + volume = 1; + } else if(volume < 0) { + volume = 0; + } + + this.volume = volume; + // This sound is being played as music + if (currentMusic == this) { + SoundStore.get().setCurrentMusicVolume(volume); + } + } + + /** + * Get the individual volume of the music + * @return The volume of this music, still effected by global SoundStore volume. 0 - 1, 1 is Max + */ + public float getVolume() { + return volume; + } + + /** + * Fade this music to the volume specified + * + * @param duration Fade time in milliseconds. + * @param endVolume The target volume + * @param stopAfterFade True if music should be stopped after fading in/out + */ + public void fade (int duration, float endVolume, boolean stopAfterFade) { + this.stopAfterFade = stopAfterFade; + fadeStartGain = volume; + fadeEndGain = endVolume; + fadeDuration = duration; + fadeTime = duration; + } + + /** + * Update the current music applying any effects that need to updated per + * tick. + * + * @param delta The amount of time in milliseconds thats passed since last update + */ + void update(int delta) { + if (!playing) { + return; + } + + if (fadeTime > 0) { + fadeTime -= delta; + if (fadeTime < 0) { + fadeTime = 0; + if (stopAfterFade) { + stop(); + return; + } + } + + float offset = (fadeEndGain - fadeStartGain) * (1 - (fadeTime / (float)fadeDuration)); + setVolume(fadeStartGain + offset); + } + } + + /** + * Seeks to a position in the music. For streaming music, seeking before the current position causes + * the stream to be reloaded. + * + * @param position Position in seconds. + * @return True if the seek was successful + */ + public boolean setPosition(float position) { + if (playing) { + requiredPosition = -1; + + positioning = true; + playing = false; + boolean result = sound.setPosition(position); + playing = true; + positioning = false; + + return result; + } else { + requiredPosition = position; + return false; + } + } + + /** + * The position into the sound thats being played + * + * @return The current position in seconds. + */ + public float getPosition () { + return sound.getPosition(); + } +} diff --git a/lib/slick-source/org/newdawn/slick/MusicListener.java b/lib/slick-source/org/newdawn/slick/MusicListener.java new file mode 100644 index 000000000..668dcdfb2 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/MusicListener.java @@ -0,0 +1,26 @@ +package org.newdawn.slick; + +/** + * The description of any class needing to recieve notification of changes + * to music state. + * + * @author kevin + */ +public interface MusicListener { + + /** + * Notification that a piece of music finished playing + * + * @param music The music that finished playing + */ + public void musicEnded(Music music); + + /** + * Notification that a piece of music has been swapped + * for another. + * + * @param music The music that has been swapped out + * @param newMusic The new music we're playing + */ + public void musicSwapped(Music music, Music newMusic); +} diff --git a/lib/slick-source/org/newdawn/slick/PackedSpriteSheet.java b/lib/slick-source/org/newdawn/slick/PackedSpriteSheet.java new file mode 100644 index 000000000..4dd893b6e --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/PackedSpriteSheet.java @@ -0,0 +1,193 @@ +package org.newdawn.slick; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; + +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +/** + * A sprite sheet packed and defined by the Pacific Software Image Packer available + * from: + * + * http://homepage.ntlworld.com/config/imagepacker/ + * + * @author kevin + */ +public class PackedSpriteSheet { + /** The image loaded for the sheet */ + private Image image; + /** The base path where the image is expected to be found based on the original definition file */ + private String basePath; + /** The section definitions */ + private HashMap sections = new HashMap(); + /** The filter used when loading the image */ + private int filter = Image.FILTER_NEAREST; + + /** + * Create a new packed sprite sheet based on a ImagePacker definition file + * + * @param def The location of the definition file to read + * @throws SlickException Indicates a failure to read the definition file + */ + public PackedSpriteSheet(String def) throws SlickException { + this(def, null); + } + + /** + * Create a new packed sprite sheet based on a ImagePacker definition file + * + * @param def The location of the definition file to read + * @param trans The color to be treated as transparent + * @throws SlickException Indicates a failure to read the definition file + */ + public PackedSpriteSheet(String def, Color trans) throws SlickException { + def = def.replace('\\', '/'); + basePath = def.substring(0,def.lastIndexOf("/")+1); + + loadDefinition(def, trans); + } + + /** + * Create a new packed sprite sheet based on a ImagePacker definition file + * + * @param def The location of the definition file to read + * @param filter The image filter to use when loading the packed sprite image + * @throws SlickException Indicates a failure to read the definition file + */ + public PackedSpriteSheet(String def, int filter) throws SlickException { + this(def, filter, null); + } + + /** + * Create a new packed sprite sheet based on a ImagePacker definition file + * + * @param def The location of the definition file to read + * @param filter The image filter to use when loading the packed sprite image + * @param trans The color to be treated as transparent + * @throws SlickException Indicates a failure to read the definition file + */ + public PackedSpriteSheet(String def, int filter, Color trans) throws SlickException { + this.filter = filter; + + def = def.replace('\\', '/'); + basePath = def.substring(0,def.lastIndexOf("/")+1); + + loadDefinition(def, trans); + } + + /** + * Get the full image contaning all the sprites/sections + * + * @return The full image containing all the sprites/sections + */ + public Image getFullImage() { + return image; + } + + /** + * Get a single named sprite from the sheet + * + * @param name The name of the sprite to retrieve + * @return The sprite requested (image of) + */ + public Image getSprite(String name) { + Section section = (Section) sections.get(name); + + if (section == null) { + throw new RuntimeException("Unknown sprite from packed sheet: "+name); + } + + return image.getSubImage(section.x, section.y, section.width, section.height); + } + + /** + * Get a sprite sheet that has been packed into the greater image + * + * @param name The name of the sprite sheet to retrieve + * @return The sprite sheet from the packed sheet + */ + public SpriteSheet getSpriteSheet(String name) { + Image image = getSprite(name); + Section section = (Section) sections.get(name); + + return new SpriteSheet(image, section.width / section.tilesx, section.height / section.tilesy); + } + + /** + * Load the definition file and parse each of the sections + * + * @param def The location of the definitions file + * @param trans The color to be treated as transparent + * @throws SlickException Indicates a failure to read or parse the definitions file + * or referenced image. + */ + private void loadDefinition(String def, Color trans) throws SlickException { + BufferedReader reader = new BufferedReader(new InputStreamReader(ResourceLoader.getResourceAsStream(def))); + + try { + image = new Image(basePath+reader.readLine(), false, filter, trans); + while (reader.ready()) { + if (reader.readLine() == null) { + break; + } + + Section sect = new Section(reader); + sections.put(sect.name, sect); + + if (reader.readLine() == null) { + break; + } + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to process definitions file - invalid format?", e); + } + } + + /** + * A single section defined within the packed sheet + * + * @author kevin + */ + private class Section { + /** The x position of the section */ + public int x; + /** The y position of the section */ + public int y; + /** The width of the section */ + public int width; + /** The height of the section */ + public int height; + /** The number of sprites across this section */ + public int tilesx; + /** The number of sprites down this section */ + public int tilesy; + /** The name of this section */ + public String name; + + /** + * Create a new section by reading the stream provided + * + * @param reader The reader from which the definition can be read + * @throws IOException Indicates a failure toread the provided stream + */ + public Section(BufferedReader reader) throws IOException { + name = reader.readLine().trim(); + + x = Integer.parseInt(reader.readLine().trim()); + y = Integer.parseInt(reader.readLine().trim()); + width = Integer.parseInt(reader.readLine().trim()); + height = Integer.parseInt(reader.readLine().trim()); + tilesx = Integer.parseInt(reader.readLine().trim()); + tilesy = Integer.parseInt(reader.readLine().trim()); + reader.readLine().trim(); + reader.readLine().trim(); + + tilesx = Math.max(1,tilesx); + tilesy = Math.max(1,tilesy); + } + } +} diff --git a/lib/slick-source/org/newdawn/slick/Renderable.java b/lib/slick-source/org/newdawn/slick/Renderable.java new file mode 100644 index 000000000..4cfef9ad2 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Renderable.java @@ -0,0 +1,18 @@ +package org.newdawn.slick; + +/** + * Description of anything that can be drawn + * + * @author kevin + */ +public interface Renderable { + + /** + * Draw this artefact at the given location + * + * @param x The x coordinate to draw the artefact at + * @param y The y coordinate to draw the artefact at + */ + public void draw(float x, float y); + +} diff --git a/lib/slick-source/org/newdawn/slick/SavedState.java b/lib/slick-source/org/newdawn/slick/SavedState.java new file mode 100644 index 000000000..b9a483828 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/SavedState.java @@ -0,0 +1,176 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.util.HashMap; + +import javax.jnlp.ServiceManager; + +import org.newdawn.slick.muffin.FileMuffin; +import org.newdawn.slick.muffin.Muffin; +import org.newdawn.slick.muffin.WebstartMuffin; +import org.newdawn.slick.util.Log; + +/** + * A utility to allow game setup/state to be stored locally. This utility will adapt to the + * current enviornment (webstart or file based). Note that this will not currently + * work in an applet. + * + * @author kappaOne + */ +public class SavedState { + /** file name of where the scores will be saved */ + private String fileName; + /** Type of Muffin to use */ + private Muffin muffin; + /** hash map where int data will be stored */ + private HashMap numericData = new HashMap(); + /** hash map where string data will be stored */ + private HashMap stringData = new HashMap(); + + /** + * Create and Test to see if the app is running + * as webstart or local app and select the appropriate + * muffin type + * + * @param fileName name of muffin where data will be saved + * @throws SlickException Indicates a failure to load the stored state + */ + public SavedState(String fileName) throws SlickException { + this.fileName = fileName; + + if (isWebstartAvailable()) { + muffin = new WebstartMuffin(); + } + else { + muffin = new FileMuffin(); + } + + try { + load(); + } catch (IOException e) { + throw new SlickException("Failed to load state on startup",e); + } + } + + /** + * Get number stored at given location + * + * @param nameOfField The name of the number to retrieve + * @return The number saved at this location + */ + public double getNumber(String nameOfField) { + return getNumber(nameOfField, 0); + } + + /** + * Get number stored at given location + * + * @param nameOfField The name of the number to retrieve + * @param defaultValue The value to return if the specified value hasn't been set + * @return The number saved at this location + */ + public double getNumber(String nameOfField, double defaultValue) { + Double value = ((Double)numericData.get(nameOfField)); + + if (value == null) { + return defaultValue; + } + + return value.doubleValue(); + } + + /** + * Save the given value at the given location + * will overwrite any previous value at this location + * + * @param nameOfField The name to store the value against + * @param value The value to store + */ + public void setNumber(String nameOfField, double value){ + numericData.put(nameOfField, new Double(value)); + } + + /** + * Get the String at the given location + * + * @param nameOfField location of string + * @return String stored at the location given + */ + public String getString(String nameOfField) { + return getString(nameOfField, null); + } + + /** + * Get the String at the given location + * + * @param nameOfField location of string + * @param defaultValue The value to return if the specified value hasn't been set + * @return String stored at the location given + */ + public String getString(String nameOfField, String defaultValue) { + String value = (String) stringData.get(nameOfField); + + if (value == null) { + return defaultValue; + } + + return value; + } + + /** + * Save the given value at the given location + * will overwrite any previous value at this location + * + * @param nameOfField location to store int + * @param value The value to store + */ + public void setString(String nameOfField, String value){ + stringData.put(nameOfField, value); + } + + /** + * Save the stored data to file/muffin + * + * @throws IOException Indicates it wasn't possible to store the state + */ + public void save() throws IOException { + muffin.saveFile(numericData, fileName + "_Number"); + muffin.saveFile(stringData, fileName + "_String"); + } + + /** + * Load the data from file/muffin + * + * @throws IOException Indicates it wasn't possible to load the state + */ + public void load() throws IOException { + numericData = muffin.loadFile(fileName + "_Number"); + stringData = muffin.loadFile(fileName + "_String"); + } + + /** + * Will delete all current data held in Score + */ + public void clear() { + numericData.clear(); + stringData.clear(); + } + + /** + * Quick test to see if running through Java webstart + * + * @return True if jws running + */ + private boolean isWebstartAvailable() { + try { + Class.forName("javax.jnlp.ServiceManager"); + // this causes to go and see if the service is available + ServiceManager.lookup("javax.jnlp.PersistenceService"); + Log.info("Webstart detected using Muffins"); + } catch (Exception e) { + Log.info("Using Local File System"); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/lib/slick-source/org/newdawn/slick/ScalableGame.java b/lib/slick-source/org/newdawn/slick/ScalableGame.java new file mode 100644 index 000000000..4b6887b6d --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/ScalableGame.java @@ -0,0 +1,188 @@ +package org.newdawn.slick; + +import org.newdawn.slick.opengl.SlickCallable; +import org.newdawn.slick.opengl.renderer.Renderer; +import org.newdawn.slick.opengl.renderer.SGL; + +/** + * 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. + * + * 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); + } + + /** + * 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(); + } +} diff --git a/lib/slick-source/org/newdawn/slick/ShapeFill.java b/lib/slick-source/org/newdawn/slick/ShapeFill.java new file mode 100644 index 000000000..3efd42ade --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/ShapeFill.java @@ -0,0 +1,33 @@ +package org.newdawn.slick; + +import org.newdawn.slick.geom.Shape; +import org.newdawn.slick.geom.Vector2f; + +/** + * A filling method for a shape. This allows changing colours at shape verticies and + * modify they're positions as required + * + * @author kevin + */ +public interface ShapeFill { + + /** + * Get the colour that should be applied at the specified location + * + * @param shape The shape being filled + * @param x The x coordinate of the point being coloured + * @param y The y coordinate of the point being coloured + * @return The colour that should be applied based on the control points of this gradient + */ + public Color colorAt(Shape shape, float x, float y); + + /** + * Get the offset for a vertex at a given location based on it's shape + * + * @param shape The shape being filled + * @param x The x coordinate of the point being drawn + * @param y The y coordinate of the point being drawn + * @return The offset to apply to this vertex + */ + public Vector2f getOffsetAt(Shape shape, float x, float y); +} diff --git a/lib/slick-source/org/newdawn/slick/SlickException.java b/lib/slick-source/org/newdawn/slick/SlickException.java new file mode 100644 index 000000000..bfb7d9572 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/SlickException.java @@ -0,0 +1,27 @@ +package org.newdawn.slick; + +/** + * A generic exception thrown by everything in the library + * + * @author kevin + */ +public class SlickException extends Exception { + /** + * Create a new exception with a detail message + * + * @param message The message describing the cause of this exception + */ + public SlickException(String message) { + super(message); + } + + /** + * Create a new exception with a detail message + * + * @param message The message describing the cause of this exception + * @param e The exception causing this exception to be thrown + */ + public SlickException(String message, Throwable e) { + super(message, e); + } +} diff --git a/lib/slick-source/org/newdawn/slick/Sound.java b/lib/slick-source/org/newdawn/slick/Sound.java new file mode 100644 index 000000000..8471af123 --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/Sound.java @@ -0,0 +1,175 @@ +package org.newdawn.slick; + +import java.io.InputStream; +import java.net.URL; + +import org.newdawn.slick.openal.Audio; +import org.newdawn.slick.openal.SoundStore; +import org.newdawn.slick.util.Log; + +/** + * A single sound effect loaded from either OGG or XM/MOD file. Sounds are allocated to + * channels dynamically - if not channel is available the sound will not play. + * + * @author kevin + */ +public class Sound { + /** The internal sound effect represent this sound */ + private Audio sound; + + /** + * Create a new Sound + * + * @param in The location of the OGG or MOD/XM to load + * @param ref The name to associate this stream + * @throws SlickException Indicates a failure to load the sound effect + */ + public Sound(InputStream in, String ref) throws SlickException { + SoundStore.get().init(); + + try { + if (ref.toLowerCase().endsWith(".ogg")) { + sound = SoundStore.get().getOgg(in); + } else if (ref.toLowerCase().endsWith(".wav")) { + sound = SoundStore.get().getWAV(in); + } else if (ref.toLowerCase().endsWith(".aif")) { + sound = SoundStore.get().getAIF(in); + } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { + sound = SoundStore.get().getMOD(in); + } else { + throw new SlickException("Only .xm, .mod, .aif, .wav and .ogg are currently supported."); + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to load sound: "+ref); + } + } + + /** + * Create a new Sound + * + * @param url The location of the OGG or MOD/XM to load + * @throws SlickException Indicates a failure to load the sound effect + */ + public Sound(URL url) throws SlickException { + SoundStore.get().init(); + String ref = url.getFile(); + + try { + if (ref.toLowerCase().endsWith(".ogg")) { + sound = SoundStore.get().getOgg(url.openStream()); + } else if (ref.toLowerCase().endsWith(".wav")) { + sound = SoundStore.get().getWAV(url.openStream()); + } else if (ref.toLowerCase().endsWith(".aif")) { + sound = SoundStore.get().getAIF(url.openStream()); + } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { + sound = SoundStore.get().getMOD(url.openStream()); + } else { + throw new SlickException("Only .xm, .mod, .aif, .wav and .ogg are currently supported."); + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to load sound: "+ref); + } + } + + /** + * Create a new Sound + * + * @param ref The location of the OGG or MOD/XM to load + * @throws SlickException Indicates a failure to load the sound effect + */ + public Sound(String ref) throws SlickException { + SoundStore.get().init(); + + try { + if (ref.toLowerCase().endsWith(".ogg")) { + sound = SoundStore.get().getOgg(ref); + } else if (ref.toLowerCase().endsWith(".wav")) { + sound = SoundStore.get().getWAV(ref); + } else if (ref.toLowerCase().endsWith(".aif")) { + sound = SoundStore.get().getAIF(ref); + } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { + sound = SoundStore.get().getMOD(ref); + } else { + throw new SlickException("Only .xm, .mod, .aif, .wav and .ogg are currently supported."); + } + } catch (Exception e) { + Log.error(e); + throw new SlickException("Failed to load sound: "+ref); + } + } + + /** + * Play this sound effect at default volume and pitch + */ + public void play() { + play(1.0f, 1.0f); + } + + /** + * Play this sound effect at a given volume and pitch + * + * @param pitch The pitch to play the sound effect at + * @param volume The volumen to play the sound effect at + */ + public void play(float pitch, float volume) { + sound.playAsSoundEffect(pitch, volume * SoundStore.get().getSoundVolume(), false); + } + + /** + * Play a sound effect from a particular location + * + * @param x The x position of the source of the effect + * @param y The y position of the source of the effect + * @param z The z position of the source of the effect + */ + public void playAt(float x, float y, float z) { + playAt(1.0f, 1.0f, x,y,z); + } + + /** + * Play a sound effect from a particular location + * + * @param pitch The pitch to play the sound effect at + * @param volume The volumen to play the sound effect at + * @param x The x position of the source of the effect + * @param y The y position of the source of the effect + * @param z The z position of the source of the effect + */ + public void playAt(float pitch, float volume, float x, float y, float z) { + sound.playAsSoundEffect(pitch, volume * SoundStore.get().getSoundVolume(), false, x,y,z); + } + /** + * Loop this sound effect at default volume and pitch + */ + public void loop() { + loop(1.0f, 1.0f); + } + + /** + * Loop this sound effect at a given volume and pitch + * + * @param pitch The pitch to play the sound effect at + * @param volume The volumen to play the sound effect at + */ + public void loop(float pitch, float volume) { + sound.playAsSoundEffect(pitch, volume * SoundStore.get().getSoundVolume(), true); + } + + /** + * Check if the sound is currently playing + * + * @return True if the sound is playing + */ + public boolean playing() { + return sound.isPlaying(); + } + + /** + * Stop the sound being played + */ + public void stop() { + sound.stop(); + } +} diff --git a/lib/slick-source/org/newdawn/slick/SpriteSheet.java b/lib/slick-source/org/newdawn/slick/SpriteSheet.java new file mode 100644 index 000000000..1a527c8ec --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/SpriteSheet.java @@ -0,0 +1,303 @@ +package org.newdawn.slick; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.newdawn.slick.opengl.Texture; + +/** + * A sheet of sprites that can be drawn individually + * + * @author Kevin Glass + */ +public class SpriteSheet extends Image { + /** The width of a single element in pixels */ + private int tw; + /** The height of a single element in pixels */ + private int th; + /** The margin of the image */ + private int margin = 0; + /** Subimages */ + private Image[][] subImages; + /** The spacing between tiles */ + private int spacing; + /** The target image for this sheet */ + private Image target; + + /** + * Create a new sprite sheet based on a image location + * + * @param ref The URL to the image to use + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @throws SlickException Indicates a failure to read image data + * @throws IOException Indicates the URL could not be opened + */ + public SpriteSheet(URL ref,int tw,int th) throws SlickException, IOException { + this(new Image(ref.openStream(), ref.toString(), false), tw, th); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param image The image to based the sheet of + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + */ + public SpriteSheet(Image image,int tw,int th) { + super(image); + + this.target = image; + this.tw = tw; + this.th = th; + + // call init manually since constructing from an image will have previously initialised + // from incorrect values + initImpl(); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param image The image to based the sheet of + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @param spacing The spacing between tiles + * @param margin The magrin around the tiles + */ + public SpriteSheet(Image image,int tw,int th,int spacing,int margin) { + super(image); + + this.target = image; + this.tw = tw; + this.th = th; + this.spacing = spacing; + this.margin = margin; + + // call init manually since constructing from an image will have previously initialised + // from incorrect values + initImpl(); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param image The image to based the sheet of + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @param spacing The spacing between tiles + */ + public SpriteSheet(Image image,int tw,int th,int spacing) { + this(image,tw,th,spacing,0); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param ref The location of the sprite sheet to load + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @param spacing The spacing between tiles + * @throws SlickException Indicates a failure to load the image + */ + public SpriteSheet(String ref,int tw,int th, int spacing) throws SlickException { + this(ref,tw,th,null,spacing); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param ref The location of the sprite sheet to load + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @throws SlickException Indicates a failure to load the image + */ + public SpriteSheet(String ref,int tw,int th) throws SlickException { + this(ref,tw,th,null); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param ref The location of the sprite sheet to load + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @param col The colour to treat as transparent + * @throws SlickException Indicates a failure to load the image + */ + public SpriteSheet(String ref,int tw,int th, Color col) throws SlickException { + this(ref, tw, th, col, 0); + } + + /** + * Create a new sprite sheet based on a image location + * + * @param ref The location of the sprite sheet to load + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @param col The colour to treat as transparent + * @param spacing The spacing between tiles + * @throws SlickException Indicates a failure to load the image + */ + public SpriteSheet(String ref,int tw,int th, Color col, int spacing) throws SlickException { + super(ref, false, FILTER_NEAREST, col); + + this.target = this; + this.tw = tw; + this.th = th; + this.spacing = spacing; + } + + /** + * Create a new sprite sheet based on a image location + * + * @param name The name to give to the image in the image cache + * @param ref The stream from which we can load the image + * @param tw The width of the tiles on the sheet + * @param th The height of the tiles on the sheet + * @throws SlickException Indicates a failure to load the image + */ + public SpriteSheet(String name, InputStream ref,int tw,int th) throws SlickException { + super(ref,name,false); + + this.target = this; + this.tw = tw; + this.th = th; + } + + /** + * @see org.newdawn.slick.Image#initImpl() + */ + protected void initImpl() { + if (subImages != null) { + return; + } + + int tilesAcross = ((getWidth()-(margin*2) - tw) / (tw + spacing)) + 1; + int tilesDown = ((getHeight()-(margin*2) - th) / (th + spacing)) + 1; + if ((getHeight() - th) % (th+spacing) != 0) { + tilesDown++; + } + + subImages = new Image[tilesAcross][tilesDown]; + for (int x=0;x= subImages.length)) { + throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); + } + if ((y < 0) || (y >= subImages[0].length)) { + throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); + } + + return subImages[x][y]; + } + + /** + * Get a sprite at a particular cell on the sprite sheet + * + * @param x The x position of the cell on the sprite sheet + * @param y The y position of the cell on the sprite sheet + * @return The single image from the sprite sheet + */ + public Image getSprite(int x, int y) { + target.init(); + initImpl(); + + if ((x < 0) || (x >= subImages.length)) { + throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); + } + if ((y < 0) || (y >= subImages[0].length)) { + throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); + } + + return target.getSubImage(x*(tw+spacing) + margin, y*(th+spacing) + margin,tw,th); + } + + /** + * Get the number of sprites across the sheet + * + * @return The number of sprites across the sheet + */ + public int getHorizontalCount() { + target.init(); + initImpl(); + + return subImages.length; + } + + /** + * Get the number of sprites down the sheet + * + * @return The number of sprite down the sheet + */ + public int getVerticalCount() { + target.init(); + initImpl(); + + return subImages[0].length; + } + + /** + * Render a sprite when this sprite sheet is in use. + * + * @see #startUse() + * @see #endUse() + * + * @param x The x position to render the sprite at + * @param y The y position to render the sprite at + * @param sx The x location of the cell to render + * @param sy The y location of the cell to render + */ + public void renderInUse(int x,int y,int sx,int sy) { + subImages[sx][sy].drawEmbedded(x, y, tw, th); + } + + /** + * @see org.newdawn.slick.Image#endUse() + */ + public void endUse() { + if (target == this) { + super.endUse(); + return; + } + target.endUse(); + } + + /** + * @see org.newdawn.slick.Image#startUse() + */ + public void startUse() { + if (target == this) { + super.startUse(); + return; + } + target.startUse(); + } + + /** + * @see org.newdawn.slick.Image#setTexture(org.newdawn.slick.opengl.Texture) + */ + public void setTexture(Texture texture) { + if (target == this) { + super.setTexture(texture); + return; + } + target.setTexture(texture); + } +} diff --git a/lib/slick-source/org/newdawn/slick/SpriteSheetFont.java b/lib/slick-source/org/newdawn/slick/SpriteSheetFont.java new file mode 100644 index 000000000..db7d53c1d --- /dev/null +++ b/lib/slick-source/org/newdawn/slick/SpriteSheetFont.java @@ -0,0 +1,111 @@ +package org.newdawn.slick; + +import java.io.UnsupportedEncodingException; + +import org.newdawn.slick.util.Log; + +/** + * A font implementation that will use the graphics inside a SpriteSheet for its data. + * This is useful when your font has a fixed width and height for each character as + * opposed to the more complex AngelCodeFont that allows different sizes and kerning + * for each character. + * + * @author Onno Scheffers + */ +public class SpriteSheetFont implements Font { + /** The SpriteSheet containing the bitmap font */ + private SpriteSheet font; + /** First character in the SpriteSheet */ + private char startingCharacter; + /** Width of each character in pixels */ + private int charWidth; + /** Height of each character in pixels */ + private int charHeight; + /** Number of characters in SpriteSheet horizontally */ + private int horizontalCount; + /** Total number of characters in SpriteSheet */ + private int numChars; + + /** + * Create a new font based on a SpriteSheet. The SpriteSheet should hold your + * fixed-width character set in ASCII order. To only get upper-case characters + * working you would usually set up a SpriteSheet with characters for these values: + *
+	 *   !"#$%&'()*+,-./
+	 *  0123456789:;<=>?
+	 *  @ABCDEFGHIJKLMNO
+	 *  PQRSTUVWXYZ[\]^_
+	 * In this set, ' ' (SPACE) would be the startingCharacter of your characterSet.
+	 *
+	 * @param font              The SpriteSheet holding the font data.
+	 * @param startingCharacter The first character that is defined in the SpriteSheet.
+	 */
+	public SpriteSheetFont(SpriteSheet font, char startingCharacter) {
+		this.font = font;
+		this.startingCharacter = startingCharacter;
+		horizontalCount = font.getHorizontalCount();
+		int verticalCount = font.getVerticalCount();
+		charWidth = font.getWidth() / horizontalCount;
+		charHeight = font.getHeight() / verticalCount;
+		numChars = horizontalCount * verticalCount;
+	}
+
+	/**
+	 * @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) {
+		try {
+			byte[] data = text.getBytes("US-ASCII");
+			for (int i = 0; i < data.length; i++) {
+				int index = data[i] - startingCharacter;
+				if (index < numChars) {
+					int xPos = (index % horizontalCount);
+					int yPos = (index / horizontalCount);
+					
+					if ((i >= startIndex) || (i <= endIndex)) {
+						font.getSprite(xPos, yPos)
+								.draw(x + (i * charWidth), y, col);
+					}
+				}
+			}
+		} catch (UnsupportedEncodingException e) {
+			// Should never happen, ASCII is supported pretty much anywhere
+			Log.error(e);
+		}
+	}
+
+	/**
+	 * @see org.newdawn.slick.Font#getHeight(java.lang.String)
+	 */
+	public int getHeight(String text) {
+		return charHeight;
+	}
+
+	/**
+	 * @see org.newdawn.slick.Font#getWidth(java.lang.String)
+	 */
+	public int getWidth(String text) {
+		return charWidth * text.length();
+	}
+
+	/**
+	 * @see org.newdawn.slick.Font#getLineHeight()
+	 */
+	public int getLineHeight() {
+		return charHeight;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/TrueTypeFont.java b/lib/slick-source/org/newdawn/slick/TrueTypeFont.java
new file mode 100644
index 000000000..082858ad7
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/TrueTypeFont.java
@@ -0,0 +1,409 @@
+package org.newdawn.slick;
+
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.newdawn.slick.opengl.GLUtils;
+import org.newdawn.slick.opengl.Texture;
+import org.newdawn.slick.opengl.renderer.Renderer;
+import org.newdawn.slick.opengl.renderer.SGL;
+import org.newdawn.slick.util.BufferedImageUtil;
+
+/**
+ * A TrueType font implementation for Slick
+ * 
+ * @author James Chambers (Jimmy)
+ * @author Jeremy Adams (elias4444)
+ * @author Kevin Glass (kevglass)
+ * @author Peter Korzuszek (genail)
+ */
+public class TrueTypeFont implements org.newdawn.slick.Font {
+	/** The renderer to use for all GL operations */
+	private static final SGL GL = Renderer.get();
+
+	/** Array that holds necessary information about the font characters */
+	private IntObject[] charArray = new IntObject[256];
+	
+	/** Map of user defined font characters (Character <-> IntObject) */
+	private Map customChars = new HashMap();
+
+	/** Boolean flag on whether AntiAliasing is enabled or not */
+	private boolean antiAlias;
+
+	/** Font's size */
+	private int fontSize = 0;
+
+	/** Font's height */
+	private int fontHeight = 0;
+
+	/** Texture used to cache the font 0-255 characters */
+	private Texture fontTexture;
+	
+	/** Default font texture width */
+	private int textureWidth = 512;
+
+	/** Default font texture height */
+	private int textureHeight = 512;
+
+	/** A reference to Java's AWT Font that we create our font texture from */
+	private java.awt.Font font;
+
+	/** The font metrics for our Java AWT font */
+	private FontMetrics fontMetrics;
+
+	/**
+	 * This is a special internal class that holds our necessary information for
+	 * the font characters. This includes width, height, and where the character
+	 * is stored on the font texture.
+	 */
+	private class IntObject {
+		/** Character's width */
+		public int width;
+
+		/** Character's height */
+		public int height;
+
+		/** Character's stored x position */
+		public int storedX;
+
+		/** Character's stored y position */
+		public int storedY;
+	}
+
+	/**
+	 * Constructor for the TrueTypeFont class Pass in the preloaded standard
+	 * Java TrueType font, and whether you want it to be cached with
+	 * AntiAliasing applied.
+	 * 
+	 * @param font
+	 *            Standard Java AWT font
+	 * @param antiAlias
+	 *            Whether or not to apply AntiAliasing to the cached font
+	 * @param additionalChars
+	 *            Characters of font that will be used in addition of first 256 (by unicode).
+	 */
+	public TrueTypeFont(java.awt.Font font, boolean antiAlias, char[] additionalChars) {
+		GLUtils.checkGLContext();
+		
+		this.font = font;
+		this.fontSize = font.getSize();
+		this.antiAlias = antiAlias;
+
+		createSet( additionalChars );
+	}
+	
+	/**
+	 * Constructor for the TrueTypeFont class Pass in the preloaded standard
+	 * Java TrueType font, and whether you want it to be cached with
+	 * AntiAliasing applied.
+	 * 
+	 * @param font
+	 *            Standard Java AWT font
+	 * @param antiAlias
+	 *            Whether or not to apply AntiAliasing to the cached font
+	 */
+	public TrueTypeFont(java.awt.Font font, boolean antiAlias) {
+		this( font, antiAlias, null );
+	}
+
+	/**
+	 * Create a standard Java2D BufferedImage of the given character
+	 * 
+	 * @param ch
+	 *            The character to create a BufferedImage for
+	 * 
+	 * @return A BufferedImage containing the character
+	 */
+	private BufferedImage getFontImage(char ch) {
+		// Create a temporary image to extract the character's size
+		BufferedImage tempfontImage = new BufferedImage(1, 1,
+				BufferedImage.TYPE_INT_ARGB);
+		Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
+		if (antiAlias == true) {
+			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+					RenderingHints.VALUE_ANTIALIAS_ON);
+		}
+		g.setFont(font);
+		fontMetrics = g.getFontMetrics();
+		int charwidth = fontMetrics.charWidth(ch);
+
+		if (charwidth <= 0) {
+			charwidth = 1;
+		}
+		int charheight = fontMetrics.getHeight();
+		if (charheight <= 0) {
+			charheight = fontSize;
+		}
+
+		// Create another image holding the character we are creating
+		BufferedImage fontImage;
+		fontImage = new BufferedImage(charwidth, charheight,
+				BufferedImage.TYPE_INT_ARGB);
+		Graphics2D gt = (Graphics2D) fontImage.getGraphics();
+		if (antiAlias == true) {
+			gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+					RenderingHints.VALUE_ANTIALIAS_ON);
+		}
+		gt.setFont(font);
+
+		gt.setColor(Color.WHITE);
+		int charx = 0;
+		int chary = 0;
+		gt.drawString(String.valueOf(ch), (charx), (chary)
+				+ fontMetrics.getAscent());
+
+		return fontImage;
+
+	}
+
+	/**
+	 * Create and store the font
+	 * 
+	 * @param customCharsArray Characters that should be also added to the cache.
+	 */
+	private void createSet( char[] customCharsArray ) {
+		// If there are custom chars then I expand the font texture twice		
+		if	(customCharsArray != null && customCharsArray.length > 0) {
+			textureWidth *= 2;
+		}
+		
+		// In any case this should be done in other way. Texture with size 512x512
+		// can maintain only 256 characters with resolution of 32x32. The texture
+		// size should be calculated dynamicaly by looking at character sizes. 
+		
+		try {
+			
+			BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
+			Graphics2D g = (Graphics2D) imgTemp.getGraphics();
+
+			g.setColor(new Color(255,255,255,1));
+			g.fillRect(0,0,textureWidth,textureHeight);
+			
+			int rowHeight = 0;
+			int positionX = 0;
+			int positionY = 0;
+			
+			int customCharsLength = ( customCharsArray != null ) ? customCharsArray.length : 0; 
+
+			for (int i = 0; i < 256 + customCharsLength; i++) {
+				
+				// get 0-255 characters and then custom characters
+				char ch = ( i < 256 ) ? (char) i : customCharsArray[i-256];
+				
+				BufferedImage fontImage = getFontImage(ch);
+
+				IntObject newIntObject = new IntObject();
+
+				newIntObject.width = fontImage.getWidth();
+				newIntObject.height = fontImage.getHeight();
+
+				if (positionX + newIntObject.width >= textureWidth) {
+					positionX = 0;
+					positionY += rowHeight;
+					rowHeight = 0;
+				}
+
+				newIntObject.storedX = positionX;
+				newIntObject.storedY = positionY;
+
+				if (newIntObject.height > fontHeight) {
+					fontHeight = newIntObject.height;
+				}
+
+				if (newIntObject.height > rowHeight) {
+					rowHeight = newIntObject.height;
+				}
+
+				// Draw it here
+				g.drawImage(fontImage, positionX, positionY, null);
+
+				positionX += newIntObject.width;
+
+				if( i < 256 ) { // standard characters
+					charArray[i] = newIntObject;
+				} else { // custom characters
+					customChars.put( new Character( ch ), newIntObject );
+				}
+
+				fontImage = null;
+			}
+
+			fontTexture = BufferedImageUtil
+					.getTexture(font.toString(), imgTemp);
+
+		} catch (IOException e) {
+			System.err.println("Failed to create font.");
+			e.printStackTrace();
+		}
+	}
+	
+	
+	/**
+	 * Draw a textured quad
+	 * 
+	 * @param drawX
+	 *            The left x position to draw to
+	 * @param drawY
+	 *            The top y position to draw to
+	 * @param drawX2
+	 *            The right x position to draw to
+	 * @param drawY2
+	 *            The bottom y position to draw to
+	 * @param srcX
+	 *            The left source x position to draw from
+	 * @param srcY
+	 *            The top source y position to draw from
+	 * @param srcX2
+	 *            The right source x position to draw from
+	 * @param srcY2
+	 *            The bottom source y position to draw from
+	 */
+	private void drawQuad(float drawX, float drawY, float drawX2, float drawY2,
+			float srcX, float srcY, float srcX2, float srcY2) {
+		float DrawWidth = drawX2 - drawX;
+		float DrawHeight = drawY2 - drawY;
+		float TextureSrcX = srcX / textureWidth;
+		float TextureSrcY = srcY / textureHeight;
+		float SrcWidth = srcX2 - srcX;
+		float SrcHeight = srcY2 - srcY;
+		float RenderWidth = (SrcWidth / textureWidth);
+		float RenderHeight = (SrcHeight / textureHeight);
+
+		GL.glTexCoord2f(TextureSrcX, TextureSrcY);
+		GL.glVertex2f(drawX, drawY);
+		GL.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
+		GL.glVertex2f(drawX, drawY + DrawHeight);
+		GL.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight);
+		GL.glVertex2f(drawX + DrawWidth, drawY + DrawHeight);
+		GL.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
+		GL.glVertex2f(drawX + DrawWidth, drawY);
+	}
+
+	/**
+	 * Get the width of a given String
+	 * 
+	 * @param whatchars
+	 *            The characters to get the width of
+	 * 
+	 * @return The width of the characters
+	 */
+	public int getWidth(String whatchars) {
+		int totalwidth = 0;
+		IntObject intObject = null;
+		int currentChar = 0;
+		for (int i = 0; i < whatchars.length(); i++) {
+			currentChar = whatchars.charAt(i);
+			if (currentChar < 256) {
+				intObject = charArray[currentChar];
+			} else {
+				intObject = (IntObject)customChars.get( new Character( (char) currentChar ) );
+			}
+			
+			if( intObject != null )
+				totalwidth += intObject.width;
+		}
+		return totalwidth;
+	}
+
+	/**
+	 * Get the font's height
+	 * 
+	 * @return The height of the font
+	 */
+	public int getHeight() {
+		return fontHeight;
+	}
+
+	/**
+	 * Get the height of a String
+	 * 
+	 * @return The height of a given string
+	 */
+	public int getHeight(String HeightString) {
+		return fontHeight;
+	}
+
+	/**
+	 * Get the font's line height
+	 * 
+	 * @return The line height of the font
+	 */
+	public int getLineHeight() {
+		return fontHeight;
+	}
+
+	/**
+	 * Draw a string
+	 * 
+	 * @param x
+	 *            The x position to draw the string
+	 * @param y
+	 *            The y position to draw the string
+	 * @param whatchars
+	 *            The string to draw
+	 * @param color
+	 *            The color to draw the text
+	 */
+	public void drawString(float x, float y, String whatchars,
+			org.newdawn.slick.Color color) {
+		drawString(x,y,whatchars,color,0,whatchars.length()-1);
+	}
+	
+	/**
+	 * @see Font#drawString(float, float, String, org.newdawn.slick.Color, int, int)
+	 */
+	public void drawString(float x, float y, String whatchars,
+			org.newdawn.slick.Color color, int startIndex, int endIndex) {
+		color.bind();
+		fontTexture.bind();
+
+		IntObject intObject = null;
+		int charCurrent;
+
+		GL.glBegin(SGL.GL_QUADS);
+
+		int totalwidth = 0;
+		for (int i = 0; i < whatchars.length(); i++) {
+			charCurrent = whatchars.charAt(i);
+			if (charCurrent < 256) {
+				intObject = charArray[charCurrent];
+			} else {
+				intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
+			} 
+			
+			if( intObject != null ) {
+				if ((i >= startIndex) || (i <= endIndex)) {
+					drawQuad((x + totalwidth), y,
+							(x + totalwidth + intObject.width),
+							(y + intObject.height), intObject.storedX,
+							intObject.storedY, intObject.storedX + intObject.width,
+							intObject.storedY + intObject.height);
+				}
+				totalwidth += intObject.width;
+			}
+		}
+
+		GL.glEnd();
+	}
+
+	/**
+	 * Draw a string
+	 * 
+	 * @param x
+	 *            The x position to draw the string
+	 * @param y
+	 *            The y position to draw the string
+	 * @param whatchars
+	 *            The string to draw
+	 */
+	public void drawString(float x, float y, String whatchars) {
+		drawString(x, y, whatchars, org.newdawn.slick.Color.white);
+	}
+
+}
\ No newline at end of file
diff --git a/lib/slick-source/org/newdawn/slick/UnicodeFont.java b/lib/slick-source/org/newdawn/slick/UnicodeFont.java
new file mode 100644
index 000000000..da8f2f77d
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/UnicodeFont.java
@@ -0,0 +1,980 @@
+
+package org.newdawn.slick;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.awt.FontMetrics;
+import java.awt.Rectangle;
+import java.awt.font.GlyphVector;
+import java.awt.font.TextAttribute;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.newdawn.slick.font.Glyph;
+import org.newdawn.slick.font.GlyphPage;
+import org.newdawn.slick.font.HieroSettings;
+import org.newdawn.slick.opengl.Texture;
+import org.newdawn.slick.opengl.TextureImpl;
+import org.newdawn.slick.opengl.renderer.Renderer;
+import org.newdawn.slick.opengl.renderer.SGL;
+import org.newdawn.slick.util.ResourceLoader;
+
+/**
+ * A Slick bitmap font that can display unicode glyphs from a TrueTypeFont.
+ * 
+ * For efficiency, glyphs are packed on to textures. Glyphs can be loaded to the textures on the fly, when they are first needed
+ * for display. However, it is best to load the glyphs that are known to be needed at startup.
+ * @author Nathan Sweet 
+ */
+public class UnicodeFont implements org.newdawn.slick.Font {
+	/** The number of display lists that will be cached for strings from this font */
+	private static final int DISPLAY_LIST_CACHE_SIZE = 200;
+	/** The highest glyph code allowed */
+	static private final int MAX_GLYPH_CODE = 0x10FFFF;
+	/** The number of glyphs on a page */
+	private static final int PAGE_SIZE = 512;
+	/** The number of pages */
+	private static final int PAGES = MAX_GLYPH_CODE / PAGE_SIZE;
+	/** Interface to OpenGL */
+	private static final SGL GL = Renderer.get();
+	/** A dummy display list used as a place holder */
+	private static final DisplayList EMPTY_DISPLAY_LIST = new DisplayList();
+
+	/**
+	 * Utility to create a Java font for a TTF file reference
+	 * 
+	 * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
+	 * @return The font created
+	 * @throws SlickException Indicates a failure to locate or load the font into Java's font
+	 * system.
+	 */
+	private static Font createFont (String ttfFileRef) throws SlickException {
+		try {
+			return Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(ttfFileRef));
+		} catch (FontFormatException ex) {
+			throw new SlickException("Invalid font: " + ttfFileRef, ex);
+		} catch (IOException ex) {
+			throw new SlickException("Error reading font: " + ttfFileRef, ex);
+		}
+	}
+
+	/**
+	 * Sorts glyphs by height, tallest first.
+	 */
+	private static final Comparator heightComparator = new Comparator() {
+		public int compare (Object o1, Object o2) {
+			return ((Glyph)o1).getHeight() - ((Glyph)o2).getHeight();
+		}
+	};
+	
+	/** The AWT font that is being rendered */
+	private Font font;
+	/** The reference to the True Type Font file that has kerning information */
+	private String ttfFileRef;
+	/** The ascent of the font */
+	private int ascent;
+	/** The decent of the font */
+	private int descent;
+	/** The leading edge of the font */
+	private int leading;
+	/** The width of a space for the font */
+	private int spaceWidth;
+	/** The glyphs that are available in this font */
+	private final Glyph[][] glyphs = new Glyph[PAGES][];
+	/** The pages that have been loaded for this font */
+	private final List glyphPages = new ArrayList();
+	/** The glyphs queued up to be rendered */
+	private final List queuedGlyphs = new ArrayList(256);
+	/** The effects that need to be applied to the font */
+	private final List effects = new ArrayList();
+	
+	/** The padding applied in pixels to the top of the glyph rendered area */
+	private int paddingTop;
+	/** The padding applied in pixels to the left of the glyph rendered area */
+	private int paddingLeft;
+	/** The padding applied in pixels to the bottom of the glyph rendered area */
+	private int paddingBottom;
+	/** The padding applied in pixels to the right of the glyph rendered area */
+	private int paddingRight;
+	/** The padding applied in pixels to horizontal advance for each glyph */
+	private int paddingAdvanceX;
+	/** The padding applied in pixels to vertical advance for each glyph */
+	private int paddingAdvanceY;
+	/** The glyph to display for missing glyphs in code points */
+	private Glyph missingGlyph;
+
+	/** The width of the glyph page generated */
+	private int glyphPageWidth = 512;
+	/** The height of the glyph page generated */
+	private int glyphPageHeight = 512;
+	
+	/** True if display list caching is turned on */
+	private boolean displayListCaching = true;
+	/** The based display list ID */
+	private int baseDisplayListID = -1;
+	/** The ID of the display list that has been around the longest time */
+	private int eldestDisplayListID;
+	/** The eldest display list  */
+	private DisplayList eldestDisplayList;
+	
+	/** The map fo the display list generated and cached - modified to allow removal of the oldest entry */
+	private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {
+		protected boolean removeEldestEntry (Entry eldest) {
+			DisplayList displayList = (DisplayList)eldest.getValue();
+			if (displayList != null) eldestDisplayListID = displayList.id;
+			return size() > DISPLAY_LIST_CACHE_SIZE;
+		}
+	};
+
+	/**
+	 * Create a new unicode font based on a TTF file
+	 * 
+	 * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
+	 * @param hieroFileRef The file system or classpath location of the Hiero settings file.
+	 * @throws SlickException if the UnicodeFont could not be initialized.
+	 */
+	public UnicodeFont (String ttfFileRef, String hieroFileRef) throws SlickException {
+		this(ttfFileRef, new HieroSettings(hieroFileRef));
+	}
+
+	/**
+	 * Create a new unicode font based on a TTF file and a set of heiro configuration
+	 * 
+	 * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
+	 * @param settings The settings configured via the Hiero tool
+	 * @throws SlickException if the UnicodeFont could not be initialized.
+	 */
+	public UnicodeFont (String ttfFileRef, HieroSettings settings) throws SlickException {
+		this.ttfFileRef = ttfFileRef;
+		Font font = createFont(ttfFileRef);
+		initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
+		loadSettings(settings);
+	}
+
+	/**
+	 * Create a new unicode font based on a TTF file alone
+	 * 
+	 * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
+	 * @param size The point size of the font to generated
+	 * @param bold True if the font should be rendered in bold typeface
+	 * @param italic True if the font should be rendered in bold typeface
+	 * @throws SlickException if the UnicodeFont could not be initialized.
+	 */
+	public UnicodeFont (String ttfFileRef, int size, boolean bold, boolean italic) throws SlickException {
+		this.ttfFileRef = ttfFileRef;
+		initializeFont(createFont(ttfFileRef), size, bold, italic);
+	}
+
+	/**
+	 * Creates a new UnicodeFont.
+	 * 
+	 * @param font The AWT font to render
+	 * @param hieroFileRef The file system or classpath location of the Hiero settings file.
+	 * @throws SlickException if the UnicodeFont could not be initialized.
+	 */
+	public UnicodeFont (Font font, String hieroFileRef) throws SlickException {
+		this(font, new HieroSettings(hieroFileRef));
+	}
+
+	/**
+	 * Creates a new UnicodeFont.
+	 * 
+	 * @param font The AWT font to render
+	 * @param settings The settings configured via the Hiero tool
+	 */
+	public UnicodeFont (Font font, HieroSettings settings) {
+		initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
+		loadSettings(settings);
+	}
+
+	/**
+	 * Creates a new UnicodeFont.
+	 * 
+	 * @param font The AWT font to render
+	 */
+	public UnicodeFont (Font font) {
+		initializeFont(font, font.getSize(), font.isBold(), font.isItalic());
+	}
+
+	/**
+	 * Creates a new UnicodeFont.
+	 * 
+	 * @param font The AWT font to render
+	 * @param size The point size of the font to generated
+	 * @param bold True if the font should be rendered in bold typeface
+	 * @param italic True if the font should be rendered in bold typeface
+	 */
+	public UnicodeFont (Font font, int size, boolean bold, boolean italic) {
+		initializeFont(font, size, bold, italic);
+	}
+
+	/**
+	 * Initialise the font to be used based on configuration
+	 * 
+	 * @param baseFont The AWT font to render
+	 * @param size The point size of the font to generated
+	 * @param bold True if the font should be rendered in bold typeface
+	 * @param italic True if the font should be rendered in bold typeface
+	 */
+	private void initializeFont(Font baseFont, int size, boolean bold, boolean italic) {
+		Map attributes = baseFont.getAttributes();
+		attributes.put(TextAttribute.SIZE, new Float(size));
+		attributes.put(TextAttribute.WEIGHT, bold ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
+		attributes.put(TextAttribute.POSTURE, italic ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
+		try {
+			attributes.put(TextAttribute.class.getDeclaredField("KERNING").get(null), TextAttribute.class.getDeclaredField(
+				"KERNING_ON").get(null));
+		} catch (Exception ignored) {
+		}
+		font = baseFont.deriveFont(attributes);
+
+		FontMetrics metrics = GlyphPage.getScratchGraphics().getFontMetrics(font);
+		ascent = metrics.getAscent();
+		descent = metrics.getDescent();
+		leading = metrics.getLeading();
+		
+		// Determine width of space glyph (getGlyphPixelBounds gives a width of zero).
+		char[] chars = " ".toCharArray();
+		GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
+		spaceWidth = vector.getGlyphLogicalBounds(0).getBounds().width;
+	}
+
+	/**
+	 * Load the hiero setting and configure the unicode font's rendering
+	 * 
+	 * @param settings The settings to be applied
+	 */
+	private void loadSettings(HieroSettings settings) {
+		paddingTop = settings.getPaddingTop();
+		paddingLeft = settings.getPaddingLeft();
+		paddingBottom = settings.getPaddingBottom();
+		paddingRight = settings.getPaddingRight();
+		paddingAdvanceX = settings.getPaddingAdvanceX();
+		paddingAdvanceY = settings.getPaddingAdvanceY();
+		glyphPageWidth = settings.getGlyphPageWidth();
+		glyphPageHeight = settings.getGlyphPageHeight();
+		effects.addAll(settings.getEffects());
+	}
+
+	/**
+	 * Queues the glyphs in the specified codepoint range (inclusive) to be loaded. Note that the glyphs are not actually loaded
+	 * until {@link #loadGlyphs()} is called.
+	 * 
+	 * Some characters like combining marks and non-spacing marks can only be rendered with the context of other glyphs. In this
+	 * case, use {@link #addGlyphs(String)}.
+	 * 
+	 * @param startCodePoint The code point of the first glyph to add
+	 * @param endCodePoint The code point of the last glyph to add
+	 */
+	public void addGlyphs(int startCodePoint, int endCodePoint) {
+		for (int codePoint = startCodePoint; codePoint <= endCodePoint; codePoint++)
+			addGlyphs(new String(Character.toChars(codePoint)));
+	}
+
+	/**
+	 * Queues the glyphs in the specified text to be loaded. Note that the glyphs are not actually loaded until
+	 * {@link #loadGlyphs()} is called.
+	 * 
+	 * @param text The text containing the glyphs to be added
+	 */
+	public void addGlyphs(String text) {
+		if (text == null) throw new IllegalArgumentException("text cannot be null.");
+
+		char[] chars = text.toCharArray();
+		GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
+		for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
+			int codePoint = text.codePointAt(vector.getGlyphCharIndex(i));
+			Rectangle bounds = getGlyphBounds(vector, i, codePoint);
+			getGlyph(vector.getGlyphCode(i), codePoint, bounds, vector, i);
+		}
+	}
+
+	/**
+	 * Queues the glyphs in the ASCII character set (codepoints 32 through 255) to be loaded. Note that the glyphs are not actually
+	 * loaded until {@link #loadGlyphs()} is called.
+	 */
+	public void addAsciiGlyphs () {
+		addGlyphs(32, 255);
+	}
+
+	/**
+	 * Queues the glyphs in the NEHE character set (codepoints 32 through 128) to be loaded. Note that the glyphs are not actually
+	 * loaded until {@link #loadGlyphs()} is called.
+	 */
+	public void addNeheGlyphs () {
+		addGlyphs(32, 32 + 96);
+	}
+
+	/**
+	 * Loads all queued glyphs to the backing textures. Glyphs that are typically displayed together should be added and loaded at
+	 * the same time so that they are stored on the same backing texture. This reduces the number of backing texture binds required
+	 * to draw glyphs.
+	 * 
+	 * @return True if the glyphs were loaded entirely
+	 * @throws SlickException if the glyphs could not be loaded.
+	 */
+	public boolean loadGlyphs () throws SlickException {
+		return loadGlyphs(-1);
+	}
+
+	/**
+	 * Loads up to the specified number of queued glyphs to the backing textures. This is typically called from the game loop to
+	 * load glyphs on the fly that were requested for display but have not yet been loaded.
+	 * 
+	 * @param maxGlyphsToLoad The maximum number of glyphs to be loaded this time
+	 * @return True if the glyphs were loaded entirely
+	 * @throws SlickException if the glyphs could not be loaded.
+	 */
+	public boolean loadGlyphs (int maxGlyphsToLoad) throws SlickException {
+		if (queuedGlyphs.isEmpty()) return false;
+
+		if (effects.isEmpty())
+			throw new IllegalStateException("The UnicodeFont must have at least one effect before any glyphs can be loaded.");
+
+		for (Iterator iter = queuedGlyphs.iterator(); iter.hasNext();) {
+			Glyph glyph = (Glyph)iter.next();
+			int codePoint = glyph.getCodePoint();
+
+			// Don't load an image for a glyph with nothing to display.
+			if (glyph.getWidth() == 0 || codePoint == ' ') {
+				iter.remove();
+				continue;
+			}
+
+			// Only load the first missing glyph.
+			if (glyph.isMissing()) {
+				if (missingGlyph != null) {
+					if (glyph != missingGlyph) iter.remove();
+					continue;
+				}
+				missingGlyph = glyph;
+			}
+		}
+
+		Collections.sort(queuedGlyphs, heightComparator);
+
+		// Add to existing pages.
+		for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
+			GlyphPage glyphPage = (GlyphPage)iter.next();
+			maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
+			if (maxGlyphsToLoad == 0 || queuedGlyphs.isEmpty())
+				return true;
+		}
+
+		// Add to new pages.
+		while (!queuedGlyphs.isEmpty()) {
+			GlyphPage glyphPage = new GlyphPage(this, glyphPageWidth, glyphPageHeight);
+			glyphPages.add(glyphPage);
+			maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
+			if (maxGlyphsToLoad == 0) return true;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Clears all loaded and queued glyphs.
+	 */
+	public void clearGlyphs () {
+		for (int i = 0; i < PAGES; i++)
+			glyphs[i] = null;
+
+		for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
+			GlyphPage page = (GlyphPage)iter.next();
+			try {
+				page.getImage().destroy();
+			} catch (SlickException ignored) {
+			}
+		}
+		glyphPages.clear();
+
+		if (baseDisplayListID != -1) {
+			GL.glDeleteLists(baseDisplayListID, displayLists.size());
+			baseDisplayListID = -1;
+		}
+
+		queuedGlyphs.clear();
+		missingGlyph = null;
+	}
+
+	/**
+	 * Releases all resources used by this UnicodeFont. This method should be called when this UnicodeFont instance is no longer
+	 * needed.
+	 */
+	public void destroy () {
+		// The destroy() method is just to provide a consistent API for releasing resources.
+		clearGlyphs();
+	}
+
+	/**
+	 * Identical to {@link #drawString(float, float, String, Color, int, int)} but returns a 
+	 * DisplayList which provides access to the width and height of the text drawn.
+	 * 
+	 * @param text The text to render
+	 * @param x The horizontal location to render at
+	 * @param y The vertical location to render at
+	 * @param color The colour to apply as a filter on the text
+	 * @param startIndex The start index into the string to start rendering at
+	 * @param endIndex The end index into the string to render to
+	 * @return The reference to the display list that was drawn and potentiall ygenerated
+	 */
+	public DisplayList drawDisplayList (float x, float y, String text, Color color, int startIndex, int endIndex) {
+		if (text == null) throw new IllegalArgumentException("text cannot be null.");
+		if (text.length() == 0) return EMPTY_DISPLAY_LIST;
+		if (color == null) throw new IllegalArgumentException("color cannot be null.");
+
+		x -= paddingLeft;
+		y -= paddingTop;
+
+		String displayListKey = text.substring(startIndex, endIndex);
+
+		color.bind();
+		TextureImpl.bindNone();
+
+		DisplayList displayList = null;
+		if (displayListCaching && queuedGlyphs.isEmpty()) {
+			if (baseDisplayListID == -1) {
+				baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE);
+				if (baseDisplayListID == 0) {
+					baseDisplayListID = -1;
+					displayListCaching = false;
+					return new DisplayList();
+				}
+			}
+			// Try to use a display list compiled for this text.
+			displayList = (DisplayList)displayLists.get(displayListKey);
+			if (displayList != null) {
+				if (displayList.invalid)
+					displayList.invalid = false;
+				else {
+					GL.glTranslatef(x, y, 0);
+					GL.glCallList(displayList.id);
+					GL.glTranslatef(-x, -y, 0);
+					return displayList;
+				}
+			} else if (displayList == null) {
+				// Compile a new display list.
+				displayList = new DisplayList();
+				int displayListCount = displayLists.size();
+				displayLists.put(displayListKey, displayList);
+				if (displayListCount < DISPLAY_LIST_CACHE_SIZE)
+					displayList.id = baseDisplayListID + displayListCount;
+				else
+					displayList.id = eldestDisplayListID;
+			}
+			displayLists.put(displayListKey, displayList);
+		}
+
+		GL.glTranslatef(x, y, 0);
+
+		if (displayList != null) GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE);
+
+		char[] chars = text.substring(0, endIndex).toCharArray();
+		GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
+
+		int maxWidth = 0, totalHeight = 0, lines = 0;
+		int extraX = 0, extraY = ascent;
+		boolean startNewLine = false;
+		Texture lastBind = null;
+		for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
+			int charIndex = vector.getGlyphCharIndex(glyphIndex);
+			if (charIndex < startIndex) continue;
+			if (charIndex > endIndex) break;
+
+			int codePoint = text.codePointAt(charIndex);
+
+			Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
+			Glyph glyph = getGlyph(vector.getGlyphCode(glyphIndex), codePoint, bounds, vector, glyphIndex);
+
+			if (startNewLine && codePoint != '\n') {
+				extraX = -bounds.x;
+				startNewLine = false;
+			}
+
+			Image image = glyph.getImage();
+			if (image == null && missingGlyph != null && glyph.isMissing()) image = missingGlyph.getImage();
+			if (image != null) {
+				// Draw glyph, only binding a new glyph page texture when necessary.
+				Texture texture = image.getTexture();
+				if (lastBind != null && lastBind != texture) {
+					GL.glEnd();
+					lastBind = null;
+				}
+				if (lastBind == null) {
+					texture.bind();
+					GL.glBegin(SGL.GL_QUADS);
+					lastBind = texture;
+				}
+				image.drawEmbedded(bounds.x + extraX, bounds.y + extraY, image.getWidth(), image.getHeight());
+			}
+
+			if (glyphIndex >= 0) extraX += paddingRight + paddingLeft + paddingAdvanceX;
+			maxWidth = Math.max(maxWidth, bounds.x + extraX + bounds.width);
+			totalHeight = Math.max(totalHeight, ascent + bounds.y + bounds.height);
+
+			if (codePoint == '\n') {
+				startNewLine = true; // Mac gives -1 for bounds.x of '\n', so use the bounds.x of the next glyph.
+				extraY += getLineHeight();
+				lines++;
+				totalHeight = 0;
+			}
+		}
+		if (lastBind != null) GL.glEnd();
+
+		if (displayList != null) {
+			GL.glEndList();
+			// Invalidate the display list if it had glyphs that need to be loaded.
+			if (!queuedGlyphs.isEmpty()) displayList.invalid = true;
+		}
+
+		GL.glTranslatef(-x, -y, 0);
+
+		if (displayList == null) displayList = new DisplayList();
+		displayList.width = (short)maxWidth;
+		displayList.height = (short)(lines * getLineHeight() + totalHeight);
+		return displayList;
+	}
+
+	public void drawString (float x, float y, String text, Color color, int startIndex, int endIndex) {
+		drawDisplayList(x, y, text, color, startIndex, endIndex);
+	}
+
+	public void drawString (float x, float y, String text) {
+		drawString(x, y, text, Color.white);
+	}
+
+	public void drawString (float x, float y, String text, Color col) {
+		drawString(x, y, text, col, 0, text.length());
+	}
+
+	/**
+	 * Returns the glyph for the specified codePoint. If the glyph does not exist yet, 
+	 * it is created and queued to be loaded.
+	 * 
+	 * @param glyphCode The code of the glyph to locate
+	 * @param codePoint The code point associated with the glyph
+	 * @param bounds The bounds of the glyph on the page
+	 * @param vector The vector the glyph is part of  
+	 * @param index The index of the glyph within the vector
+	 * @return The glyph requested
+	 */
+	private Glyph getGlyph (int glyphCode, int codePoint, Rectangle bounds, GlyphVector vector, int index) {
+		if (glyphCode < 0 || glyphCode >= MAX_GLYPH_CODE) {
+			// GlyphVector#getGlyphCode sometimes returns negative numbers on OS X.
+			return new Glyph(codePoint, bounds, vector, index, this) {
+				public boolean isMissing () {
+					return true;
+				}
+			};
+		}
+		int pageIndex = glyphCode / PAGE_SIZE;
+		int glyphIndex = glyphCode & (PAGE_SIZE - 1);
+		Glyph glyph = null;
+		Glyph[] page = glyphs[pageIndex];
+		if (page != null) {
+			glyph = page[glyphIndex];
+			if (glyph != null) return glyph;
+		} else
+			page = glyphs[pageIndex] = new Glyph[PAGE_SIZE];
+		// Add glyph so size information is available and queue it so its image can be loaded later.
+		glyph = page[glyphIndex] = new Glyph(codePoint, bounds, vector, index, this);
+		queuedGlyphs.add(glyph);
+		return glyph;
+	}
+
+	/**
+	 * Returns the bounds of the specified glyph.\
+	 * 
+	 * @param vector The vector the glyph is part of
+	 * @param index The index of the glyph within the vector
+	 * @param codePoint The code point associated with the glyph
+	 */
+	private Rectangle getGlyphBounds (GlyphVector vector, int index, int codePoint) {
+		Rectangle bounds = vector.getGlyphPixelBounds(index, GlyphPage.renderContext, 0, 0);
+		if (codePoint == ' ') bounds.width = spaceWidth;
+		return bounds;
+	}
+
+	/**
+	 * Returns the width of the space character.
+	 */
+	public int getSpaceWidth () {
+		return spaceWidth;
+	}
+
+	/**
+	 * @see org.newdawn.slick.Font#getWidth(java.lang.String)
+	 */
+	public int getWidth (String text) {
+		if (text == null) throw new IllegalArgumentException("text cannot be null.");
+		if (text.length() == 0) return 0;
+
+		if (displayListCaching) {
+			DisplayList displayList = (DisplayList)displayLists.get(text);
+			if (displayList != null) return displayList.width;
+		}
+
+		char[] chars = text.toCharArray();
+		GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
+
+		int width = 0;
+		int extraX = 0;
+		boolean startNewLine = false;
+		for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
+			int charIndex = vector.getGlyphCharIndex(glyphIndex);
+			int codePoint = text.codePointAt(charIndex);
+			Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
+
+			if (startNewLine && codePoint != '\n') extraX = -bounds.x;
+
+			if (glyphIndex > 0) extraX += paddingLeft + paddingRight + paddingAdvanceX;
+			width = Math.max(width, bounds.x + extraX + bounds.width);
+
+			if (codePoint == '\n') startNewLine = true;
+		}
+
+		return width;
+	}
+
+	/**
+	 * @see org.newdawn.slick.Font#getHeight(java.lang.String)
+	 */
+	public int getHeight (String text) {
+		if (text == null) throw new IllegalArgumentException("text cannot be null.");
+		if (text.length() == 0) return 0;
+
+		if (displayListCaching) {
+			DisplayList displayList = (DisplayList)displayLists.get(text);
+			if (displayList != null) return displayList.height;
+		}
+
+		char[] chars = text.toCharArray();
+		GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
+
+		int lines = 0, height = 0;
+		for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
+			int charIndex = vector.getGlyphCharIndex(i);
+			int codePoint = text.codePointAt(charIndex);
+			if (codePoint == ' ') continue;
+			Rectangle bounds = getGlyphBounds(vector, i, codePoint);
+
+			height = Math.max(height, ascent + bounds.y + bounds.height);
+
+			if (codePoint == '\n') {
+				lines++;
+				height = 0;
+			}
+		}
+		return lines * getLineHeight() + height;
+	}
+
+	/**
+	 * Returns the distance from the y drawing location to the top most pixel of the 
+	 * specified text.
+	 * 
+	 * @param text The text to analyse 
+	 * @return The distance fro the y drawing location ot the top most pixel of the specified text
+	 */
+	public int getYOffset (String text) {
+		if (text == null) throw new IllegalArgumentException("text cannot be null.");
+
+		DisplayList displayList = null;
+		if (displayListCaching) {
+			displayList = (DisplayList)displayLists.get(text);
+			if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();
+		}
+
+		int index = text.indexOf('\n');
+		if (index != -1) text = text.substring(0, index);
+		char[] chars = text.toCharArray();
+		GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
+		int yOffset = ascent + vector.getPixelBounds(null, 0, 0).y;
+
+		if (displayList != null) displayList.yOffset = new Short((short)yOffset);
+
+		return yOffset;
+	}
+
+	/**
+	 * Returns the TrueTypeFont for this UnicodeFont.
+	 * 
+	 * @return The AWT Font being rendered 
+	 */
+	public Font getFont() {
+		return font;
+	}
+
+	/**
+	 * Returns the padding above a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @return The padding at the top of the glyphs when drawn
+	 */
+	public int getPaddingTop() {
+		return paddingTop;
+	}
+
+	/**
+	 * Sets the padding above a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @param paddingTop The padding at the top of the glyphs when drawn
+	 */
+	public void setPaddingTop(int paddingTop) {
+		this.paddingTop = paddingTop;
+	}
+
+	/**
+	 * Returns the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @return The padding at the left of the glyphs when drawn
+	 */
+	public int getPaddingLeft() {
+		return paddingLeft;
+	}
+
+	/**
+	 * Sets the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @param paddingLeft The padding at the left of the glyphs when drawn
+	 */
+	public void setPaddingLeft(int paddingLeft) {
+		this.paddingLeft = paddingLeft;
+	}
+
+	/**
+	 * Returns the padding below a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @return The padding at the bottom of the glyphs when drawn
+	 */
+	public int getPaddingBottom() {
+		return paddingBottom;
+	}
+
+	/**
+	 * Sets the padding below a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @param paddingBottom The padding at the bottom of the glyphs when drawn
+	 */
+	public void setPaddingBottom(int paddingBottom) {
+		this.paddingBottom = paddingBottom;
+	}
+
+	/**
+	 * Returns the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @return The padding at the right of the glyphs when drawn
+	 */
+	public int getPaddingRight () {
+		return paddingRight;
+	}
+
+	/**
+	 * Sets the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.
+	 * 
+	 * @param paddingRight The padding at the right of the glyphs when drawn
+	 */
+	public void setPaddingRight (int paddingRight) {
+		this.paddingRight = paddingRight;
+	}
+
+	/**
+	 * Gets the additional amount to offset glyphs on the x axis.
+	 * 
+	 * @return The padding applied for each horizontal advance (i.e. when a glyph is rendered)
+	 */
+	public int getPaddingAdvanceX() {
+		return paddingAdvanceX;
+	}
+
+	/**
+	 * Sets the additional amount to offset glyphs on the x axis. This is typically set to a negative number when left or right
+	 * padding is used so that glyphs are not spaced too far apart.
+	 * 
+	 * @param paddingAdvanceX The padding applied for each horizontal advance (i.e. when a glyph is rendered)
+	 */
+	public void setPaddingAdvanceX (int paddingAdvanceX) {
+		this.paddingAdvanceX = paddingAdvanceX;
+	}
+
+	/**
+	 * Gets the additional amount to offset a line of text on the y axis.
+	 * 
+	 * @return The padding applied for each vertical advance (i.e. when a glyph is rendered)
+	 */
+	public int getPaddingAdvanceY () {
+		return paddingAdvanceY;
+	}
+
+	/**
+	 * Sets the additional amount to offset a line of text on the y axis. This is typically set to a negative number when top or
+	 * bottom padding is used so that lines of text are not spaced too far apart.
+	 * 
+	 * @param paddingAdvanceY The padding applied for each vertical advance (i.e. when a glyph is rendered)
+	 */
+	public void setPaddingAdvanceY (int paddingAdvanceY) {
+		this.paddingAdvanceY = paddingAdvanceY;
+	}
+
+	/**
+	 * Returns the distance from one line of text to the next. This is the sum of the descent, ascent, leading, padding top,
+	 * padding bottom, and padding advance y. To change the line height, use {@link #setPaddingAdvanceY(int)}.
+	 */
+	public int getLineHeight() {
+		return descent + ascent + leading + paddingTop + paddingBottom + paddingAdvanceY;
+	}
+
+	/**
+	 * Gets the distance from the baseline to the y drawing location.
+	 * 
+	 * @return The ascent of this font
+	 */
+	public int getAscent() {
+		return ascent;
+	}
+
+	/**
+	 * Gets the distance from the baseline to the bottom of most alphanumeric characters 
+	 * with descenders.
+	 * 
+	 * @return The distance from the baseline to the bottom of the font
+	 */
+	public int getDescent () {
+		return descent;
+	}
+
+	/**
+	 * Gets the extra distance between the descent of one line of text to the ascent of the next.
+	 * 
+	 * @return The leading edge of the font
+	 */
+	public int getLeading () {
+		return leading;
+	}
+
+	/**
+	 * Returns the width of the backing textures.
+	 * 
+	 * @return The width of the glyph pages in this font
+	 */
+	public int getGlyphPageWidth () {
+		return glyphPageWidth;
+	}
+
+	/**
+	 * Sets the width of the backing textures. Default is 512.
+	 * 
+	 * @param glyphPageWidth The width of the glyph pages in this font
+	 */
+	public void setGlyphPageWidth(int glyphPageWidth) {
+		this.glyphPageWidth = glyphPageWidth;
+	}
+
+	/**
+	 * Returns the height of the backing textures.
+	 * 
+	 * @return The height of the glyph pages in this font
+	 */
+	public int getGlyphPageHeight() {
+		return glyphPageHeight;
+	}
+
+	/**
+	 * Sets the height of the backing textures. Default is 512.
+	 * 
+	 * @param glyphPageHeight The width of the glyph pages in this font
+	 */
+	public void setGlyphPageHeight(int glyphPageHeight) {
+		this.glyphPageHeight = glyphPageHeight;
+	}
+
+	/**
+	 * Returns the GlyphPages for this UnicodeFont.
+	 * 
+	 * @return The glyph pages that have been loaded into this font
+	 */
+	public List getGlyphPages () {
+		return glyphPages;
+	}
+
+	/**
+	 * Returns a list of {@link org.newdawn.slick.font.effects.Effect}s that will be applied 
+	 * to the glyphs.
+	 * 
+	 * @return The list of effects to be applied to the font
+	 */
+	public List getEffects () {
+		return effects;
+	}
+
+	/**
+	 * Returns true if this UnicodeFont caches the glyph drawing instructions to 
+	 * improve performance.
+	 * 
+	 * @return True if caching is turned on
+	 */
+	public boolean isCaching () {
+		return displayListCaching;
+	}
+
+	/**
+	 * Sets if this UnicodeFont caches the glyph drawing instructions to improve performance. 
+	 * Default is true. Text rendering is very slow without display list caching.
+	 * 
+	 * @param displayListCaching True if caching should be turned on
+	 */
+	public void setDisplayListCaching (boolean displayListCaching) {
+		this.displayListCaching = displayListCaching;
+	}
+
+	/**
+	 * Returns the path to the TTF file for this UnicodeFont, or null. If this UnicodeFont was created without specifying the TTF
+	 * file, it will try to determine the path using Sun classes. If this fails, null is returned.
+	 * 
+	 * @return The reference to the font file that the kerning was loaded from
+	 */
+	public String getFontFile () {
+		if (ttfFileRef == null) {
+			// Worst case if this UnicodeFont was loaded without a ttfFileRef, try to get the font file from Sun's classes.
+			try {
+				Object font2D = Class.forName("sun.font.FontManager").getDeclaredMethod("getFont2D", new Class[] {Font.class})
+					.invoke(null, new Object[] {font});
+				Field platNameField = Class.forName("sun.font.PhysicalFont").getDeclaredField("platName");
+				platNameField.setAccessible(true);
+				ttfFileRef = (String)platNameField.get(font2D);
+			} catch (Throwable ignored) {
+			}
+			if (ttfFileRef == null) ttfFileRef = "";
+		}
+		if (ttfFileRef.length() == 0) return null;
+		return ttfFileRef;
+	}
+
+	/**
+	 * A simple descriptor for display lists cached within this font
+	 */
+	public static class DisplayList {
+		/** True if this display list has been invalidated */
+		boolean invalid;
+		/** The ID of the display list this descriptor represents */
+		int id;
+		/** The vertical offset to the top of this display list */
+		Short yOffset;
+
+		/** The width of rendered text in the list */
+		public short width;
+		/** The height of the rendered text in the list */
+		public short height;
+		/** Application data stored in the list */
+		public Object userData;
+
+		DisplayList () {
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/XMLPackedSheet.java b/lib/slick-source/org/newdawn/slick/XMLPackedSheet.java
new file mode 100644
index 000000000..db2b20435
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/XMLPackedSheet.java
@@ -0,0 +1,65 @@
+package org.newdawn.slick;
+
+import java.util.HashMap;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.newdawn.slick.util.ResourceLoader;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * A sprite sheet based on an XML descriptor generated from the simple slick tool
+ * 
+ * @author kevin
+ */
+public class XMLPackedSheet {
+	/** The full sheet image */
+	private Image image;
+	/** The sprites stored on the image */
+	private HashMap sprites = new HashMap();
+	
+	/**
+	 * Create a new XML packed sheet from the XML output by the slick tool
+	 * 
+	 * @param imageRef The reference to the image
+	 * @param xmlRef The reference to the XML
+	 * @throws SlickException Indicates a failure to parse the XML or read the image
+	 */
+	public XMLPackedSheet(String imageRef, String xmlRef) throws SlickException
+	{
+		image = new Image(imageRef, false, Image.FILTER_NEAREST);
+	
+		try {
+			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+			Document doc = builder.parse(ResourceLoader.getResourceAsStream(xmlRef));
+			
+			NodeList list = doc.getElementsByTagName("sprite");
+			for (int i=0;iBasicCommand
+Provides abstract input by mapping physical device inputs (mouse, keyboard and controllers) to abstract
+commands that are relevant to a particular game.
+
\ No newline at end of file
diff --git a/lib/slick-source/org/newdawn/slick/data/.gitattributes b/lib/slick-source/org/newdawn/slick/data/.gitattributes
new file mode 100644
index 000000000..ca82ef549
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/data/.gitattributes
@@ -0,0 +1 @@
+*.{psd,tga,ogg} filter=lfs diff=lfs merge=lfs -text
\ No newline at end of file
diff --git a/lib/slick-source/org/newdawn/slick/data/defaultfont.fnt b/lib/slick-source/org/newdawn/slick/data/defaultfont.fnt
new file mode 100644
index 000000000..af16759cd
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/data/defaultfont.fnt
@@ -0,0 +1,195 @@
+info face="Courier New Bold" size=16 bold=1 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
+common lineHeight=19 base=26 scaleW=256 scaleH=256 pages=1 packed=0
+page id=0 file="defaultfont.png"
+chars count=189
+char id=32   x=0     y=0     width=0     height=0     xoffset=0     yoffset=14    xadvance=9     page=0  chnl=0 
+char id=253   x=0     y=0     width=12     height=16     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=254   x=12     y=0     width=11     height=15     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=221   x=23     y=0     width=12     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=218   x=35     y=0     width=11     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=217   x=46     y=0     width=11     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=211   x=57     y=0     width=11     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=210   x=68     y=0     width=11     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=205   x=79     y=0     width=10     height=15     xoffset=1     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=204   x=89     y=0     width=10     height=15     xoffset=1     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=201   x=99     y=0     width=10     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=200   x=109     y=0     width=10     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=199   x=119     y=0     width=11     height=15     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=197   x=130     y=0     width=12     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=193   x=142     y=0     width=12     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=192   x=154     y=0     width=12     height=15     xoffset=0     yoffset=0    xadvance=9     page=0  chnl=0 
+char id=36   x=166     y=0     width=9     height=15     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=106   x=175     y=0     width=8     height=15     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=255   x=183     y=0     width=12     height=14     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=219   x=195     y=0     width=11     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=213   x=206     y=0     width=11     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=212   x=217     y=0     width=11     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=209   x=228     y=0     width=11     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=206   x=239     y=0     width=10     height=14     xoffset=1     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=202   x=0     y=16     width=10     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=195   x=10     y=16     width=12     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=194   x=22     y=16     width=12     height=14     xoffset=0     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=162   x=34     y=16     width=10     height=14     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=92   x=44     y=16     width=9     height=14     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=47   x=53     y=16     width=9     height=14     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=93   x=62     y=16     width=6     height=14     xoffset=2     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=91   x=68     y=16     width=6     height=14     xoffset=4     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=41   x=74     y=16     width=5     height=14     xoffset=2     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=40   x=79     y=16     width=5     height=14     xoffset=3     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=250   x=84     y=16     width=11     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=249   x=95     y=16     width=11     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=243   x=106     y=16     width=10     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=242   x=116     y=16     width=10     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=237   x=126     y=16     width=10     height=13     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=236   x=136     y=16     width=10     height=13     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=233   x=146     y=16     width=11     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=232   x=157     y=16     width=11     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=229   x=168     y=16     width=10     height=13     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=225   x=178     y=16     width=10     height=13     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=224   x=188     y=16     width=10     height=13     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=220   x=198     y=16     width=11     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=216   x=209     y=16     width=11     height=13     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=214   x=220     y=16     width=11     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=207   x=231     y=16     width=10     height=13     xoffset=1     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=203   x=241     y=16     width=10     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=196   x=0     y=30     width=12     height=13     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=182   x=12     y=30     width=10     height=13     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=167   x=22     y=30     width=10     height=13     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=166   x=32     y=30     width=4     height=13     xoffset=4     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=124   x=36     y=30     width=4     height=13     xoffset=4     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=125   x=40     y=30     width=6     height=13     xoffset=3     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=123   x=46     y=30     width=6     height=13     xoffset=2     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=81   x=52     y=30     width=11     height=13     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=251   x=63     y=30     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=245   x=74     y=30     width=10     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=244   x=84     y=30     width=10     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=241   x=94     y=30     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=240   x=105     y=30     width=10     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=238   x=115     y=30     width=10     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=234   x=125     y=30     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=231   x=136     y=30     width=10     height=12     xoffset=0     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=227   x=146     y=30     width=10     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=226   x=156     y=30     width=10     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=223   x=166     y=30     width=9     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=181   x=175     y=30     width=11     height=12     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=127   x=186     y=30     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=35   x=197     y=30     width=10     height=12     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=64   x=207     y=30     width=8     height=12     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=48   x=215     y=30     width=9     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=57   x=224     y=30     width=9     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=56   x=233     y=30     width=9     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=55   x=242     y=30     width=9     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=54   x=0     y=43     width=9     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=53   x=9     y=43     width=9     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=52   x=18     y=43     width=9     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=51   x=27     y=43     width=9     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=50   x=36     y=43     width=8     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=121   x=44     y=43     width=12     height=12     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=113   x=56     y=43     width=11     height=12     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=112   x=67     y=43     width=11     height=12     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=108   x=78     y=43     width=10     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=107   x=88     y=43     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=105   x=99     y=43     width=10     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=104   x=109     y=43     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=103   x=120     y=43     width=11     height=12     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=102   x=131     y=43     width=10     height=12     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=100   x=141     y=43     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=98   x=152     y=43     width=11     height=12     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=252   x=163     y=43     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=248   x=174     y=43     width=10     height=11     xoffset=0     yoffset=5    xadvance=9     page=0  chnl=0 
+char id=246   x=184     y=43     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=239   x=194     y=43     width=10     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=235   x=204     y=43     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=228   x=215     y=43     width=10     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=222   x=225     y=43     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=208   x=235     y=43     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=198   x=0     y=55     width=12     height=11     xoffset=-1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=191   x=12     y=55     width=9     height=11     xoffset=1     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=190   x=21     y=55     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=189   x=33     y=55     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=188   x=45     y=55     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=177   x=57     y=55     width=9     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=174   x=66     y=55     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=169   x=77     y=55     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=165   x=88     y=55     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=163   x=100     y=55     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=161   x=110     y=55     width=4     height=11     xoffset=3     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=38   x=114     y=55     width=9     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=37   x=123     y=55     width=9     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=63   x=132     y=55     width=8     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=33   x=140     y=55     width=4     height=11     xoffset=3     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=49   x=144     y=55     width=10     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=116   x=154     y=55     width=9     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=90   x=163     y=55     width=9     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=89   x=172     y=55     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=88   x=184     y=55     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=87   x=195     y=55     width=13     height=11     xoffset=-1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=86   x=208     y=55     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=85   x=219     y=55     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=84   x=230     y=55     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=83   x=240     y=55     width=9     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=82   x=0     y=66     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=80   x=12     y=66     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=79   x=22     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=78   x=33     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=77   x=44     y=66     width=13     height=11     xoffset=-1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=76   x=57     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=75   x=68     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=74   x=79     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=73   x=90     y=66     width=10     height=11     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=72   x=100     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=71   x=111     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=70   x=122     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=69   x=133     y=66     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=68   x=143     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=67   x=154     y=66     width=11     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=66   x=165     y=66     width=10     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=65   x=175     y=66     width=12     height=11     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=247   x=187     y=66     width=10     height=10     xoffset=0     yoffset=5    xadvance=9     page=0  chnl=0 
+char id=62   x=197     y=66     width=10     height=10     xoffset=0     yoffset=5    xadvance=9     page=0  chnl=0 
+char id=60   x=207     y=66     width=11     height=10     xoffset=0     yoffset=5    xadvance=9     page=0  chnl=0 
+char id=59   x=218     y=66     width=6     height=10     xoffset=2     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=230   x=224     y=66     width=12     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=164   x=236     y=66     width=11     height=9     xoffset=0     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=43   x=0     y=77     width=9     height=9     xoffset=1     yoffset=5    xadvance=9     page=0  chnl=0 
+char id=122   x=9     y=77     width=9     height=9     xoffset=1     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=120   x=18     y=77     width=11     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=119   x=29     y=77     width=13     height=9     xoffset=-1     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=118   x=42     y=77     width=11     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=117   x=53     y=77     width=11     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=115   x=64     y=77     width=9     height=9     xoffset=1     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=114   x=73     y=77     width=10     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=111   x=83     y=77     width=10     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=110   x=93     y=77     width=11     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=109   x=104     y=77     width=12     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=101   x=116     y=77     width=11     height=9     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=97   x=127     y=77     width=10     height=9     xoffset=1     yoffset=6    xadvance=9     page=0  chnl=0 
+char id=42   x=137     y=77     width=10     height=8     xoffset=1     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=58   x=147     y=77     width=3     height=8     xoffset=3     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=99   x=150     y=77     width=10     height=8     xoffset=0     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=215   x=160     y=77     width=7     height=7     xoffset=1     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=187   x=167     y=77     width=10     height=7     xoffset=0     yoffset=8    xadvance=9     page=0  chnl=0 
+char id=186   x=177     y=77     width=8     height=7     xoffset=2     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=184   x=185     y=77     width=5     height=7     xoffset=3     yoffset=12    xadvance=9     page=0  chnl=0 
+char id=178   x=190     y=77     width=7     height=7     xoffset=2     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=172   x=197     y=77     width=11     height=7     xoffset=0     yoffset=8    xadvance=9     page=0  chnl=0 
+char id=171   x=208     y=77     width=11     height=7     xoffset=0     yoffset=8    xadvance=9     page=0  chnl=0 
+char id=94   x=219     y=77     width=8     height=7     xoffset=1     yoffset=3    xadvance=9     page=0  chnl=0 
+char id=44   x=227     y=77     width=4     height=7     xoffset=3     yoffset=11    xadvance=9     page=0  chnl=0 
+char id=39   x=231     y=77     width=4     height=7     xoffset=4     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=34   x=235     y=77     width=8     height=7     xoffset=2     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=185   x=243     y=77     width=5     height=6     xoffset=3     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=179   x=248     y=77     width=7     height=6     xoffset=3     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=170   x=0     y=86     width=7     height=6     xoffset=2     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=180   x=7     y=86     width=4     height=5     xoffset=3     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=176   x=11     y=86     width=5     height=5     xoffset=2     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=126   x=16     y=86     width=9     height=5     xoffset=1     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=61   x=25     y=86     width=11     height=5     xoffset=0     yoffset=7    xadvance=9     page=0  chnl=0 
+char id=96   x=36     y=86     width=4     height=5     xoffset=3     yoffset=2    xadvance=9     page=0  chnl=0 
+char id=183   x=40     y=86     width=3     height=3     xoffset=3     yoffset=8    xadvance=9     page=0  chnl=0 
+char id=175   x=43     y=86     width=13     height=3     xoffset=-1     yoffset=1    xadvance=9     page=0  chnl=0 
+char id=168   x=56     y=86     width=6     height=3     xoffset=2     yoffset=4    xadvance=9     page=0  chnl=0 
+char id=95   x=62     y=86     width=13     height=3     xoffset=-1     yoffset=17    xadvance=9     page=0  chnl=0 
+char id=45   x=75     y=86     width=9     height=3     xoffset=1     yoffset=8    xadvance=9     page=0  chnl=0 
+char id=46   x=84     y=86     width=3     height=3     xoffset=3     yoffset=12    xadvance=9     page=0  chnl=0 
+kernings count=-1
diff --git a/lib/slick-source/org/newdawn/slick/data/defaultfont.png b/lib/slick-source/org/newdawn/slick/data/defaultfont.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e56abd1d99d1209fb9637af2187ad862f5d7987
GIT binary patch
literal 18648
zcmV)kK%l>gP)B%T4ZxT4Y{QHUZTJ*`PvqIZ
zs}g=w0N}1XCOu0#v$XL%0NVge127yw9e@@9tpIKc{N^zLF93Kr&;H*C;N+CK`r=X4
z+0I2CLNm)W2fnWYP){OJ%>P#>)Yr$kY6Y-~>#HwN?i&F90N`6Bv@;UM;3NQ#16as8
zxhSCx?*s7f0R9WWH5|JKJ>{+iumiwK01t5895n$b05G4Y#XisXJGp_ok7Dx=1+bcn
zus1{7<^%XVfX%w2p##7I0Jmo7TOWXX0L)HkZ!dr|I8h}T+gX;<
z&a^fba1r+a7zyA*0R92MEdY)r)LR%u(S@9=tZT{6S64#0E&yN3I9HZnd>%j{fcpV_
zj$`9uD}crP*X4YM^0fV{D2jG+JkMqMP#b0fxPW6el7zC!Q?3|5DS%VBC-#uAgvOwi
z@Bbk8(NX}X2Fg~G0AJ4ixB``5VmF6SaC
z1+X2!+X=t!-_ErO0Jld`G}joj48T}HI4%&sy#~NA@%JO-m};D7a-ClU
z;IkywKjrr!?)qe|&uShN^BwV`pIl5afX*n276E`UW{jr|l>o*B-t$PxUK~IGhLFM@
ziK3{JFDzq+>%W!I9(xX)e@
zo35Py?`B<-JkN^6;Eo6INw)O|o=qWfHYc_kz-CU?n}Nx4A=_zXz8X(C*~at8nuf3y
zz>6vGwfMT&$P)p)Cq?A5M0AQ%uiFpzbaofu+yoKF*Y2eHpXX*A8AZ`j>NXe_7^qp7e$M`@$%S)*EBCc-nArg2*#2Wo^eW#?+dDsNwVd;@KQy
zOo{Vp=XaZoF=LG}4aS&-e72BfrieNYv)&r#ePczPlb!EY7-O29XRRz-Zj3o7>t|ow
zXB8}8X^h#y=Ld~3hxq?x#+YJH|K^E$Y@G-A4qK*{@9<%TQ{R?Axx;K*3Hz3WmJIK;
zu8h
zu!qlg7-I?pZ74LxRPpmpoB7Ey-zBzl%UJPJG0=ANwzKg*0}V+)IGTSi
z<@bgB-63Plh=fH|CE8*;xWO6J8bP4!;BA&*EIRvfj!-J?+Mr7H3`8J9U?{O{=d|2I5x
za6g}SihjP~=vfLxoA+@H7qH)50DkSf7XW%VJ~OER+DWlVve5uAIUwR(>pXi4z$Jo>
zJObc`1k+m=#g>iKhL523I)r@c{|^8k;Tl}bvaiQZytl$Z3Nf(
zx1uO=m8pyPuhj9>0az79(e42Op6V01
zke*8#1Y^utLCq?-IXaz%eLEF|EmZof1@O=bv>{t1&!Yk5RU!LsHLv9W9&?FX6h+Iq
zF)uy=ZN7ndxb=&h$-
zC8UUKq%8Y5Hz`sSds*u*ppK?IilT?2D7ra{qO&N=Hyp)y-pP5jTKL7sI6r01AtTpG
zk5k6}KV%u`amq^EOv1NN=mm~10U*A?6ZJNp78NvWB`J=I0W2eH)&^h~xrm!RlYTSn
z1Wi!=Cy)S?3GTo*_;Rv>6Dv%4WhbD`O$i#_0R$kR#h17az}L8O7LpJ*lUtbJl$}V<
z<7vvkFR;Ijfx2(wx>?V^H?wSrU}UYooWyUAki|!E4RsyGc>bJiEaqB&^BCu6r{Dwz
z>D7dyT$ZnO#Ra042P8biWGDAw4b7+T4grXwsGFz4D4rIZ0X&oP`_0tI9^r&N2jD^g
zKk<}(l^n%J9DlE>H2gv=Am89aOu%mT=VP9Ut>6OOegfJ&k}R^duAEI1m5aqu6h$Ot
zM>wbd>}xX(@iRnBIu6zjuUWI6%dZ3z_DNHJnx5RkQ(?EG+76f?$5;`3EO
zQ(Y82#Jx85crUU-kL+?kDRdTZy&eGn!q4%7HA7ZWwgr`eJ{D>p7yUmv&;OCnd&ES{
z;%PF{vi8Q9O5_}#wz9u(aj+*EV;+y9Xrrii4j14HCx~z@Cn4C#le=D+i(Gu1<5SDc
zzTRVAD+6#2`+C2pF1w+hKk&J3Z8F9@%yo1FiPj>goez)$
zn`4amC}JOskC7wlBv%5*yj@3S+%Q4l_R1N;-jl5PHwX1u{(ibLZ
z4{KO%3pJA!DcZ*A)KDJe?^>vh_m!=rgo2dO9clO;NKCkzTRXM+J_l*Bo(TA17EY(0ww>8;~=JVMcpMz{mnRwnV
ztQO^>Z~4^kt{>wuZz6ntsA^}C_-yu}NtFwx+#zNmv}bs)Ngx18pO(NT
znI3p9{X`B4Q(owKzV*vb6X&fw!B@j#fwOi;^Y`*oo^eddIgYg{2cq>!s7)|Yy7i7o
zFhicOPei$RLy9*QJRVKOjxOL=?*a>aCf~Ca=>aq+Q%>tdvMX*swdzNxn8^}5P?fO_
ziW7?=jzwU*N2j4kKtx4Hqema`%n8=iD-?vmv-^Wircwd0i
zly#lQ_nZ{?{j*|y&Bx%7YWe$q#C{p6d!#^`xELjsB~JM+#GY(F;3oNELU|k0-U?s~
z278|Qa6W+F!|zeHifsOEc}~LJ)Ksq(n$~vlsTAfO`P`aq7ElX2B%#e)0^eN}FyXm#
zF$3uVJ8-ncXQmLOta%bnM{=D0tw@f?KbrRC+r}GdJ$N>Q=ynm3^CT2-^QDb<8Lcl>
z{CfmEwtunKKgZ4YH0S(0t_zn-XBLQ5>jhL3eqLQ7c2(ygq?0?CoI|p$k~rW=tXRPU6+B=u(9`Ff>czkt|;cb9XR
z4dGr`h}f5=7I7%^YL1=Hr{-$JuG&u{c8|UqgMDPKYxph_n3p|zst4KLX5`F)
zcO@OV5l{cg$c51QU_(5MNXewWU@?;Ijn@k53FJarkw8Ufu@j3C$HZ^OKm}Y8Pv`B3
zZOCbO9I+F{hdl#q!$nVFKZdey8FOw$gB|S4DH!NUb|yE+VgO$e&p$`vI05N70_1P*
z#Xxsn7c)p8??bv`(&kC1fZy5Xe13l&(iOQEaZ!I5$+6}$NX+uhdfk$LqA^zUZI%TMCH)5ZYesaxKh~3^7vCJMG8f!@8T$X()``pd(
zDnuL_e!C!szX@;!Jplfm|NkP#4+)3a72a>i`X5X&DOvmH7|(aJQcMj8obr1TOe(`@
znsYHNA!=|$2J_6}1XHDJW3EatM>PjRUKcvihc-g6LX2~jC;CrD3BKcbMl;bn&jL|+
zH8fGBnY5}{uG&*qp%Y11nh?PkYJVt7usUP=tXZrsAu7<0Q!VQpk+6m;gjsvoG1k{C
zA~VaRsLbc(!iT~bW2TDvw(D~*|E+ZV7p$L2qX_r4^-grkR(YaC%RP~kHdMIE2@`Hh
ziU!=r6Kx||rc=acu0t5
z`Q6%a2=}GbYfN-lz!GkP
z5s1^TjU-!HNw(Iqmrt`@ixc{6`x9!vyRl{?c0<0D?|p`H=}ZrST1|pKfo1Px8(STM
z(Zzn=hd9&DIF_5`VRaut?4mo8|DTA#(Sw~C>&pt8a;%5RKIZTpL`lti=t?46h(8n*vkah^mFPt
z9#7H9e3RwU2*ABu_&?x=sv;NCK~7Pwx391*j|nYy7nQD+7#K|SA{D>yrM!L#$GgI#
zd-y3g*bPE**;ZN{!y3oK^@^@3X0OZDfn-qKsIgj;R6bq>c^u@xzPHp~po}|BW
zh}$Hd%r71%0&ud!Zb{Cd8t*=;0X*ReYkroKK8&2she%9TIVa3!V@zj)8S7YyquoF)
zfimSnVO%YUnh#QO{6L09T|`zs%RD@g&nC%$C)kf=+#7dBQM6VBn{|-2FA(cxqA{k-
ziWUOrh&6UJT)+f!oO?*rhH`&C;;YZ)06G|2`kXk?WS;kaAtx@QOgKgm1|KZD-^lW<
z;*=`#G=4iZ%~p1;OfjK75P1JY60`@8<3#)7ysd_~90`t2RPg1K(BZwFXWfVsfViha
zog=e$^PP208>*3hNN#<96F8A~dWew~`F1;E@iWMU8}}GvrjQ_2Qv8y8?Rj##3uVYL#fK(t;G-1*
zte)cI-1JQpNh-5&02a4Ut{F;Yf;EYaCY7JdQ`+M5Bm}oOqCh`4{$^p9v;%rSzx%E;
zkY{l+r&2~+gyi((Hqk>9%fE$OTCYwdXEe$emYBIV-51!GCc&xnaI;S0WZ%F^C`4>`
z|DlKNyTcgspNQ?KVu#i>-z^7BQ%Wj`L_yv?S5p$l;+Z7tt1Qml{1NTg@;1bvOf
z>7y+F0OD}vVL~(^A924jP5y98u`TZux^tsm)
zEojI4fD@&-&||`@^h6+T5oXi0h{A@HC`J=VLOI=IRzAeFQ0j^L+~%owmw10M*URC+
zSlaQa@tBbJc-oq^KDkERHZF9`hBkZgF!r&I`=B_*R65;h#}-cl-u9HpP`O{SnDPfj
z1D4TTQl8@2aj|2{xD;`+#peUhuXSX>#gzNq=RPNe5}pook=-@+8$9X0M{W7tJaPIH
zG}iMRbER|yvbY_}ZHQTu7)tHrlhB{pb&fAvPmXM~WPsr9RgjS{9g_e!jEs
z7{;mxCq7e__hz8~EjXFVLksEc*z72Hev8;+t`PooXOgIV2MWbs2wbWxql~WNsaKG4
z5)FkvdJdjbU6!+fCBG(zVevV{fkq{Q`}iZxHa=X0IB@9qfsma_Sgu=&qKa;ofn(2w+}%0A?OscBjpZ$#{t`bY8k4Puku(c&be2MgK$
z4E3x9_Tr&xm3rzROL-K}~l>r&j=bAgklSRn7C%Uqv>zZ3D(s2#<)V+)H_hgS7=OO0;
zii3em6|CN1FZzYkB(uy>bt&Zg|`#YZU`7AzbbF%O3=bY5DTqu)M6UX|1lNPPSbE(H3F@Mt>
z+1t&4v_By$QlpbX%>9fCxOf+{@4EsSvJMzyhI2nvv7Pk^Hz+OjWS?qx^gs2UGOmte
znP;xrGu#$6;ASAJjFDF?HK!=f_VY!fgnx2K(NM+#Y7j)HDGvgWrW9=V_&V2#1v`xI
ztYz859v|^h9NbadFe^N_1(i6r?NoE)?dOK``LWMp8;0}!JDfOzDyFrn?`-Tg66bpMe;c36I+rkvUe?nt4w}&-Raw2r8cYbw=u>{WM_@IvM6-28e`08
zX^$4C*1JIfS~-Yi&csxSB$jS4jJ<}eG(&Jqi3oN{yNAu@fAi&ws}S!Q&ic~w#7*Yp
zj}mt&j`akOROOL#&bF~SKzt4wW0rU}OsR)kv31o4I5s=(w!d{5uKJoT=Fbh(@~w@g
zV+*qTrVreIjjU!GGQ4
zD7!@a*JV(IK8rXoW&``w?$IA~0;u*}YSD{+^YsEEpM_5mtXDlu3?`6&Oqz}c_9bx
zCN8pTMBSGo-CJ)ba@QkWB@{?c!tW_q9#9HeryI-4K8?YFFb^Ue9CIq-Jn+Rd500dg
zaRlNZ8#!JpNI;&VY4kcyYzv9VPUP;8s}%KFx6<=GbF~S%D6e9ctEA%6zWW}wX=?@r
z@JhDbzWaJE#AS|>>1n=WHN}f2MmVm(zzD-%aO}RyK7W-PpvhU>PqB@c^V7q*4dHLu
zS9yI~5=xH)5qOCv!Vb3A5}p?Wro|foJi-7UxtR-{zuiM`C&%O_p4R2a1$If|dJp@(
z%NWy6Vz@6sIWF7Pi@?PrZg7UR1U$}#H9nw)K9>`n7K%HA^{nGSewcMHKpfT9Pwje|
z{nK6mQi+^PV=29y3aO2j*bdLlK13~YaSlI+69~fz3Agcw(
zVB;UY7DdtZ4BB~$o2(1K-MpN{Qsw(>ed66!&byo0->*ec^zkT)E<_wI-9cht$K`vp
zUZfF#@myffaiLdnEFWWilB@U{&7>m`d+xQ65Y;1&Hry_(AkUNVe;%<1o4X-jN6vlw
zvxuYDdXOB5^Luir=OT_hnMrNAJ2%(TAH-&7y6q_a9xkIszlMF>A;v7U;X0AK0Jk^~
zB2@&#W?V3E?Y*4T1q
zk;-l3qFKa+Yp+TA1=5o&)^kwD0r*#8#yf)p?_Soq2EZs|%ovA_oW}P)dz7a_HR9NC
zmjzwxoJ{uL7aZc!#m(ZLU|D?g&k;n`*0)={yEo6ie2D)xazoqCOb-Dn;C%lb|9*y>
z@$V=`_4D&Q;#iMKJoTq|-g_2W32E-CabLTmDB3Jo@Xt6!Y(FLm7JU?sF2b){
z5Y}$wVk|m6EVs?(a|`#+N^ahdig|HkH;NrD;QL(L8@YCG7Tkf|pX14WpA|*XNOILZ
z2uH$#2T~bwH$)GDr7$*d#(D)>*sGq8#Y5>Wau?qq0ocKgl!;S4>m>Cg=p-Z_;D9X?
zzVMeImmA>G6#s_OgoRNquPqaD7T^Tq@cCLnfGUvlKFb%KLgsX3&|V~a1N!g_!CGz%
z^wq@_atvcBHb^`BWD@Fo06Y{$(Gm7{uT#fDzVA-XMIqVQu_-!$CIGvmC|Ylf+38s`
zUnOx(yB8u0UZH4mi2Z$%`U)GLAh8~CevI#Uh!V(pH`vHODyBzySn?n7Q-oYp;bUTs
zivc`uueFP!XuC1S2BC#;g&^381i&WsoZ);o2f1sij}{jpz6yFffRV;%ar}TNGu{|;1{XTW=6CV^HY&23ZM+0ISMqO=
zfb?NtfXl_?7`7W@9^=9Ej7RKy+N~%8KdO%;8;G6Ky}WL1F8T3AOJ~3K~%yNXLAFFatuxt
z&RGY!KBReglIZhc$2n@aGw1eB%tDW^itFUFI>Bjd80&3HyBh2ea#?sfC|AFarYZM1
z9L;bksrPfFa%(Bi%M3oRIdW7DfJdV!x`zbdFi#_^guIn@wgtkp3TWEg|(NY2ch3C9u)^eM5SI)u__m9r>6<;c`s09Mf+3ffP13U+4*2is;a
zIgJy%IIuALDBG7hEN&4^P@DO?68?RJI*1by6{k@AS;I#FPNHm4>d2r)@aHKDg9w8aPdcJ5;q#m_B-JdMqlOEXNW$!EESdAx{yjT#-t&p^g%)8o7{B
zA5||E%E&hpPLqn1@p6e;WlEiLKC+(Jr?Nj?RK!&Xq8tjZt>AcNX?KxO?!GC;ygbi#
z+I851*e~N1a(cf*9GZPJu$HEohL+tm^?{IaqsfF*Ra`FclnDj*AJjAB(2?^!1
z)>%XghRliCi-95WLov{&!mhh#j#^Rg@e4obT+3aOB40KK?o2244~e
zdq4YZDI2m6?v>?ZJg%p+JhIjvA(vGM*IFa|r=>hQ#&LN)7)a3IewL*rW(c^zCe^t=
zDaa#5A0iWu7`jgTOFUPvHwj<(mO$Au>PMO=`&RM2V;x`m
zHYX6N+@s$)$amY^{nHcL*d+88q4HVl939wSwT>HjQgW8UJjb|1IvPAoE%PdBjzi82
za}kH@S}gaR8Vix^Dm;@X%6-VCS(-v^`S(dNhx7aOl#k{ib|AWktb7e}4kf#hOFzAq
zzmb^5`d1-#wCUwLhEaTZSa2D0Df?Bkp81qjXVN@#DNm%g*_Lu7-(~~WQI3I$Q_{?x
zR?{BBojk>AGSt^X9nD;haV2ueP?lq0x;D$P%wap`3%`Lsq`b$rsmw9S-k-6Zl8fkx
zqUb$>yPFUweZtNj<`Yiim^+&&=i2DJq&kMPSin*4X5GUOI-qCOXR?C@0;iqRj
z!6pl>dx!(5_5|ukYrr;7$YhJ5olbBVv&8S$ks4P9f?3*}1dKK?sLg4IJ$*MYZoxh;
z6WVCE{DuHXtTSH#9S70zwOPJ3n&nF+743tYY+!)gec!qYWAN;oCd4U
zk+JV4WtyKrcU}k3CnD-*35x+p-6l9(h1IYkilU8_VVk*NYpAza?va^`cpAKH6_2c=
zg9^eBUa(FVEZ+kX`8*6WUGhMyq6r`NY4FDJ(FPGLvl5Jpo15ZU8sfqz$B+O~|GB_90Y$^D%Dz
zN{%5oY3^7(h~!&~Ln6azr0jT5}v=^wXV13t%byCAM+|04QGK!+*
zQ4~EFMbV|~-yK2`_%`B;XdX@(?|VsTo@5)U5PMdxLF}Yt1G(Pf*!>qp5Gg8ViWOjr
z@9ivqlMtoGQe?OD_}|B(1UxLVl8z#~aoAzQ%g8nsdX!rgf>m41GFdYF){`CD7GSw5
z$=VxZ%uG+)R|pnqPQrUevu!p>$QJ3K5)c85r~KYPjC(#Jz(LX8!VKlJhyd~u(iYF}
zmI`HGljDrjloW@b@a|pwxMEU!f(S6t8S7?`qRWLOw4{Wiv??YspE>7VK^)hutfrxaX3>%jm*0$svpYFe8d0U%=4A!i+Z~
zfzG`|<<1jila3&cVoQpcTTG6moYeOrDsz5yj2u8IPwc0UmoEZOi6mT)+&zjdRLX5V
zhF2Exq<_jICap~wQ+O1}!!nMW%e~Gg+udLlzs8tWuEmXr9loq0^#W(Z6p|}C0)LE2
z5^M;sz5metSU>-To`vf*|$fL5HQCO;zrJCUd};QDYlVX;?Vcj
z=IvHqA?%gAoyo62&a*BpQmufEx9rU|!jf=3Luq}mtn(tIr(}K|Id8)>HW(Oq@xt-M
z1a81}h~pBzdjkADipc8PnDQQe+K;k6dkauyK*UKBe>%k9RYHVuMVd-x+UY^;wP`IV
zcOjR=^#ZZ}>i9WALSrvKd7a|MRNjpAB!BZIuD7poUH4IhEf>V*(?YZVFme~KJV3E@
z9_#C5U(O*hn?S#oVO;-}0Gf<37a(_8$!ym1OW`+jztDTE7h-LnRfsw|fFACV(QNZ|
zM=Ju17yjB!WT6fV`*4l86k{UGSf7C%9#(dyVBK3Cf8{2!($o0=Nn}wEvHn^|B(ay2
zETwI?O!#iEX4(2Y?VnZr%@%%Ygf_KNSS0Eb(p5Ezwz-$8O!k!BB3Qp+f;DUvi#S#&Nc)O)xLkQ;rVhW~|I}-b-2Eh?IPfSwAwjVW>`f
zQ&3%o49zF}NZ7@qxu#n;eK@xx>#If^*3G(*0
zoejJaIZr-`lWCWJluEpP2xk`p<){WOiZe2_`&xRcmBD|DZrX2KsPJ4H5Q}W|myOU`
zg=9Z}S@t;$42J5bmsSOG9)52lj@=(2?AE?mh^vr`?|%{l{l|-Fs$Gll727_X#CfZs
z?&oR@jwn2deY%25))x>5XIXQZjnLdqpN(e3@#%K1&q&c9%wm6j>3nw{!rqeh<|Z4Z
zXRQy9a6B&)I;6E2m>a;>p+^K$mnV;1S`iQr$2xi`U8}u7_qkvHAe~GqPW;W$Qo5
z@!Y!0@ua#zT%1tH4N*_)!vt#R=hOW15o(;LIj7Y^;RN$6HN}_mG_+-3MeO68JeHYS}wDzm^NSTI_*SSgwn6Xq}XXmM58us}4P4|CYoZEVL
z5@nYGxQGtFMc69$5l+voh772o1|Y3BQ0c9gAM0Cq-E
z)M1QS$+dSm{CNa^LT9dN9EWb6{z*#3anwy&_w_3Sj$Iw}Ll`AEmPP>o$a!;fBfP~E
zx{2J(8+2qF?&wbP#dC-+q47B)twPQ%{YlRGE@RAkayjb}PL1#7018R1X3``Wa$77F
zl>9(K&M_PDW}~L|@|2mC;DTz+gqCtn;lB$hD{TqL%2rk#Epi;zr|AGZ9LN?c3W$iT
zJ>ud=D>BHkZGmx}?ojkmj$F8b-~(EeENgg!ZbIA9LjVj*Iq08wuxgK
zx(05Yuw9RJ^4R$z4BPV20VJ%+DH)-hHWU!sspI!i8Bqk2&u)wYs}(ao@{d|YkWUJ7QYv9
zaM5#!_&fZIn|MLgsu}$a2@Qf9?tWOUlQm2#x!DsiX0E+({Y!eB@Gh
z@8sCu%@ba>Yd*Q6N@|K%!+&jcF=gxzB2HN?&8|I&gIta)sE?V+@w$)e=w%GP2*h$r
zR`&m4hU&jSozyD!Z6*9sdHZO!X%d`K9jyZ)uICk=$am1xIhJ~!*YWP77Qhox6n&Wb
zqRZLlM&vSF2REt36=d;F5$y8gQ55|ZS()Ea5mznimU@(BKjy~&I$7o=RJWBLDabi15F;_{4B
z_C5eDr0QD$TuhhK1`JGHJqiO8W8BYAh-Ix2%2a8dyUf}6RUX&oJGsG&g~(!+OEW2s
z+(F_xlEmmL7@Zrb!iu1|(uVJ6Q$}wIT
zAOKb&`hr;76&QRCoqHgaA(z+RwmS3-V(-hl8@Y=bK7oOkHf(lm=ij2X{!VfrGomQEQY_BPxiD`LC$=qnB#NSs
z7-K3)c!nVkO})iA-L1SW@$Ui0nOkW=0d*T65=5bf9D&{Vr49iF`~EZJ!gDLQiKMSV
zspqtJHTKtVGrr_$V*`MV#+V}vy3qx;JlZK^|L08@|HWUyW0{pwe^l1
zCGDaMGRk0cSbkfEveQH!_;x2pVUni#up=8UOtAt?b=*r=@SPikhP5%^it3i#;*r-w
zzTRyKcPWIh!qLR8cKbdHQe4Pw{|`F(CTsI>IhXki%h_-DPKNzi
z^diTS7W`D6DmDbP5C5}ZU6n&_(MMbMc;Zt$(O&n=jg7>7Jwv-g_Y0iPKKbf6lfOIK
zdAE7;?Wz1W{&%53a4(y2?Ds`j5wTkMPNdcuF|j~XKpuZCu$ZsK;M;3^6OZFy%QMh;t33P8MWrkdPjM;(@~~8
z;#sgES8AEs+1Cbm+IpkVwJhiNO`iHBP9xiAlSuxU+p-*Do2V0TV@Wy7U+1H7-PRS5KSjO=_DArD?r_3HlTyd|ETP8%L{bE13j!;z|)BD(b5`$VI
z&?q*q%>p}#jhGYY=mvJgmLE>L@*zPqrip>5;JZed+?|6J&ZNsQ(L%nb(L-2P%u+F~&duIJ@&
z;Z;~I`Y_oeJ6AaUx1UCjo}f}JMp<@)LjcM=Q6c`hu09GgNYDx6UKg`D@*Vy)S`Aa{A*Y5o4K@5MGj
zutpqBN-zNEilV54pGV0~)dbkMUTVN?`R&NvCor64ZX^3V&Ze!RiQ;CmxQ&iC6=;d5
z1~7{$ruK>F7CqwM8k%^9kWKFute~saeuHdeuV9OPve_TRxZF$6!SytY{W5`}dXx2b
ziQm8JX+x*z(;FW4vPaBEuQNw(ziwlHt`_WM0lf~N!N4p{Z`uAcEAm&!rfy{&olakX
zI6r>}Ekz@cONS-b!aL>od5YedRn|O3nST^Tl5dN#f0ce5u1D#Bw(_%+7J@P%9`y4)
z6%?b^QpD&>kdv>W=;Hef^;nut;hHU_iEk72Bi6jQ1#yIFFJkY`k2ro4zWpQT{4rV{
zJ|nCi8~OjuwB)#E&l5B??ViTDWyrT0=a99(?p3%$VvKn@<+V?6vS-p>TgF8&`8Y{PE7*_ibgp>!WHCQJ
zEs{UJKfzyMYd|Lvy0D~*o_G`a+izpw#i|49XW8-GkYCBz_QkZZpM%)_*ed*<_mur6
zKS^!1PE2FDMkHaqNthjf!Ob`xekZlNsS8QcgM8j|D149a-NZ3pN7LZ~9(o;=_tTV_
zHZMaL&8uI)z%0%d6CJ&is}Hfh1J^}Sw2HYb_N3YU(+I#+49t_h2+8rpl_Gnqy_;_?
zl{9C2PHtB@7K(Mg8F8-pg&3TUTH<#k0+UD~w^GTNFN&Pg%%Ykn?{F%0?)LolJoe+o
zcYy$m!QccXF3K^`@#z{2OtRTcZa^yJs*pX0Kn=3r>LZE#5!&(Hwbq+wRlc^CBbTG{
zByy7LISw27jxPlWz#SHYVjda$MUfO%ZT5`~=}Va3sZ~b%d0!gVgbIKb#^SX{a6UVPf0aDO%`qv<)RR5HXwWH!A~3+
zZZm+1#+VV#@8%$vX(n`nJjCCRO*v`SlW;9ZE{{RJC=8gq)>3@2x4+(>(e7P-oCv@>
zIvIq0(ARK7*u_4Qo5jYMKa{|hUXAdbdoDiC%{PWbWkgDO%Vz&rEZ9!26JG@6t%1Wv
z>HxM!QS^U=hzZu$!G74!?c9uMcQ&5HL*rD>cS9+zEaiM&&yD{h_*3P5A91|PE!>E$
zjZ#0UV?BBf4|AuoPDCb
zpEkz)HQA{G#DPqgh?8)F*swQJ9xW&FneVXrI|ap;sh6LnBIp6KfUOvuo~x4e{fzbB
z!TL5ji`}-NRE+n<80gx0GIHU#HyL9(dGg3PlGmv}PFZms3E?l;KRXWpEf%RwdtGdd
zY2ou3o>NbzsIC)bfx$Wa@A8xz#uIrx=eU5M+qt1OQSsHq_E`UgH9S%K0&TUTW)&5j
zk8zFe7VEZzzda&k=$V1_Z&P$Vjlp5TR>3v{IscO^*Q0-zhtVB8K+6TUP|5mUbq>l)
zs8qDq))#ONhOuvN1`e4SHiOS8WE8Etd;1=4JV%u&DxLz)CZlZBBpCx?4YKU-D6_kGAAsLuV4i?J%GtjM@JD|BK#`>ev0p_ipWnzn
zyuiQhIK7P69NmZ5?*2PkJKXWS2EawgZm#V|>~4OQLxA3gu%Ceaob7r43zYl&IhMac
z?9v><`GI+{YxgqhA+8nT>3qI>2>*YpqY(c+2HpgHHvhhhhTIR;x`8zF=(2{>v4gn4U;|dy3{kLtseHhSa{t@k+0lw#Xjha?>v#6
zW;8#`oI1+H*zWN3se+zvt66p|-!oZ=EPE)f+00SpfhfFk@y>0Ws}{buERg9p?V=Rx
zA-O?#uO3Rt@H0$^GoegO)+KwQXuqL1ZJ8KXH{+ErgLfUrXG!R0pezL706R5H$dd~K
z*Cj1=He##CcX^gr^dnezgK(wYB$9hJi1t~x*9sAS*(L_6kc*>T3`RK@Rl8?^EKwN4
z#bj^KZSfF_7ALl064R))kpL_aeHzU=H+bR}njAgDC^5m)9eH?}h|L%)HnhD8Y*!#8
zdN%trM#N8ycFIoWBAje;w>A61UY81&W9zEABM_S)+kV(4*c69O8ON^KaXq$cWwocS
z8nGq{g}e4-Zumw~e@h@7w_Q9hr`Zj{?YUBHnEHf|Bmfm=;1y?+g==(Z^H(@}v6&9>
zKEyRq>*#4LK^P`ta%N^A0F|t7uS=9{{<|iA)(It36De7t$fG`som?pbRSG?mzS23d
zLRVpzvJ*?ihS(r{{ik|-uG`rW>mxreAppdeqt5s25Zk(d6WG9iMIolw#IpcscHpOqLHnL_40fC0PgQei=@fFYfMELaI4pBQ&vCj@Q!R)kO
zX8wY%uybuBuCVxNAedtz0-mHVACALLcq?Gq6oPUQE02KE{Sf?xu-g*UFMDI>#s%M;
zB7e2y_fpc?Ctzn7203=QCws|1Q;aR__1P~#YRO?a{%;z@ZqUbqwf$Kc`lm$2Lc0Do
zF9$4xb1$NilyFg9tFsc%-MHjE2~{>>u{{(mR7KVcK~3gXNP~tmuvTwA>}Y=VOs00u
z*~+d2WT4+oT<&SMZezxkk1~U)C7Be-$BA?qGkfrIohPaDVZPax!v$VlrS{s9tU;PO
zEBryxJH_a0m;+*~OCZMr=2v3{gWl4kyaAry#P++JTVY<3
zLx7|bhlvTnhOD5~PtXIcS=v!L(r9aXSq4QPT;W#2pK5ow3zCqFt2Ug#&4mlCB@T2m
z4PJZd@@H7&z=G6ao~JlutDfRw*`?O2^E*bCe08GDBQ8&+oc{r5$oci%ENsPP3)1SiACbZ
zcJR|F%>rtgEEbed^f`*myOBM^J2$%&C^+#6gdddZ^O!>N_bJT~s?zwtoY10i?WPyj
zu6GC?E$<6ob0G#XmL5^E)zc8S&a2p%5&@-Nt50r>O<1=KJZr@9`e{`y2C8WtS~YzC
zb>pZnWe3xX0&E3}MiErgboVFD(e}Ze!=*PSwXZ;~uji3HV~wU(DyXX`UkwK|WiK@E
zmYB}p1Nz!i$qReG^?PU!E(jG10&D>PuE
zrZro4rBTJT(a0Tmu$~_PC8*-qt?1ewd2S=dUtt@!{_~ukT?)iCaTx
zrdXnDPC|Z^_w7qu8_=q8j5`>!0ugx*BwC=FkVZNMTLbG7tM1TN#nE-Sb3>L@z7@Rs>(Q3un+NXh#pg%sE_VnW(<;p6T7Vtra$Qnc4{Bv;vD`P*HPzk4O`}KRA&oJ)IG(Ab1&BZ|RgN6NM|IhT4hN@uWIiX1oa(19;&WG9;LQGjM
zdr_(=L+?1&iB{{48h@B+6MW5k!ne=@<#89$rw?P
z#pVouQ%mRL`{ww~wZi3Wiu**v{Lx463a2gVxOUe;*qya}QFAV?xp@>}j{-6cxkODI
zDMwZBeTwHXvv#k70zQ{2Qz~AS>z>YPOZPk6t6PBFun&V@q#bWjVWmGd4XP=sX4UG^
z;_|8zR4*0y%yu<2XLp=-sf*Z#&TDIdvEPmGNG69uUEwYWDFPfKQKZFqk+7g-ER7&tN`U79;1IAr
z0Q|#7+X33rKWw>w;kX?@!qR{k8Ug+t_b~tjO!|?d|IZNqVf`P170#0+sviv689T^V
R%)TEKIpSRG>ahW-e*+MEpS1u0

literal 0
HcmV?d00001

diff --git a/lib/slick-source/org/newdawn/slick/data/helvetica_svg b/lib/slick-source/org/newdawn/slick/data/helvetica_svg
new file mode 100644
index 000000000..59e7ade69
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/data/helvetica_svg
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ !"#$%&'()*+,-./0123456789:;<>?
+@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+`abcdefghijklmnopqrstuvwxyz{|}~
+
+
diff --git a/lib/slick-source/org/newdawn/slick/data/package.html b/lib/slick-source/org/newdawn/slick/data/package.html
new file mode 100644
index 000000000..fd7ed7984
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/data/package.html
@@ -0,0 +1,4 @@
+
+This package contains the default data required for the basic functions of YASL. Currently this includes a default
+font to ensure text can always be displayed.
+
\ No newline at end of file
diff --git a/lib/slick-source/org/newdawn/slick/fills/GradientFill.java b/lib/slick-source/org/newdawn/slick/fills/GradientFill.java
new file mode 100644
index 000000000..a93b0cf96
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/fills/GradientFill.java
@@ -0,0 +1,253 @@
+package org.newdawn.slick.fills;
+
+import org.newdawn.slick.Color;
+import org.newdawn.slick.ShapeFill;
+import org.newdawn.slick.geom.Shape;
+import org.newdawn.slick.geom.Vector2f;
+
+/**
+ * A fill effect used to define gradients when filling and drawing shapes. A gradient is defined
+ * by two control points. Each point that is rendered is coloured based on it's proximity to the 
+ * points. Note that the points are defined relative to the center of the shape being drawn. This 
+ * is with the intention that the gradient fills can be used and do not need to be updated when
+ * the geometry is moved
+ *
+ * @author kevin
+ */
+public class GradientFill implements ShapeFill {
+	/** The contant offset */
+	private Vector2f none = new Vector2f(0,0);
+	/** The start position of the gradient */
+	private Vector2f start;
+	/** The end poisition of the gradient */
+	private Vector2f end;
+	/** The starting colour of the gradient */
+	private Color startCol;
+	/** The ending colour of the gradient */
+	private Color endCol;
+	/** True if the graident is defined in shape coordinates */
+	private boolean local = false;
+	
+	/**
+	 * Create a gradient fill
+	 * 
+	 * @param sx The x coordinate of the starting control point
+	 * @param sy The y coordinate of the starting control point
+	 * @param startCol The colour to apply at the starting control point
+	 * @param ex The x coordinate of the ending control point
+	 * @param ey The y coordinate of the ending control point
+	 * @param endCol The colour to apply at the ending control point
+	 */
+	public GradientFill(float sx, float sy, Color startCol, float ex, float ey, Color endCol) 
+	{
+		this(sx,sy,startCol,ex,ey,endCol,false);
+	}
+
+	/**
+	 * Create a gradient fill
+	 * 
+	 * @param sx The x coordinate of the starting control point
+	 * @param sy The y coordinate of the starting control point
+	 * @param startCol The colour to apply at the starting control point
+	 * @param ex The x coordinate of the ending control point
+	 * @param ey The y coordinate of the ending control point
+	 * @param endCol The colour to apply at the ending control point
+	 * @param local True if the gradient is defined in local shape coordinates
+	 */
+	public GradientFill(float sx, float sy, Color startCol, float ex, float ey, Color endCol, boolean local) 
+	{
+		this(new Vector2f(sx,sy), startCol, new Vector2f(ex,ey), endCol, local);
+	}
+	
+	/**
+	 * Create a gradient fill
+	 * 
+	 * @param start The position of the starting control point
+	 * @param startCol The colour to apply at the starting control point
+	 * @param end The position of the ending control point
+	 * @param endCol The colour to apply at the ending control point
+	 * @param local True if the gradient is defined in local shape coordinates
+	 */
+	public GradientFill(Vector2f start, Color startCol, Vector2f end, Color endCol, boolean local) {
+		this.start = new Vector2f(start);
+		this.end = new Vector2f(end);
+		this.startCol = new Color(startCol);
+		this.endCol = new Color(endCol);
+		this.local = local;
+	}
+	
+	/**
+	 * Get an inverted copy of the gradient
+	 * 
+	 * @return The copy with the colours inverted
+	 */
+	public GradientFill getInvertedCopy() {
+		return new GradientFill(start, endCol, end, startCol, local);
+	}
+	
+	/**
+	 * Indicate if the gradient is defined in shape local coordinates
+	 * 
+	 * @param local True if the gradient is defined in shape local coordinates
+ 	 */
+	public void setLocal(boolean local) {
+		this.local = local;
+	}
+	
+	/**
+	 * Get the position of the start control point
+	 * 
+	 * @return The position of the start control point
+	 */
+	public Vector2f getStart() {
+		return start;
+	}
+
+	/**
+	 * Get the position of the end control point
+	 * 
+	 * @return The position of the end control point
+	 */
+	public Vector2f getEnd() {
+		return end;
+	}
+	
+	/**
+	 * Get the colour at the start control point
+	 * 
+	 * @return The color at the start control point
+	 */
+	public Color getStartColor() {
+		return startCol;
+	}
+
+	/**
+	 * Get the colour at the end control point
+	 * 
+	 * @return The color at the end control point
+	 */
+	public Color getEndColor() {
+		return endCol;
+	}
+	
+	/**
+	 * Set the start point's position
+	 * 
+	 * @param x The x coordinate of the start control point
+	 * @param y The y coordinate of the start control point
+	 */
+	public void setStart(float x, float y) {
+		setStart(new Vector2f(x,y));
+	}
+	
+	/**
+	 * Set the start control point's position
+	 * 
+	 * @param start The new poisition for the start point
+	 */
+	public void setStart(Vector2f start) {
+		this.start = new Vector2f(start);
+	}
+	
+	/**
+	 * Set the end control point's position
+	 * 
+	 * @param x The x coordinate of the end control point
+	 * @param y The y coordinate of the end control point
+	 */
+	public void setEnd(float x, float y) {
+		setEnd(new Vector2f(x,y));
+	}
+	
+	/**
+	 * Set the end control point's position
+	 * 
+	 * @param end The new position for the end point
+	 */
+	public void setEnd(Vector2f end) {
+		this.end = new Vector2f(end);
+	}
+	
+	/**
+	 * Set the colour to apply at the start control's position
+	 * 
+	 * @param color The colour to apply at the start control point
+	 */
+	public void setStartColor(Color color) {
+		this.startCol = new Color(color);
+	}
+
+	/**
+	 * Set the colour to apply at the end control's position
+	 * 
+	 * @param color The colour to apply at the end control point
+	 */
+	public void setEndColor(Color color) {
+		this.endCol = new Color(color);
+	}
+	
+	/**
+	 * Get the colour that should be applied at the specified location
+	 * 
+	 * @param shape The shape being filled
+	 * @param x The x coordinate of the point being coloured 
+	 * @param y The y coordinate of the point being coloured
+	 * @return The colour that should be applied based on the control points of this gradient
+	 */
+	public Color colorAt(Shape shape, float x, float y) {
+		if (local) {
+			return colorAt(x-shape.getCenterX(),y-shape.getCenterY());
+		} else {
+			return colorAt(x,y);
+		}
+	}
+
+	/**
+	 * Get the colour that should be applied at the specified location
+	 * 
+	 * @param x The x coordinate of the point being coloured 
+	 * @param y The y coordinate of the point being coloured
+	 * @return The colour that should be applied based on the control points of this gradient
+	 */
+	public Color colorAt(float x, float y) {
+		float dx1 = end.getX() - start.getX();
+		float dy1 = end.getY() - start.getY();
+		
+		float dx2 = -dy1;
+		float dy2 = dx1;
+		float denom = (dy2 * dx1) - (dx2 * dy1);
+		
+		if (denom == 0) {
+			return Color.black;
+		}
+		
+		float ua = (dx2 * (start.getY() - y)) - (dy2 * (start.getX() - x));
+		ua /= denom;
+		float ub = (dx1 * (start.getY() - y)) - (dy1 * (start.getX() - x));
+		ub /= denom;
+		float u = ua;
+		if (u < 0) {
+			u = 0;
+		} 
+		if (u > 1) {
+			u = 1;
+		}
+		float v = 1 - u;
+
+		// u is the proportion down the line we are
+		Color col = new Color(1,1,1,1);
+		col.r = (u * endCol.r) + (v * startCol.r);
+		col.b = (u * endCol.b) + (v * startCol.b);
+		col.g = (u * endCol.g) + (v * startCol.g);
+		col.a = (u * endCol.a) + (v * startCol.a);
+		
+		return col;
+	}
+
+	/**
+	 * @see org.newdawn.slick.ShapeFill#getOffsetAt(org.newdawn.slick.geom.Shape, float, float)
+	 */
+	public Vector2f getOffsetAt(Shape shape, float x, float y) {
+		return none;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/fills/package.html b/lib/slick-source/org/newdawn/slick/fills/package.html
new file mode 100644
index 000000000..d1db3793c
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/fills/package.html
@@ -0,0 +1,3 @@
+
+Fill effects used to colour and mogrify shapes during rendering
+
\ No newline at end of file
diff --git a/lib/slick-source/org/newdawn/slick/font/Glyph.java b/lib/slick-source/org/newdawn/slick/font/Glyph.java
new file mode 100644
index 000000000..faf1b0346
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/Glyph.java
@@ -0,0 +1,152 @@
+
+package org.newdawn.slick.font;
+
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.font.GlyphMetrics;
+import java.awt.font.GlyphVector;
+
+import org.newdawn.slick.Image;
+import org.newdawn.slick.UnicodeFont;
+
+/**
+ * Represents the glyph in a font for a unicode codepoint.
+ * 
+ * @author Nathan Sweet 
+ */
+public class Glyph {
+	/** The code point in which this glyph is found */
+	private int codePoint;
+	/** The width of this glyph in pixels */
+	private short width;
+	/** The height of this glyph in pixels */
+	private short height;
+	/** The offset on the y axis to draw the glyph at */
+	private short yOffset;
+	/** True if the glyph isn't defined */
+	private boolean isMissing;
+	/** The shape drawn for this glyph */
+	private Shape shape;
+	/** The image generated for this glyph */
+	private Image image;
+
+	/**
+	 * Create a new glyph
+	 * 
+	 * @param codePoint The code point in which this glyph can be found
+	 * @param bounds The bounds that this glrph can fill
+	 * @param vector The vector this glyph is part of
+	 * @param index The index of this glyph within the vector
+	 * @param unicodeFont The font this glyph forms part of
+	 */
+	public Glyph(int codePoint, Rectangle bounds, GlyphVector vector, int index, UnicodeFont unicodeFont) {
+		this.codePoint = codePoint;
+
+		GlyphMetrics metrics = vector.getGlyphMetrics(index);
+		int lsb = (int)metrics.getLSB();
+		if (lsb > 0) lsb = 0;
+		int rsb = (int)metrics.getRSB();
+		if (rsb > 0) rsb = 0;
+
+		int glyphWidth = bounds.width - lsb - rsb;
+		int glyphHeight = bounds.height;
+		if (glyphWidth > 0 && glyphHeight > 0) {
+			int padTop = unicodeFont.getPaddingTop();
+			int padRight = unicodeFont.getPaddingRight();
+			int padBottom = unicodeFont.getPaddingBottom();
+			int padLeft = unicodeFont.getPaddingLeft();
+			int glyphSpacing = 1; // Needed to prevent filtering problems.
+			width = (short)(glyphWidth + padLeft + padRight + glyphSpacing);
+			height = (short)(glyphHeight + padTop + padBottom + glyphSpacing);
+			yOffset = (short)(unicodeFont.getAscent() + bounds.y - padTop);
+		}
+
+		shape = vector.getGlyphOutline(index, -bounds.x + unicodeFont.getPaddingLeft(), -bounds.y + unicodeFont.getPaddingTop());
+
+		isMissing = !unicodeFont.getFont().canDisplay((char)codePoint);
+	}
+
+	/**
+	 * The unicode codepoint the glyph represents.
+	 * 
+	 * @return The codepoint the glyph represents
+	 */
+	public int getCodePoint () {
+		return codePoint;
+	}
+
+	/**
+	 * Returns true if the font does not have a glyph for this codepoint.
+	 * 
+	 * @return True if this glyph is not defined in the given code point
+	 */
+	public boolean isMissing () {
+		return isMissing;
+	}
+
+	/**
+	 * The width of the glyph's image.
+	 * 
+	 * @return The width in pixels of the glyphs image
+	 */
+	public int getWidth () {
+		return width;
+	}
+
+	/**
+	 * The height of the glyph's image.
+	 * 
+	 * @return The height in pixels of the glyphs image
+	 */
+	public int getHeight () {
+		return height;
+	}
+
+	/**
+	 * The shape to use to draw this glyph. This is set to null after the glyph is stored 
+	 * in a GlyphPage.
+	 * 
+	 * @return The shape drawn for this glyph
+	 */
+	public Shape getShape () {
+		return shape;
+	}
+
+	/**
+	 * Set the shape that should be drawn for this glyph
+	 * 
+	 * @param shape The shape that should be drawn for this glyph
+	 */
+	public void setShape(Shape shape) {
+		this.shape = shape;
+	}
+
+	/**
+	 * The image to use for this glyph. This is null until after the glyph is stored in a 
+	 * GlyphPage.
+	 * 
+	 * @return The image that has been generated for this glyph
+	 */
+	public Image getImage () {
+		return image;
+	}
+
+	/**
+	 * Set the image that has been generated for this glyph
+	 * 
+	 * @param image The image that has been generated for this glyph
+	 */
+	public void setImage(Image image) {
+		this.image = image;
+	}
+
+	/**
+	 * The distance from drawing y location to top of this glyph, causing the glyph to sit 
+	 * on the baseline.
+	 * 
+	 * @return The offset on the y axis this glyph should be drawn at
+	 */
+	public int getYOffset() {
+		return yOffset;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/GlyphPage.java b/lib/slick-source/org/newdawn/slick/font/GlyphPage.java
new file mode 100644
index 000000000..d97c4a20a
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/GlyphPage.java
@@ -0,0 +1,260 @@
+
+package org.newdawn.slick.font;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.font.FontRenderContext;
+import java.awt.image.BufferedImage;
+import java.awt.image.WritableRaster;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.newdawn.slick.Color;
+import org.newdawn.slick.Image;
+import org.newdawn.slick.SlickException;
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.effects.Effect;
+import org.newdawn.slick.opengl.TextureImpl;
+import org.newdawn.slick.opengl.renderer.Renderer;
+import org.newdawn.slick.opengl.renderer.SGL;
+
+/**
+ * Stores a number of glyphs on a single texture.
+ * 
+ * @author Nathan Sweet 
+ */
+public class GlyphPage {
+	/** The interface to OpenGL */
+	private static final SGL GL = Renderer.get();
+
+	/** The maxium size of an individual glyph */
+	public static final int MAX_GLYPH_SIZE = 256;
+
+	/** A temporary working buffer */
+    private static ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4);
+
+    static {
+		scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+    }
+    
+    /** A temporary working buffer */
+    private static IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer();
+    
+    
+	/** A temporary image used to generate the glyph page */
+	private static BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB);
+	/** The graphics context form the temporary image */
+	private static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics();
+	
+	static {
+		scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+		scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+		scratchGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+	}
+	
+	/** The render context in which the glyphs will be generated */
+    public static FontRenderContext renderContext = scratchGraphics.getFontRenderContext();
+	
+	/**
+	 * Get the scratch graphics used to generate the page of glyphs
+	 * 
+	 * @return The scratch graphics used to build the page
+	 */
+	public static Graphics2D getScratchGraphics() {
+		return scratchGraphics;
+	}
+	
+	/** The font this page is part of */
+	private final UnicodeFont unicodeFont;
+	/** The width of this page's image */
+	private final int pageWidth;
+	/** The height of this page's image */
+	private final int pageHeight;
+	/** The image containing the glyphs */
+	private final Image pageImage;
+	/** The x position of the page */
+	private int pageX;
+	/** The y position of the page */
+	private int pageY;
+	/** The height of the last row on the page */
+	private int rowHeight;
+	/** True if the glyphs are ordered */
+	private boolean orderAscending;
+	/** The list of glyphs on this page */
+	private final List pageGlyphs = new ArrayList(32);
+
+	/**
+	 * Create a new page of glyphs
+	 * 
+	 * @param unicodeFont The font this page forms part of
+	 * @param pageWidth The width of the backing texture.
+	 * @param pageHeight The height of the backing texture.
+	 * @throws SlickException if the backing texture could not be created.
+	 */
+	public GlyphPage(UnicodeFont unicodeFont, int pageWidth, int pageHeight) throws SlickException {
+		this.unicodeFont = unicodeFont;
+		this.pageWidth = pageWidth;
+		this.pageHeight = pageHeight;
+
+		pageImage = new Image(pageWidth, pageHeight);
+	}
+
+	/**
+	 * Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list.
+	 * 
+	 * If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit.
+	 * This reduces texture binds when drawing since glyphs loaded at once are typically displayed together.
+	 * @param glyphs The glyphs to load.
+	 * @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the
+	 *           glyphs.
+	 * @return The number of glyphs that were actually loaded.
+	 * @throws SlickException if the glyph could not be rendered.
+	 */
+	public int loadGlyphs (List glyphs, int maxGlyphsToLoad) throws SlickException {
+		if (rowHeight != 0 && maxGlyphsToLoad == -1) {
+			// If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit.
+			int testX = pageX;
+			int testY = pageY;
+			int testRowHeight = rowHeight;
+			for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
+				Glyph glyph = (Glyph)iter.next();
+				int width = glyph.getWidth();
+				int height = glyph.getHeight();
+				if (testX + width >= pageWidth) {
+					testX = 0;
+					testY += testRowHeight;
+					testRowHeight = height;
+				} else if (height > testRowHeight) {
+					testRowHeight = height;
+				}
+				if (testY + testRowHeight >= pageWidth) return 0;
+				testX += width;
+			}
+		}
+
+		Color.white.bind();
+		pageImage.bind();
+
+		int i = 0;
+		for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
+			Glyph glyph = (Glyph)iter.next();
+			int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth());
+			int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight());
+
+			if (rowHeight == 0) {
+				// The first glyph always fits.
+				rowHeight = height;
+			} else {
+				// Wrap to the next line if needed, or break if no more fit.
+				if (pageX + width >= pageWidth) {
+					if (pageY + rowHeight + height >= pageHeight) break;
+					pageX = 0;
+					pageY += rowHeight;
+					rowHeight = height;
+				} else if (height > rowHeight) {
+					if (pageY + height >= pageHeight) break;
+					rowHeight = height;
+				}
+			}
+
+			renderGlyph(glyph, width, height);
+			pageGlyphs.add(glyph);
+
+			pageX += width;
+
+			iter.remove();
+			i++;
+			if (i == maxGlyphsToLoad) {
+				// If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time.
+				orderAscending = !orderAscending;
+				break;
+			}
+		}
+
+		TextureImpl.bindNone();
+
+		// Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together.
+		orderAscending = !orderAscending;
+
+		return i;
+	}
+
+	/**
+	 * Loads a single glyph to the backing texture, if it fits.
+	 * 
+	 * @param glyph The glyph to be rendered
+	 * @param width The expected width of the glyph
+	 * @param height The expected height of the glyph
+	 * @throws SlickException if the glyph could not be rendered.
+	 */
+	private void renderGlyph(Glyph glyph, int width, int height) throws SlickException {
+		// Draw the glyph to the scratch image using Java2D.
+		scratchGraphics.setComposite(AlphaComposite.Clear);
+		scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE);
+		scratchGraphics.setComposite(AlphaComposite.SrcOver);
+		scratchGraphics.setColor(java.awt.Color.white);
+		for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();)
+			((Effect)iter.next()).draw(scratchImage, scratchGraphics, unicodeFont, glyph);
+		glyph.setShape(null); // The shape will never be needed again.
+
+		WritableRaster raster = scratchImage.getRaster();
+		int[] row = new int[width];
+		for (int y = 0; y < height; y++) {
+			raster.getDataElements(0, y, width, 1, row);
+			scratchIntBuffer.put(row);
+		}
+		GL.glTexSubImage2D(SGL.GL_TEXTURE_2D, 0, pageX, pageY, width, height, SGL.GL_BGRA, SGL.GL_UNSIGNED_BYTE,
+			scratchByteBuffer);
+		scratchIntBuffer.clear();
+
+		glyph.setImage(pageImage.getSubImage(pageX, pageY, width, height));
+	}
+
+	/**
+	 * Returns an iterator for the specified glyphs, sorted either ascending or descending.
+	 * 
+	 * @param glyphs The glyphs to return if present
+	 * @return An iterator of the sorted list of glyphs
+	 */
+	private Iterator getIterator(List glyphs) {
+		if (orderAscending) return glyphs.iterator();
+		final ListIterator iter = glyphs.listIterator(glyphs.size());
+		return new Iterator() {
+			public boolean hasNext () {
+				return iter.hasPrevious();
+			}
+
+			public Object next () {
+				return iter.previous();
+			}
+
+			public void remove () {
+				iter.remove();
+			}
+		};
+	}
+
+	/**
+	 * Returns the glyphs stored on this page.
+	 * 
+	 * @return A list of {@link Glyph} elements on this page
+	 */
+	public List getGlyphs () {
+		return pageGlyphs;
+	}
+
+	/**
+	 * Returns the backing texture for this page.
+	 * 
+	 * @return The image of this page of glyphs
+	 */
+	public Image getImage () {
+		return pageImage;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/HieroSettings.java b/lib/slick-source/org/newdawn/slick/font/HieroSettings.java
new file mode 100644
index 000000000..72a05ff17
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/HieroSettings.java
@@ -0,0 +1,380 @@
+
+package org.newdawn.slick.font;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.newdawn.slick.SlickException;
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.effects.ConfigurableEffect;
+import org.newdawn.slick.font.effects.ConfigurableEffect.Value;
+import org.newdawn.slick.util.ResourceLoader;
+
+/**
+ * Holds the settings needed to configure a UnicodeFont.
+ * 
+ * @author Nathan Sweet 
+ */
+public class HieroSettings {
+	/** The size of the font to be generated */
+	private int fontSize = 12;
+	/** True if the font is rendered bold */
+	private boolean bold = false;
+	/** True fi the font if rendered italic */
+	private boolean italic = false;
+	/** The padding applied in pixels to the top of the glyph rendered area */
+	private int paddingTop;
+	/** The padding applied in pixels to the left of the glyph rendered area */
+	private int paddingLeft;
+	/** The padding applied in pixels to the bottom of the glyph rendered area */
+	private int paddingBottom;
+	/** The padding applied in pixels to the right of the glyph rendered area */
+	private int paddingRight;
+	/** The padding applied in pixels to horizontal advance for each glyph */
+	private int paddingAdvanceX;
+	/** The padding applied in pixels to vertical advance for each glyph */
+	private int paddingAdvanceY;
+	/** The width of the glyph page generated */
+	private int glyphPageWidth = 512;
+	/** The height of the glyph page generated */
+	private int glyphPageHeight = 512;
+	/** The list of effects applied */
+	private final List effects = new ArrayList();
+
+	/**
+	 * Default constructor for injection
+	 */
+	public HieroSettings() {
+	}
+
+	/**
+	 * Create a new set of configuration from a file
+	 * 
+	 * @param hieroFileRef The file system or classpath location of the Hiero settings file.
+	 * @throws SlickException if the file could not be read.
+	 */
+	public HieroSettings(String hieroFileRef) throws SlickException {
+		this(ResourceLoader.getResourceAsStream(hieroFileRef));
+	}
+	
+	/**
+	 * Create a new set of configuration from a file
+	 * 
+	 * @param in The stream from which to read the settings from
+	 * @throws SlickException if the file could not be read.
+	 */
+	public HieroSettings(InputStream in) throws SlickException {
+		try {
+			BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+			while (true) {
+				String line = reader.readLine();
+				if (line == null) break;
+				line = line.trim();
+				if (line.length() == 0) continue;
+				String[] pieces = line.split("=", 2);
+				String name = pieces[0].trim();
+				String value = pieces[1];
+				if (name.equals("font.size")) {
+					fontSize = Integer.parseInt(value);
+				} else if (name.equals("font.bold")) {
+					bold = Boolean.valueOf(value).booleanValue();
+				} else if (name.equals("font.italic")) {
+					italic = Boolean.valueOf(value).booleanValue();
+				} else if (name.equals("pad.top")) {
+					paddingTop = Integer.parseInt(value);
+				} else if (name.equals("pad.right")) {
+					paddingRight = Integer.parseInt(value);
+				} else if (name.equals("pad.bottom")) {
+					paddingBottom = Integer.parseInt(value);
+				} else if (name.equals("pad.left")) {
+					paddingLeft = Integer.parseInt(value);
+				} else if (name.equals("pad.advance.x")) {
+					paddingAdvanceX = Integer.parseInt(value);
+				} else if (name.equals("pad.advance.y")) {
+					paddingAdvanceY = Integer.parseInt(value);
+				} else if (name.equals("glyph.page.width")) {
+					glyphPageWidth = Integer.parseInt(value);
+				} else if (name.equals("glyph.page.height")) {
+					glyphPageHeight = Integer.parseInt(value);
+				} else if (name.equals("effect.class")) {
+					try {
+						effects.add(Class.forName(value).newInstance());
+					} catch (Exception ex) {
+						throw new SlickException("Unable to create effect instance: " + value, ex);
+					}
+				} else if (name.startsWith("effect.")) {
+					// Set an effect value on the last added effect.
+					name = name.substring(7);
+					ConfigurableEffect effect = (ConfigurableEffect)effects.get(effects.size() - 1);
+					List values = effect.getValues();
+					for (Iterator iter = values.iterator(); iter.hasNext();) {
+						Value effectValue = (Value)iter.next();
+						if (effectValue.getName().equals(name)) {
+							effectValue.setString(value);
+							break;
+						}
+					}
+					effect.setValues(values);
+				}
+			}
+			reader.close();
+		} catch (Exception ex) {
+			throw new SlickException("Unable to load Hiero font file", ex);
+		}
+	}
+
+	/**
+	 * @see UnicodeFont#getPaddingTop()
+	 * 
+	 * @return The padding for the top of the glyph area in pixels
+	 */
+	public int getPaddingTop () {
+		return paddingTop;
+	}
+
+	/**
+	 * @see UnicodeFont#setPaddingTop(int)
+	 * 
+	 * @param paddingTop The padding for the top of the glyph area in pixels
+	 */
+	public void setPaddingTop(int paddingTop) {
+		this.paddingTop = paddingTop;
+	}
+
+	/**
+	 * @see UnicodeFont#getPaddingLeft()
+	 * 
+	 * @return The padding for the left of the glyph area in pixels
+	 */
+	public int getPaddingLeft() {
+		return paddingLeft;
+	}
+
+	/**
+	 * @see UnicodeFont#setPaddingLeft(int)
+	 * 
+	 * @param paddingLeft The padding for the left of the glyph area in pixels
+	 */
+	public void setPaddingLeft(int paddingLeft) {
+		this.paddingLeft = paddingLeft;
+	}
+
+	/**
+	 * @see UnicodeFont#getPaddingBottom()
+	 * 
+	 * @return The padding for the bottom of the glyph area in pixels
+	 */
+	public int getPaddingBottom() {
+		return paddingBottom;
+	}
+
+	/**
+	 * @see UnicodeFont#setPaddingBottom(int)
+	 * 
+	 * @param paddingBottom The padding for the bottom of the glyph area in pixels
+	 */
+	public void setPaddingBottom(int paddingBottom) {
+		this.paddingBottom = paddingBottom;
+	}
+
+	/**
+	 * @see UnicodeFont#getPaddingRight()
+	 * 
+	 * @return The padding for the right of the glyph area in pixels
+	 */
+	public int getPaddingRight() {
+		return paddingRight;
+	}
+
+	/**
+	 * @see UnicodeFont#setPaddingRight(int)
+	 * 
+	 * @param paddingRight The padding for the right of the glyph area in pixels
+	 */
+	public void setPaddingRight(int paddingRight) {
+		this.paddingRight = paddingRight;
+	}
+
+	/**
+	 * @see UnicodeFont#getPaddingAdvanceX()
+	 * 
+	 * @return The padding for the horizontal advance of each glyph
+	 */
+	public int getPaddingAdvanceX() {
+		return paddingAdvanceX;
+	}
+
+	/**
+	 * @see UnicodeFont#setPaddingAdvanceX(int)
+	 * 
+	 * @param paddingAdvanceX The padding for the horizontal advance of each glyph
+	 */
+	public void setPaddingAdvanceX(int paddingAdvanceX) {
+		this.paddingAdvanceX = paddingAdvanceX;
+	}
+
+	/**
+	 * @see UnicodeFont#getPaddingAdvanceY()
+	 * 
+	 * @return The padding for the vertical advance of each glyph
+	 */
+	public int getPaddingAdvanceY() {
+		return paddingAdvanceY;
+	}
+
+	/**
+	 * @see UnicodeFont#setPaddingAdvanceY(int)
+	 * 
+	 * @param paddingAdvanceY The padding for the vertical advance of each glyph
+	 */
+	public void setPaddingAdvanceY(int paddingAdvanceY) {
+		this.paddingAdvanceY = paddingAdvanceY;
+	}
+
+	/**
+	 * @see UnicodeFont#getGlyphPageWidth()
+	 * 
+	 * @return The width of the generate glyph pages
+	 */
+	public int getGlyphPageWidth() {
+		return glyphPageWidth;
+	}
+
+	/**
+	 * @see UnicodeFont#setGlyphPageWidth(int)
+	 * 
+	 * @param glyphPageWidth The width of the generate glyph pages
+	 */
+	public void setGlyphPageWidth(int glyphPageWidth) {
+		this.glyphPageWidth = glyphPageWidth;
+	}
+
+	/**
+	 * @see UnicodeFont#getGlyphPageHeight()
+	 * 
+	 * @return The height of the generate glyph pages
+	 */
+	public int getGlyphPageHeight() {
+		return glyphPageHeight;
+	}
+
+	/**
+	 * @see UnicodeFont#setGlyphPageHeight(int)
+	 * 
+	 * @param glyphPageHeight The height of the generate glyph pages
+	 */
+	public void setGlyphPageHeight(int glyphPageHeight) {
+		this.glyphPageHeight = glyphPageHeight;
+	}
+
+	/**
+	 * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
+	 * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
+	 * 
+	 * @return The point size of the font generated
+	 */
+	public int getFontSize() {
+		return fontSize;
+	}
+
+	/**
+	 * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
+	 * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
+	 * 
+	 * @param fontSize The point size of the font generated
+	 */
+	public void setFontSize (int fontSize) {
+		this.fontSize = fontSize;
+	}
+
+	/**
+	 * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
+	 * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
+	 * 
+	 * @return True if the font was generated in bold typeface
+	 */
+	public boolean isBold () {
+		return bold;
+	}
+
+	/**
+	 * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
+	 * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
+	 * 
+	 * @param bold True if the font was generated in bold typeface
+	 */
+	public void setBold (boolean bold) {
+		this.bold = bold;
+	}
+
+	/**
+	 * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
+	 * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
+	 * 
+	 * @return True if the font was generated in italic typeface
+	 */
+	public boolean isItalic () {
+		return italic;
+	}
+
+	/**
+	 * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
+	 * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
+	 * 
+	 * @param italic True if the font was generated in italic typeface
+	 */
+	public void setItalic (boolean italic) {
+		this.italic = italic;
+	}
+
+	/**
+	 * @see UnicodeFont#getEffects()
+	 * 
+	 * @return The list of effects applied to the text
+	 */
+	public List getEffects() {
+		return effects;
+	}
+
+	/**
+	 * Saves the settings to a file.
+	 * 
+	 * @param file The file we're saving to
+	 * @throws IOException if the file could not be saved.
+	 */
+	public void save(File file) throws IOException {
+		PrintStream out = new PrintStream(new FileOutputStream(file));
+		out.println("font.size=" + fontSize);
+		out.println("font.bold=" + bold);
+		out.println("font.italic=" + italic);
+		out.println();
+		out.println("pad.top=" + paddingTop);
+		out.println("pad.right=" + paddingRight);
+		out.println("pad.bottom=" + paddingBottom);
+		out.println("pad.left=" + paddingLeft);
+		out.println("pad.advance.x=" + paddingAdvanceX);
+		out.println("pad.advance.y=" + paddingAdvanceY);
+		out.println();
+		out.println("glyph.page.width=" + glyphPageWidth);
+		out.println("glyph.page.height=" + glyphPageHeight);
+		out.println();
+		for (Iterator iter = effects.iterator(); iter.hasNext();) {
+			ConfigurableEffect effect = (ConfigurableEffect)iter.next();
+			out.println("effect.class=" + effect.getClass().getName());
+			for (Iterator iter2 = effect.getValues().iterator(); iter2.hasNext();) {
+				Value value = (Value)iter2.next();
+				out.println("effect." + value.getName() + "=" + value.getString());
+			}
+			out.println();
+		}
+		out.close();
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/ColorEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/ColorEffect.java
new file mode 100644
index 000000000..d02476953
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/ColorEffect.java
@@ -0,0 +1,92 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.Glyph;
+
+/**
+ * Makes glyphs a solid color.
+ * 
+ * @author Nathan Sweet 
+ */
+public class ColorEffect implements ConfigurableEffect {
+	/** The colour that will be applied across the text */
+	private Color color = Color.white;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public ColorEffect() {
+	}
+
+	/**
+	 * Create a new effect to colour the text
+	 * 
+	 * @param color The colour to apply across the text
+	 */
+	public ColorEffect(Color color) {
+		this.color = color;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
+	 */
+	public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
+		g.setColor(color);
+		g.fill(glyph.getShape());
+	}
+
+	/**
+	 * Get the colour being applied by this effect
+	 * 
+	 * @return The colour being applied by this effect
+	 */
+	public Color getColor() {
+		return color;
+	}
+
+	/**
+	 * Set the colour being applied by this effect
+	 * 
+	 * @param color The colour being applied by this effect
+	 */
+	public void setColor(Color color) {
+		if (color == null) throw new IllegalArgumentException("color cannot be null.");
+		this.color = color;
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString () {
+		return "Color";
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
+	 */
+	public List getValues() {
+		List values = new ArrayList();
+		values.add(EffectUtil.colorValue("Color", color));
+		return values;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
+	 */
+	public void setValues(List values) {
+		for (Iterator iter = values.iterator(); iter.hasNext();) {
+			Value value = (Value)iter.next();
+			if (value.getName().equals("Color")) {
+				setColor((Color)value.getObject());
+			}
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/ConfigurableEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/ConfigurableEffect.java
new file mode 100644
index 000000000..2bd2e7686
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/ConfigurableEffect.java
@@ -0,0 +1,53 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.util.List;
+
+/**
+ * An effect that has a number of configuration values. This allows the effect to be configured in the Hiero GUI and to be saved
+ * and loaded to and from a file.
+ * 
+ * @author Nathan Sweet 
+ */
+public interface ConfigurableEffect extends Effect {
+	/**
+	 * Returns the list of {@link Value}s for this effect. This list is not typically backed by the effect, so changes to the
+	 * values will not take affect until {@link #setValues(List)} is called.
+	 */
+	public List getValues();
+
+	/**
+	 * Sets the list of {@link Value}s for this effect.
+	 */
+	public void setValues(List values);
+
+	/**
+	 * Represents a configurable value for an effect.
+	 */
+	static public interface Value {
+		/**
+		 * Returns the name of the value.
+		 */
+		public String getName ();
+
+		/**
+		 * Sets the string representation of the value.
+		 */
+		public void setString (String value);
+
+		/**
+		 * Gets the string representation of the value.
+		 */
+		public String getString ();
+
+		/**
+		 * Gets the object representation of the value.
+		 */
+		public Object getObject ();
+
+		/**
+		 * Shows a dialog allowing a user to configure this value.
+		 */
+		public void showDialog ();
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/Effect.java b/lib/slick-source/org/newdawn/slick/font/effects/Effect.java
new file mode 100644
index 000000000..fe07d2591
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/Effect.java
@@ -0,0 +1,25 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.Glyph;
+
+/**
+ * A graphical effect that is applied to glyphs in a {@link UnicodeFont}.
+ * 
+ * @author Nathan Sweet 
+ */
+public interface Effect {
+	/**
+	 * Called to draw the effect.
+	 * 
+	 * @param image The image to draw into
+	 * @param g The graphics context to use for applying the effect
+	 * @param unicodeFont The font being rendered
+	 * @param glyph The particular glyph being rendered
+	 */
+	public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph);
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/EffectUtil.java b/lib/slick-source/org/newdawn/slick/font/effects/EffectUtil.java
new file mode 100644
index 000000000..ec297f3c8
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/EffectUtil.java
@@ -0,0 +1,368 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextArea;
+import javax.swing.SpinnerNumberModel;
+
+import org.newdawn.slick.font.GlyphPage;
+import org.newdawn.slick.font.effects.ConfigurableEffect.Value;
+
+/**
+ * Provides utility methods for effects.
+ * 
+ * @author Nathan Sweet 
+ */
+public class EffectUtil {
+	/** A graphics 2D temporary surface to be used when generating effects */
+	static private BufferedImage scratchImage = new BufferedImage(GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE,
+		BufferedImage.TYPE_INT_ARGB);
+
+	/**
+	 * Returns an image that can be used by effects as a temp image.
+	 * 
+	 * @return The scratch image used for temporary operations
+	 */
+	static public BufferedImage getScratchImage() {
+		Graphics2D g = (Graphics2D)scratchImage.getGraphics();
+		g.setComposite(AlphaComposite.Clear);
+		g.fillRect(0, 0, GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE);
+		g.setComposite(AlphaComposite.SrcOver);
+		g.setColor(java.awt.Color.white);
+		return scratchImage;
+	}
+
+	/**
+	 * Prompts the user for a colour value
+	 * 
+	 * @param name Thename of the value being configured
+	 * @param currentValue The default value that should be selected
+	 * @return The value selected
+	 */
+	static public Value colorValue(String name, Color currentValue) {
+		return new DefaultValue(name, EffectUtil.toString(currentValue)) {
+			public void showDialog () {
+				Color newColor = JColorChooser.showDialog(null, "Choose a color", EffectUtil.fromString(value));
+				if (newColor != null) value = EffectUtil.toString(newColor);
+			}
+
+			public Object getObject () {
+				return EffectUtil.fromString(value);
+			}
+		};
+	}
+
+	/**
+	 * Prompts the user for int value
+	 * 
+	 * @param name The name of the dialog to show
+	 * @param currentValue The current value to be displayed
+	 * @param description The help text to provide
+	 * @return The value selected by the user
+	 */
+	static public Value intValue (String name, final int currentValue, final String description) {
+		return new DefaultValue(name, String.valueOf(currentValue)) {
+			public void showDialog () {
+				JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, Short.MIN_VALUE, Short.MAX_VALUE, 1));
+				if (showValueDialog(spinner, description)) value = String.valueOf(spinner.getValue());
+			}
+
+			public Object getObject () {
+				return Integer.valueOf(value);
+			}
+		};
+	}
+
+	/**
+	 * Prompts the user for float value
+	 * 
+	 * @param name The name of the dialog to show
+	 * @param currentValue The current value to be displayed
+	 * @param description The help text to provide
+	 * @param min The minimum value to allow
+	 * @param max The maximum value to allow
+	 * @return The value selected by the user
+	 */
+	static public Value floatValue (String name, final float currentValue, final float min, final float max,
+		final String description) {
+		return new DefaultValue(name, String.valueOf(currentValue)) {
+			public void showDialog () {
+				JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, min, max, 0.1f));
+				if (showValueDialog(spinner, description)) value = String.valueOf(((Double)spinner.getValue()).floatValue());
+			}
+
+			public Object getObject () {
+				return Float.valueOf(value);
+			}
+		};
+	}
+
+	/**
+	 * Prompts the user for boolean value
+	 * 
+	 * @param name The name of the dialog to show
+	 * @param currentValue The current value to be displayed
+	 * @param description The help text to provide
+	 * @return The value selected by the user
+	 */
+	static public Value booleanValue (String name, final boolean currentValue, final String description) {
+		return new DefaultValue(name, String.valueOf(currentValue)) {
+			public void showDialog () {
+				JCheckBox checkBox = new JCheckBox();
+				checkBox.setSelected(currentValue);
+				if (showValueDialog(checkBox, description)) value = String.valueOf(checkBox.isSelected());
+			}
+
+			public Object getObject () {
+				return Boolean.valueOf(value);
+			}
+		};
+	}
+
+	
+	/**
+	 * Prompts the user for a value that represents a fixed number of options. 
+	 * All options are strings.
+	 * 
+	 * @param options The first array has an entry for each option. Each entry is either a String[1] that is both the display value
+	 *           and actual value, or a String[2] whose first element is the display value and second element is the actual value.
+	 *
+	 * @param name The name of the value being prompted for
+	 * @param currentValue The current value to show as default
+	 * @param description The description of the value
+	 * @return The value selected by the user
+	 */
+	static public Value optionValue (String name, final String currentValue, final String[][] options, final String description) {
+		return new DefaultValue(name, currentValue.toString()) {
+			public void showDialog () {
+				int selectedIndex = -1;
+				DefaultComboBoxModel model = new DefaultComboBoxModel();
+				for (int i = 0; i < options.length; i++) {
+					model.addElement(options[i][0]);
+					if (getValue(i).equals(currentValue)) selectedIndex = i;
+				}
+				JComboBox comboBox = new JComboBox(model);
+				comboBox.setSelectedIndex(selectedIndex);
+				if (showValueDialog(comboBox, description)) value = getValue(comboBox.getSelectedIndex());
+			}
+
+			private String getValue (int i) {
+				if (options[i].length == 1) return options[i][0];
+				return options[i][1];
+			}
+
+			public String toString () {
+				for (int i = 0; i < options.length; i++)
+					if (getValue(i).equals(value)) return options[i][0].toString();
+				return "";
+			}
+
+			public Object getObject () {
+				return value;
+			}
+		};
+	}
+
+	/**
+	 * Convers a color to a string.
+	 * 
+	 * @param color The color to encode to a string
+	 * @return The colour as a string
+	 */
+	static public String toString (Color color) {
+		if (color == null) throw new IllegalArgumentException("color cannot be null.");
+		String r = Integer.toHexString(color.getRed());
+		if (r.length() == 1) r = "0" + r;
+		String g = Integer.toHexString(color.getGreen());
+		if (g.length() == 1) g = "0" + g;
+		String b = Integer.toHexString(color.getBlue());
+		if (b.length() == 1) b = "0" + b;
+		return r + g + b;
+	}
+
+	/**
+	 * Converts a string to a color.
+	 * 
+	 * @param rgb The string encoding the colour
+	 * @return The colour represented by the given encoded string
+	 */
+	static public Color fromString (String rgb) {
+		if (rgb == null || rgb.length() != 6) return Color.white;
+		return new Color(Integer.parseInt(rgb.substring(0, 2), 16), Integer.parseInt(rgb.substring(2, 4), 16), Integer.parseInt(rgb
+			.substring(4, 6), 16));
+	}
+
+	/**
+	 * Provides generic functionality for an effect's configurable value.
+	 */
+	static private abstract class DefaultValue implements Value {
+		/** The value being held */
+		String value;
+		/** The key/name of the value */
+		String name;
+
+		/**
+		 * Create a default value
+		 * 
+		 * @param name The name of the value being configured  
+		 * @param value The value to use for the default
+		 */
+		public DefaultValue(String name, String value) {
+			this.value = value;
+			this.name = name;
+		}
+
+		/**
+		 * @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#setString(java.lang.String)
+		 */
+		public void setString(String value) {
+			this.value = value;
+		}
+
+		/**
+		 * @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#getString()
+		 */
+		public String getString() {
+			return value;
+		}
+
+		/**
+		 * @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#getName()
+		 */
+		public String getName() {
+			return name;
+		}
+
+		/**
+		 * @see java.lang.Object#toString()
+		 */
+		public String toString() {
+			if (value == null) {
+				return "";
+			}
+			return value.toString();
+		}
+
+		/**
+		 * Prompt the user for a value
+		 * 
+		 * @param component The component to use as parent for the prompting dialog
+		 * @param description The description of the value being prompted for
+		 * @return True if the value was configured
+		 */
+		public boolean showValueDialog(final JComponent component, String description) {
+			ValueDialog dialog = new ValueDialog(component, name, description);
+			dialog.setTitle(name);
+			dialog.setLocationRelativeTo(null);
+			EventQueue.invokeLater(new Runnable() {
+				public void run () {
+					JComponent focusComponent = component;
+					if (focusComponent instanceof JSpinner)
+						focusComponent = ((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField();
+					focusComponent.requestFocusInWindow();
+				}
+			});
+			dialog.setVisible(true);
+			return dialog.okPressed;
+		}
+	};
+
+	/**
+	 * Provides generic functionality for a dialog to configure a value.
+	 */
+	static private class ValueDialog extends JDialog {
+		/** True if OK was pressed */
+		public boolean okPressed = false;
+
+		/**
+		 * Create a new dialog to configure a specific value
+		 * 
+		 * @param component The component to use as the parent of the dialog prompting the user
+		 * @param name The name of the value being configured
+		 * @param description The description of the value being configured
+		 */
+		public ValueDialog(JComponent component, String name, String description) {
+			setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+			setLayout(new GridBagLayout());
+			setModal(true);
+
+			if (component instanceof JSpinner)
+				((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField().setColumns(4);
+
+			JPanel descriptionPanel = new JPanel();
+			descriptionPanel.setLayout(new GridBagLayout());
+			getContentPane().add(
+				descriptionPanel,
+				new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0,
+					0), 0, 0));
+			descriptionPanel.setBackground(Color.white);
+			descriptionPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.black));
+			{
+				JTextArea descriptionText = new JTextArea(description);
+				descriptionPanel.add(descriptionText, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
+					GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
+				descriptionText.setWrapStyleWord(true);
+				descriptionText.setLineWrap(true);
+				descriptionText.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+				descriptionText.setEditable(false);
+			}
+
+			JPanel panel = new JPanel();
+			getContentPane().add(
+				panel,
+				new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 0,
+					5), 0, 0));
+			panel.add(new JLabel(name + ":"));
+			panel.add(component);
+
+			JPanel buttonPanel = new JPanel();
+			getContentPane().add(
+				buttonPanel,
+				new GridBagConstraints(0, 2, 2, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE,
+					new Insets(0, 0, 0, 0), 0, 0));
+			{
+				JButton okButton = new JButton("OK");
+				buttonPanel.add(okButton);
+				okButton.addActionListener(new ActionListener() {
+					public void actionPerformed (ActionEvent evt) {
+						okPressed = true;
+						setVisible(false);
+					}
+				});
+			}
+			{
+				JButton cancelButton = new JButton("Cancel");
+				buttonPanel.add(cancelButton);
+				cancelButton.addActionListener(new ActionListener() {
+					public void actionPerformed (ActionEvent evt) {
+						setVisible(false);
+					}
+				});
+			}
+
+			setSize(new Dimension(320, 175));
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/FilterEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/FilterEffect.java
new file mode 100644
index 000000000..8ca37ed74
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/FilterEffect.java
@@ -0,0 +1,62 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.Glyph;
+
+/**
+ * Applys a {@link BufferedImageOp} filter to glyphs. Many filters can be found 
+ * here: http://www.jhlabs.com/ip/filters/index.html
+ * 
+ * @author Nathan Sweet 
+ */
+public class FilterEffect implements Effect {
+	/** The filter to be applied */
+	private BufferedImageOp filter;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public FilterEffect () {
+	}
+
+	/**
+	 * Create a new filtering effect based on a convolution operation
+	 * 
+	 * @param filter The filter to apply
+	 */
+	public FilterEffect (BufferedImageOp filter) {
+		this.filter = filter;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
+	 */
+	public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
+		BufferedImage scratchImage = EffectUtil.getScratchImage();
+		filter.filter(image, scratchImage);
+		image.getGraphics().drawImage(scratchImage, 0, 0, null);
+	}
+
+	/**
+	 * Get the filter being applied by this effect
+	 * 
+	 * @return The filter being applied by this effect
+	 */
+	public BufferedImageOp getFilter() {
+		return filter;
+	}
+
+	/**
+	 * Set the filter being applied by this effect
+	 * 
+	 * @param filter The filter being used by this effect
+	 */
+	public void setFilter(BufferedImageOp filter) {
+		this.filter = filter;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/GradientEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/GradientEffect.java
new file mode 100644
index 000000000..4b7a7a7ef
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/GradientEffect.java
@@ -0,0 +1,195 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.Glyph;
+
+/**
+ * Paints glyphs with a gradient fill.
+ * 
+ * @author Nathan Sweet 
+ */
+public class GradientEffect implements ConfigurableEffect {
+	/** The top of gradients colour */
+	private Color topColor = Color.cyan;
+	/** The bottom of the gradient's colour */
+	private Color bottomColor = Color.blue;
+	/** The offset the gradient starts at */
+	private int offset = 0;
+	/** The scaling of the graident */
+	private float scale = 1;
+	/** True if the graident should cycle back and forth across the surface */
+	private boolean cyclic;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public GradientEffect() {
+	}
+
+	/**
+	 * Create a new effect to apply a graident
+	 * 
+	 * @param topColor The colour at the top of the graident
+	 * @param bottomColor The colour at the bottom of the gradient
+	 * @param scale The scale of the graident
+	 */
+	public GradientEffect(Color topColor, Color bottomColor, float scale) {
+		this.topColor = topColor;
+		this.bottomColor = bottomColor;
+		this.scale = scale;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
+	 */
+	public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
+		int ascent = unicodeFont.getAscent();
+		float height = (ascent) * scale;
+		float top = -glyph.getYOffset() + unicodeFont.getDescent() + offset + ascent / 2 - height / 2;
+		g.setPaint(new GradientPaint(0, top, topColor, 0, top + height, bottomColor, cyclic));
+		g.fill(glyph.getShape());
+	}
+
+	/**
+	 * Get the colour at the top of the graident
+	 * 
+	 * @return The colour at the top of the gradient
+	 */
+	public Color getTopColor() {
+		return topColor;
+	}
+
+	/**
+	 * Set the colour at the top of the graident
+	 * 
+	 * @param topColor The colour at the top of the graident
+	 */
+	public void setTopColor(Color topColor) {
+		this.topColor = topColor;
+	}
+
+	/**
+	 * Get the colour at the bottom of the graident
+	 * 
+	 * @return The colour at the bottom of the gradient
+	 */
+	public Color getBottomColor () {
+		return bottomColor;
+	}
+
+	/**
+	 * Set the colour at the bottom of the graident
+	 * 
+	 * @param bottomColor The colour at the bottom of the graident
+	 */
+	public void setBottomColor(Color bottomColor) {
+		this.bottomColor = bottomColor;
+	}
+
+	/**
+	 * Get the offset the gradients starts at
+	 * 
+	 * @return The offset the gradient starts at
+	 */
+	public int getOffset() {
+		return offset;
+	}
+
+	/**
+	 * Sets the pixel offset to move the gradient up or down. 
+	 * The gradient is normally centered on the glyph.
+	 * 
+	 * @param offset The offset the gradient is moved by
+	 */
+	public void setOffset (int offset) {
+		this.offset = offset;
+	}
+
+	/**
+	 * Get the percentage scaling being applied to the gradient across the surface
+	 * 
+	 * @return The scale of the graident
+	 */
+	public float getScale() {
+		return scale;
+	}
+
+	/**
+	 * Changes the height of the gradient by a percentage. The gradient is 
+	 * normally the height of most glyphs in the font.
+	 * 
+	 * @param scale The scale to apply
+	 */
+	public void setScale (float scale) {
+		this.scale = scale;
+	}
+
+	/**
+	 * Check if the graident is repeating
+	 * 
+	 * @return True if the gradient is repeating
+	 */
+	public boolean isCyclic() {
+		return cyclic;
+	}
+
+	/**
+	 * If set to true, the gradient will repeat.
+	 * 
+	 * @param cyclic True if the graident repeats
+	 */
+	public void setCyclic(boolean cyclic) {
+		this.cyclic = cyclic;
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		return "Gradient";
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
+	 */
+	public List getValues() {
+		List values = new ArrayList();
+		values.add(EffectUtil.colorValue("Top color", topColor));
+		values.add(EffectUtil.colorValue("Bottom color", bottomColor));
+		values.add(EffectUtil.intValue("Offset", offset,
+			"This setting allows you to move the gradient up or down. The gradient is normally centered on the glyph."));
+		values.add(EffectUtil.floatValue("Scale", scale, 0, 1, "This setting allows you to change the height of the gradient by a"
+			+ "percentage. The gradient is normally the height of most glyphs in the font."));
+		values.add(EffectUtil.booleanValue("Cyclic", cyclic, "If this setting is checked, the gradient will repeat."));
+		return values;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
+	 */
+	public void setValues(List values) {
+		for (Iterator iter = values.iterator(); iter.hasNext();) {
+			Value value = (Value)iter.next();
+			if (value.getName().equals("Top color")) {
+				topColor = (Color)value.getObject();
+			} else if (value.getName().equals("Bottom color")) {
+				bottomColor = (Color)value.getObject();
+			} else if (value.getName().equals("Offset")) {
+				offset = ((Integer)value.getObject()).intValue();
+			} else if (value.getName().equals("Scale")) {
+				scale = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("Cyclic")) {
+				cyclic = ((Boolean)value.getObject()).booleanValue();
+			}
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/OutlineEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/OutlineEffect.java
new file mode 100644
index 000000000..8684e0542
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/OutlineEffect.java
@@ -0,0 +1,178 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.Glyph;
+
+/**
+ * Strokes glyphs with an outline.
+ * 
+ * @author Nathan Sweet 
+ */
+public class OutlineEffect implements ConfigurableEffect {
+	/** The width of the outline in pixels */
+	private float width = 2;
+	/** The colour of the outline */
+	private Color color = Color.black;
+	/** The type of join at the line joins of the out line */
+	private int join = BasicStroke.JOIN_BEVEL;
+	/** The stroke used to draw the outline */
+	private Stroke stroke;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public OutlineEffect() {
+	}
+
+	/**
+	 * Create a new effect to draw the outline of the text
+	 * 
+	 * @param width The width of the outline
+	 * @param color The colour of the outline
+	 */
+	public OutlineEffect(int width, Color color) {
+		this.width = width;
+		this.color = color;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
+	 */
+	public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
+		g = (Graphics2D)g.create();
+		if (stroke != null)
+			g.setStroke(stroke);
+		else
+			g.setStroke(getStroke());
+		g.setColor(color);
+		g.draw(glyph.getShape());
+		g.dispose();
+	}
+
+	/**
+	 * Get the width of the outline being drawn
+	 * 
+	 * @return The width of the outline being drawn
+	 */
+	public float getWidth() {
+		return width;
+	}
+
+	/**
+	 * Sets the width of the outline. The glyphs will need padding so the 
+	 * outline doesn't get clipped.
+	 * 
+	 * @param width The width of the outline being drawn
+	 */
+	public void setWidth (int width) {
+		this.width = width;
+	}
+
+	/**
+	 * Get the colour of the outline being drawn
+	 * 
+	 * @return The colour of the outline being drawn
+	 */
+	public Color getColor() {
+		return color;
+	}
+
+	/**
+	 * Set the colour of the outline being drawn
+	 * 
+	 * @param color The colour of the outline to draw
+	 */
+	public void setColor(Color color) {
+		this.color = color;
+	}
+
+	/**
+	 * Get the join type as indicated by @see BasicStroke
+	 * 
+	 * @return The join type between segments in the outline 
+	 */
+	public int getJoin() {
+		return join;
+	}
+
+	/**
+	 * Get the stroke being used to draw the outline
+	 * 
+	 * @return The stroke being used to draw the outline
+	 */
+	public Stroke getStroke() {
+		if (stroke == null) {
+			return new BasicStroke(width, BasicStroke.CAP_SQUARE, join);
+		}
+		
+		return stroke;
+	}
+
+	/**
+	 * Sets the stroke to use for the outline. If this is set, 
+	 * the other outline settings are ignored.
+	 * 
+	 * @param stroke The stroke to be used to draw the outline
+	 */
+	public void setStroke (Stroke stroke) {
+		this.stroke = stroke;
+	}
+
+	/**
+	 * Sets how the corners of the outline are drawn. This is usually only noticeable 
+	 * at large outline widths.
+	 * 
+	 * @param join One of: {@link BasicStroke#JOIN_BEVEL}, {@link BasicStroke#JOIN_MITER}, {@link BasicStroke#JOIN_ROUND}
+	 */
+	public void setJoin (int join) {
+		this.join = join;
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString () {
+		return "Outline";
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
+	 */
+	public List getValues () {
+		List values = new ArrayList();
+		values.add(EffectUtil.colorValue("Color", color));
+		values.add(EffectUtil.floatValue("Width", width, 0.1f, 999, "This setting controls the width of the outline. "
+			+ "The glyphs will need padding so the outline doesn't get clipped."));
+		values.add(EffectUtil.optionValue("Join", String.valueOf(join), new String[][] { {"Bevel", BasicStroke.JOIN_BEVEL + ""},
+			{"Miter", BasicStroke.JOIN_MITER + ""}, {"Round", BasicStroke.JOIN_ROUND + ""}},
+			"This setting defines how the corners of the outline are drawn. "
+				+ "This is usually only noticeable at large outline widths."));
+		return values;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
+	 */
+	public void setValues (List values) {
+		for (Iterator iter = values.iterator(); iter.hasNext();) {
+			Value value = (Value)iter.next();
+			if (value.getName().equals("Color")) {
+				color = (Color)value.getObject();
+			} else if (value.getName().equals("Width")) {
+				width = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("Join")) {
+				join = Integer.parseInt((String)value.getObject());
+			}
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/OutlineWobbleEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/OutlineWobbleEffect.java
new file mode 100644
index 000000000..9e8c3035c
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/OutlineWobbleEffect.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2006 Jerry Huxtable
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
+ * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.FlatteningPathIterator;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * An effect that genrates a wobbly line around the outline of the text
+ * 
+ * @author Jerry Huxtable
+ * @author Nathan Sweet 
+ */
+public class OutlineWobbleEffect extends OutlineEffect {
+	/** How often the line wobbles */
+	private float detail = 1;
+	/** The amount of the line wobbles */
+	private float amplitude = 1;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public OutlineWobbleEffect () {
+		setStroke(new WobbleStroke());
+	}
+
+	/**
+	 * Gets the detail of the wobble effect.
+	 * 
+	 * @return The detail of the wobble effect
+	 */
+	public float getDetail() {
+		return detail;
+	}
+
+	/**
+	 * Sets the detail of the wobble effect.
+	 * 
+	 * @param detail The detail of the wobble effect
+	 */
+	public void setDetail(float detail) {
+		this.detail = detail;
+	}
+
+	/**
+	 * Gets the amplitude of the wobble effect.
+	 * 
+	 * @return The amplitude of the wobble effect
+	 */
+	public float getAmplitude() {
+		return amplitude;
+	}
+
+	/**
+	 * Sets the amplitude of the wobble effect.
+	 * 
+	 * @param amplitude The detail of the wobble effect
+	 */
+	public void setAmplitude(float amplitude) {
+		this.amplitude = amplitude;
+	}
+
+	/**
+	 * Create a new effect that generates a wobbly line around the text
+	 * 
+	 * @param width The width of the line
+	 * @param color The colour of the line
+	 */
+	public OutlineWobbleEffect (int width, Color color) {
+		super(width, color);
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.OutlineEffect#toString()
+	 */
+	public String toString() {
+		return "Outline (Wobble)";
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.OutlineEffect#getValues()
+	 */
+	public List getValues() {
+		List values = super.getValues();
+		values.remove(2); // Remove "Join".
+		values.add(EffectUtil.floatValue("Detail", detail, 1, 50, "This setting controls how detailed the outline will be. "
+			+ "Smaller numbers cause the outline to have more detail."));
+		values.add(EffectUtil.floatValue("Amplitude", amplitude, 0.5f, 50, "This setting controls the amplitude of the outline."));
+		return values;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.OutlineEffect#setValues(java.util.List)
+	 */
+	public void setValues(List values) {
+		super.setValues(values);
+		for (Iterator iter = values.iterator(); iter.hasNext();) {
+			Value value = (Value)iter.next();
+			if (value.getName().equals("Detail")) {
+				detail = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("Amplitude")) {
+				amplitude = ((Float)value.getObject()).floatValue();
+			}
+		}
+	}
+
+	/**
+	 * A stroke that generate a wobbly line
+	 * 
+	 * @author Jerry Huxtable
+	 * @author Nathan Sweet 
+	 */
+	private class WobbleStroke implements Stroke {
+		/** The flattening factor of the stroke */
+		private static final float FLATNESS = 1;
+
+		/**
+		 * @see java.awt.Stroke#createStrokedShape(java.awt.Shape)
+		 */
+		public Shape createStrokedShape (Shape shape) {
+			GeneralPath result = new GeneralPath();
+			shape = new BasicStroke(getWidth(), BasicStroke.CAP_SQUARE, getJoin()).createStrokedShape(shape);
+			PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);
+			float points[] = new float[6];
+			float moveX = 0, moveY = 0;
+			float lastX = 0, lastY = 0;
+			float thisX = 0, thisY = 0;
+			int type = 0;
+			float next = 0;
+			while (!it.isDone()) {
+				type = it.currentSegment(points);
+				switch (type) {
+				case PathIterator.SEG_MOVETO:
+					moveX = lastX = randomize(points[0]);
+					moveY = lastY = randomize(points[1]);
+					result.moveTo(moveX, moveY);
+					next = 0;
+					break;
+
+				case PathIterator.SEG_CLOSE:
+					points[0] = moveX;
+					points[1] = moveY;
+					// Fall into....
+
+				case PathIterator.SEG_LINETO:
+					thisX = randomize(points[0]);
+					thisY = randomize(points[1]);
+					float dx = thisX - lastX;
+					float dy = thisY - lastY;
+					float distance = (float)Math.sqrt(dx * dx + dy * dy);
+					if (distance >= next) {
+						float r = 1.0f / distance;
+						while (distance >= next) {
+							float x = lastX + next * dx * r;
+							float y = lastY + next * dy * r;
+							result.lineTo(randomize(x), randomize(y));
+							next += detail;
+						}
+					}
+					next -= distance;
+					lastX = thisX;
+					lastY = thisY;
+					break;
+				}
+				it.next();
+			}
+
+			return result;
+		}
+
+		/**
+		 * Get a random wobble factor
+		 * 
+		 * @param x The position on the line
+		 * @return The wobble factor
+		 */
+		private float randomize(float x) {
+			return x + (float)Math.random() * amplitude * 2 - 1;
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/OutlineZigzagEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/OutlineZigzagEffect.java
new file mode 100644
index 000000000..0d9c5a03b
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/OutlineZigzagEffect.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2006 Jerry Huxtable
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
+ * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.FlatteningPathIterator;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * An effect to generate a uniformly zigzaging line around text
+ * 
+ * @author Jerry Huxtable
+ * @author Nathan Sweet 
+ */
+public class OutlineZigzagEffect extends OutlineEffect {
+	/** The amount the line moves away from the text */
+	private float amplitude = 1;
+	/** How often the line zigs and zags */
+	private float wavelength = 3;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public OutlineZigzagEffect() {
+		setStroke(new ZigzagStroke());
+	}
+
+	/**
+	 * Gets the wavelength of the wobble effect.
+	 * 
+	 * @return The wavelength of the wobble effect
+	 */
+	public float getWavelength() {
+		return wavelength;
+	}
+
+	/**
+	 * Sets the wavelength of the wobble effect.
+	 * 
+	 * @param wavelength The wavelength of the wobble effect
+	 */
+	public void setWavelength(float wavelength) {
+		this.wavelength = wavelength;
+	}
+
+	/**
+	 * Gets the amplitude of the wobble effect.
+	 * 
+	 * @return The amplitude of the wobble effect
+	 */
+	public float getAmplitude() {
+		return amplitude;
+	}
+
+	/**
+	 * Sets the amplitude of the wobble effect.
+	 * 
+	 * @param amplitude The detail of the wobble effect
+	 */
+	public void setAmplitude(float amplitude) {
+		this.amplitude = amplitude;
+	}
+	
+	/**
+	 * Create a new effect to generate a zigzagging line around the text
+	 * 
+	 * @param width The width of the line
+	 * @param color The colour of the line
+	 */
+	public OutlineZigzagEffect(int width, Color color) {
+		super(width, color);
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.OutlineEffect#toString()
+	 */
+	public String toString () {
+		return "Outline (Zigzag)";
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.OutlineEffect#getValues()
+	 */
+	public List getValues() {
+		List values = super.getValues();
+		values.add(EffectUtil.floatValue("Wavelength", wavelength, 1, 100, "This setting controls the wavelength of the outline. "
+			+ "The smaller the value, the more segments will be used to draw the outline."));
+		values.add(EffectUtil.floatValue("Amplitude", amplitude, 0.5f, 50, "This setting controls the amplitude of the outline. "
+			+ "The bigger the value, the more the zigzags will vary."));
+		return values;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.OutlineEffect#setValues(java.util.List)
+	 */
+	public void setValues(List values) {
+		super.setValues(values);
+		for (Iterator iter = values.iterator(); iter.hasNext();) {
+			Value value = (Value)iter.next();
+			if (value.getName().equals("Wavelength")) {
+				wavelength = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("Amplitude")) {
+				amplitude = ((Float)value.getObject()).floatValue();
+			}
+		}
+	}
+
+	/**
+	 * A stroke to generate zigzags
+	 * 
+	 * @author Jerry Huxtable
+	 * @author Nathan Sweet 
+	 */
+	private class ZigzagStroke implements Stroke {
+		/** The flattening factor applied to the path iterator */
+		private static final float FLATNESS = 1;
+
+		/** 
+		 * @see java.awt.Stroke#createStrokedShape(java.awt.Shape)
+		 */
+		public Shape createStrokedShape (Shape shape) {
+			GeneralPath result = new GeneralPath();
+			PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);
+			float points[] = new float[6];
+			float moveX = 0, moveY = 0;
+			float lastX = 0, lastY = 0;
+			float thisX = 0, thisY = 0;
+			int type = 0;
+			float next = 0;
+			int phase = 0;
+			while (!it.isDone()) {
+				type = it.currentSegment(points);
+				switch (type) {
+				case PathIterator.SEG_MOVETO:
+					moveX = lastX = points[0];
+					moveY = lastY = points[1];
+					result.moveTo(moveX, moveY);
+					next = wavelength / 2;
+					break;
+
+				case PathIterator.SEG_CLOSE:
+					points[0] = moveX;
+					points[1] = moveY;
+					// Fall into....
+
+				case PathIterator.SEG_LINETO:
+					thisX = points[0];
+					thisY = points[1];
+					float dx = thisX - lastX;
+					float dy = thisY - lastY;
+					float distance = (float)Math.sqrt(dx * dx + dy * dy);
+					if (distance >= next) {
+						float r = 1.0f / distance;
+						while (distance >= next) {
+							float x = lastX + next * dx * r;
+							float y = lastY + next * dy * r;
+							if ((phase & 1) == 0)
+								result.lineTo(x + amplitude * dy * r, y - amplitude * dx * r);
+							else
+								result.lineTo(x - amplitude * dy * r, y + amplitude * dx * r);
+							next += wavelength;
+							phase++;
+						}
+					}
+					next -= distance;
+					lastX = thisX;
+					lastY = thisY;
+					if (type == PathIterator.SEG_CLOSE) result.closePath();
+					break;
+				}
+				it.next();
+			}
+			return new BasicStroke(getWidth(), BasicStroke.CAP_SQUARE, getJoin()).createStrokedShape(result);
+		}
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/font/effects/ShadowEffect.java b/lib/slick-source/org/newdawn/slick/font/effects/ShadowEffect.java
new file mode 100644
index 000000000..2e1ea4655
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/font/effects/ShadowEffect.java
@@ -0,0 +1,321 @@
+
+package org.newdawn.slick.font.effects;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.awt.image.ConvolveOp;
+import java.awt.image.Kernel;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.newdawn.slick.UnicodeFont;
+import org.newdawn.slick.font.Glyph;
+
+/**
+ * An effect to generate soft shadows beneath text 
+ * 
+ * @author Nathan Sweet 
+ */
+public class ShadowEffect implements ConfigurableEffect {
+	/** The number of kernels to apply */
+	public static final int NUM_KERNELS = 16;
+	/** The blur kernels applied across the effect */
+	public static final float[][] GAUSSIAN_BLUR_KERNELS = generateGaussianBlurKernels(NUM_KERNELS);
+
+	/** The colour of the shadow to render */
+	private Color color = Color.black;
+	/** The transparency factor of the shadow */
+	private float opacity = 0.6f;
+	/** The distance on the x axis of the shadow from the text */
+	private float xDistance = 2;
+	/** The distance on the y axis of the shadow from the text */
+	private float yDistance = 2;
+	/** The size of the kernel used to blur the shadow */
+	private int blurKernelSize = 0;
+	/** The number of passes applied to create the blur */
+	private int blurPasses = 1;
+
+	/**
+	 * Default constructor for injection
+	 */
+	public ShadowEffect() {
+	}
+
+	/**
+	 * Create a new effect to apply a drop shadow to text
+	 * 
+	 * @param color The colour of the shadow to generate
+	 * @param xDistance The distance from the text on the x axis the shadow should be rendered
+	 * @param yDistance The distance from the text on the y axis the shadow should be rendered
+	 * @param opacity The transparency factor of the shadow
+	 */
+	public ShadowEffect (Color color, int xDistance, int yDistance, float opacity) {
+		this.color = color;
+		this.xDistance = xDistance;
+		this.yDistance = yDistance;
+		this.opacity = opacity;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
+	 */
+	public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
+		g = (Graphics2D)g.create();
+		g.translate(xDistance, yDistance);
+		g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round(opacity * 255)));
+		g.fill(glyph.getShape());
+
+		// Also shadow the outline, if one exists.
+		for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();) {
+			Effect effect = (Effect)iter.next();
+			if (effect instanceof OutlineEffect) {
+				Composite composite = g.getComposite();
+				g.setComposite(AlphaComposite.Src); // Prevent shadow and outline shadow alpha from combining.
+
+				g.setStroke(((OutlineEffect)effect).getStroke());
+				g.draw(glyph.getShape());
+
+				g.setComposite(composite);
+				break;
+			}
+		}
+
+		g.dispose();
+		if (blurKernelSize > 1 && blurKernelSize < NUM_KERNELS && blurPasses > 0) blur(image);
+	}
+
+	/**
+	 * Apply blurring to the generate image
+	 * 
+	 * @param image The image to be blurred
+	 */
+	private void blur(BufferedImage image) {
+		float[] matrix = GAUSSIAN_BLUR_KERNELS[blurKernelSize - 1];
+		Kernel gaussianBlur1 = new Kernel(matrix.length, 1, matrix);
+		Kernel gaussianBlur2 = new Kernel(1, matrix.length, matrix);
+		RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+		ConvolveOp gaussianOp1 = new ConvolveOp(gaussianBlur1, ConvolveOp.EDGE_NO_OP, hints);
+		ConvolveOp gaussianOp2 = new ConvolveOp(gaussianBlur2, ConvolveOp.EDGE_NO_OP, hints);
+		BufferedImage scratchImage = EffectUtil.getScratchImage();
+		for (int i = 0; i < blurPasses; i++) {
+			gaussianOp1.filter(image, scratchImage);
+			gaussianOp2.filter(scratchImage, image);
+		}
+	}
+
+	/**
+	 * Get the colour of the shadow generated
+	 * 
+	 * @return The colour of the shadow generated
+	 */
+	public Color getColor() {
+		return color;
+	}
+
+	/**
+	 * Set the colour of the shadow to be generated
+	 * 
+	 * @param color The colour ofthe shadow to be generated
+	 */
+	public void setColor(Color color) {
+		this.color = color;
+	}
+
+	/**
+	 * Get the distance on the X axis from the text the shadow should
+	 * be generated at
+	 * 
+	 * @return The distance on the X axis the shadow will be from the text
+	 */
+	public float getXDistance() {
+		return xDistance;
+	}
+
+	/**
+	 * Sets the pixels to offset the shadow on the x axis. The glyphs will need padding so the 
+	 * shadow doesn't get clipped.
+	 * 
+	 * @param distance The offset on the x axis
+	 */
+	public void setXDistance(float distance) {
+		xDistance = distance;
+	}
+
+	/**
+	 * Get the distance on the Y axis from the text the shadow should
+	 * be generated at
+	 * 
+	 * @return The distance on the Y axis the shadow will be from the text
+	 */
+	public float getYDistance() {
+		return yDistance;
+	}
+
+	/**
+	 * Sets the pixels to offset the shadow on the y axis. The glyphs will need 
+	 * padding so the shadow doesn't get clipped.
+	 * 
+	 * @param distance The offset on the y axis
+	 */
+	public void setYDistance (float distance) {
+		yDistance = distance;
+	}
+
+	/**
+	 * Get the size of the kernel used to apply the blur
+	 * 
+	 * @return The blur kernel size
+	 */
+	public int getBlurKernelSize() {
+		return blurKernelSize;
+	}
+
+	/**
+	 * Sets how many neighboring pixels are used to blur the shadow. Set to 0 for no blur.
+	 * 
+	 * @param blurKernelSize The size of the kernel to apply the blur with
+	 */
+	public void setBlurKernelSize (int blurKernelSize) {
+		this.blurKernelSize = blurKernelSize;
+	}
+
+	/**
+	 * Get the number of passes to apply the kernel for blurring
+	 * 
+	 * @return The number of passes
+	 */
+	public int getBlurPasses() {
+		return blurPasses;
+	}
+
+	/**
+	 * Sets the number of times to apply a blur to the shadow. Set to 0 for no blur.
+	 * 
+	 * @param blurPasses The number of passes to apply when blurring
+	 */
+	public void setBlurPasses (int blurPasses) {
+		this.blurPasses = blurPasses;
+	}
+
+	/**
+	 * Get the opacity of the shadow, i.e. how transparent it is
+	 * 
+	 * @return The opacity of the shadow
+	 */
+	public float getOpacity() {
+		return opacity;
+	}
+
+	/**
+	 * Set the opacity of the shadow, i.e. how transparent it is
+	 * 
+	 * @param opacity The opacity of the shadow
+	 */
+	public void setOpacity(float opacity) {
+		this.opacity = opacity;
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		return "Shadow";
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
+	 */
+	public List getValues() {
+		List values = new ArrayList();
+		values.add(EffectUtil.colorValue("Color", color));
+		values.add(EffectUtil.floatValue("Opacity", opacity, 0, 1, "This setting sets the translucency of the shadow."));
+		values.add(EffectUtil.floatValue("X distance", xDistance, Float.MIN_VALUE, Float.MAX_VALUE, "This setting is the amount of pixels to offset the shadow on the"
+			+ " x axis. The glyphs will need padding so the shadow doesn't get clipped."));
+		values.add(EffectUtil.floatValue("Y distance", yDistance, Float.MIN_VALUE, Float.MAX_VALUE, "This setting is the amount of pixels to offset the shadow on the"
+			+ " y axis. The glyphs will need padding so the shadow doesn't get clipped."));
+
+		List options = new ArrayList();
+		options.add(new String[] {"None", "0"});
+		for (int i = 2; i < NUM_KERNELS; i++)
+			options.add(new String[] {String.valueOf(i)});
+		String[][] optionsArray = (String[][])options.toArray(new String[options.size()][]);
+		values.add(EffectUtil.optionValue("Blur kernel size", String.valueOf(blurKernelSize), optionsArray,
+			"This setting controls how many neighboring pixels are used to blur the shadow. Set to \"None\" for no blur."));
+
+		values.add(EffectUtil.intValue("Blur passes", blurPasses,
+			"The setting is the number of times to apply a blur to the shadow. Set to \"0\" for no blur."));
+		return values;
+	}
+
+	/**
+	 * @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
+	 */
+	public void setValues(List values) {
+		for (Iterator iter = values.iterator(); iter.hasNext();) {
+			Value value = (Value)iter.next();
+			if (value.getName().equals("Color")) {
+				color = (Color)value.getObject();
+			} else if (value.getName().equals("Opacity")) {
+				opacity = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("X distance")) {
+				xDistance = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("Y distance")) {
+				yDistance = ((Float)value.getObject()).floatValue();
+			} else if (value.getName().equals("Blur kernel size")) {
+				blurKernelSize = Integer.parseInt((String)value.getObject());
+			} else if (value.getName().equals("Blur passes")) {
+				blurPasses = ((Integer)value.getObject()).intValue();
+			}
+		}
+	}
+
+	/**
+	 * Generate the blur kernels which will be repeatedly applied when blurring images
+	 * 
+	 * @param level The number of kernels to generate
+	 * @return The kernels generated
+	 */
+	private static float[][] generateGaussianBlurKernels(int level) {
+		float[][] pascalsTriangle = generatePascalsTriangle(level);
+		float[][] gaussianTriangle = new float[pascalsTriangle.length][];
+		for (int i = 0; i < gaussianTriangle.length; i++) {
+			float total = 0.0f;
+			gaussianTriangle[i] = new float[pascalsTriangle[i].length];
+			for (int j = 0; j < pascalsTriangle[i].length; j++)
+				total += pascalsTriangle[i][j];
+			float coefficient = 1 / total;
+			for (int j = 0; j < pascalsTriangle[i].length; j++)
+				gaussianTriangle[i][j] = coefficient * pascalsTriangle[i][j];
+		}
+		return gaussianTriangle;
+	}
+
+	/**
+	 * Generate Pascal's triangle
+	 * 
+	 * @param level The level of the triangle to generate
+	 * @return The Pascal's triangle kernel
+	 */
+	private static float[][] generatePascalsTriangle(int level) {
+		if (level < 2) level = 2;
+		float[][] triangle = new float[level][];
+		triangle[0] = new float[1];
+		triangle[1] = new float[2];
+		triangle[0][0] = 1.0f;
+		triangle[1][0] = 1.0f;
+		triangle[1][1] = 1.0f;
+		for (int i = 2; i < level; i++) {
+			triangle[i] = new float[i + 1];
+			triangle[i][0] = 1.0f;
+			triangle[i][i] = 1.0f;
+			for (int j = 1; j < triangle[i].length - 1; j++)
+				triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
+		}
+		return triangle;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/BasicTriangulator.java b/lib/slick-source/org/newdawn/slick/geom/BasicTriangulator.java
new file mode 100644
index 000000000..2d893888c
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/BasicTriangulator.java
@@ -0,0 +1,437 @@
+package org.newdawn.slick.geom;
+
+import java.util.ArrayList;
+
+/**
+ * Triangulates a polygon into triangles - duh. Doesn't handle
+ * holes in polys
+ * 
+ * @author Based on Public Source from FlipCode
+ */
+public class BasicTriangulator implements Triangulator {
+	/** The accepted error value */
+	private static final float EPSILON = 0.0000000001f;
+	/** The list of points to be triangulated */
+	private PointList poly = new PointList();
+	/** The list of points describing the triangles */
+	private PointList tris = new PointList();
+	/** True if we've tried to triangulate */
+	private boolean tried;
+	
+	/**
+	 * Create a new triangulator
+	 */
+	public BasicTriangulator() {
+	}
+	
+	/**
+	 * Add a point describing the polygon to be triangulated
+	 * 
+	 * @param x The x coordinate of the point
+	 * @param y the y coordinate of the point
+	 */
+	public void addPolyPoint(float x, float y) {
+		Point p = new Point(x,y);
+		if (!poly.contains(p)) {
+			poly.add(p);
+		}
+	}
+	
+	/**
+	 * Get the number of points in the polygon
+	 * 
+	 * @return The number of points in the polygon
+	 */
+	public int getPolyPointCount() {
+		return poly.size();
+	}
+
+	/**
+	 * Get the coordinates of the point at the specified index
+	 * 
+	 * @param index The index of the point to retrieve
+	 * @return The oordinates of the point at the specified index
+	 */
+	public float[] getPolyPoint(int index) {
+		return new float[] {poly.get(index).x,poly.get(index).y};
+	}
+	
+	/**
+	 * Cause the triangulator to split the polygon
+	 * 
+	 * @return True if we managed the task
+	 */
+	public boolean triangulate() {
+		tried = true;
+		
+		boolean worked = process(poly,tris);
+		return worked;
+	}
+	
+	/**
+	 * Get a count of the number of triangles produced
+	 * 
+	 * @return The number of triangles produced
+	 */
+	public int getTriangleCount() {
+		if (!tried) {
+			throw new RuntimeException("Call triangulate() before accessing triangles");
+		}
+		return tris.size() / 3;
+	}
+	
+	/**
+	 * Get a point on a specified generated triangle
+	 * 
+	 * @param tri The index of the triangle to interegate
+	 * @param i The index of the point within the triangle to retrieve
+	 * (0 - 2)
+	 * @return The x,y coordinate pair for the point
+	 */
+	public float[] getTrianglePoint(int tri, int i) {
+		if (!tried) {
+			throw new RuntimeException("Call triangulate() before accessing triangles");
+		}
+		
+		return tris.get((tri*3)+i).toArray();
+	}
+	
+	/** 
+	 * Find the area of a polygon defined by the series of points
+	 * in the list
+	 * 
+	 * @param contour The list of points defined the contour of the polygon
+	 * (Vector2f)
+	 * @return The area of the polygon defined
+	 */
+	private float area(PointList contour) {
+		int n = contour.size();
+
+		float A = 0.0f;
+
+		for (int p = n - 1, q = 0; q < n; p = q++) {
+			Point contourP = contour.get(p);
+			Point contourQ = contour.get(q);
+
+			A += contourP.getX() * contourQ.getY() - contourQ.getX()
+					* contourP.getY();
+		}
+		return A * 0.5f;
+	}
+
+	/**
+	 * Check if the point P is inside the triangle defined by
+	 * the points A,B,C
+	 * 
+	 * @param Ax Point A x-coordinate
+	 * @param Ay Point A y-coordinate
+	 * @param Bx Point B x-coordinate
+	 * @param By Point B y-coordinate
+	 * @param Cx Point C x-coordinate
+	 * @param Cy Point C y-coordinate
+	 * @param Px Point P x-coordinate
+	 * @param Py Point P y-coordinate
+	 * @return True if the point specified is within the triangle
+	 */
+	private boolean insideTriangle(float Ax, float Ay, float Bx,
+			float By, float Cx, float Cy, float Px, float Py) {
+		float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
+		float cCROSSap, bCROSScp, aCROSSbp;
+
+		ax = Cx - Bx;
+		ay = Cy - By;
+		bx = Ax - Cx;
+		by = Ay - Cy;
+		cx = Bx - Ax;
+		cy = By - Ay;
+		apx = Px - Ax;
+		apy = Py - Ay;
+		bpx = Px - Bx;
+		bpy = Py - By;
+		cpx = Px - Cx;
+		cpy = Py - Cy;
+
+		aCROSSbp = ax * bpy - ay * bpx;
+		cCROSSap = cx * apy - cy * apx;
+		bCROSScp = bx * cpy - by * cpx;
+
+		return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
+	}
+
+	/**
+	 * Cut a the contour and add a triangle into V to describe the 
+	 * location of the cut
+	 * 
+	 * @param contour The list of points defining the polygon
+	 * @param u The index of the first point
+	 * @param v The index of the second point
+	 * @param w The index of the third point
+	 * @param n ?
+	 * @param V The array to populate with indicies of triangles
+	 * @return True if a triangle was found
+	 */
+	private boolean snip(PointList contour, int u, int v, int w, int n,
+			int[] V) {
+		int p;
+		float Ax, Ay, Bx, By, Cx, Cy, Px, Py;
+
+		Ax = contour.get(V[u]).getX();
+		Ay = contour.get(V[u]).getY();
+
+		Bx = contour.get(V[v]).getX();
+		By = contour.get(V[v]).getY();
+
+		Cx = contour.get(V[w]).getX();
+		Cy = contour.get(V[w]).getY();
+
+		if (EPSILON > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) {
+			return false;
+		}
+
+		for (p = 0; p < n; p++) {
+			if ((p == u) || (p == v) || (p == w)) {
+				continue;
+			}
+
+			Px = contour.get(V[p]).getX();
+			Py = contour.get(V[p]).getY();
+
+			if (insideTriangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py)) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Process a list of points defining a polygon
+	 * @param contour The list of points describing the polygon
+	 * @param result The list of points describing the triangles. Groups
+	 * of 3 describe each triangle 
+	 * 
+	 * @return True if we succeeded in completing triangulation
+	 */
+	private boolean process(PointList contour, PointList result) {
+		result.clear();
+		
+		/* allocate and initialize list of Vertices in polygon */
+
+		int n = contour.size();
+		if (n < 3)
+			return false;
+
+		int[] V = new int[n];
+
+		/* we want a counter-clockwise polygon in V */
+
+		if (0.0f < area(contour)) {
+			for (int v = 0; v < n; v++)
+				V[v] = v;
+		} else {
+			for (int v = 0; v < n; v++)
+				V[v] = (n - 1) - v;
+		}
+
+		int nv = n;
+
+		/*  remove nv-2 Vertices, creating 1 triangle every time */
+		int count = 2 * nv; /* error detection */
+
+		for (int m = 0, v = nv - 1; nv > 2;) {
+			/* if we loop, it is probably a non-simple polygon */
+			if (0 >= (count--)) {
+				//** Triangulator4: ERROR - probable bad polygon!
+				return false;
+			}
+
+			/* three consecutive vertices in current polygon,  */
+			int u = v;
+			if (nv <= u)
+				u = 0; /* previous */
+			v = u + 1;
+			if (nv <= v)
+				v = 0; /* new v    */
+			int w = v + 1;
+			if (nv <= w)
+				w = 0; /* next     */
+
+			if (snip(contour, u, v, w, nv, V)) {
+				int a, b, c, s, t;
+
+				/* true names of the vertices */
+				a = V[u];
+				b = V[v];
+				c = V[w];
+
+				/* output Triangle */
+				result.add(contour.get(a));
+				result.add(contour.get(b));
+				result.add(contour.get(c));
+
+				m++;
+
+				/* remove v from remaining polygon */
+				for (s = v, t = v + 1; t < nv; s++, t++) {
+					V[s] = V[t];
+				}
+				nv--;
+
+				/* resest error detection counter */
+				count = 2 * nv;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * A single point handled by the triangulator
+	 * 
+	 * @author Kevin Glass
+	 */
+	private class Point {
+		/** The x coorindate of this point */
+		private float x;
+		/** The y coorindate of this point */
+		private float y;
+		/** The points in an array */
+		private float[] array;
+		
+		/**
+		 * Create a new point
+		 * 
+		 * @param x The x coordindate of the point
+		 * @param y The y coordindate of the point
+		 */
+		public Point(float x, float y) {
+			this.x = x;
+			this.y = y;
+			array = new float[] {x,y};
+		}
+
+		/**
+		 * Get the x coordinate of the point
+		 * 
+		 * @return The x coordinate of the point
+		 */
+		public float getX() {
+			return x;
+		}
+
+		/**
+		 * Get the y coordinate of the point
+		 * 
+		 * @return The y coordinate of the point
+		 */
+		public float getY() {
+			return y;
+		}
+	
+		/**
+		 * Convert this point into a float array
+		 * 
+		 * @return The contents of this point as a float array
+		 */
+		public float[] toArray() {
+			return array;
+		}
+		
+		/**
+		 * @see java.lang.Object#hashCode()
+		 */
+		public int hashCode() {
+			return (int) (x * y * 31);
+		}
+		
+		/**
+		 * @see java.lang.Object#equals(java.lang.Object)
+		 */
+		public boolean equals(Object other) {
+			if (other instanceof Point) {
+				Point p = (Point) other;
+				return (p.x == x) && (p.y == y);
+			}
+			
+			return false;
+		}
+	}
+	
+	/**
+	 * A list of type Point
+	 * 
+	 * @author Kevin Glass
+	 */
+	private class PointList {
+		/** The list of points */
+		private ArrayList points = new ArrayList();
+		
+		/**
+		 * Create a new empty list
+		 */
+		public PointList() {
+		}
+		
+		/**
+		 * Check if the list contains a point
+		 * 
+		 * @param p The point to look for
+		 * @return True if the point is in the list
+		 */
+		public boolean contains(Point p) {
+			return points.contains(p);
+		}
+		
+		/**
+		 * Add a point to the list 
+		 * 
+		 * @param point The point to add
+		 */
+		public void add(Point point) {
+			points.add(point);
+		}
+		
+		/**
+		 * Remove a point from the list
+		 * 
+		 * @param point The point to remove
+		 */
+		public void remove(Point point) {
+			points.remove(point);
+		}
+		
+		/**
+		 * Get the size of the list
+		 * 
+		 * @return The size of the list
+		 */
+		public int size() {
+			return points.size();
+		}
+		
+		/**
+		 * Get a point a specific index in the list
+		 * 
+		 * @param i The index of the point to retrieve
+		 * @return The point
+		 */
+		public Point get(int i) {
+			return (Point) points.get(i);
+		}
+		
+		/**
+		 * Clear the list
+		 */
+		public void clear() {
+			points.clear();
+		}
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Triangulator#startHole()
+	 */
+	public void startHole() {
+		// TODO Auto-generated method stub
+		
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/Circle.java b/lib/slick-source/org/newdawn/slick/geom/Circle.java
new file mode 100644
index 000000000..d8060d255
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/Circle.java
@@ -0,0 +1,234 @@
+package org.newdawn.slick.geom;
+
+/**
+ * A simple Circle geometry
+ * 
+ * @author Kevin Glass
+ */
+public strictfp class Circle extends Ellipse {
+	/** The radius of the circle */
+	public float radius;
+	
+	/**
+	 * Create a new circle based on its radius
+	 * 
+	 * @param centerPointX The x location of the center of the circle
+	 * @param centerPointY The y location of the center of the circle
+	 * @param radius The radius of the circle
+	 */
+	public Circle(float centerPointX, float centerPointY, float radius) {
+        this(centerPointX, centerPointY, radius, DEFAULT_SEGMENT_COUNT);
+	}
+
+	/**
+	 * Create a new circle based on its radius
+	 * 
+	 * @param centerPointX The x location of the center of the circle
+	 * @param centerPointY The y location of the center of the circle
+	 * @param radius The radius of the circle
+	 * @param segmentCount The number of segments to build the circle out of
+	 */
+	public Circle(float centerPointX, float centerPointY, float radius, int segmentCount) {
+        super(centerPointX, centerPointY, radius, radius, segmentCount);
+        this.x = centerPointX - radius;
+        this.y = centerPointY - radius;
+        this.radius = radius;
+        boundingCircleRadius = radius;
+	}
+	
+	/** 
+	 * Get the x coordinate of the centre of the circle
+	 * 
+	 * @return The x coordinate of the centre of the circle
+	 */
+	public float getCenterX() {
+		return getX() + radius;
+	}
+	
+	/** 
+	 * Get the y coordinate of the centre of the circle
+	 * 
+	 * @return The y coordinate of the centre of the circle
+	 */
+	public float getCenterY() {
+		return getY() + radius;
+	}
+
+	/** 
+	 * Get the coordinates of the center of the circle
+	 * 
+	 * @return 2-element array with the center of the circle.
+	 */
+	@Override
+	public float[] getCenter() {
+		return new float[] { getCenterX(), getCenterY() };
+	}
+	
+	/**
+	 * Set the radius of this circle
+	 * 
+	 * @param radius The radius of this circle
+	 */
+	public void setRadius(float radius) {
+		if (radius != this.radius) {
+	        pointsDirty = true;
+			this.radius = radius;
+	        setRadii(radius, radius);
+		}
+	}
+	
+	/**
+	 * Get the radius of the circle
+	 * 
+	 * @return The radius of the circle
+	 */
+	public float getRadius() {
+		return radius;
+	}
+	
+	/**
+	 * Check if this circle touches another
+	 * 
+	 * @param shape The other circle
+	 * @return True if they touch
+	 */
+	public boolean intersects(Shape shape) {
+        if(shape instanceof Circle) {
+            Circle other = (Circle)shape;
+    		float totalRad2 = getRadius() + other.getRadius();
+    		
+    		if (Math.abs(other.getCenterX() - getCenterX()) > totalRad2) {
+    			return false;
+    		}
+    		if (Math.abs(other.getCenterY() - getCenterY()) > totalRad2) {
+    			return false;
+    		}
+    		
+    		totalRad2 *= totalRad2;
+    		
+    		float dx = Math.abs(other.getCenterX() - getCenterX());
+    		float dy = Math.abs(other.getCenterY() - getCenterY());
+    		
+    		return totalRad2 >= ((dx*dx) + (dy*dy));
+        }
+        else if(shape instanceof Rectangle) {
+            return intersects((Rectangle)shape);
+        }
+        else {
+            return super.intersects(shape);
+        }
+	}
+	
+	/**
+	 * Check if a point is contained by this circle
+	 * 
+	 * @param x The x coordinate of the point to check
+	 * @param y The y coorindate of the point to check
+	 * @return True if the point is contained by this circle
+	 */
+    public boolean contains(float x, float y) 
+    { 
+        float xDelta = x - getCenterX(), yDelta = y - getCenterY();
+        return xDelta * xDelta + yDelta * yDelta < getRadius() * getRadius();
+    }
+    
+    /**
+     * Check if circle contains the line 
+     * @param line Line to check against 
+     * @return True if line inside circle 
+     */ 
+    private boolean contains(Line line) { 
+         return contains(line.getX1(), line.getY1()) && contains(line.getX2(), line.getY2()); 
+    }
+    
+	/**
+	 * @see org.newdawn.slick.geom.Ellipse#findCenter()
+	 */
+    protected void findCenter() {
+        center = new float[2];
+        center[0] = x + radius;
+        center[1] = y + radius;
+    }
+
+    /**
+     * @see org.newdawn.slick.geom.Ellipse#calculateRadius()
+     */
+    protected void calculateRadius() {
+        boundingCircleRadius = radius;
+    }
+
+    /**
+	 * Check if this circle touches a rectangle
+	 * 
+	 * @param other The rectangle to check against
+	 * @return True if they touch
+	 */
+	private boolean intersects(Rectangle other) {
+		Rectangle box = other;
+		Circle circle = this;
+		
+		if (box.contains(x+radius,y+radius)) {
+			return true;
+		}
+		
+		float x1 = box.getX();
+		float y1 = box.getY();
+		float x2 = box.getX() + box.getWidth();
+		float y2 = box.getY() + box.getHeight();
+		
+		Line[] lines = new Line[4];
+		lines[0] = new Line(x1,y1,x2,y1);
+		lines[1] = new Line(x2,y1,x2,y2);
+		lines[2] = new Line(x2,y2,x1,y2);
+		lines[3] = new Line(x1,y2,x1,y1);
+		
+		float r2 = circle.getRadius() * circle.getRadius();
+		
+		Vector2f pos = new Vector2f(circle.getCenterX(), circle.getCenterY());
+		
+		for (int i=0;i<4;i++) {
+			float dis = lines[i].distanceSquared(pos);
+			if (dis < r2) {
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
+	/** 
+     * Check if circle touches a line. 
+     * @param other The line to check against 
+     * @return True if they touch 
+     */ 
+    private boolean intersects(Line other) { 
+        // put it nicely into vectors 
+        Vector2f lineSegmentStart = new Vector2f(other.getX1(), other.getY1()); 
+        Vector2f lineSegmentEnd = new Vector2f(other.getX2(), other.getY2()); 
+        Vector2f circleCenter = new Vector2f(getCenterX(), getCenterY()); 
+
+        // calculate point on line closest to the circle center and then 
+        // compare radius to distance to the point for intersection result 
+        Vector2f closest; 
+        Vector2f segv = lineSegmentEnd.copy().sub(lineSegmentStart); 
+        Vector2f ptv = circleCenter.copy().sub(lineSegmentStart); 
+        float segvLength = segv.length(); 
+        float projvl = ptv.dot(segv) / segvLength; 
+        if (projvl < 0) 
+        { 
+            closest = lineSegmentStart; 
+        } 
+        else if (projvl > segvLength) 
+        { 
+            closest = lineSegmentEnd; 
+        } 
+        else 
+        { 
+            Vector2f projv = segv.copy().scale(projvl / segvLength); 
+            closest = lineSegmentStart.copy().add(projv); 
+        } 
+        boolean intersects = circleCenter.copy().sub(closest).lengthSquared() <= getRadius()*getRadius(); 
+        
+        return intersects; 
+    } 
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/Curve.java b/lib/slick-source/org/newdawn/slick/geom/Curve.java
new file mode 100644
index 000000000..ab73c1171
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/Curve.java
@@ -0,0 +1,113 @@
+package org.newdawn.slick.geom;
+
+/**
+ * A beizer curve implementation. The curve is defined by a start point, an end point
+ * and two control points that it will tend towards. This is implementation is fixed
+ * segmenting meaning it doesn't scale too well.
+ *
+ * @author kevin
+ */
+public class Curve extends Shape {
+	/** The start point of the curve */
+	private Vector2f p1;
+	/** The first control point */
+	private Vector2f c1;
+	/** The second control point */
+	private Vector2f c2;
+	/** The end point of the curve */
+	private Vector2f p2;
+	/** The number of lines segments the curve is built out of */
+	private int segments;
+	
+	/**
+	 * Create a new curve with the default segments (20)
+	 * 
+	 * @param p1 The start of the curve
+	 * @param c1 The first control point
+	 * @param c2 The second control point
+	 * @param p2 The end of the curve
+	 */
+	public Curve(Vector2f p1, Vector2f c1, Vector2f c2, Vector2f p2) {
+		this(p1,c1,c2,p2,20);
+	}
+
+	/**
+	 * Create a new curve 
+	 * 
+	 * @param p1 The start of the curve
+	 * @param c1 The first control point
+	 * @param c2 The second control point
+	 * @param p2 The end of the curve
+	 * @param segments The number of segments to use
+	 */
+	public Curve(Vector2f p1, Vector2f c1, Vector2f c2, Vector2f p2, int segments) {
+		this.p1 = new Vector2f(p1);
+		this.c1 = new Vector2f(c1);
+		this.c2 = new Vector2f(c2);
+		this.p2 = new Vector2f(p2);
+	
+		this.segments = segments;
+		pointsDirty = true;
+	}
+	
+	/**
+	 * Get the point at a particular location on the curve
+	 * 
+	 * @param t A value between 0 and 1 defining the location of the curve the point is at
+	 * @return The point on the curve
+	 */
+	public Vector2f pointAt(float t) {
+		float a = 1 - t;
+		float b = t;
+		
+		float f1 = a * a * a;
+		float f2 = 3 * a * a * b;
+		float f3 = 3 * a * b * b;
+		float f4 = b * b * b;
+		
+		float nx = (p1.x * f1) + (c1.x * f2) + (c2.x * f3) + (p2.x * f4);
+		float ny = (p1.y * f1) + (c1.y * f2) + (c2.y * f3) + (p2.y * f4);
+		
+		return new Vector2f(nx,ny);
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Shape#createPoints()
+	 */
+	protected void createPoints() {
+		float step = 1.0f / segments;
+		points = new float[(segments+1) * 2];
+		for (int i=0;iShape contract. The ellipse is actually an approximation using 
+ * a series of points generated around the contour of the ellipse.
+ * 
+ * @author Mark
+ */
+public class Ellipse extends Shape {
+    /**
+     * Default number of segments to draw this ellipse with
+     */
+    protected static final int DEFAULT_SEGMENT_COUNT = 50;
+    
+    /**
+     * The number of segments for graphical representation.
+     */
+    private int segmentCount;
+    /**
+     * horizontal radius
+     */
+    private float radius1;
+    /**
+     * vertical radius
+     */
+    private float radius2;
+
+    /**
+     * Creates a new Ellipse object.
+     *
+     * @param centerPointX x coordinate of the center of the ellipse
+     * @param centerPointY y coordinate of the center of the ellipse
+     * @param radius1 horizontal radius
+     * @param radius2 vertical radius
+     */
+    public Ellipse(float centerPointX, float centerPointY, float radius1, float radius2) {
+        this(centerPointX, centerPointY, radius1, radius2, DEFAULT_SEGMENT_COUNT);
+    }
+
+    /**
+     * Creates a new Ellipse object.
+     *
+     * @param centerPointX x coordinate of the center of the ellipse
+     * @param centerPointY y coordinate of the center of the ellipse
+     * @param radius1 horizontal radius
+     * @param radius2 vertical radius
+     * @param segmentCount how fine to make the ellipse.
+     */
+    public Ellipse(float centerPointX, float centerPointY, float radius1, float radius2, int segmentCount) {
+        this.x = centerPointX - radius1;
+        this.y = centerPointY - radius2;
+        this.radius1 = radius1;
+        this.radius2 = radius2;
+        this.segmentCount = segmentCount;
+        checkPoints();
+    }
+
+    /**
+     * Change the shape of this Ellipse
+     * 
+     * @param radius1 horizontal radius
+     * @param radius2 vertical radius
+     */
+    public void setRadii(float radius1, float radius2) {
+    	setRadius1(radius1);
+    	setRadius2(radius2);
+    }
+
+    /**
+     * Get the horizontal radius of the ellipse
+     * 
+     * @return The horizontal radius of the ellipse
+     */
+    public float getRadius1() {
+        return radius1;
+    }
+
+    /**
+     * Set the horizontal radius of the ellipse
+     * 
+     * @param radius1 The horizontal radius to set
+     */
+    public void setRadius1(float radius1) {
+    	if (radius1 != this.radius1) {
+	        this.radius1 = radius1;
+	        pointsDirty = true;
+    	}
+    }
+
+    /**
+     * Get the vertical radius of the ellipse
+     * 
+     * @return The vertical radius of the ellipse
+     */
+    public float getRadius2() {
+        return radius2;
+    }
+
+    /**
+     * Set the vertical radius of the ellipse
+     * 
+     * @param radius2 The vertical radius to set
+     */
+    public void setRadius2(float radius2) {
+    	if (radius2 != this.radius2) {
+	        this.radius2 = radius2;
+	        pointsDirty = true;
+    	}
+    }
+
+    /**
+     * Generate the points to outline this ellipse.
+     *
+     */
+    protected void createPoints() {
+        ArrayList tempPoints = new ArrayList();
+
+        maxX = -Float.MIN_VALUE;
+        maxY = -Float.MIN_VALUE;
+        minX = Float.MAX_VALUE;
+        minY = Float.MAX_VALUE;
+
+        float start = 0;
+        float end = 359;
+        
+        float cx = x + radius1;
+        float cy = y + radius2;
+        
+        int step = 360 / segmentCount;
+        
+        for (float a=start;a<=end+step;a+=step) {
+            float ang = a;
+            if (ang > end) {
+                ang = end;
+            }
+            float newX = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * radius1));
+            float newY = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * radius2));
+
+            if(newX > maxX) {
+                maxX = newX;
+            }
+            if(newY > maxY) {
+                maxY = newY;
+            }
+            if(newX < minX) {
+            	minX = newX;
+            }
+            if(newY < minY) {
+            	minY = newY;
+            }
+            
+            tempPoints.add(new Float(newX));
+            tempPoints.add(new Float(newY));
+        }
+        points = new float[tempPoints.size()];
+        for(int i=0;i radius2) ? radius1 : radius2;
+    }
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/GeomUtil.java b/lib/slick-source/org/newdawn/slick/geom/GeomUtil.java
new file mode 100644
index 000000000..f7d7f2b24
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/GeomUtil.java
@@ -0,0 +1,449 @@
+package org.newdawn.slick.geom;
+
+import java.util.ArrayList;
+
+/**
+ * A set of utilities to play with geometry
+ * 
+ * @author kevin
+ */
+public class GeomUtil {
+	/** The tolerance for determining changes and steps */
+	public float EPSILON = 0.0001f;
+	/** The tolerance for determining direction change */
+	public float EDGE_SCALE = 1f;
+	/** The maximum number of points returned by an operation - prevents full lockups */
+	public int MAX_POINTS = 10000;
+	/** The listener to notify of operations */
+	public GeomUtilListener listener;
+	
+	/**
+	 * Subtract one shape from another - note this is experimental and doesn't
+	 * currently handle islands
+	 *  
+	 * @param target The target to be subtracted from
+	 * @param missing The shape to subtract 
+	 * @return The newly created shapes
+	 */
+	public Shape[] subtract(Shape target, Shape missing) {	
+		target = target.transform(new Transform());
+		missing = missing.transform(new Transform());
+
+		int count = 0;
+		for (int i=0;i MAX_POINTS) {
+				break;
+			}
+			
+			// add the current point to the result shape
+			poly.addPoint(px,py);
+			if (listener != null) {
+				listener.pointUsed(px,py);
+			}
+
+			// if the line between the current point and the next one intersect the
+			// other shape work out where on the other shape and start traversing it's 
+			// path instead
+			Line line = getLine(current, px, py, rationalPoint(current, point+dir));
+			HitResult hit = intersect(other, line);
+			
+			if (hit != null) {
+				Line hitLine = hit.line;
+				Vector2f pt = hit.pt;
+				px = pt.x;
+				py = pt.y;
+				
+				if (listener != null) {
+					listener.pointIntersected(px,py);
+				}
+				
+				if (other.hasVertex(px, py)) {
+					point = other.indexOf(pt.x,pt.y);
+					dir = 1;
+					px = pt.x;
+					py = pt.y;
+					
+					Shape temp = current;
+					current = other;
+					other = temp;
+					continue;
+				}
+				
+				float dx = hitLine.getDX() / hitLine.length();
+				float dy = hitLine.getDY() / hitLine.length();
+				dx *= EDGE_SCALE;
+				dy *= EDGE_SCALE;
+				
+				if (current.contains(pt.x + dx, pt.y + dy)) {
+					// the point is the next one, we need to take the first and traverse
+					// the path backwards
+					if (subtract) {
+						if (current == missing) {
+							point = hit.p2;
+							dir = -1;
+						} else {
+							point = hit.p1;
+							dir = 1;
+						}
+					} else {
+						if (current == target) {
+							point = hit.p2;
+							dir = -1;
+						} else {
+							point = hit.p2;
+							dir = -1;
+						}
+					}
+					
+					// swap the shapes over, we'll traverse the other one 
+					Shape temp = current;
+					current = other;
+					other = temp;
+				} else if (current.contains(pt.x - dx, pt.y - dy)) {
+					if (subtract) {
+						if (current == target) {
+							point = hit.p2;
+							dir = -1;
+						} else {
+							point = hit.p1;
+							dir = 1;
+						}
+					} else {
+						if (current == missing) {
+							point = hit.p1;
+							dir = 1;
+						} else {
+							point = hit.p1;
+							dir = 1;
+						}
+					}
+					
+					// swap the shapes over, we'll traverse the other one 
+					Shape temp = current;
+					current = other;
+					other = temp;
+				} else {
+					// give up
+					if (subtract) {
+						break;
+					} else {
+						point = hit.p1;
+						dir = 1;
+						Shape temp = current;
+						current = other;
+						other = temp;
+						
+						point = rationalPoint(current, point+dir);
+						px = current.getPoint(point)[0];
+						py = current.getPoint(point)[1];
+					}
+				}
+			} else {
+				// otherwise just move to the next point in the current shape
+				point = rationalPoint(current, point+dir);
+				px = current.getPoint(point)[0];
+				py = current.getPoint(point)[1];
+			}
+		}
+
+		poly.addPoint(px,py);
+		if (listener != null) {
+			listener.pointUsed(px,py);
+		}
+		
+		return poly;
+	}
+	
+	/**
+	 * Intersect a line with a shape
+	 * 
+	 * @param shape The shape to compare
+	 * @param line The line to intersect against the shape
+	 * @return The result describing the intersection or null if none
+	 */
+	public HitResult intersect(Shape shape, Line line) {
+		float distance = Float.MAX_VALUE;
+		HitResult hit = null;
+		
+		for (int i=0;i EPSILON)) {
+					hit = new HitResult();
+					hit.pt = pt;
+					hit.line = local;
+					hit.p1 = i;
+					hit.p2 = next;
+					distance = newDis;
+				}
+			}
+		}
+		
+		return hit;
+	}
+	
+	/**
+	 * Rationalise a point in terms of a given shape
+	 * 
+	 * @param shape The shape 
+	 * @param p The index of the point
+	 * @return The index that is rational for the shape
+	 */
+	public static int rationalPoint(Shape shape, int p) {
+		while (p < 0) {
+			p += shape.getPointCount();
+		}
+		while (p >= shape.getPointCount()) {
+			p -=  shape.getPointCount();
+		}
+		
+		return p;
+	}
+	
+	/**
+	 * Get a line between two points in a shape
+	 * 
+	 * @param shape The shape
+	 * @param s The index of the start point
+	 * @param e The index of the end point
+	 * @return The line between the two points
+	 */
+	public Line getLine(Shape shape, int s, int e) {
+		float[] start = shape.getPoint(s);
+		float[] end = shape.getPoint(e);
+		
+		Line line = new Line(start[0],start[1],end[0],end[1]);
+		return line;
+	}
+
+	/**
+	 * Get a line between two points in a shape
+	 * 
+	 * @param shape The shape
+	 * @param sx The x coordinate of the start point
+	 * @param sy The y coordinate of the start point
+	 * @param e The index of the end point
+	 * @return The line between the two points
+	 */
+	public Line getLine(Shape shape, float sx, float sy, int e) {
+		float[] end = shape.getPoint(e);
+		
+		Line line = new Line(sx,sy,end[0],end[1]);
+		return line;
+	}
+	
+	/**
+	 * A lightweigtht description of a intersection between a shape and 
+	 * line.
+	 */
+	public class HitResult {
+		/** The line on the target shape that intersected */
+		public Line line;
+		/** The index of the first point on the target shape that forms the line */
+		public int p1;
+		/** The index of the second point on the target shape that forms the line */
+		public int p2;
+		/** The position of the intersection */
+		public Vector2f pt;
+	}
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/GeomUtilListener.java b/lib/slick-source/org/newdawn/slick/geom/GeomUtilListener.java
new file mode 100644
index 000000000..80c1d179b
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/GeomUtilListener.java
@@ -0,0 +1,32 @@
+package org.newdawn.slick.geom;
+
+/**
+ * Debug listener for notifications assocaited with geometry utilities
+ * 
+ * @author kevin
+ */
+public interface GeomUtilListener {
+	/**
+	 * Notification that a point was excluded from geometry 
+	 * 
+	 * @param x The x coordinate of the point
+	 * @param y The y coordinate of the point
+	 */
+	public void pointExcluded(float x, float y);
+
+	/**
+	 * Notification that a point was intersected between two geometries 
+	 * 
+	 * @param x The x coordinate of the point
+	 * @param y The y coordinate of the point
+	 */
+	public void pointIntersected(float x, float y);
+
+	/**
+	 * Notification that a point was used to build a new geometry
+	 * 
+	 * @param x The x coordinate of the point
+	 * @param y The y coordinate of the point
+	 */
+	public void pointUsed(float x, float y);
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/Line.java b/lib/slick-source/org/newdawn/slick/geom/Line.java
new file mode 100644
index 000000000..bb6a0c559
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/Line.java
@@ -0,0 +1,482 @@
+package org.newdawn.slick.geom;
+
+/**
+ * Implemenation of a bunch of maths functions to do with lines. Note that lines
+ * can't be used as dynamic shapes right now - also collision with the end of a
+ * line is undefined.
+ * 
+ * @author Kevin Glass
+ */
+public class Line extends Shape {
+	/** The start point of the line */
+	private Vector2f start;
+	/** The end point of the line */
+	private Vector2f end;
+	/** The vector between the two points */
+	private Vector2f vec;
+	/** The length of the line squared */
+	private float lenSquared;
+
+	/** Temporary storage - declared globally to reduce GC */
+	private Vector2f loc = new Vector2f(0, 0);
+	/** Temporary storage - declared globally to reduce GC */
+	private Vector2f v = new Vector2f(0, 0);
+	/** Temporary storage - declared globally to reduce GC */
+	private Vector2f v2 = new Vector2f(0, 0);
+	/** Temporary storage - declared globally to reduce GC */
+	private Vector2f proj = new Vector2f(0, 0);
+
+	/** Temporary storage - declared globally to reduce GC */
+	private Vector2f closest = new Vector2f(0, 0);
+	/** Temporary storage - declared globally to reduce GC */
+	private Vector2f other = new Vector2f(0, 0);
+
+	/** True if this line blocks on the outer edge */
+	private boolean outerEdge = true;
+	/** True if this line blocks on the inner edge */
+	private boolean innerEdge = true;
+
+	/**
+	 * Create a new line based on the origin and a single point
+	 * 
+	 * @param x
+	 *            The end point of the line
+	 * @param y
+	 *            The end point of the line
+	 * @param inner
+	 *            True if this line blocks on it's inner edge
+	 * @param outer
+	 *            True if this line blocks on it's outer edge
+	 */
+	public Line(float x, float y, boolean inner, boolean outer) {
+		this(0, 0, x, y);
+	}
+
+	/**
+	 * Create a new line based on the origin and a single point
+	 * 
+	 * @param x
+	 *            The end point of the line
+	 * @param y
+	 *            The end point of the line
+	 */
+	public Line(float x, float y) {
+		this(x, y, true, true);
+	}
+
+	/**
+	 * Create a new line based on two points
+	 * 
+	 * @param x1
+	 *            The x coordinate of the start point
+	 * @param y1
+	 *            The y coordinate of the start point
+	 * @param x2
+	 *            The x coordinate of the end point
+	 * @param y2
+	 *            The y coordinate of the end point
+	 */
+	public Line(float x1, float y1, float x2, float y2) {
+		this(new Vector2f(x1, y1), new Vector2f(x2, y2));
+	}
+
+	/**
+	 * Create a line with relative second point
+	 * 
+	 * @param x1
+	 *            The x coordinate of the start point
+	 * @param y1
+	 *            The y coordinate of the start point
+	 * @param dx
+	 *            The x change to get to the second point
+	 * @param dy
+	 *            The y change to get to the second point
+	 * @param dummy
+	 *            A dummy value
+	 */
+	public Line(float x1, float y1, float dx, float dy, boolean dummy) {
+		this(new Vector2f(x1, y1), new Vector2f(x1 + dx, y1 + dy));
+	}
+
+	/**
+	 * Create a new line based on two points
+	 * 
+	 * @param start
+	 *            The start point
+	 * @param end
+	 *            The end point
+	 */
+	public Line(float[] start, float[] end) {
+		super();
+
+		set(start, end);
+	}
+
+	/**
+	 * Create a new line based on two points
+	 * 
+	 * @param start
+	 *            The start point
+	 * @param end
+	 *            The end point
+	 */
+	public Line(Vector2f start, Vector2f end) {
+		super();
+
+		set(start, end);
+	}
+
+	/**
+	 * Configure the line
+	 * 
+	 * @param start
+	 *            The start point of the line
+	 * @param end
+	 *            The end point of the line
+	 */
+	public void set(float[] start, float[] end) {
+		set(start[0], start[1], end[0], end[1]);
+	}
+
+	/**
+	 * Get the start point of the line
+	 * 
+	 * @return The start point of the line
+	 */
+	public Vector2f getStart() {
+		return start;
+	}
+
+	/**
+	 * Get the end point of the line
+	 * 
+	 * @return The end point of the line
+	 */
+	public Vector2f getEnd() {
+		return end;
+	}
+
+	/**
+	 * Find the length of the line
+	 * 
+	 * @return The the length of the line
+	 */
+	public float length() {
+		return vec.length();
+	}
+
+	/**
+	 * Find the length of the line squared (cheaper and good for comparisons)
+	 * 
+	 * @return The length of the line squared
+	 */
+	public float lengthSquared() {
+		return vec.lengthSquared();
+	}
+
+	/**
+	 * Configure the line
+	 * 
+	 * @param start
+	 *            The start point of the line
+	 * @param end
+	 *            The end point of the line
+	 */
+	public void set(Vector2f start, Vector2f end) {
+		super.pointsDirty = true;
+		if (this.start == null) {
+			this.start = new Vector2f();
+		}
+		this.start.set(start);
+
+		if (this.end == null) {
+			this.end = new Vector2f();
+		}
+		this.end.set(end);
+
+		vec = new Vector2f(end);
+		vec.sub(start);
+
+		lenSquared = vec.lengthSquared();
+	}
+
+	/**
+	 * Configure the line without garbage
+	 * 
+	 * @param sx
+	 *            The x coordinate of the start
+	 * @param sy
+	 *            The y coordinate of the start
+	 * @param ex
+	 *            The x coordiante of the end
+	 * @param ey
+	 *            The y coordinate of the end
+	 */
+	public void set(float sx, float sy, float ex, float ey) {
+		super.pointsDirty = true;
+		start.set(sx, sy);
+		end.set(ex, ey);
+		float dx = (ex - sx);
+		float dy = (ey - sy);
+		vec.set(dx,dy);
+		
+		lenSquared = (dx * dx) + (dy * dy);
+	}
+
+	/**
+	 * Get the x direction of this line
+	 * 
+	 * @return The x direction of this line
+	 */
+	public float getDX() {
+		return end.getX() - start.getX();
+	}
+
+	/**
+	 * Get the y direction of this line
+	 * 
+	 * @return The y direction of this line
+	 */
+	public float getDY() {
+		return end.getY() - start.getY();
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Shape#getX()
+	 */
+	public float getX() {
+		return getX1();
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Shape#getY()
+	 */
+	public float getY() {
+		return getY1();
+	}
+
+	/**
+	 * Get the x coordinate of the start point
+	 * 
+	 * @return The x coordinate of the start point
+	 */
+	public float getX1() {
+		return start.getX();
+	}
+
+	/**
+	 * Get the y coordinate of the start point
+	 * 
+	 * @return The y coordinate of the start point
+	 */
+	public float getY1() {
+		return start.getY();
+	}
+
+	/**
+	 * Get the x coordinate of the end point
+	 * 
+	 * @return The x coordinate of the end point
+	 */
+	public float getX2() {
+		return end.getX();
+	}
+
+	/**
+	 * Get the y coordinate of the end point
+	 * 
+	 * @return The y coordinate of the end point
+	 */
+	public float getY2() {
+		return end.getY();
+	}
+
+	/**
+	 * Get the shortest distance from a point to this line
+	 * 
+	 * @param point
+	 *            The point from which we want the distance
+	 * @return The distance from the line to the point
+	 */
+	public float distance(Vector2f point) {
+		return (float) Math.sqrt(distanceSquared(point));
+	}
+
+	/**
+	 * Check if the given point is on the line
+	 * 
+	 * @param point
+	 *            The point to check
+	 * @return True if the point is on this line
+	 */
+	public boolean on(Vector2f point) {
+		getClosestPoint(point, closest);
+
+		return point.equals(closest);
+	}
+
+	/**
+	 * Get the shortest distance squared from a point to this line
+	 * 
+	 * @param point
+	 *            The point from which we want the distance
+	 * @return The distance squared from the line to the point
+	 */
+	public float distanceSquared(Vector2f point) {
+		getClosestPoint(point, closest);
+		closest.sub(point);
+
+		float result = closest.lengthSquared();
+
+		return result;
+	}
+
+	/**
+	 * Get the closest point on the line to a given point
+	 * 
+	 * @param point
+	 *            The point which we want to project
+	 * @param result
+	 *            The point on the line closest to the given point
+	 */
+	public void getClosestPoint(Vector2f point, Vector2f result) {
+		loc.set(point);
+		loc.sub(start);
+
+		float projDistance = vec.dot(loc);
+
+		projDistance /= vec.lengthSquared();
+
+		if (projDistance < 0) {
+			result.set(start);
+			return;
+		}
+		if (projDistance > 1) {
+			result.set(end);
+			return;
+		}
+
+		result.x = start.getX() + projDistance * vec.getX();
+		result.y = start.getY() + projDistance * vec.getY();
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		return "[Line " + start + "," + end + "]";
+	}
+
+	/**
+	 * Intersect this line with another
+	 * 
+	 * @param other
+	 *            The other line we should intersect with
+	 * @return The intersection point or null if the lines are parallel
+	 */
+	public Vector2f intersect(Line other) {
+		return intersect(other, false);
+	}
+
+	/**
+	 * Intersect this line with another
+	 * 
+	 * @param other
+	 *            The other line we should intersect with
+	 * @param limit
+	 *            True if the collision is limited to the extent of the lines
+	 * @return The intersection point or null if the lines don't intersect
+	 */
+	public Vector2f intersect(Line other, boolean limit) {
+		Vector2f temp = new Vector2f();
+
+		if (!intersect(other, limit, temp)) {
+			return null;
+		}
+
+		return temp;
+	}
+
+	/**
+	 * Intersect this line with another
+	 * 
+	 * @param other
+	 *            The other line we should intersect with
+	 * @param limit
+	 *            True if the collision is limited to the extent of the lines
+	 * @param result
+	 *            The resulting intersection point if any
+	 * @return True if the lines intersect
+	 */
+	public boolean intersect(Line other, boolean limit, Vector2f result) {
+		float dx1 = end.getX() - start.getX();
+		float dx2 = other.end.getX() - other.start.getX();
+		float dy1 = end.getY() - start.getY();
+		float dy2 = other.end.getY() - other.start.getY();
+		float denom = (dy2 * dx1) - (dx2 * dy1);
+
+		if (denom == 0) {
+			return false;
+		}
+
+		float ua = (dx2 * (start.getY() - other.start.getY()))
+				- (dy2 * (start.getX() - other.start.getX()));
+		ua /= denom;
+		float ub = (dx1 * (start.getY() - other.start.getY()))
+				- (dy1 * (start.getX() - other.start.getX()));
+		ub /= denom;
+
+		if ((limit) && ((ua < 0) || (ua > 1) || (ub < 0) || (ub > 1))) {
+			return false;
+		}
+
+		float u = ua;
+
+		float ix = start.getX() + (u * (end.getX() - start.getX()));
+		float iy = start.getY() + (u * (end.getY() - start.getY()));
+
+		result.set(ix, iy);
+		return true;
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Shape#createPoints()
+	 */
+	protected void createPoints() {
+		points = new float[4];
+		points[0] = getX1();
+		points[1] = getY1();
+		points[2] = getX2();
+		points[3] = getY2();
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
+	 */
+	public Shape transform(Transform transform) {
+		float[] temp = new float[4];
+		createPoints();
+		transform.transform(points, 0, temp, 0, 2);
+
+		return new Line(temp[0], temp[1], temp[2], temp[3]);
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Shape#closed()
+	 */
+	public boolean closed() {
+		return false;
+	}
+	
+	/**
+	 * @see org.newdawn.slick.geom.Shape#intersects(org.newdawn.slick.geom.Shape)
+	 */
+	public boolean intersects(Shape shape) 
+    { 
+        if (shape instanceof Circle) 
+        { 
+            return shape.intersects(this); 
+        } 
+        return super.intersects(shape); 
+    }
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/MannTriangulator.java b/lib/slick-source/org/newdawn/slick/geom/MannTriangulator.java
new file mode 100644
index 000000000..ce4fc8259
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/MannTriangulator.java
@@ -0,0 +1,617 @@
+/*
+ * Triangulator0.java
+ *
+ * (BSD license)
+ *
+ * Copyright (c) 2005, Matthias Mann (www.matthiasmann.de)
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ *   * Neither the name of the Matthias Mann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Created on 17. March 2005, 22:19
+ */
+package org.newdawn.slick.geom;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A 2D Triangulator. Graciously made available by the man(n) himself.
+ * 
+ * @author Matthias Mann
+ */
+public class MannTriangulator implements Triangulator {
+	/** The allowed error value */
+	private static final double EPSILON = 1e-5;
+	
+	/** The outer countour of the shape */
+	protected PointBag contour;
+	/** The holes defined in the polygon */
+	protected PointBag holes;
+	/** The next available point bag */
+	private PointBag nextFreePointBag;
+	/** The next available point */
+	private Point nextFreePoint;
+	/** The list of triangles created (or rather points in triangles, 3xn) */
+	private List triangles = new ArrayList();
+	
+	/** Creates a new instance of Triangulator0 */
+	public MannTriangulator() {
+		contour = getPointBag();
+	}
+	
+	/**
+	 * @see org.newdawn.slick.geom.Triangulator#addPolyPoint(float, float)
+	 */
+	public void addPolyPoint(float x, float y) {
+    	addPoint(new Vector2f(x,y));
+    }
+
+	/**
+	 * Reset the internal state of the triangulator
+	 */
+	public void reset() {
+		while (holes != null) {
+			holes = freePointBag(holes);
+		}
+
+		contour.clear();
+		holes = null;
+	}
+
+	/**
+	 * Begin adding a hole to the polygon
+	 */
+	public void startHole() {
+		PointBag newHole = getPointBag();
+		newHole.next = holes;
+		holes = newHole;
+	}
+
+	/**
+	 * Add a defined point to the current contour
+	 * 
+	 * @param pt The point to add
+	 */
+	private void addPoint(Vector2f pt) {
+		if (holes == null) {
+			Point p = getPoint(pt);
+			contour.add(p);
+		} else {
+			Point p = getPoint(pt);
+			holes.add(p);
+		}
+	}
+
+	/**
+	 * Triangulate the points given
+	 * 
+	 * @param result The array to fill with the result or use to determine type
+	 * @return The resultng triangles
+	 */
+	private Vector2f[] triangulate(Vector2f[] result) {
+		// Step 1: Compute all angles
+		contour.computeAngles();
+		for (PointBag hole = holes; hole != null; hole = hole.next) {
+			hole.computeAngles();
+		}
+
+		// Step 2: Connect the holes with the contour (build bridges)
+		while (holes != null) {
+			Point pHole = holes.first;
+			outer: do {
+				if (pHole.angle <= 0) {
+					Point pContour = contour.first;
+					do {
+						inner: if (pHole.isInfront(pContour)
+								&& pContour.isInfront(pHole)) {
+							if (!contour.doesIntersectSegment(pHole.pt,
+									pContour.pt)) {
+								PointBag hole = holes;
+								do {
+									if (hole.doesIntersectSegment(pHole.pt,
+											pContour.pt)) {
+										break inner;
+									}
+								} while ((hole = hole.next) != null);
+
+								Point newPtContour = getPoint(pContour.pt);
+								pContour.insertAfter(newPtContour);
+
+								Point newPtHole = getPoint(pHole.pt);
+								pHole.insertBefore(newPtHole);
+
+								pContour.next = pHole;
+								pHole.prev = pContour;
+
+								newPtHole.next = newPtContour;
+								newPtContour.prev = newPtHole;
+
+								pContour.computeAngle();
+								pHole.computeAngle();
+								newPtContour.computeAngle();
+								newPtHole.computeAngle();
+
+								// detach the points from the hole
+								holes.first = null;
+								break outer;
+							}
+						}
+					} while ((pContour = pContour.next) != contour.first);
+				}
+			} while ((pHole = pHole.next) != holes.first);
+
+			// free the hole
+			holes = freePointBag(holes);
+		}
+
+		// Step 3: Make sure we have enough space for the result
+		int numTriangles = contour.countPoints() - 2;
+		int neededSpace = numTriangles * 3 + 1; // for the null
+		if (result.length < neededSpace) {
+			result = (Vector2f[]) Array.newInstance(result.getClass()
+					.getComponentType(), neededSpace);
+		}
+
+		// Step 4: Extract the triangles
+		int idx = 0;
+		for (;;) {
+			Point pContour = contour.first;
+
+			if (pContour == null) {
+				break;
+			}
+			// Are there 2 or less points left ?
+			if (pContour.next == pContour.prev) {
+				break;
+			}
+
+			outer: do {
+				if (pContour.angle > 0) {
+					Point prev = pContour.prev;
+					Point next = pContour.next;
+
+					if (next.next == prev || prev.isInfront(next) && next.isInfront(prev)) {
+						if (!contour.doesIntersectSegment(prev.pt, next.pt)) {
+							result[idx++] = pContour.pt;
+							result[idx++] = next.pt;
+							result[idx++] = prev.pt;
+							break outer;
+						}
+					}
+				}
+			} while ((pContour = pContour.next) != contour.first);
+			
+			// remove the point - we do it in every case to prevent endless loop
+			Point prev = pContour.prev;
+			Point next = pContour.next;
+
+			contour.first = prev;
+			pContour.unlink();
+			freePoint(pContour);
+
+			next.computeAngle();
+			prev.computeAngle();
+		}
+
+		// Step 5: Append a null (see Collection.toArray)
+		result[idx] = null;
+
+		// Step 6: Free memory
+		contour.clear();
+
+		// Finished !
+		return result;
+	}
+
+	/**
+	 * Create a new point bag (or recycle an old one)
+	 * 
+	 * @return The new point bag
+	 */
+	private PointBag getPointBag() {
+		PointBag pb = nextFreePointBag;
+		if (pb != null) {
+			nextFreePointBag = pb.next;
+			pb.next = null;
+			return pb;
+		}
+		return new PointBag();
+	}
+
+	/**
+	 * Release a pooled bag
+	 * 
+	 * @param pb The bag to release
+	 * @return The next available bag
+	 */
+	private PointBag freePointBag(PointBag pb) {
+		PointBag next = pb.next;
+		pb.clear();
+		pb.next = nextFreePointBag;
+		nextFreePointBag = pb;
+		return next;
+	}
+
+	/**
+	 * Create or reuse a point
+	 * 
+	 * @param pt The point data to set
+	 * @return The new point
+	 */
+	private Point getPoint(Vector2f pt) {
+		Point p = nextFreePoint;
+		if (p != null) {
+			nextFreePoint = p.next;
+			// initialize new point to safe values
+			p.next = null;
+			p.prev = null;
+			p.pt = pt;
+			return p;
+		}
+		return new Point(pt);
+	}
+
+	/**
+	 * Release a point into the pool
+	 * 
+	 * @param p The point to release
+	 */
+	private void freePoint(Point p) {
+		p.next = nextFreePoint;
+		nextFreePoint = p;
+	}
+
+	/**
+	 * Release all points
+	 * 
+	 * @param head The head of the points bag
+	 */
+	private void freePoints(Point head) {
+		head.prev.next = nextFreePoint;
+		head.prev = null;
+		nextFreePoint = head;
+	}
+
+	/**
+	 * A single point being considered during triangulation
+	 *
+	 * @author Matthias Mann
+	 */
+	private static class Point implements Serializable {
+		/** The location of the point */
+		protected Vector2f pt;
+		/** The previous point in the contour */
+		protected Point prev;
+		/** The next point in the contour */
+		protected Point next;
+		/** The x component of the of the normal */
+		protected double nx;
+		/** The y component of the of the normal */
+		protected double ny;
+		/** The angle at this point in the path */
+		protected double angle;
+		/** The distance of this point from */
+		protected double dist;
+
+		/**
+		 * Create a new point
+		 * 
+		 * @param pt The points location
+		 */
+		public Point(Vector2f pt) {
+			this.pt = pt;
+		}
+
+		/**
+		 * Remove this point from it's contour
+		 */
+		public void unlink() {
+			prev.next = next;
+			next.prev = prev;
+			next = null;
+			prev = null;
+		}
+
+		/**
+		 * Insert a point before this one (see LinkedList)
+		 * 
+		 * @param p The point to insert
+		 */
+		public void insertBefore(Point p) {
+			prev.next = p;
+			p.prev = prev;
+			p.next = this;
+			prev = p;
+		}
+
+		/**
+		 * Insert a point after this one (see LinkedList)
+		 * 
+		 * @param p The point to insert
+		 */
+		public void insertAfter(Point p) {
+			next.prev = p;
+			p.prev = this;
+			p.next = next;
+			next = p;
+		}
+
+		/**
+		 * Java 5 hypot method
+		 * 
+		 * @param x The x component
+		 * @param y The y component
+		 * @return The hypotenuse
+		 */
+		private double hypot(double x, double y) {
+			return Math.sqrt(x*x + y*y);
+		}
+		
+		/**
+		 * Compute the angle at this point
+		 */
+		public void computeAngle() {
+			if (prev.pt.equals(pt)) {
+				pt.x += 0.01f;
+			}
+			double dx1 = pt.x - prev.pt.x;
+			double dy1 = pt.y - prev.pt.y;
+			double len1 = hypot(dx1, dy1);
+			dx1 /= len1;
+			dy1 /= len1;
+
+			if (next.pt.equals(pt)) {
+				pt.y += 0.01f;
+			}
+			double dx2 = next.pt.x - pt.x;
+			double dy2 = next.pt.y - pt.y;
+			double len2 = hypot(dx2, dy2);
+			dx2 /= len2;
+			dy2 /= len2;
+
+			double nx1 = -dy1;
+			double ny1 = dx1;
+
+			nx = (nx1 - dy2) * 0.5;
+			ny = (ny1 + dx2) * 0.5;
+
+			if (nx * nx + ny * ny < EPSILON) {
+				nx = dx1;
+				ny = dy2;
+				angle = 1; // TODO: nx1,ny1 and nx2,ny2 facing ?
+				if (dx1 * dx2 + dy1 * dy2 > 0) {
+					nx = -dx1;
+					ny = -dy1;
+				}
+			} else {
+				angle = nx * dx2 + ny * dy2;
+			}
+		}
+
+		/**
+		 * Get the angle of this point to another
+		 * 
+		 * @param p The other point
+		 * @return The angle between this point and another
+		 */
+		public double getAngle(Point p) {
+			double dx = p.pt.x - pt.x;
+			double dy = p.pt.y - pt.y;
+			double dlen = hypot(dx, dy);
+
+			return (nx * dx + ny * dy) / dlen;
+		}
+
+		/**
+		 * Check if this point is convave
+		 * 
+		 * @return True if this point remains concave
+		 */
+		public boolean isConcave() {
+			return angle < 0;
+		}
+
+		/**
+		 * Check if this point is infront of another
+		 * 
+		 * @param dx The other x
+		 * @param dy The other y
+		 * @return True if this point is infront (in the contour)
+		 */
+		public boolean isInfront(double dx, double dy) {
+			// no nead to normalize, amplitude does not metter for side
+			// detection
+			boolean sidePrev = ((prev.pt.y - pt.y) * dx + (pt.x - prev.pt.x)
+					* dy) >= 0;
+			boolean sideNext = ((pt.y - next.pt.y) * dx + (next.pt.x - pt.x)
+					* dy) >= 0;
+
+			return (angle < 0) ? (sidePrev | sideNext) : (sidePrev & sideNext);
+		}
+
+		/**
+		 * Check if this point is infront of another
+		 * 
+		 * @param p The other point
+		 * @return True if this point is infront (in the contour)
+		 */
+		public boolean isInfront(Point p) {
+			return isInfront(p.pt.x - pt.x, p.pt.y - pt.y);
+		}
+	}
+
+	/**
+	 * A bag/pool of point objects
+	 *
+	 * @author kevin
+	 */
+	protected class PointBag implements Serializable {
+		/** The first point in the bag - head of the list */
+		protected Point first;
+		/** The next bag in the list of bags */
+		protected PointBag next;
+
+		/**
+		 * Clear all the points from this bag
+		 */
+		public void clear() {
+			if (first != null) {
+				freePoints(first);
+				first = null;
+			}
+		}
+
+		/**
+		 * Add a point to the bag
+		 * 
+		 * @param p The point to add
+		 */
+		public void add(Point p) {
+			if (first != null) {
+				first.insertBefore(p);
+			} else {
+				first = p;
+				p.next = p;
+				p.prev = p;
+			}
+		}
+
+		/**
+		 * Compute the angles for the points in this bag
+		 */
+		public void computeAngles() {
+			if (first == null) {
+				return;
+			}
+
+			Point p = first;
+			do {
+				p.computeAngle();
+			} while ((p = p.next) != first);
+		}
+
+		/**
+		 * Check if the points in this bag form a path intersecting
+		 * with the specified path
+		 * 
+		 * @param v1 The start point of the segment
+		 * @param v2 The end point of the segment
+		 * @return True if points in this contour intersect with the segment
+		 */
+		public boolean doesIntersectSegment(Vector2f v1, Vector2f v2) {
+			double dxA = v2.x - v1.x;
+			double dyA = v2.y - v1.y;
+
+			for (Point p = first;;) {
+				Point n = p.next;
+				if (p.pt != v1 && n.pt != v1 && p.pt != v2 && n.pt != v2) {
+					double dxB = n.pt.x - p.pt.x;
+					double dyB = n.pt.y - p.pt.y;
+					double d = (dxA * dyB) - (dyA * dxB);
+
+					if (Math.abs(d) > EPSILON) {
+						double tmp1 = p.pt.x - v1.x;
+						double tmp2 = p.pt.y - v1.y;
+						double tA = (dyB * tmp1 - dxB * tmp2) / d;
+						double tB = (dyA * tmp1 - dxA * tmp2) / d;
+
+						if (tA >= 0 && tA <= 1 && tB >= 0 && tB <= 1) {
+							return true;
+						}
+					}
+				}
+
+				if (n == first) {
+					return false;
+				}
+				p = n;
+			}
+		}
+
+		/**
+		 * Get the number of points in the bag 
+		 * 
+		 * @return The number of points in the bag
+		 */
+		public int countPoints() {
+			if (first == null) {
+				return 0;
+			}
+
+			int count = 0;
+			Point p = first;
+			do {
+				++count;
+			} while ((p = p.next) != first);
+			return count;
+		}
+		
+		/**
+		 * Check if the point provided was contained
+		 * 
+		 * @param point The point provided
+		 * @return True if it's in the bag
+		 */
+		public boolean contains(Vector2f point) {
+			if (first == null) {
+				return false;
+			}
+			
+			if (first.prev.pt.equals(point)) {
+				return true;
+			}
+			if (first.pt.equals(point)) {
+				return true;
+			}
+			return false;
+		}
+	}
+
+	public boolean triangulate() {
+		Vector2f[] temp = triangulate(new Vector2f[0]);
+
+		for (int i = 0; i < temp.length; i++) {
+			if (temp[i] == null) {
+				break;
+			} else {
+				triangles.add(temp[i]);
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
+	 */
+	public int getTriangleCount() {
+		return triangles.size() / 3;
+	}
+
+	/**
+	 * @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
+	 */
+	public float[] getTrianglePoint(int tri, int i) {
+		Vector2f pt = (Vector2f) triangles.get((tri * 3) + i);
+
+		return new float[] { pt.x, pt.y };
+	}
+
+}
diff --git a/lib/slick-source/org/newdawn/slick/geom/MorphShape.java b/lib/slick-source/org/newdawn/slick/geom/MorphShape.java
new file mode 100644
index 000000000..5485c2fbc
--- /dev/null
+++ b/lib/slick-source/org/newdawn/slick/geom/MorphShape.java
@@ -0,0 +1,193 @@
+package org.newdawn.slick.geom;
+
+import java.util.ArrayList;
+
+/**
+ * A shape that morphs between a set of other shapes
+ *  
+ * @author kevin
+ */
+public class MorphShape extends Shape {
+	/** The shapes to morph between */
+	private ArrayList shapes = new ArrayList();
+	/** The offset between the shapes */
+	private float offset;
+	
+	/** The current shape */
+	private Shape current;
+	/** The next shape */
+	private Shape next;
+	
+	/**
+	 * Create a new mighty morphin shape
+	 * 
+	 * @param base The base shape we're starting the morph from
+	 */
+	public MorphShape(Shape base) {
+		shapes.add(base);
+		float[] copy = base.points;
+		this.points = new float[copy.length];
+		
+		current = base;
+		next = base;
+	}
+
+	/**
+	 * Add a subsequent shape that we should morph too in order
+	 * 
+	 * @param shape The new shape that forms part of the morphing shape
+	 */
+	public void addShape(Shape shape) {
+		if (shape.points.length != points.length) {
+			throw new RuntimeException("Attempt to morph between two shapes with different vertex counts");
+		}
+		
+		Shape prev = (Shape) shapes.get(shapes.size()-1);
+		if (equalShapes(prev, shape)) {
+			shapes.add(prev);
+		} else {
+			shapes.add(shape);
+		}
+		
+		if (shapes.size() == 2) {
+			next = (Shape) shapes.get(1);
+		}
+	}
+	
+	/**
+	 * Check if the shape's points are all equal
+	 * 
+	 * @param a The first shape to compare
+ 	 * @param b The second shape to compare
+	 * @return True if the shapes are equal
+	 */
+	private boolean equalShapes(Shape a, Shape b) {
+		a.checkPoints();
+		b.checkPoints();
+		
+		for (int i=0;i 1) {
+			int index = shapes.indexOf(next);
+			if (index < 1) {
+				index = 0;
+			}
+			
+			int nframe = rational(index+1);
+			setFrame(index, nframe, offset);
+			offset -= 1;
+		} else {
+			pointsDirty = true;
+		}
+	}
+	
+	/**
+	 * Set the current frame
+	 * 
+	 * @param current The current frame
+	 */
+	public void setExternalFrame(Shape current) {
+		this.current = current;
+		next = (Shape) shapes.get(0);
+		offset = 0;
+	}
+	
+	/**
+	 * Get an index that is rational, i.e. fits inside this set of shapes
+	 * 
+	 * @param n The index to rationalize
+	 * @return The index rationalized
+	 */
+	private int rational(int n) {
+		while (n >= shapes.size()) {
+			n -= shapes.size();
+		}
+		while (n < 0) {
+			n += shapes.size();
+		}
+		
+		return n;
+	}
+	
+	/**
+	 * Set the frame to be represented 
+	 * 
+	 * @param a The index of the first shape
+	 * @param b The index of the second shape
+	 * @param offset The offset between the two shapes to represent
+	 */
+	private void setFrame(int a, int b, float offset) {
+		current = (Shape) shapes.get(a);
+		next = (Shape) shapes.get(b);
+		this.offset = offset;
+		pointsDirty = true;
+	}
+	
+	/**
+	 * @see MorphShape#createPoints()
+	 */
+	protected void createPoints() {
+		if (current == next) {
+			System.arraycopy(current.points,0,points,0,points.length);
+			return;
+		}
+		
+		float[] apoints = current.points;
+		float[] bpoints = next.points;
+		
+		for (int i=0;i (k = findEdge(i, j)))
+        {
+            throw new InternalException("Attempt to delete unknown edge");
+        } 
+        else
+        {
+            edges[k] = edges[--numEdges];
+            return;
+        }
+    }
+
+    /**
+     * Mark an edge as either a suspect or not
+     * 
+     * @param i The index of the first vert
+     * @param j The index of the second vert
+     * @param flag True if the edge is a suspect
+     * @throws InternalException Indicates the edge didn't exist
+     */
+    void markSuspect(int i, int j, boolean flag) throws InternalException
+    {
+        int k;
+        if(0 > (k = findEdge(i, j)))
+        {
+            throw new InternalException("Attempt to mark unknown edge");
+        } else
+        {
+            edges[k].suspect = flag;
+            return;
+        }
+    }
+
+    /**
+     * Choose the suspect to become part of the triangle
+     * 
+     * @return The edge selected
+     */
+    private Edge chooseSuspect()
+    {
+        for(int i = 0; i < numEdges; i++)
+        {
+            Edge edge = edges[i];
+            if(edge.suspect)
+            {
+                edge.suspect = false;
+                if(edge.t0 >= 0 && edge.t1 >= 0)
+                    return edge;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Factor rho.
+     * 
+     * @param f Factor 1
+     * @param f1 Factor 2
+     * @param f2 Factor 3
+     * @param f3 Factor 4
+     * @param f4 Factor 5
+     * @param f5 Factor 6
+     * @return The computation of rho
+     */
+    private static float rho(float f, float f1, float f2, float f3, float f4, float f5)
+    {
+        float f6 = f4 - f2;
+        float f7 = f5 - f3;
+        float f8 = f - f4;
+        float f9 = f1 - f5;
+        float f18 = f6 * f9 - f7 * f8;
+        if(f18 > 0.0F)
+        {
+            if(f18 < 1E-006F)
+                f18 = 1E-006F;
+            float f12 = f6 * f6;
+            float f13 = f7 * f7;
+            float f14 = f8 * f8;
+            float f15 = f9 * f9;
+            float f10 = f2 - f;
+            float f11 = f3 - f1;
+            float f16 = f10 * f10;
+            float f17 = f11 * f11;
+            return ((f12 + f13) * (f14 + f15) * (f16 + f17)) / (f18 * f18);
+        } else
+        {
+            return -1F;
+        }
+    }
+
+	/**
+	 * Check if the point P is inside the triangle defined by
+	 * the points A,B,C
+	 * 
+	 * @param f Point A x-coordinate
+	 * @param f1 Point A y-coordinate
+	 * @param f2 Point B x-coordinate
+	 * @param f3 Point B y-coordinate
+	 * @param f4 Point C x-coordinate
+	 * @param f5 Point C y-coordinate
+	 * @param f6 Point P x-coordinate
+	 * @param f7 Point P y-coordinate
+	 * @return True if the point specified is within the triangle
+	 */
+    private static boolean insideTriangle(float f, float f1, float f2, float f3, float f4, float f5, float f6, float f7)
+    {
+        float f8 = f4 - f2;
+        float f9 = f5 - f3;
+        float f10 = f - f4;
+        float f11 = f1 - f5;
+        float f12 = f2 - f;
+        float f13 = f3 - f1;
+        float f14 = f6 - f;
+        float f15 = f7 - f1;
+        float f16 = f6 - f2;
+        float f17 = f7 - f3;
+        float f18 = f6 - f4;
+        float f19 = f7 - f5;
+        float f22 = f8 * f17 - f9 * f16;
+        float f20 = f12 * f15 - f13 * f14;
+        float f21 = f10 * f19 - f11 * f18;
+        return f22 >= 0.0D && f21 >= 0.0D && f20 >= 0.0D;
+    }
+
+	/**
+	 * Cut a the contour and add a triangle into V to describe the 
+	 * location of the cut
+	 * 
+	 * @param i The index of the first point
+	 * @param j The index of the second point
+	 * @param k The index of the third point
+	 * @param l ?
+	 * @return True if a triangle was found
+	 */
+    private boolean snip(int i, int j, int k, int l)
+    {
+        float f = pointsX[V[i]];
+        float f1 = pointsY[V[i]];
+        float f2 = pointsX[V[j]];
+        float f3 = pointsY[V[j]];
+        float f4 = pointsX[V[k]];
+        float f5 = pointsY[V[k]];
+        if(1E-006F > (f2 - f) * (f5 - f1) - (f3 - f1) * (f4 - f))
+            return false;
+        for(int i1 = 0; i1 < l; i1++)
+            if(i1 != i && i1 != j && i1 != k)
+            {
+                float f6 = pointsX[V[i1]];
+                float f7 = pointsY[V[i1]];
+                if(insideTriangle(f, f1, f2, f3, f4, f5, f6, f7))
+                    return false;
+            }
+
+        return true;
+    }
+
+    /**
+     * Get the area defined by the points
+     * 
+     * @return The area defined by the points
+     */
+    private float area()
+    {
+        float f = 0.0F;
+        int i = numPoints - 1;
+        for(int j = 0; j < numPoints;)
+        {
+            f += pointsX[i] * pointsY[j] - pointsY[i] * pointsX[j];
+            i = j++;
+        }
+
+        return f * 0.5F;
+    }
+
+    /**
+     * Perform simple triangulation
+     * 
+     * @throws InternalException Indicates a polygon that can't be triangulated
+     */
+    public void basicTriangulation() throws InternalException
+    {
+        int i = numPoints;
+        if(i < 3)
+            return;
+        numEdges = 0;
+        numTriangles = 0;
+        V = new int[i];
+        
+        if(0.0D < area())
+        {
+            for(int k = 0; k < i; k++)
+                V[k] = k;
+
+        } else
+        {
+            for(int l = 0; l < i; l++)
+                V[l] = numPoints - 1 - l;
+
+        }
+        int k1 = 2 * i;
+        int i1 = i - 1;
+        while(i > 2) 
+        {
+            if(0 >= k1--) {
+                throw new InternalException("Bad polygon");
+            }
+            
+            int j = i1;
+            if(i <= j)
+                j = 0;
+            i1 = j + 1;
+            if(i <= i1)
+                i1 = 0;
+            int j1 = i1 + 1;
+            if(i <= j1)
+                j1 = 0;
+            if(snip(j, i1, j1, i))
+            {
+                int l1 = V[j];
+                int i2 = V[i1];
+                int j2 = V[j1];
+                if(numTriangles == triangles.length)
+                {
+                    Triangle atriangle[] = new Triangle[triangles.length * 2];
+                    System.arraycopy(triangles, 0, atriangle, 0, numTriangles);
+                    triangles = atriangle;
+                }
+                triangles[numTriangles] = new Triangle(l1, i2, j2);
+                addEdge(l1, i2, numTriangles);
+                addEdge(i2, j2, numTriangles);
+                addEdge(j2, l1, numTriangles);
+                numTriangles++;
+                int k2 = i1;
+                for(int l2 = i1 + 1; l2 < i; l2++)
+                {
+                    V[k2] = V[l2];
+                    k2++;
+                }
+
+                i--;
+                k1 = 2 * i;
+            }
+        }
+        V = null;
+    }
+
+    /**
+     * Optimize the triangulation by applying delauney rules
+     * 
+     * @throws InternalException Indicates an invalid polygon
+     */
+    private void optimize() throws InternalException
+    {
+        do
+        {
+            Edge edge;
+            if ((edge = chooseSuspect()) == null) {
+                break;
+            }
+            int i1 = edge.v0;
+            int k1 = edge.v1;
+            int i = edge.t0;
+            int j = edge.t1;
+            int j1 = -1;
+            int l1 = -1;
+            for (int k = 0; k < 3; k++)
+            {
+                int i2 = triangles[i].v[k];
+                if(i1 == i2 || k1 == i2) {
+                    continue;
+                }
+                l1 = i2;
+                break;
+            }
+
+            for (int l = 0; l < 3; l++)
+            {
+                int j2 = triangles[j].v[l];
+                if(i1 == j2 || k1 == j2) {
+                    continue;
+                }
+                j1 = j2;
+                break;
+            }
+
+            if(-1 == j1 || -1 == l1) {
+                throw new InternalException("can't find quad");
+            }
+            
+            float f = pointsX[i1];
+            float f1 = pointsY[i1];
+            float f2 = pointsX[j1];
+            float f3 = pointsY[j1];
+            float f4 = pointsX[k1];
+            float f5 = pointsY[k1];
+            float f6 = pointsX[l1];
+            float f7 = pointsY[l1];
+            float f8 = rho(f, f1, f2, f3, f4, f5);
+            float f9 = rho(f, f1, f4, f5, f6, f7);
+            float f10 = rho(f2, f3, f4, f5, f6, f7);
+            float f11 = rho(f2, f3, f6, f7, f, f1);
+            if(0.0F > f8 || 0.0F > f9) {
+                throw new InternalException("original triangles backwards");
+            }
+            if(0.0F <= f10 && 0.0F <= f11)
+            {
+                if(f8 > f9) {
+                    f8 = f9;
+                }
+                if(f10 > f11) {
+                    f10 = f11;
+                }
+                if(f8 > f10) {
+                    deleteEdge(i1, k1);
+                    triangles[i].v[0] = j1;
+                    triangles[i].v[1] = k1;
+                    triangles[i].v[2] = l1;
+                    triangles[j].v[0] = j1;
+                    triangles[j].v[1] = l1;
+                    triangles[j].v[2] = i1;
+                    addEdge(j1, k1, i);
+                    addEdge(k1, l1, i);
+                    addEdge(l1, j1, i);
+                    addEdge(l1, i1, j);
+                    addEdge(i1, j1, j);
+                    addEdge(j1, l1, j);
+                    markSuspect(j1, l1, false);
+                }
+            }
+        } while(true);
+    }
+
+    /**
+     * Upate the triangles
+     */
+    public boolean triangulate()
+    {
+        try
+        {
+            basicTriangulation();
+            //optimize();
+            return true;
+        }
+        catch (InternalException e)
+        {
+            numEdges = 0;
+        }
+        return false;
+    }
+
+    /** 
+     * Add a point to the polygon
+     */
+    public void addPolyPoint(float x, float y)
+    {
+    	for (int i=0;i