Przeglądaj źródła

added savegames, levels can now be completed, fixed framerate bug for good computers, added water, collision boxes are now divided into movement and collision boxes, added simpleconfig

Kajetan Johannes Hammerle 7 lat temu
rodzic
commit
4ad207b545

+ 10 - 7
levels/00.map

@@ -7,16 +7,18 @@
 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
 #
-2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1
+2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
 2, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
-2, 1, 1, 2, 2, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1
-2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1
-2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
-2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1
-2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1
-2, 2, 2, 5, 5, 5, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1
+2, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1
+2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 7, 1
+2, 1, 1, 1, 1, 1, 3, 6, 6, 3, 3, 1, 4, 1, 1, 1, 3, 1
+2, 3, 3, 1, 1, 1, 3, 6, 3, 3, 3, 3, 3, 3, 6, 6, 2, 1
+2, 2, 2, 5, 5, 5, 2, 2, 2, 2, 2, 1, 1, 3, 3, 3, 3, 1
 #
 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
@@ -27,3 +29,4 @@
 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1

+ 2 - 0
savegame.txt

@@ -0,0 +1,2 @@
+level.00=true
+level.01=false

+ 35 - 9
src/me/hammerle/supersnuvi/entity/Entity.java

@@ -6,7 +6,6 @@ import javafx.scene.paint.Color;
 import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.input.IKeyHandler;
 import me.hammerle.supersnuvi.rendering.GameRenderer;
-import me.hammerle.supersnuvi.gamelogic.StateRenderer;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
@@ -31,14 +30,15 @@ public class Entity
     private double motionX;
     private double motionY;
     private boolean onGround;
+    private boolean inWater;
     
-    private CollisionBox collisionBox;
-    private double width;
-    private double height;
+    private final CollisionBox collisionBox;
+    private final double width;
+    private final double height;
     
     private Image image;
     
-    private Level level;
+    private final Level level;
     
     public Entity(Level level, double x, double y, double width, double height)
     {
@@ -50,6 +50,7 @@ public class Entity
         this.motionX = 0;
         this.motionY = 0;
         this.onGround = false;
+        this.inWater = false;
         this.collisionBox = new CollisionBox(0, 0, width, height);
         this.width = width;
         this.height = height;
@@ -57,6 +58,11 @@ public class Entity
         image = Utils.getColoredImage(Color.BLACK, (int) width, (int) height);
     }
 
+    public Level getLevel()
+    {
+        return level;
+    }
+    
     //--------------------------------------------------------------------------
     // ticking
     //--------------------------------------------------------------------------
@@ -65,7 +71,7 @@ public class Entity
     {
     }
     
-    public final void tick(long delta) 
+    public final void tick() 
     {
         if(isAffectedByGravity())
         {
@@ -97,7 +103,7 @@ public class Entity
             motionX *= 0.7;
         }
         
-        LinkedList<CollisionBox> list = level.getCollisionBoxesAt(this, getBox());
+        LinkedList<CollisionBox> list = level.getMovementBoxesAt(this, getBox());
         if(!list.isEmpty())
         {
             System.out.println(getBox());
@@ -111,7 +117,7 @@ public class Entity
     }
     
     //--------------------------------------------------------------------------
-    // gravity
+    // gravity, friction
     //--------------------------------------------------------------------------
     
     public final void jump()
@@ -138,6 +144,26 @@ public class Entity
         return onGround;
     }
     
+    public final double getMaxSpeedModifier()
+    {
+        double max = 1;
+        if(inWater)
+        {
+            max *= 0.65;
+        }
+        return max;
+    }
+    
+    public final void setInWater(boolean b)
+    {
+        inWater = b;
+    }
+    
+    public final void resetMovementData()
+    {
+        inWater = false;
+    }
+    
     //--------------------------------------------------------------------------
     // collision stuff
     //--------------------------------------------------------------------------
@@ -174,7 +200,7 @@ public class Entity
         // expanding area by the size of the entity
         CollisionBox expandedBox = eBox.copy().expand(motionX, motionY);
         
-        LinkedList<CollisionBox> allBoxes = level.getCollisionBoxesAt(this, expandedBox);
+        LinkedList<CollisionBox> allBoxes = level.getMovementBoxesAt(this, expandedBox);
         if(allBoxes.isEmpty())
         {
             return;

+ 3 - 3
src/me/hammerle/supersnuvi/entity/Hero.java

@@ -14,7 +14,7 @@ public class Hero extends AnimatedEntity
     @Override
     public double getJumpPower() 
     {
-        return 10;
+        return 10 * getMaxSpeedModifier();
     }
     
     @Override
@@ -33,11 +33,11 @@ public class Hero extends AnimatedEntity
         //motionY = -2;
         if(keyHandler.isKeyDown(KeyCode.LEFT))
         {
-            setMotionX(-2);
+            setMotionX(-2 * getMaxSpeedModifier());
         }
         else if(keyHandler.isKeyDown(KeyCode.RIGHT))
         {
-            setMotionX(2);
+            setMotionX(2 * getMaxSpeedModifier());
         }
         /*if(keyHandler.isKeyDown(KeyCode.UP))
         {

+ 57 - 6
src/me/hammerle/supersnuvi/gamelogic/Level.java

@@ -33,6 +33,8 @@ public class Level
     private final HashMap<Integer, Entity> entities;
     private int entityCounter;
     
+    private boolean shouldReset;
+    
     public Level(StateRenderer state, File f)
     {
         this.state = state;
@@ -41,11 +43,10 @@ public class Level
         this.worldLoaded = loadMap(f);
         this.entities = new HashMap<>();
         this.entityCounter = 0;
+        this.shouldReset = false;
         
         // test spawning
-        Entity ent = new Entity(this, 80, 340, 32, 64);
         Hero hero = new Hero(this, 32, 100, 32, 48);
-        entities.put(entityCounter++, ent);
         entities.put(entityCounter++, hero);
     }
     
@@ -68,6 +69,24 @@ public class Level
         return name;
     }
     
+    public void finishLevel()
+    {
+        shouldReset = true;
+    }
+    
+    public boolean shouldFinish()
+    {
+        return shouldReset;
+    }
+    
+    public void resetLevel()
+    {
+        shouldReset = false;
+        Hero hero = new Hero(this, 32, 100, 32, 48);
+        entities.clear();
+        entities.put(entityCounter++, hero);
+    }
+    
     // -------------------------------------------------------------------------
     // map loading
     // -------------------------------------------------------------------------
@@ -230,7 +249,7 @@ public class Level
     // tick
     // -------------------------------------------------------------------------
     
-    public void tick(long delta, IKeyHandler keys)
+    public void tick(IKeyHandler keys)
     {
         if(worldLoaded)
         {
@@ -238,7 +257,8 @@ public class Level
             entities.values().forEach(entity -> 
             {
                 entity.controlTick(keys);
-                entity.tick(delta);
+                entity.resetMovementData();
+                entity.tick();
             });
 
             int startX = renderer.getFirstVisibleBlockX();
@@ -295,6 +315,37 @@ public class Level
         return state.getInteractionTile(background2[x][y]);
     }
     
+    public CollisionBox getTileMovementBox(int x, int y)
+    {
+        if(x < 0 || y < 0 || x >= width || y >= height)
+        {
+            return CollisionBox.NULL_BOX;
+        }
+        Tile tile = state.getInteractionTile(background2[x][y]);
+        return tile.getMovementBox().reset().offset(renderer.toCoord(x), renderer.toCoord(y));
+    }
+    
+    public LinkedList<Location> getTilesInMovementOf(CollisionBox cb)
+    {
+        LinkedList<Location> boxes = new LinkedList<>();
+        int startX = renderer.toBlock(cb.getMinX());
+        int endX = renderer.toBlock(cb.getMaxX());
+        int startY = renderer.toBlock(cb.getMinY());
+        int endY = renderer.toBlock(cb.getMaxY());
+        
+        for(int x = startX; x <= endX; x++)
+        {
+            for(int y = startY; y <= endY; y++)
+            {
+                if(getTileMovementBox(x, y).intersects(cb))
+                {
+                    boxes.add(new Location(getInteractionTile(x, y), x, y));
+                }
+            }
+        }
+        return boxes;
+    }
+    
     public CollisionBox getTileCollisionBox(int x, int y)
     {
         if(x < 0 || y < 0 || x >= width || y >= height)
@@ -331,7 +382,7 @@ public class Level
         return entities.values().stream().filter(ent -> ent != not && ent.getBox().intersects(cb)).collect(Collectors.toList());
     }
     
-    public LinkedList<CollisionBox> getCollisionBoxesAt(Entity not, CollisionBox cb)
+    public LinkedList<CollisionBox> getMovementBoxesAt(Entity not, CollisionBox cb)
     {
         LinkedList<CollisionBox> boxes = new LinkedList<>(entities.values().stream()
                         .filter(ent -> ent != not && ent.getBox().intersects(cb))
@@ -347,7 +398,7 @@ public class Level
         {
             for(int y = startY; y <= endY; y++)
             {
-                box = getTileCollisionBox(x, y);
+                box = getTileMovementBox(x, y);
                 if(box.intersects(cb))
                 {
                     boxes.add(box.copy());

+ 49 - 17
src/me/hammerle/supersnuvi/gamelogic/StateRenderer.java

@@ -9,9 +9,12 @@ import javafx.scene.input.KeyCode;
 import javafx.scene.paint.Color;
 import me.hammerle.supersnuvi.input.IKeyHandler;
 import me.hammerle.supersnuvi.rendering.IGameRenderer;
+import me.hammerle.supersnuvi.savegame.SimpleConfig;
+import me.hammerle.supersnuvi.tiles.Goal;
 import me.hammerle.supersnuvi.tiles.Ice;
 import me.hammerle.supersnuvi.tiles.TrampolinTile;
 import me.hammerle.supersnuvi.tiles.Tile;
+import me.hammerle.supersnuvi.tiles.Water;
 
 public class StateRenderer
 {
@@ -19,15 +22,16 @@ public class StateRenderer
     private static final Image FALLBACK_IMAGE = new WritableImage(1, 1);
     
     // rendering
-    private IGameRenderer renderer;   
+    private final IGameRenderer renderer;   
     
     // tiles
-    private HashMap<Integer, Tile> registeredTiles;
+    private final HashMap<Integer, Tile> registeredTiles;
     
     // levels
     private Level currentLevel;
-    private Level[] levels;
+    private final Level[] levels;
     private int levelIndex;
+    private final SimpleConfig config;
     
     public StateRenderer(IGameRenderer renderer)
     {
@@ -43,6 +47,8 @@ public class StateRenderer
         }
         currentLevel = null;
         levelIndex = 0;
+        config = new SimpleConfig("savegame.txt");
+        config.load();
         
         registerTiles();
     }    
@@ -64,8 +70,8 @@ public class StateRenderer
         
         registeredTiles.put(4, new TrampolinTile().setDefaultCollisionBox());
         registeredTiles.put(5, new Ice().setDefaultCollisionBox());
-        registerTile(6, 255, 255, 0, 255);
-        registerTile(7, 255, 255, 255, 255);
+        registeredTiles.put(6, new Water());
+        registeredTiles.put(7, new Goal());
     }
     
     public Image getTile(int id)
@@ -96,22 +102,23 @@ public class StateRenderer
     private static final int MENU_X = 12;
     private static final int MENU_Y = 5;
     private static final int MENU_MAX = 11;
-    private static final char[] TABLE_TOP = getCharLine((char) 131, (char) 136, MENU_WIDTH - 2, (char) 133);
-    private static final char[] TABLE_HEADING = getCharLine((char) 134, "Wähle ein Level ...", (char) 134);
-    private static final char[] TABLE_MID = getCharLine((char) 130, (char) 136, MENU_WIDTH - 2, (char) 132);
-    private static final char[] TABLE_BOTTOM = getCharLine((char) 137, (char) 136, MENU_WIDTH - 2, (char) 138);
-    private static final char[] TABLE_MORE = getCharLine((char) 134, "...", (char) 134);
+    private static final char[] TABLE_TOP = getCharLine((char) 131, (char) 136, (char) 133, (char) 136);
+    private static final char[] TABLE_HEADING = getCharLine((char) 134, "Wähle ein Level ...", (char) 134, ' ');
+    private static final char[] TABLE_MID = getCharLine((char) 130, (char) 136, (char) 132, (char) 129);
+    private static final char[] TABLE_BOTTOM = getCharLine((char) 137, (char) 136, (char) 138, (char) 135);
+    private static final char[] TABLE_MORE = getCharLine((char) 134, "...", (char) 134, (char) 134);
             
-    private static char[] getCharLine(char start, char mid, int times, char end)
+    private static char[] getCharLine(char start, char mid, char end, char spacer)
     {
-        char[] c = new char[times + 2];
+        char[] c = new char[MENU_WIDTH];
         Arrays.fill(c, 1, c.length - 1, mid);
         c[0] = start;
         c[c.length - 1] = end;
+        c[c.length - 3] = spacer;
         return c;
     }
     
-    private static char[] getCharLine(char start, String s, char end)
+    private static char[] getCharLine(char start, String s, char end, char spacer)
     {
         char[] chars = new char[MENU_WIDTH];
         chars[0] = start;
@@ -121,19 +128,28 @@ public class StateRenderer
         {
             chars[i + 1] = s.charAt(i);
         }
+        chars[chars.length - 3] = spacer;
         return chars;
     }
     
-    public void tick(long delta, IKeyHandler keys)
+    public void tick(IKeyHandler keys)
     {
         if(currentLevel != null)
         {
-            currentLevel.tick(delta, keys);
+            currentLevel.tick(keys);
             
             if(keys.isKeyJustReleased(KeyCode.ESCAPE) > 0)
             {
                 currentLevel = null;
             }
+            // doing that here to prevent concurent modification
+            if(currentLevel.shouldFinish())
+            {
+                currentLevel.resetLevel();
+                config.set("level." + currentLevel.getName(), true);
+                currentLevel = null;
+                config.save();
+            }
         }
         else
         {
@@ -236,12 +252,21 @@ public class StateRenderer
         for(int j = from; j < length; j++)
         {
             s = levels[j].getName();
-            border = Math.min(MENU_WIDTH - 2, s.length());
+            border = Math.min(MENU_WIDTH - 4, s.length());
             for(int i = 0; i < border; i++)
             {
                 chars[i + 1] = s.charAt(i);
             }
             Arrays.fill(chars, border + 1, MENU_WIDTH - 1, (char) 0);
+            chars[chars.length - 3] = 134;
+            if(config.getBoolean("level." + s, false))
+            {
+                chars[chars.length - 2] = 'x';
+            }
+            else
+            {
+                chars[chars.length - 2] = 'o';
+            }
             renderer.drawChars(chars, x, y);
             y += lineHeight;
         }
@@ -253,7 +278,14 @@ public class StateRenderer
         renderer.fillRec(
                 x + renderer.getStringWidth(1),
                 y + pos * lineHeight - 1, 
-                renderer.getStringWidth(MENU_WIDTH - 2), 
+                renderer.getStringWidth(MENU_WIDTH - 4), 
+                lineHeight + 1, 
+                Color.GRAY);
+        
+        renderer.fillRec(
+                x + renderer.getStringWidth(MENU_WIDTH - 2),
+                y + pos * lineHeight - 1, 
+                renderer.getStringWidth(1), 
                 lineHeight + 1, 
                 Color.GRAY);
     }

+ 4 - 4
src/me/hammerle/supersnuvi/input/KeyHandler.java

@@ -6,9 +6,9 @@ import javafx.scene.input.KeyCode;
 
 public class KeyHandler implements IKeyHandler
 {
-    private LinkedList<KeyCode> removeQueue;
-    private EnumMap<KeyCode, Integer> keys;
-    private EnumMap<KeyCode, Integer> justReleased;
+    private final LinkedList<KeyCode> removeQueue;
+    private final EnumMap<KeyCode, Integer> keys;
+    private final EnumMap<KeyCode, Integer> justReleased;
     
     public KeyHandler()
     {
@@ -53,7 +53,7 @@ public class KeyHandler implements IKeyHandler
         return keys.containsKey(key);
     }
 
-    public void tick(long delta) 
+    public void tick() 
     {
         keys.replaceAll((k, v) -> v + 1);
         justReleased.clear();

+ 17 - 11
src/me/hammerle/supersnuvi/rendering/GameRenderer.java

@@ -18,14 +18,15 @@ public class GameRenderer extends AnimationTimer implements IGameRenderer
     public final static int TILE_SIZE = 32;
     
     // basic graphic stuff
-    private Scene scene;
-    private Canvas canvas;
+    private final Scene scene;
+    private final Canvas canvas;
     
     // input handler
-    private KeyHandler keys;
+    private final KeyHandler keys;
     
     // timing
     private long lastHandle;
+    private long timingSum;
     
     // rendering
     private double cameraX;
@@ -35,10 +36,10 @@ public class GameRenderer extends AnimationTimer implements IGameRenderer
     private GraphicsContext context;
     
     // text rendering
-    private SnuviTextPainter textPainter;
+    private final SnuviTextPainter textPainter;
     
     // world rendering
-    private StateRenderer state;
+    private final StateRenderer state;
     
     public GameRenderer(Scene scene, Canvas canvas)
     {
@@ -48,7 +49,8 @@ public class GameRenderer extends AnimationTimer implements IGameRenderer
         // input handler
         this.keys = new KeyHandler();
         //timing
-        this.lastHandle = System.nanoTime();
+        this.lastHandle = 0;
+        this.timingSum = 0;
         // rendering
         this.cameraX = 0;
         this.cameraY = 0;
@@ -94,13 +96,17 @@ public class GameRenderer extends AnimationTimer implements IGameRenderer
     @Override
     public void handle(long now) 
     {
-        long delta = (now - lastHandle) / 1000;
+        timingSum += now - lastHandle;
         lastHandle = now;
         
-        keys.tick(delta);
-        
-        prepareRendering();
-        state.tick(delta, keys);
+        if(timingSum >= 15900000)
+        {
+            timingSum = 0;
+            keys.tick();
+
+            prepareRendering();
+            state.tick(keys);
+        }
     }
     
     private void prepareRendering()

+ 1 - 0
src/me/hammerle/supersnuvi/rendering/GameWindow.java

@@ -5,6 +5,7 @@ import javafx.scene.Group;
 import javafx.scene.Scene;
 import javafx.scene.canvas.Canvas;
 import javafx.stage.Stage;
+import me.hammerle.supersnuvi.savegame.SimpleConfig;
 
 public class GameWindow extends Application
 {

+ 215 - 0
src/me/hammerle/supersnuvi/savegame/SimpleConfig.java

@@ -0,0 +1,215 @@
+package me.hammerle.supersnuvi.savegame;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.MalformedInputException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+public class SimpleConfig
+{            
+    protected final TreeMap<String, Object> conf;
+    private final File file;
+    
+    public SimpleConfig(String path)
+    {    
+        file = new File(path);
+        conf = new TreeMap<>();
+    }
+    
+    private Object convertInput(String s)
+    {
+        if(s.equals("true"))
+        {
+            return true;
+        }
+        else if(s.equals("false"))
+        {
+            return false;
+        }
+        try
+        {
+            return Integer.parseInt(s);
+        }
+        catch(NumberFormatException ex)
+        {
+            try
+            {
+                return Double.parseDouble(s);
+            }
+            catch(NumberFormatException ex2)
+            {
+            }
+        }
+        return s;
+    }
+    
+    public final void load()
+    {
+        if(!exists())
+        {
+            return;
+        }
+        try
+        {
+            String warning = "wrong syntax in '" + file.getPath() + "'";
+            Files.readAllLines(file.toPath()).stream().forEach(s -> 
+            {
+                int b = s.indexOf("=");
+                if(b == -1)
+                {
+                    System.err.println(warning);
+                    System.err.println(s);
+                }
+                else
+                {
+                    conf.put(s.substring(0, b).trim(), convertInput(s.substring(b + 1)));
+                }
+            });
+        } 
+        catch(MalformedInputException ex) 
+        {
+            System.err.println("'" + file.getPath() + "' contains an illegal character, change file encoding");
+        }
+        catch(OutOfMemoryError ex) 
+        {
+            System.err.println("'" + file.getPath() + "' is too big");
+        }
+        catch(SecurityException ex) 
+        {
+            System.err.println("'" + file.getPath() + "' is not accessable");
+            System.err.println(ex.getMessage());
+        }
+        catch(IOException ex) 
+        {
+            System.err.println("'" + file.getPath() + "' cannot be read");
+            System.err.println(ex.getMessage());
+        }
+    }   
+    
+    public final boolean exists()
+    {
+        return file.exists();
+    }
+    
+    public final boolean delete()
+    {
+        return file.delete();
+    }
+    
+    public final boolean save()
+    {
+        try
+        {
+            if(file.getParentFile() != null)
+            {
+                file.getParentFile().mkdirs();
+            }
+            if(!file.exists())
+            {
+                try
+                {
+                    file.createNewFile();
+                }
+                catch(IOException ex)
+                {
+                    System.err.println("'" + file.getPath() + "' cannot be created");
+                    System.err.println(ex.getMessage());
+                    return false;
+                }
+            }
+            Files.write(Paths.get(file.toURI()), conf.entrySet().stream()
+                            .map(e -> 
+                            {
+                                if(e.getValue().getClass() == String.class)
+                                {
+                                    return e.getKey() + "=\"" + e.getValue() + "\"";
+                                }
+                                return e.getKey() + "=" + String.valueOf(e.getValue());
+                            })
+                            .collect(Collectors.toList()), StandardCharsets.UTF_8);
+            return true;
+        }
+        catch(UnsupportedOperationException ex)
+        {
+            System.err.println("an unsupported operation was used");
+            System.err.println(ex.getMessage());
+            return false;
+        }
+        catch(SecurityException ex)
+        {
+            System.err.println("'" + file.getPath() + "' is not accessable");
+            System.err.println(ex.getMessage());
+            return false;
+        }
+        catch(IOException ex)
+        {
+            System.err.println("cannot write to '" + file.getPath() + "'");
+            System.err.println(ex.getMessage());
+            return false;
+        }
+    }
+    
+    // -------------------------------------------------------------------------
+    // getter
+    // -------------------------------------------------------------------------
+    
+    public final <T> T get(String key, Class<T> c, T error)
+    {
+        try
+        {
+            Object o = conf.get(key);
+            if(o == null)
+            {
+                return error;
+            }
+            return c.cast(o);
+        }
+        catch(ClassCastException ex)
+        {
+            return error;
+        }
+    }
+    
+    public final String getString(String key, String error)
+    {
+        return get(key, String.class, error);
+    }
+    
+    public final String getString(String key)
+    {
+        return getString(key, null);
+    }
+    
+    public final float getFloat(String key, float error)
+    {
+        return get(key, Float.class, error);
+    }
+    
+    public final double getDouble(String key, double error)
+    {
+        return get(key, Double.class, error);
+    }
+    
+    public final int getInt(String key, int error)
+    {
+        return get(key, Integer.class, error);
+    }
+    
+    public final boolean getBoolean(String key, boolean error)
+    {
+        return get(key, Boolean.class, error);
+    }
+    
+    // -------------------------------------------------------------------------
+    // set
+    // -------------------------------------------------------------------------
+    
+    public final void set(String key, Object o)
+    {
+        conf.put(key, o);
+    }
+}

+ 26 - 0
src/me/hammerle/supersnuvi/tiles/Goal.java

@@ -0,0 +1,26 @@
+package me.hammerle.supersnuvi.tiles;
+
+import javafx.scene.paint.Color;
+import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.entity.Hero;
+import me.hammerle.supersnuvi.util.CollisionBox;
+import me.hammerle.supersnuvi.util.Face;
+
+public class Goal extends Tile
+{
+    public Goal() 
+    {
+        super(Color.GOLD);
+        super.setCollisionBox(CollisionBox.createScaledBox(0.1, 0.1, 0.9, 0.9));
+        super.setMovementBox(CollisionBox.NULL_BOX);
+    }
+
+    @Override
+    public void onEntityCollide(Entity ent, int x, int y, Face face) 
+    {
+        if(ent instanceof Hero)
+        {
+            ent.getLevel().finishLevel();
+        }
+    }
+}

+ 37 - 9
src/me/hammerle/supersnuvi/tiles/Tile.java

@@ -13,13 +13,16 @@ public class Tile
 {
     public final static Tile FALLBACK_TILE = new Tile(new Color(1, 1, 1, 1));
     
-    private Image image;
-    private CollisionBox cb;
+    private final Image image;
+    
+    private CollisionBox movementCollision;
+    private CollisionBox collisionBox;
     
     public Tile(String path)
     {
         image = new Image("me/hammerle/supersnuvi/resources/" + path + ".png", TILE_SIZE, TILE_SIZE, true, false);
-        cb = null;
+        movementCollision = null;
+        collisionBox = null;
     }
     
     public Tile(Color c)
@@ -40,28 +43,53 @@ public class Tile
         return image;
     }
     
-    public boolean hasHitBox()
+    /** Sets the collision box for movement
+     *
+     * @param cb a collision box
+     * @return the tile which the change was applied to
+     */
+    public Tile setMovementBox(CollisionBox cb)
     {
-        return cb != null;
+        this.movementCollision = cb.copy();
+        return this;
     }
     
+    /** Sets the collision box for normal collision
+     *
+     * @param cb a collision box
+     * @return the tile which the change was applied to
+     */
     public Tile setCollisionBox(CollisionBox cb)
     {
-        this.cb = cb;
+        this.collisionBox = cb.copy();
         return this;
     }
     
+    /** Sets the default collision box for movement and for collision
+     *
+     * @return the tile which the change was applied to
+     */
     public Tile setDefaultCollisionBox()
     {
-        this.cb = CollisionBox.DEFAULT_BOX.copy();
+        this.movementCollision = CollisionBox.DEFAULT_BOX.copy();
+        this.collisionBox = CollisionBox.DEFAULT_BOX.copy();
         return this;
     }
     
     public CollisionBox getCollisionBox()
     {
-        if(hasHitBox())
+        if(collisionBox != null)
+        {
+            return collisionBox;
+        }
+        return CollisionBox.NULL_BOX.reset();
+    }
+    
+    public CollisionBox getMovementBox()
+    {
+        if(movementCollision != null)
         {
-            return cb;
+            return movementCollision;
         }
         return CollisionBox.NULL_BOX.reset();
     }

+ 35 - 0
src/me/hammerle/supersnuvi/tiles/Water.java

@@ -0,0 +1,35 @@
+package me.hammerle.supersnuvi.tiles;
+
+import javafx.scene.paint.Color;
+import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.util.CollisionBox;
+import me.hammerle.supersnuvi.util.Face;
+
+public class Water extends Tile
+{
+    public Water() 
+    {
+        super(Color.BLUE);
+        super.setCollisionBox(CollisionBox.createScaledBox(0.1, 0.1, 0.9, 0.9));
+        super.setMovementBox(CollisionBox.NULL_BOX);
+    }
+
+    @Override
+    public void onEntityCollide(Entity ent, int x, int y, Face face) 
+    {
+        super.onEntityCollide(ent, x, y, face);
+        
+        ent.setInWater(true);
+        
+        double motionY = ent.getMotionY();
+        if(motionY < 0)
+        {
+            motionY *= 0.8d;
+            if(Math.abs(motionY) < 0.1)
+            {
+                motionY = 0;
+            }
+            ent.setMotionY(motionY);
+        }
+    }
+}

+ 6 - 1
src/me/hammerle/supersnuvi/util/CollisionBox.java

@@ -5,7 +5,7 @@ import me.hammerle.supersnuvi.rendering.GameRenderer;
 public class CollisionBox 
 {
     public final static CollisionBox NULL_BOX = new CollisionBox(0, 0, 0, 0);
-    public final static CollisionBox DEFAULT_BOX = new CollisionBox(0, 0, GameRenderer.TILE_SIZE, GameRenderer.TILE_SIZE);
+    public final static CollisionBox DEFAULT_BOX = createScaledBox(0, 0, 1, 1);
     
     private final double minX;
     private final double maxX;
@@ -29,6 +29,11 @@ public class CollisionBox
         cMaxY = maxY;
     }
     
+    public static CollisionBox createScaledBox(double x1, double y1, double x2, double y2)
+    {
+        return new CollisionBox(x1 * GameRenderer.TILE_SIZE, y1 * GameRenderer.TILE_SIZE, x2 * GameRenderer.TILE_SIZE, y2 * GameRenderer.TILE_SIZE);
+    }
+    
     public CollisionBox copy()
     {
         return new CollisionBox(cMinX, cMinY, cMaxX, cMaxY);