소스 검색

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 년 전
부모
커밋
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);