Bladeren bron

level logic separated from rendering, common abstract level base, cleanup, refactoring, entities no more know about their level, repaired left texture border of lanterns

Kajetan Johannes Hammerle 5 jaren geleden
bovenliggende
commit
0ce879b152
43 gewijzigde bestanden met toevoegingen van 1058 en 1270 verwijderingen
  1. BIN
      lib/SnuviEngine.jar
  2. BIN
      resources/tiles.png
  3. BIN
      resources/tiles.xcf
  4. 33 21
      src/me/hammerle/supersnuvi/Game.java
  5. 4 13
      src/me/hammerle/supersnuvi/entity/Entity.java
  6. 14 16
      src/me/hammerle/supersnuvi/entity/EntityBuilder.java
  7. 7 6
      src/me/hammerle/supersnuvi/entity/components/DefaultHealth.java
  8. 4 0
      src/me/hammerle/supersnuvi/entity/components/Health.java
  9. 0 18
      src/me/hammerle/supersnuvi/entity/components/NoHealth.java
  10. 2 1
      src/me/hammerle/supersnuvi/entity/components/ai/Controller.java
  11. 2 1
      src/me/hammerle/supersnuvi/entity/components/ai/HumanController.java
  12. 4 3
      src/me/hammerle/supersnuvi/entity/components/ai/LondonerController.java
  13. 2 1
      src/me/hammerle/supersnuvi/entity/components/ai/PlatformController.java
  14. 2 1
      src/me/hammerle/supersnuvi/entity/components/ai/StartScreenHeroController.java
  15. 2 1
      src/me/hammerle/supersnuvi/entity/components/ai/StoneController.java
  16. 216 0
      src/me/hammerle/supersnuvi/gamelogic/FileLevel.java
  17. 0 33
      src/me/hammerle/supersnuvi/gamelogic/ILevel.java
  18. 112 679
      src/me/hammerle/supersnuvi/gamelogic/Level.java
  19. 4 0
      src/me/hammerle/supersnuvi/gamelogic/LevelData.java
  20. 28 388
      src/me/hammerle/supersnuvi/gamelogic/StartScreenLevel.java
  21. 8 0
      src/me/hammerle/supersnuvi/rendering/IRenderer.java
  22. 470 0
      src/me/hammerle/supersnuvi/rendering/LevelRenderer.java
  23. 49 0
      src/me/hammerle/supersnuvi/rendering/TileUpdater.java
  24. 7 0
      src/me/hammerle/supersnuvi/rendering/UpdateConsumer.java
  25. 5 5
      src/me/hammerle/supersnuvi/tiles/BaseTile.java
  26. 9 9
      src/me/hammerle/supersnuvi/tiles/BottledSoulTile.java
  27. 8 8
      src/me/hammerle/supersnuvi/tiles/CrumblingStoneTile.java
  28. 3 3
      src/me/hammerle/supersnuvi/tiles/GoalTile.java
  29. 9 9
      src/me/hammerle/supersnuvi/tiles/HeadHitOnceTile.java
  30. 2 2
      src/me/hammerle/supersnuvi/tiles/HeadHitTile.java
  31. 3 3
      src/me/hammerle/supersnuvi/tiles/InteractTile.java
  32. 2 2
      src/me/hammerle/supersnuvi/tiles/KillTile.java
  33. 4 4
      src/me/hammerle/supersnuvi/tiles/Location.java
  34. 3 3
      src/me/hammerle/supersnuvi/tiles/NullTile.java
  35. 3 3
      src/me/hammerle/supersnuvi/tiles/PressureTile.java
  36. 2 2
      src/me/hammerle/supersnuvi/tiles/RampTile.java
  37. 2 2
      src/me/hammerle/supersnuvi/tiles/SlipperyTile.java
  38. 4 4
      src/me/hammerle/supersnuvi/tiles/SpikeTile.java
  39. 3 3
      src/me/hammerle/supersnuvi/tiles/StartTile.java
  40. 12 12
      src/me/hammerle/supersnuvi/tiles/Tile.java
  41. 2 2
      src/me/hammerle/supersnuvi/tiles/TrampolinTile.java
  42. 5 5
      src/me/hammerle/supersnuvi/tiles/WaterTile.java
  43. 7 7
      src/me/hammerle/supersnuvi/util/BlockDataStorage.java

BIN
lib/SnuviEngine.jar


BIN
resources/tiles.png


BIN
resources/tiles.xcf


+ 33 - 21
src/me/hammerle/supersnuvi/Game.java

@@ -12,8 +12,10 @@ import me.hammerle.snuviscript.code.SnuviParser;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
 import me.hammerle.supersnuvi.entity.components.ai.PlatformController;
+import me.hammerle.supersnuvi.gamelogic.FileLevel;
 import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.gamelogic.StartScreenLevel;
+import me.hammerle.supersnuvi.rendering.LevelRenderer;
 import me.hammerle.supersnuvi.savegame.SimpleConfig;
 import me.hammerle.supersnuvi.snuviscript.SnuviLogger;
 import me.hammerle.supersnuvi.snuviscript.SnuviScheduler;
@@ -73,6 +75,11 @@ public class Game extends Engine
     
     // sound
     private boolean sound = config.getBoolean("sound", false);
+    
+    // renderer
+    private final LevelRenderer levelRenderer = new LevelRenderer();
+    // this dummy will be removed later
+    private final Shader dummy = new Shader();
 
     public Game()
     {
@@ -82,16 +89,23 @@ public class Game extends Engine
         
         File[] files = new File("./levels").listFiles();
         Arrays.sort(files, (o1, o2) -> o1.compareTo(o2));
-        ArrayList<Level> levelList = new ArrayList<>();
+        ArrayList<FileLevel> levelList = new ArrayList<>();
         for(File file : files)
         {
             if(file.isFile() && file.getName().endsWith(".map"))
             {
-                Level l = new Level(file);
-                levelList.add(l);
+                try
+                {
+                    FileLevel l = new FileLevel(file);
+                    levelList.add(l);
+                }
+                catch(IllegalArgumentException ex)
+                {
+                    //System.out.println(ex.getMessage());
+                }
             }
         }
-        levels = levelList.toArray(new Level[levelList.size()]); 
+        levels = levelList.toArray(new FileLevel[levelList.size()]); 
     }
     
     @Override
@@ -118,7 +132,7 @@ public class Game extends Engine
             currentLevel.tick();
             
             // doing that here to prevent concurent modification
-            if(currentLevel.shouldFinish())
+            if(currentLevel.isFinished())
             {
                 String base = "level." + currentLevel.getFileName();
                 SimpleConfig sp = saveSlots[slotScreenIndex];
@@ -143,17 +157,14 @@ public class Game extends Engine
                 // final save
                 sp.save();
                 
-                currentLevel.resetLevel();
+                currentLevel.reset();
                 currentLevel = null;
                 return;
             }
             
             if(currentLevel.shouldReset())
             {
-                if(currentLevel.resetLevel())
-                {
-                    currentLevel = null;
-                }
+                currentLevel.reset();
             }
             
             if(Keys.ESCAPE.isReleased())
@@ -295,7 +306,6 @@ public class Game extends Engine
         }
     }
     
-    private final static int COLOR_BROWN = 0xFF13458B;
     private final static int COLOR_OVERLAY = 0x77000000;
 
     private String getKeyName(KeyBinding key)
@@ -312,10 +322,12 @@ public class Game extends Engine
     {
         if(currentLevel != null)
         {
-            currentLevel.renderTick(lag);
+            levelRenderer.renderTick(dummy, lag, currentLevel);
+            //currentLevel.renderTick(lag);
             return;
         }
-        startScreenLevel.renderTick(lag);
+        levelRenderer.renderTick(dummy, lag, startScreenLevel);
+
         Shader.translateTo(0.0f, 0.0f);
         Shader.updateMatrix();
         switch(screen)
@@ -531,15 +543,15 @@ public class Game extends Engine
                 {
                     Level l = levels[i];
                     fr.drawString(left, y, l.getName()); 
-                    fr.drawString(secLeft, y, l.formatBottles(sc.getInt("level." + l.getFileName() + ".bottles", 0))); 
-                    y = fr.drawString(thirdLeft, y, l.formatTime(sc.getFloat("level." + l.getFileName() + ".time", 0))); 
+                    fr.drawString(secLeft, y, LevelRenderer.formatBottles(sc.getInt("level." + l.getFileName() + ".bottles", 0), l.getMaxBottles())); 
+                    y = fr.drawString(thirdLeft, y, LevelRenderer.formatTime(sc.getFloat("level." + l.getFileName() + ".time", 0))); 
                 }
                 if(lastIndex == levels.length)
                 {
                     Level l = levels[lastIndex - 1];
                     fr.drawString(left, y, l.getName()); 
-                    fr.drawString(secLeft, y, l.formatBottles(sc.getInt("level." + l.getFileName() + ".bottles", 0))); 
-                    fr.drawString(thirdLeft, y, l.formatTime(sc.getFloat("level." + l.getFileName() + ".time", 0))); 
+                    fr.drawString(secLeft, y, LevelRenderer.formatBottles(sc.getInt("level." + l.getFileName() + ".bottles", 0), l.getMaxBottles())); 
+                    fr.drawString(thirdLeft, y, LevelRenderer.formatTime(sc.getFloat("level." + l.getFileName() + ".time", 0))); 
                 }
                 else
                 {
@@ -647,7 +659,7 @@ public class Game extends Engine
         return registeredTiles[id];
     }
     
-    public void resetTiles(Level l)
+    public void resetTiles(FileLevel l)
     {
         for(Tile t : registeredTiles)
         {
@@ -722,8 +734,8 @@ public class Game extends Engine
     private void addSnuviCommands()
     {
         snuviParser.registerFunction("level.getname", (sc, in) -> currentLevel.getName());
-        snuviParser.registerFunction("level.getwidth", (sc, in) -> (double) currentLevel.getWidth());
-        snuviParser.registerFunction("level.getheight", (sc, in) -> (double) currentLevel.getHeight());
+        snuviParser.registerFunction("level.getwidth", (sc, in) -> (double) currentLevel.getData().getWidth());
+        snuviParser.registerFunction("level.getheight", (sc, in) -> (double) currentLevel.getData().getHeight());
         snuviParser.registerFunction("level.getlayers", (sc, in) -> (double) currentLevel.getData().getLayers());
         snuviParser.registerFunction("level.getbackgroundindex", (sc, in) -> (double) currentLevel.getData().getBackgroundIndex());
         snuviParser.registerFunction("level.gettile", (sc, in) -> currentLevel.getData().getTile(in[0].getInt(sc), in[1].getInt(sc), in[2].getInt(sc)));
@@ -734,7 +746,7 @@ public class Game extends Engine
             int y = in[2].getInt(sc);
             int tile = in[3].getInt(sc);
             currentLevel.getData().setTile(layer, x, y, tile);
-            currentLevel.updateTile(layer, x, y);
+            currentLevel.getTileUpdater().add(layer, x, y);
             Game.get().getTile(tile).reset(x, y, currentLevel);
             return Void.TYPE;
         });

+ 4 - 13
src/me/hammerle/supersnuvi/entity/Entity.java

@@ -6,11 +6,11 @@ import me.hammerle.supersnuvi.entity.components.Energy;
 import me.hammerle.supersnuvi.entity.components.Health;
 import me.hammerle.supersnuvi.entity.components.ItemCollector;
 import me.hammerle.supersnuvi.entity.components.Movement;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.Utils;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public final class Entity
 {
@@ -40,9 +40,6 @@ public final class Entity
     // a flag indicating that the entity is on the ground
     private boolean onGround = true;
     
-    // the level of the entity
-    private final ILevel level;
-    
     // entity components
     protected Controller controller = Controller.NULL;
     protected Health health = Health.NULL;
@@ -55,7 +52,7 @@ public final class Entity
     
     private final String type;
     
-    protected Entity(String type, ILevel level, float x, float y, CollisionObject box)
+    protected Entity(String type, float x, float y, CollisionObject box)
     {
         lastPosX = x;
         lastPosY = y;
@@ -70,15 +67,9 @@ public final class Entity
         motionX = 0.0f;
         motionY = 0.0f;
         
-        this.level = level;
         this.type = type;
     }
     
-    public ILevel getLevel()
-    {
-        return level;
-    }   
-    
     public String getType()
     {
         return type;
@@ -237,12 +228,12 @@ public final class Entity
     // ticking
     //--------------------------------------------------------------------------
     
-    public void tick() 
+    public void tick(Level level) 
     {
         lastPosX = posX;
         lastPosY = posY;
         
-        controller.tick();
+        controller.tick(level);
         energy.tick();
         
         preMotionX = motionX;

+ 14 - 16
src/me/hammerle/supersnuvi/entity/EntityBuilder.java

@@ -6,43 +6,42 @@ import me.hammerle.supersnuvi.entity.components.DefaultEnergy;
 import me.hammerle.supersnuvi.entity.components.StoneMovement;
 import me.hammerle.supersnuvi.entity.components.ai.HumanController;
 import me.hammerle.supersnuvi.entity.components.ai.LondonerController;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.entity.components.IDeath;
 import me.hammerle.supersnuvi.entity.components.ItemCollector;
-import me.hammerle.supersnuvi.entity.components.NoHealth;
 import me.hammerle.supersnuvi.entity.components.ai.PlatformController;
 import me.hammerle.supersnuvi.entity.components.ai.StartScreenHeroController;
 import me.hammerle.supersnuvi.entity.components.ai.StoneController;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.SoundUtils.Sound;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public final class EntityBuilder 
 {
-    public static Entity buildHero(ILevel level, float x, float y)
+    public static Entity buildHero(Level level, float x, float y)
     {
-        Entity hero = new Entity("Hero", level, x, y, CollisionBox.createScaledTileBox(0.0f, 0.15625f, 0.84375f, 2.0f));
+        Entity hero = new Entity("Hero", x, y, CollisionBox.createScaledTileBox(0.0f, 0.15625f, 0.84375f, 2.0f));
         hero.controller = new HumanController(hero);
-        hero.health = new DefaultHealth(hero, (ent) -> ent.getLevel().scheduleReset(), 100.0f, null, Sound.MIRROR_CRACK, Sound.MIRROR_BREAK);
+        hero.health = new DefaultHealth(hero, (ent) -> level.scheduleReset(), 100.0f, null, Sound.MIRROR_CRACK, Sound.MIRROR_BREAK);
         hero.energy = new DefaultEnergy(hero, 100.0f);
         hero.move = new DefaultMovement(hero, 12.0f, 0.0f, 50.0f);
         hero.itemCollector = ItemCollector.HERO;
         return hero;
     }
     
-    public static Entity buildStartScreenHero(ILevel level, float x, float y)
+    public static Entity buildStartScreenHero(Level level, float x, float y)
     {
-        Entity hero = new Entity("Hero", level, x, y, CollisionBox.createScaledTileBox(0.0f, 0.15625f, 0.84375f, 2.0f));
+        Entity hero = new Entity("Hero", x, y, CollisionBox.createScaledTileBox(0.0f, 0.15625f, 0.84375f, 2.0f));
         hero.controller = new StartScreenHeroController(hero);
-        hero.health = new DefaultHealth(hero, (ent) -> ent.getLevel().scheduleReset(), 100.0f, null, Sound.MIRROR_CRACK, Sound.MIRROR_BREAK);
+        hero.health = new DefaultHealth(hero, (ent) -> level.scheduleReset(), 100.0f, null, Sound.MIRROR_CRACK, Sound.MIRROR_BREAK);
         hero.energy = new DefaultEnergy(hero, 100.0f);
         hero.move = new DefaultMovement(hero, 12.0f, 0.0f, 50.0f);
         hero.itemCollector = ItemCollector.HERO;
         return hero;
     }
     
-    public static Entity buildLondoner(ILevel level, float x, float y, boolean evil)
+    public static Entity buildLondoner(Level level, float x, float y, boolean evil)
     {
-        Entity londoner = new Entity(evil ? "EvilLondoner" : "Londoner", level, x, y, CollisionBox.createScaledTileBox(0.21875f, 0.59375f, 0.65625f, 2.0f));
+        Entity londoner = new Entity(evil ? "EvilLondoner" : "Londoner", x, y, CollisionBox.createScaledTileBox(0.21875f, 0.59375f, 0.65625f, 2.0f));
         londoner.controller = new LondonerController(londoner, evil);
         londoner.health = new DefaultHealth(londoner, IDeath.NULL, 100.0f, null, null, null);
         londoner.energy = new DefaultEnergy(londoner, 100.0f);
@@ -50,24 +49,23 @@ public final class EntityBuilder
         return londoner;
     }
     
-    public static Entity buildCrumblingStone(ILevel level, float x, float y)
+    public static Entity buildCrumblingStone(Level level, float x, float y)
     {
-        Entity stone = new Entity("CrumblingStone", level, x, y, CollisionBox.DEFAULT_TILE_BOX);
+        Entity stone = new Entity("CrumblingStone", x, y, CollisionBox.DEFAULT_TILE_BOX);
         stone.controller = new StoneController(stone);
         stone.move = new StoneMovement(stone);
-        stone.health = new NoHealth(stone);
         return stone;
     }
     
-    public static Entity buildPlatform(ILevel level, float x, float y, int tx)
+    public static Entity buildPlatform(Level level, float x, float y, int tx)
     {
-        Entity platform = new Entity("Platform", level, x, y, CollisionBox.createScaledTileBox(0.03125f, 0.0f, 0.03125f + tx, 0.78125f));
+        Entity platform = new Entity("Platform", x, y, CollisionBox.createScaledTileBox(0.03125f, 0.0f, 0.03125f + tx, 0.78125f));
         platform.controller = new PlatformController(platform);
         platform.move = new StoneMovement(platform);
         return platform;
     }
     
-    public static Entity fromId(int id, ILevel level, float x, float y)
+    public static Entity fromId(int id, Level level, float x, float y)
     {
         switch(id)
         {

+ 7 - 6
src/me/hammerle/supersnuvi/entity/components/DefaultHealth.java

@@ -48,11 +48,6 @@ public class DefaultHealth extends Health
         {
             hurtTicks++;
         }
-        
-        if(shouldDespawn())
-        {
-            death.onDeath(ent);
-        }
     }
 
     @Override
@@ -64,7 +59,13 @@ public class DefaultHealth extends Health
     @Override
     public boolean shouldDespawn() 
     {
-        return (isDead() && !ent.isAnimated()) || (ent.getY() > ent.getLevel().getHeight() * Tile.SIZE);
+        return (isDead() && !ent.isAnimated());
+    }
+
+    @Override
+    public void onDespawn()
+    {
+        death.onDeath(ent);
     }
     
     @Override

+ 4 - 0
src/me/hammerle/supersnuvi/entity/components/Health.java

@@ -27,6 +27,10 @@ public class Health
         return false;
     }
     
+    public void onDespawn()
+    {
+    }
+    
     public boolean wasHurt()
     {
         return false;

+ 0 - 18
src/me/hammerle/supersnuvi/entity/components/NoHealth.java

@@ -1,18 +0,0 @@
-package me.hammerle.supersnuvi.entity.components;
-
-import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.tiles.Tile;
-
-public class NoHealth extends Health
-{
-    public NoHealth(Entity ent)
-    {
-        super(ent);
-    }
-
-    @Override
-    public boolean shouldDespawn()
-    {
-        return ent.getY() > ent.getLevel().getHeight() * Tile.SIZE;
-    }
-}

+ 2 - 1
src/me/hammerle/supersnuvi/entity/components/ai/Controller.java

@@ -1,6 +1,7 @@
 package me.hammerle.supersnuvi.entity.components.ai;
 
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.util.Face;
 
@@ -15,7 +16,7 @@ public class Controller
         this.ent = ent;
     }
     
-    public void tick()
+    public void tick(Level level)
     {
     }
     

+ 2 - 1
src/me/hammerle/supersnuvi/entity/components/ai/HumanController.java

@@ -4,6 +4,7 @@ import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.supersnuvi.Keys;
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.SoundUtils;
@@ -72,7 +73,7 @@ public class HumanController extends Controller
     }
     
     @Override
-    public void tick() 
+    public void tick(Level level) 
     {
         if(ent.getHealth().isDead())
         {

+ 4 - 3
src/me/hammerle/supersnuvi/entity/components/ai/LondonerController.java

@@ -3,6 +3,7 @@ package me.hammerle.supersnuvi.entity.components.ai;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.RampTile;
 import me.hammerle.supersnuvi.tiles.Tile;
@@ -50,7 +51,7 @@ public class LondonerController extends Controller
     }
     
     @Override
-    public void tick() 
+    public void tick(Level level) 
     {
         if(evil)
         {
@@ -65,7 +66,7 @@ public class LondonerController extends Controller
                     return;
                 }
             }
-            else if(ent.getLevel().getHero().getSquaredDistance(ent) <= 16384) // 4 Tiles
+            else if(level.getHero().getSquaredDistance(ent) <= 16384) // 4 Tiles
             {
                 transformFrame = 0;
             }
@@ -90,7 +91,7 @@ public class LondonerController extends Controller
         
         if(hasRedEyes())
         {
-            float hx = ent.getLevel().getHero().getCenterX();
+            float hx = level.getHero().getCenterX();
             if(hx < ent.getCenterX())
             {
                 ent.setMotionX(-ent.getMovement().getVelocityX());

+ 2 - 1
src/me/hammerle/supersnuvi/entity/components/ai/PlatformController.java

@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.Utils;
@@ -69,7 +70,7 @@ public class PlatformController extends Controller
     }
     
     @Override
-    public void tick() 
+    public void tick(Level level) 
     {
         if(dataList.isEmpty())
         {

+ 2 - 1
src/me/hammerle/supersnuvi/entity/components/ai/StartScreenHeroController.java

@@ -1,6 +1,7 @@
 package me.hammerle.supersnuvi.entity.components.ai;
 
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.RampTile;
 import me.hammerle.supersnuvi.tiles.Tile;
@@ -16,7 +17,7 @@ public class StartScreenHeroController extends HumanController
     }
     
     @Override
-    public void tick() 
+    public void tick(Level level) 
     {
         ent.setMotionX(ent.getMovement().getVelocityX());
         

+ 2 - 1
src/me/hammerle/supersnuvi/entity/components/ai/StoneController.java

@@ -3,6 +3,7 @@ package me.hammerle.supersnuvi.entity.components.ai;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.Utils;
 
@@ -38,7 +39,7 @@ public class StoneController extends Controller
     }
 
     @Override
-    public void tick()
+    public void tick(Level level)
     {
         if(frame < 40)
         {

+ 216 - 0
src/me/hammerle/supersnuvi/gamelogic/FileLevel.java

@@ -0,0 +1,216 @@
+package me.hammerle.supersnuvi.gamelogic;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.supersnuvi.Game;
+import me.hammerle.supersnuvi.Keys;
+import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.entity.EntityBuilder;
+import me.hammerle.supersnuvi.tiles.Tile;
+import me.hammerle.supersnuvi.util.Utils;
+
+public final class FileLevel extends Level
+{
+    // level data
+    private final LevelData data;
+    private final String name;
+    private final String fileName;
+    // level stats
+    private int souls = 0;
+    private int maxSouls = 0;
+    private float time = 0.0f;
+    private final LinkedList<String> messages = new LinkedList<>();
+    // controlling
+    private TreeSet<Point> spawns = new TreeSet<>();
+    // scripting
+    private Script levelScript = null;
+
+    public FileLevel(File f)
+    {
+        this.data = new LevelData(f);
+        if(!data.load())
+        {
+            throw new IllegalArgumentException(f + " is not a valid level.");
+        }
+        
+        this.name = data.getString("name", "error");
+        data.forEachInteractTile((x, y, tile) -> 
+        {
+            Tile t = Game.get().getTile(tile);
+            if(t != null)
+            {
+                maxSouls += t.getBottleScore();
+            }
+
+            if(tile == 24) // start tile
+            {
+                spawns.add(new Point(x, y - 1));
+            }
+        });
+        data.forEachEntity((x, y, tile) -> 
+        {            
+            if(tile == 1)
+            {
+                maxSouls++;
+            }
+        }, 0, data.getWidth(), 0, data.getHeight());
+        
+        fileName = f.getName();
+        
+        // there must be at least one spawn
+        if(spawns.isEmpty())
+        {
+            spawns.add(new Point(5, 5));
+        }
+
+        // make sure hero is spawned before any script starts
+        reset();
+        
+        String scriptName = f.getPath().replace(".map", "");
+        // mark current level as active to make currentLevel calls work
+        Level l = Game.get().getCurrentLevel();
+        Game.get().setCurrentLevel(this);
+        levelScript = Game.get().getParser().startScript(false, ".snuvi", scriptName);
+        // call level reset here, because levelScript was null in resetLevel()
+        callEvent("level_reset");
+        // mark previous level as active
+        Game.get().setCurrentLevel(l); 
+    }
+    
+    // -------------------------------------------------------------------------
+    // level data
+    // -------------------------------------------------------------------------
+
+    @Override
+    public LevelData getData()
+    {
+        return data;
+    }
+    
+    @Override
+    public String getName() 
+    {
+        return name;
+    }
+    
+    @Override
+    public String getFileName() 
+    {
+        return fileName;
+    }
+    
+    @Override
+    public void addMessage(String message)
+    {
+        messages.add(message);
+    }
+    
+    @Override
+    public String getMessage()
+    {
+        return messages.isEmpty() ? null : messages.getFirst();
+    }
+    
+    // -------------------------------------------------------------------------
+    // level stats
+    // -------------------------------------------------------------------------
+    
+    @Override
+    public int getCurrentBottles()
+    {
+        return souls;
+    }
+    
+    @Override
+    public int getMaxBottles()
+    {
+        return maxSouls;
+    }
+    
+    @Override
+    public void addBottles(int score)
+    {
+        souls += score;
+    }
+    
+    @Override
+    public float getTime()
+    {
+        return time;
+    }
+    
+    // -------------------------------------------------------------------------
+    // controlling
+    // -------------------------------------------------------------------------
+    
+    @Override
+    public void tick()
+    {
+        if(!messages.isEmpty() && Keys.ENTER.getTime() == 1)
+        {
+            messages.removeFirst();
+        }
+
+        time += Game.SECS_PER_TICK;
+        Game.get().tickTiles();
+
+        tickEntities();
+    }  
+    
+    @Override
+    protected void onReset()
+    {
+        Game.get().resetTiles(this);
+        data.activateEntities();
+        souls = 0;
+        time = 0.0f;
+        Entity h = spawnHero(true);
+        setHero(h);
+        removeEntities();
+        spawnEntity(h);
+        messages.clear();
+        
+        getTileUpdater().updateAll();
+        
+        callEvent("level_reset");
+    }
+    
+    public Entity spawnHero(boolean first)
+    {
+        Point p;
+        if(getHero() == null || getHero().getX() < 0 || first)
+        {
+            // first spawn or out of map, use first spawn
+            p = spawns.first();
+        }
+        else
+        {
+            // hero is somewhere in the map, getting last spawn
+            p = spawns.floor(new Point(Utils.toBlock(getHero().getX()), Utils.toBlock(getHero().getY())));
+            if(p == null)
+            {
+                p = spawns.first();
+            }
+        }       
+        return EntityBuilder.buildHero(this, Utils.toCoord(p.getX()), Utils.toCoord(p.getY()));
+    }
+         
+    // -------------------------------------------------------------------------
+    // scripting
+    // -------------------------------------------------------------------------
+    
+    @Override
+    public void callEvent(String name, Consumer<Script> before, Consumer<Script> after)
+    {
+        if(levelScript != null)
+        {
+            //Level l = Game.get().getCurrentLevel();
+            //Game.get().setCurrentLevel(this);
+            Game.get().getParser().callEvent(name, levelScript, before, after);
+            //Game.get().setCurrentLevel(l);
+        }
+    }
+}

+ 0 - 33
src/me/hammerle/supersnuvi/gamelogic/ILevel.java

@@ -1,33 +0,0 @@
-package me.hammerle.supersnuvi.gamelogic;
-
-import java.util.List;
-import java.util.function.Consumer;
-import me.hammerle.snuviscript.code.Script;
-import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.tiles.Location;
-import me.hammerle.supersnuvi.util.CollisionObject;
-
-public interface ILevel
-{
-    public int getWidth();
-    public int getHeight();
-    
-    public Entity getHero();
-    
-    public void spawnEntity(Entity ent);
-    
-    public void scheduleReset();
-    public void finishLevel();
-    
-    public void increaseSouls(int score);
-    
-    public void updateTile(int layer, int x, int y);
-    public void updateTile(int x, int y);
-    
-    public List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not);
-    public List<Location> getCollisionBoxesAt(CollisionObject cb);
-    public List<Entity> getEntitiesCollidingWith(Entity not, CollisionObject cb);
-    
-    public void callEvent(String name, Consumer<Script> before, Consumer<Script> after);
-    public void callEvent(String name);
-}

+ 112 - 679
src/me/hammerle/supersnuvi/gamelogic/Level.java

@@ -1,756 +1,208 @@
 package me.hammerle.supersnuvi.gamelogic;
 
-import java.io.File;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import me.hammerle.snuviengine.api.Shader;
-import me.hammerle.snuviengine.api.Texture;
-import me.hammerle.snuviengine.api.TextureRenderer;
-import me.hammerle.snuviengine.util.Rectangle;
 import me.hammerle.snuviscript.code.Script;
 import me.hammerle.supersnuvi.Game;
-import me.hammerle.supersnuvi.Keys;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
+import me.hammerle.supersnuvi.rendering.TileUpdater;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Utils;
 
-public final class Level implements ILevel
+public abstract class Level
 {
-    public final static float ERROR = 1f / 65536f;
-    
-    public final static Texture TILES = new Texture("resources/tiles.png");
-    private final static Texture GUI = new Texture("resources/gui.png");
-    private final static TextureRenderer GUI_RENDERER = new TextureRenderer(60);
-    
-    private final boolean worldLoaded;
-    private final LevelData data;
-    
-    private final String fileName;
-    private final String name;
-    
-    private final HashMap<Integer, Entity> entities = new HashMap<>();
-    private final LinkedList<Entity> spawnQueue = new LinkedList<>();
-    private Entity hero;
-    private int entityCounter = 0;
-    
-    private boolean shouldReset = false;
-    private boolean done = true; // this will be reseted in resetLevel()
-    
-    private int souls;
-    private int maxSouls;
-    
-    private float time = 0.0f;
-    
-    private float cameraX = 0.0f;
-    private float cameraY = 0.0f;
-    private float oldCameraX = 0.0f;
-    private float oldCameraY = 0.0f;
-    
-    private final int meshSize = 16;
-    private final int meshLayers;
-    private final int meshWidth;
-    private final int meshHeight;
-    private final TextureRenderer[][][] meshes;
-    
-    //private int tries = 7;
+    // -------------------------------------------------------------------------
+    // level data
+    // -------------------------------------------------------------------------
     
-    private TreeSet<Point> spawns = new TreeSet<>();
+    private final TileUpdater tileUpdater = new TileUpdater();
     
-    private Script levelScript = null;
-    private final LinkedList<String> messages = new LinkedList<>();
+    public abstract LevelData getData();
     
-    public Level(File f)
-    {
-        this.data = new LevelData(f);
-        this.worldLoaded = data.load();
-        
-        if(worldLoaded)
-        {
-            this.name = data.getString("name", "error");
-            
-            meshLayers = data.getLayers() - 1;
-            meshWidth = (int) Math.ceil((double) data.getWidth() / meshSize);
-            meshHeight = (int) Math.ceil((double) data.getHeight() / meshSize);
-            meshes = new TextureRenderer[meshLayers][meshWidth][meshHeight];
-            for(int l = 0; l < meshLayers; l++)
-            {
-                for(int mx = 0; mx < meshWidth; mx++)
-                {
-                    for(int my = 0; my < meshHeight; my++)
-                    {
-                        meshes[l][mx][my] = new TextureRenderer(meshSize * meshSize * 2);
-                    }
-                }
-            }
-            
-            // debug stuff
-            /*if(name.equals("01-Tech Demo"))
-            {
-                int l = data.getBackgroundIndex();
-                    
-            }*/
-            // end debug stuff
-            
-            maxSouls = 0;
-            data.forEachInteractTile((x, y, tile) -> 
-            {
-                Tile t = Game.get().getTile(tile);
-                if(t != null)
-                {
-                    maxSouls += t.getBottleScore();
-                }
-                
-                if(tile == 24) // start tile
-                {
-                    spawns.add(new Point(x, y - 1));
-                }
-            });
-            data.forEachEntity((x, y, tile) -> 
-            {            
-                if(tile == 1)
-                {
-                    maxSouls++;
-                }
-            }, 0, data.getWidth(), 0, data.getHeight());
-        }
-        else
-        {
-            this.name = f.getName();
-            maxSouls = 0;
-            
-            meshLayers = 0;
-            meshWidth = 0;
-            meshHeight = 0;
-            meshes = new TextureRenderer[0][0][0];
-        }
-        fileName = f.getName();
-        
-        // there must be at least one spawn
-        if(spawns.isEmpty())
-        {
-            spawns.add(new Point(5, 5));
-        }
-
-        // make sure hero is spawned before any script starts
-        resetLevel();
-        
-        String scriptName = f.getPath().replace(".map", "");
-        // mark current level as active to make currentLevel calls work
-        Level l = Game.get().getCurrentLevel();
-        Game.get().setCurrentLevel(this);
-        levelScript = Game.get().getParser().startScript(false, ".snuvi", scriptName);
-        // call level reset here, because levelScript was null in resetLevel()
-        callEvent("level_reset");
-        // mark previous level as active
-        Game.get().setCurrentLevel(l); 
-    }
-
-    // -------------------------------------------------------------------------
-    // basic stuff
-    // -------------------------------------------------------------------------
-
-    @Override
-    public Entity getHero()
+    public String getName()
     {
-        return hero;
+        return "Unknown";
     }
     
-    public String getFileName() 
+    public String getFileName()
     {
-        return fileName;
+        return "Unknown";
     }
     
-    public String getName() 
+    public final TileUpdater getTileUpdater()
     {
-        return name;
+        return tileUpdater;
     }
     
-    @Override
-    public void finishLevel()
-    {
-        shouldReset = true;
-        done = true;
-    }
+    // -------------------------------------------------------------------------
+    // level stats
+    // -------------------------------------------------------------------------
     
-    public boolean shouldFinish()
+    public int getCurrentBottles()
     {
-        return done;
+        return 0;
     }
     
-    public boolean shouldReset()
+    public int getMaxBottles()
     {
-        return shouldReset;
+        return 0;
     }
     
-    @Override
-    public void scheduleReset()
+    public void addBottles(int bottles)
     {
-        shouldReset = true;
     }
     
-    public boolean resetLevel()
+    public float getTime()
     {
-        Game.get().resetTiles(this);
-        data.activateEntities();
-        souls = 0;
-        time = 0.0f;
-        shouldReset = false;
-        done = false;
-        Entity h = spawnHero(true);
-        hero = h;
-        entities.clear();
-        spawnEntity(hero);
-        
-        for(int l = 0; l < meshLayers; l++)
-        {
-            for(int x = 0; x < meshWidth; x++)
-            {
-                for(int y = 0; y < meshHeight; y++)
-                {
-                    meshes[l][x][y].clear();
-                }
-            }
-        }
-        
-        messages.clear();
-        
-        callEvent("level_reset");
-        return false;
+        return 0;
     }
     
-    @Override
-    public void spawnEntity(Entity ent)
+    public void addMessage(String message)
     {
-        spawnQueue.add(ent);
     }
     
-    public Entity spawnHero(boolean first)
+    public String getMessage()
     {
-        Entity h = hero;
-        Point p;
-        if(h == null || hero.getX() < 0 || first)
-        {
-            // first spawn or out of map, use first spawn
-            p = spawns.first();
-        }
-        else
-        {
-            // hero is somewhere in the map, getting last spawn
-            p = spawns.floor(new Point(Utils.toBlock(hero.getX()), Utils.toBlock(hero.getY())));
-            if(p == null)
-            {
-                p = spawns.first();
-            }
-        }
-        
-        Entity newHero = EntityBuilder.buildHero(this, Utils.toCoord(p.getX()), Utils.toCoord(p.getY()));
-        
-        // reset the camera
-        oldCameraX = -getViewX(newHero.getCenterX());
-        oldCameraY = -getViewY(newHero.getCenterY());
-        cameraX = oldCameraX;
-        cameraY = oldCameraY;
-        
-        return newHero;
+        return null;
     }
     
-    @Override
-    public void increaseSouls(int score)
-    {
-        souls += score;
-    }
+    // -------------------------------------------------------------------------
+    // entity related
+    // -------------------------------------------------------------------------
     
-    public int getCurrentBottles()
-    {
-        return souls;
-    }
+    private final HashMap<Integer, Entity> entities = new HashMap<>();
+    private final LinkedList<Entity> spawnQueue = new LinkedList<>();
+    private Entity hero = null;
+    private int entityCounter = 0;
     
-    public LevelData getData()
+    public final Entity getHero()
     {
-        return data;
+        return hero;
     }
     
-    public float getTime()
+    public final void setHero(Entity hero)
     {
-        return time;
-    }  
+        this.hero = hero;
+    }
     
-    @Override
-    public int getWidth()
+    public final void removeEntities()
     {
-        return data.getWidth();
+        entities.clear();
     }
     
-    @Override
-    public int getHeight()
+    public final void spawnEntity(Entity ent)
     {
-        return data.getHeight();
+        spawnQueue.add(ent);
     }
-
-    // -------------------------------------------------------------------------
-    // tick
-    // -------------------------------------------------------------------------
     
-    public void tick()
+    public final void tickEntities()
     {
-        if(worldLoaded)
+        entities.values().removeIf(ent -> 
         {
-            if(!messages.isEmpty() && Keys.ENTER.getTime() == 1)
-            {
-                messages.removeFirst();
-            }
-            
-            time += Game.SECS_PER_TICK;
-            Game.get().tickTiles();
-            
-            // doing entity logic first
-            entities.values().removeIf(entity -> 
-            {
-                entity.tick();
-                if(entity.getHealth().shouldDespawn())
-                {
-                    callEvent("entity_despawn", (sc) -> 
-                    {
-                        sc.setVar("entity", entity);
-                    }, null);
-                    return true;
-                }
-                return false;
-            });
-            
-            if(!spawnQueue.isEmpty())
+            ent.tick(this);
+            if(ent.getHealth().shouldDespawn() || (ent.getY() > getData().getHeight() * Tile.SIZE))
             {
-                spawnQueue.forEach(ent -> 
+                ent.getHealth().onDespawn();
+                callEvent("entity_despawn", (sc) -> 
                 {
-                    entities.put(entityCounter++, ent);
-                    callEvent("entity_spawn", (sc) -> 
-                    {
-                        sc.setVar("entity", ent);
-                    }, null);
-                });
-                spawnQueue.clear();
+                    sc.setVar("entity", ent);
+                }, null);
+                return true;
             }
-            
-            // calculate new camera position
-            oldCameraX = cameraX;
-            oldCameraY = cameraY;
-            cameraX = -getViewX(hero.getCenterX());
-            cameraY = -getViewY(hero.getCenterY());
-            
-            // entity spawn layer after camera update
-            int startX = (int) (-cameraX / Tile.SIZE);
-            int startY = (int) (-cameraY / Tile.SIZE);
-            int endX = Math.min((int) Math.ceil((-cameraX + Shader.getViewWidth()) / Tile.SIZE), data.getWidth());
-            int endY = Math.min((int) Math.ceil((-cameraY + Shader.getViewHeight()) / Tile.SIZE), data.getWidth());
-            data.forEachEntity((x, y, tile) -> 
+            return false;
+        });
+
+        if(!spawnQueue.isEmpty())
+        {
+            spawnQueue.forEach(ent -> 
             {
-                if(tile > 0)
+                entities.put(entityCounter++, ent);
+                callEvent("entity_spawn", (sc) -> 
                 {
-                    data.deactivateEntity(x, y);
- 
-                    Entity ent = EntityBuilder.fromId(tile, this, Utils.toCoord(x), Utils.toCoord(y));
-                    if(ent != null)
-                    {
-                        entities.put(entityCounter++, ent);
-                    }
-                }
-            }, startX, endX, startY, endY);
-        }
-    }   
-    
-    public String formatBottles(int bottles)
-    {
-        char[] c = new char[5];
-        if(bottles <= 9)
-        {
-            c[0] = '0';
-            c[1] = (char) (bottles + '0');
-        }
-        else if(bottles > 99)
-        {
-            c[0] = 'X';
-            c[1] = 'X';
-        }
-        else
-        {
-            c[0] = (char) ((bottles / 10) + '0');
-            c[1] = (char) ((bottles % 10) + '0');
-        }
-        c[2] = '/';
-        
-        int currentMaxSouls = Math.max(bottles, maxSouls);
-        if(currentMaxSouls <= 9)
-        {
-            c[3] = '0';
-            c[4] = (char) (currentMaxSouls + '0');
-        }
-        else if(currentMaxSouls > 99)
-        {
-            c[3] = 'X';
-            c[4] = 'X';
-        }
-        else
-        {
-            c[3] = (char) ((currentMaxSouls / 10) + '0');
-            c[4] = (char) ((currentMaxSouls % 10) + '0');
+                    sc.setVar("entity", ent);
+                }, null);
+            });
+            spawnQueue.clear();
         }
-        return new String(c);
     }
     
-    public String formatTime(float time)
-    {
-        if(time == -1.0f)
-        {
-            return "-----";
-        }
-        else if(time >= 999.9f)
-        {
-            return "999.9";
-        }
-        return String.format("%05.1f", time);
-    }   
-    
-    private float getViewX(float x) 
+    public final List<Entity> getEntitiesCollidingWith(Entity not, CollisionObject cb)
     {
-        x -= Shader.getViewWidth() >> 1;
-        if(x < 0)
-        {
-            return 0;
-        }
-        float max = data.getWidth() * Tile.SIZE - Shader.getViewWidth();
-        if(x > max)
-        {
-            return max;
-        }
-        return x;
+        return entities.values().stream().filter(ent -> ent != not && ent.getBox().isColliding(cb)).collect(Collectors.toList());
     }
     
-    private float getViewY(float y) 
+    public final void forEachEntity(Consumer<Entity> c)
     {
-        y -= Shader.getViewHeight() >> 1;
-        if(y < 0)
-        {
-            return 0;
-        }
-        float max = data.getHeight() * Tile.SIZE - Shader.getViewHeight();
-        if(y > max)
-        {
-            return max;
-        }
-        return y;
+        entities.values().forEach(c);
     }
     
-    @Override
-    public void updateTile(int layer, int x, int y)
+    // -------------------------------------------------------------------------
+    // controlling
+    // -------------------------------------------------------------------------
+    
+    private boolean shouldReset = false;
+    private boolean done = false;
+    
+    public abstract void tick();
+    
+    public final void finishLevel()
     {
-        if(layer == data.getBackgroundIndex() + 1)
-        {
-            // do not update changes on entity layer
-            return;
-        }
-        if(layer > data.getBackgroundIndex())
-        {
-            layer--;
-        }
-        meshes[layer][x / meshSize][y / meshSize].clear();
+        shouldReset = true;
+        done = true;
     }
     
-    @Override
-    public void updateTile(int x, int y)
+    public final boolean isFinished()
     {
-        updateTile(data.getBackgroundIndex(), x, y);
+        return done;
     }
     
-    private void drawMesh(int l, int tl, int mx, int my)
+    public final boolean shouldReset()
     {
-        TextureRenderer tr = meshes[l][mx][my];
-        if(!tr.isBuilt())
-        {
-            int tsx = mx * meshSize;
-            int tsy = my * meshSize;
-            int tex = Math.min(tsx + meshSize, data.getWidth());
-            int tey = Math.min(tsy + meshSize, data.getHeight());
-            for(int x = tsx; x < tex; x++)
-            {
-                for(int y = tsy; y < tey; y++)
-                {
-                    Tile t = Game.get().getTile(data.getTile(tl, x, y));
-                    if(t.shouldRender(x, y, this))
-                    {
-                        float minX = x * Tile.SIZE + t.getOffsetX();
-                        float minY = y * Tile.SIZE + t.getOffsetY();
-                        tr.addRectangle(minX, minY, 
-                                minX + t.getWidth(), minY + t.getHeight(), 
-                                t.getTextureMinX(x, y, this) + ERROR, t.getTextureMinY(x, y, this) + ERROR,
-                                t.getTextureMaxX(x, y, this) - ERROR, t.getTextureMaxY(x, y, this) - ERROR);
-                    }
-                }
-            }
-            tr.build();
-        }
-        meshes[l][mx][my].draw();
+        return shouldReset;
     }
     
-    private String[] split(String s)
+    public final void scheduleReset()
     {
-        ArrayList<String> list = new ArrayList<>();
-        int old = 0;
-        int index = 0;
-        while(index < s.length())
-        {
-            switch(s.charAt(index))
-            {
-                case '\n':
-                    list.add(s.substring(old, index));
-                    list.add("\n");
-                    old = index + 1;
-                    break;
-                case ' ':
-                    list.add(s.substring(old, index));
-                    old = index + 1;
-                    break;
-            }
-            index++;
-        }
-        if(old < s.length())
-        {
-            list.add(s.substring(old, index));
-        }
-        return list.toArray(new String[list.size()]);
+        shouldReset = true;
     }
     
-    public void renderTick(float lag)
+    public final void reset()
     {
-        if(worldLoaded)
+        onReset();
+        shouldReset = false;
+        done = false;
+        
+        // spawn entities
+        getData().forEachEntity((x, y, tile) -> 
         {
-            float camX = Utils.interpolate(oldCameraX, cameraX, lag);
-            float camY = Utils.interpolate(oldCameraY, cameraY, lag);
-            
-            Shader.translateTo(camX, camY);
-            Shader.updateMatrix();
-             
-            int startX = (int) (-camX / (meshSize * Tile.SIZE));
-            int startY = (int) (-camY / (meshSize * Tile.SIZE));
-            int endX = (int) Math.ceil((-camX + Shader.getViewWidth()) / (meshSize * Tile.SIZE));
-            int endY = (int) Math.ceil((-camY + Shader.getViewHeight()) / (meshSize * Tile.SIZE));
-            
-            startX = Math.min(Math.max(startX, 0), meshWidth);
-            startY = Math.min(Math.max(startY, 0), meshHeight);
-            endX = Math.min(Math.max(endX, 0), meshWidth);
-            endY = Math.min(Math.max(endY, 0), meshHeight);
-            
-            // background
-            Shader.setColorEnabled(false);
-            Shader.setTextureEnabled(true);
-            Shader.setBlendingEnabled(true);
-            TILES.bind();
-            
-            int fromLayer = 0;
-            int toLayer = data.getBackgroundIndex() + 1;
-            
-            for(int l = fromLayer; l < toLayer; l++)
-            {
-                for(int mx = startX; mx < endX; mx++)
-                {
-                    for(int my = startY; my < endY; my++)
-                    {
-                        drawMesh(l, l, mx, my);
-                    }
-                }
-            }
-            
-            // entities
-            
-            for(Entity entity : entities.values())
-            {
-                entity.renderTick(lag);
-            }
-            
-            // foreground
-            Shader.setColorEnabled(false);
-            Shader.setTextureEnabled(true);
-            Shader.setBlendingEnabled(true);
-            TILES.bind();
-            
-            fromLayer = toLayer + 1;
-            toLayer = data.getLayers();
-            
-            for(int l = fromLayer; l < toLayer; l++)
-            {
-                for(int mx = startX; mx < endX; mx++)
-                {
-                    for(int my = startY; my < endY; my++)
-                    {
-                        drawMesh(l - 1, l, mx, my);
-                    }
-                }
-            }
-            
-            // menu rendering
-            Shader.translateTo(0.0f, 0.0f);
-            Shader.updateMatrix();
-            
-            // grey background of clock and bottles
-            float lineHeight = Shader.getFontRenderer().getHeight();
-            float lineWidth = Shader.getFontRenderer().getWidth();
-            Shader.setColorEnabled(true);
-            Shader.setTextureEnabled(false);
-            Shader.getColorRenderer().drawRectangle(0.0f, 0.0f, (lineWidth * 6.0f) + 10.0f, (lineHeight * 2.0f + 10.0f), 0x77000000);
-            Shader.setTextureEnabled(true);
-            float y = 5.0f;
-            y = Shader.getFontRenderer().drawString(13.0f, y, formatBottles(souls));
-            Shader.getFontRenderer().drawString(13.0f, y, formatTime(time));
-            Shader.setColorEnabled(false);
-            
-            // draw messages
-            if(!messages.isEmpty())
+            if(tile > 0)
             {
-                String[] message = split(messages.getFirst());
-                int index = 0;
-                ArrayList<StringBuilder> list = new ArrayList<>();
-                list.add(new StringBuilder());
-                float currentWidth = 0;
-
-                float w = Shader.getViewWidth() - 26;
-                for(String s : message)
-                {
-                    if(s.equals("\n"))
-                    {
-                        currentWidth = w;
-                        continue;
-                    }
-                    Rectangle rec = Shader.getFontRenderer().getSize(s);
-                    // + lineWidth for the space
-                    if(currentWidth + rec.getWidth() + lineWidth < w)
-                    {
-                        currentWidth += rec.getWidth();
-                        
-                        StringBuilder sb = list.get(index);
-                        if(sb.length() == 0)
-                        {
-                            sb.append(s);
-                        }
-                        else
-                        {
-                            sb.append(" ");
-                            sb.append(s);
-                            
-                            currentWidth += lineWidth;
-                        }
-                    }
-                    else
-                    {
-                        StringBuilder sb = new StringBuilder();
-                        list.add(sb);
-                        index++;
-                        sb.append(s);
-                        currentWidth = rec.getWidth();
-                    }
-                }
-                float height = list.size() * lineHeight;
-                
-                Shader.setColorEnabled(true);
-                Shader.setTextureEnabled(false);
-                float messageY = Shader.getViewHeight() - height - 26;
-                Shader.getColorRenderer().drawRectangle(0.0f, messageY, Shader.getViewWidth(), Shader.getViewHeight(), 0x77000000);
-                messageY += 13;
-                Shader.setTextureEnabled(true);
-                for(StringBuilder sb : list)
+                Entity ent = EntityBuilder.fromId(tile, this, Utils.toCoord(x), Utils.toCoord(y));
+                if(ent != null)
                 {
-                    messageY = Shader.getFontRenderer().drawString(13.0f, messageY, sb.toString());
+                    entities.put(entityCounter++, ent);
                 }
-                Shader.setColorEnabled(false);
             }
-            /*Shader.setColorEnabled(true);
-            Shader.setTextureEnabled(false);
-            Shader.getColorRenderer().drawRectangle(0.0f, 0.0f, (lineWidth * 6.0f) + 10.0f, (lineHeight * 2.0f + 10.0f), 0x77000000);
-            Shader.setTextureEnabled(true);
-            y = 5.0f;
-            y = Shader.getFontRenderer().drawString(13.0f, y, formatBottles(souls));
-            Shader.getFontRenderer().drawString(13.0f, y, formatTime(time));
-            Shader.setColorEnabled(false);*/
-            
-
-            GUI.bind();
-            GUI_RENDERER.clear();
-            
-            int scale = Shader.getViewScale();
-            
-            // bottles
-            switch(scale)
-            {
-                case 1: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.0f, 0.046875f, 0.01171875f, 0.068359375f); break;
-                case 2: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.01171875f, 0.046875f, 0.037109375f, 0.0859375f); break;
-                default: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.037109375f, 0.046875f, 0.06640625f, 0.10546875f); break;
-            }
-            
-            // clock
-            switch(scale)
-            {
-                case 1: GUI_RENDERER.addRectangle(4.0f, y, 13.0f, y + 9.0f, 0.0f, 0.265625f, 0.017578125f, 0.283203125f); break;
-                case 2: GUI_RENDERER.addRectangle(4.5f, y, 13.0f, y + 8.5f, 0.017578125f, 0.265625f, 0.05078125f, 0.298828125f); break;
-                default: GUI_RENDERER.addRectangle(4.666666666f, y, 13.0f, y + 8.333333333f, 0.05078125f, 0.265625f, 0.099609375f, 0.314453125f); break;
-            }
-            
-            float w = Shader.getViewWidth();
-            
-            // gui background 
-            GUI_RENDERER.addRectangle(w - 111.0f, 0.0f, w - 44.0f, 24.0f, 0.0f, 0.0f, 0.130859375f, 0.046875f);
-            GUI_RENDERER.addRectangle(w - 44.0f, 0.0f, w - 18.0f, 16.0f, 0.130859375f, 0.0f, 0.181640625f, 0.03125f);
-            GUI_RENDERER.addRectangle(w - 76.0f, 24.0f, w - 45.0f, 57.0f, 0.068359375f, 0.046875f, 0.12890625f, 0.111328125f);
-            
-            // health mirror    
-            int healthFrame = (int) (hero.getHealth().getHealthPercent() * 7);
-            float leftMirror = (7 - healthFrame) * 0.0625f;
-            GUI_RENDERER.addRectangle(w - 39.0f, 8.0f, w - 7.0f, 46.0f, leftMirror, 0.15625f, leftMirror + 0.0625f, 0.23046875f);
-            
-            // energy
-            float energy = hero.getEnergy().getEnergyPercent();
-            float fullEnd =  w - 109.0f + 64.0f * energy;
-            GUI_RENDERER.addRectangle(w - 109.0f, 13.0f, fullEnd, 21.0f, 0.0f, 0.140625f, 0.125f * energy, 0.15625f);
-            GUI_RENDERER.addRectangle(fullEnd, 13.0f, w - 45.0f, 21.0f, 0.125f * energy, 0.125f, 0.125f, 0.140625f);
-            
-            // gui foreground
-            GUI_RENDERER.addRectangle(w - 49.0f, 0.0f, w, 64.0f, 0.201171875f, 0.0f, 0.296875f, 0.125f);
-            GUI_RENDERER.addRectangle(w - 109.0f, 15.0f, w - 106.0f, 18.0f, 0.15625f, 0.03125f, 0.162109375f, 0.037109375f);
-            GUI_RENDERER.addRectangle(w - 97.0f, 15.0f, w - 92.0f, 20.0f, 0.1796875f, 0.03125f, 0.189453125f, 0.041015625f);
-            
-            // health number overlay
-            GUI_RENDERER.addRectangle(w - 30.0f, 53.0f, w - 12.0f, 62.0f, leftMirror, 0.23828125f, leftMirror + 0.03515625f, 0.255859375f);
-            
-            GUI_RENDERER.build();
-            GUI_RENDERER.draw();
-            
-            // dynamic clock hand
-            Shader.setColorEnabled(true);
-            Shader.setTextureEnabled(false);
-            switch(scale)
-            {
-                case 1: Shader.translateTo(8.5f, y + 4.5f); break;
-                case 2: Shader.translateTo(8.75f, y + 4.25f); break;
-                default: Shader.translateTo(8.8333333333f, y + 4.16666667f); break;
-            }
-            Shader.rotate(-time * 72.0f);
-            Shader.updateMatrix();
-            Shader.getColorRenderer().drawRectangle(-0.5f / scale, -0.5f / scale, 0.5f / scale, 4.0f - 0.5f * scale, 0xFF000000);    
-        }
+        }, 0, getData().getWidth(), 0, getData().getHeight());
     }
     
+    protected void onReset()
+    {
+    };
+    
     // -------------------------------------------------------------------------
-    // collision box, interaction layer
+    // collision
     // -------------------------------------------------------------------------
     
     private Tile getInteractionTile(int x, int y)
     {
-        int i = data.getInteractionTile(x, y);
+        int i = getData().getInteractionTile(x, y);
         if(i == -1)
         {
             return Game.FALLBACK_TILE;
@@ -760,7 +212,7 @@ public final class Level implements ILevel
     
     private CollisionObject getMovementBox(int x, int y)
     {
-        int i = data.getInteractionTile(x, y);
+        int i = getData().getInteractionTile(x, y);
         if(i == -1)
         {
             return CollisionObject.NULL_BOX;
@@ -768,8 +220,7 @@ public final class Level implements ILevel
         return Game.get().getTile(i).getMovementBox(x, y, this).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
     }
     
-    @Override
-    public List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not)
+    public final List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not)
     {
         List<CollisionObject> boxes;
         if(not != null)
@@ -801,17 +252,15 @@ public final class Level implements ILevel
     
     private CollisionObject getCollisionBox(int x, int y)
     {
-        int i = data.getInteractionTile(x, y);
+        int i = getData().getInteractionTile(x, y);
         if(i == -1)
         {
             return CollisionObject.NULL_BOX;
         }
-        Tile tile = Game.get().getTile(i);
-        return tile.getCollisionBox(x, y, this).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
+        return Game.get().getTile(i).getCollisionBox(x, y, this).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
     }
     
-    @Override
-    public List<Location> getCollisionBoxesAt(CollisionObject cb)
+    public final List<Location> getCollisionBoxesAt(CollisionObject cb)
     {
         LinkedList<Location> boxes = new LinkedList<>();
         int startX = Utils.toBlock(cb.getMinX());
@@ -832,32 +281,16 @@ public final class Level implements ILevel
         return boxes;
     }
     
-    @Override
-    public List<Entity> getEntitiesCollidingWith(Entity not, CollisionObject cb)
-    {
-        return entities.values().stream().filter(ent -> ent != not && ent.getBox().isColliding(cb)).collect(Collectors.toList());
-    }
+    // -------------------------------------------------------------------------
+    // scripting
+    // -------------------------------------------------------------------------
     
-    @Override
     public void callEvent(String name, Consumer<Script> before, Consumer<Script> after)
     {
-        if(levelScript != null)
-        {
-            //Level l = Game.get().getCurrentLevel();
-            //Game.get().setCurrentLevel(this);
-            Game.get().getParser().callEvent(name, levelScript, before, after);
-            //Game.get().setCurrentLevel(l);
-        }
     }
     
-    @Override
-    public void callEvent(String name)
+    public final void callEvent(String name)
     {
         callEvent(name, null, null);
     }
-    
-    public void addMessage(String message)
-    {
-        messages.add(message);
-    }
 }

+ 4 - 0
src/me/hammerle/supersnuvi/gamelogic/LevelData.java

@@ -85,6 +85,10 @@ public class LevelData
         int length = readInt(in);
         byte[] b = new byte[length];
         in.read(b);
+        if(length > 1024 * 1024)
+        {
+            throw new IOException("invalid level file");
+        }
         return new String(b);
     }
     

+ 28 - 388
src/me/hammerle/supersnuvi/gamelogic/StartScreenLevel.java

@@ -1,73 +1,40 @@
 package me.hammerle.supersnuvi.gamelogic;
 
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Random;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import me.hammerle.snuviengine.api.Shader;
-import me.hammerle.snuviengine.api.TextureRenderer;
-import me.hammerle.snuviscript.code.Script;
+import java.util.Random;;
 import me.hammerle.supersnuvi.Game;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
-import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.Tile;
-import me.hammerle.supersnuvi.util.CollisionObject;
-import me.hammerle.supersnuvi.util.Utils;
 
-public final class StartScreenLevel implements ILevel
+public final class StartScreenLevel extends Level
 {
+    // level data
     private final LevelData data;
- 
-    private final HashMap<Integer, Entity> entities = new HashMap<>();
-    private final LinkedList<Entity> spawnQueue = new LinkedList<>();
-    private final Entity hero;
-    private int entityCounter = 0;
-   
-    private float cameraX = 0.0f;
-    private float cameraY = 0.0f;
-    private float oldCameraX = 0.0f;
-    private float oldCameraY = 0.0f;
-    
+    // level stats
+    private float time = 0.0f;
+    // controlling
     private float heroSpawnX;
     private float heroSpawnY;
     
-    private final int meshSize = 16;
-    private final int meshLayers;
-    private final int meshWidth;
-    private final int meshHeight;
-    private final TextureRenderer[][][] meshes;   
-    
     public StartScreenLevel()
     {
-        hero = EntityBuilder.buildStartScreenHero(this, 0.0f, 0.0f);
+        Entity hero = EntityBuilder.buildStartScreenHero(this, 0.0f, 0.0f);
+        setHero(hero);
         spawnEntity(hero);
         
         this.data = generate();
-
-        meshLayers = data.getLayers() - 1;
-        meshWidth = (int) Math.ceil((double) data.getWidth() / meshSize);
-        meshHeight = (int) Math.ceil((double) data.getHeight() / meshSize);
-        meshes = new TextureRenderer[meshLayers][meshWidth][meshHeight];
-        for(int l = 0; l < meshLayers; l++)
-        {
-            for(int mx = 0; mx < meshWidth; mx++)
-            {
-                for(int my = 0; my < meshHeight; my++)
-                {
-                    meshes[l][mx][my] = new TextureRenderer(meshSize * meshSize * 2);
-                }
-            }
-        }
-        
-        oldCameraX = -getViewX(hero.getCenterX());
-        oldCameraY = -getViewY(hero.getCenterY());
-        cameraX = oldCameraX;
-        cameraY = oldCameraY;
     }
     
+    // -------------------------------------------------------------------------
+    // level data
+    // -------------------------------------------------------------------------
+    
+    @Override
+    public LevelData getData()
+    {
+        return data;
+    }
+        
     private void placeLamp(LevelData ld, int ox, int oy)
     {
         ox -= 2;
@@ -91,7 +58,7 @@ public final class StartScreenLevel implements ILevel
         
         heroSpawnX = 0;
         heroSpawnY = (hh - 2) * Tile.SIZE;
-        hero.setPosition(heroSpawnX, heroSpawnY);
+        getHero().setPosition(heroSpawnX, heroSpawnY);
         
         LevelData ld = new LevelData(null, 1, l, w, h);
         
@@ -332,354 +299,27 @@ public final class StartScreenLevel implements ILevel
         return ld;
     }
 
-    // -------------------------------------------------------------------------
-    // basic stuff
-    // -------------------------------------------------------------------------
-
     @Override
-    public Entity getHero()
+    public float getTime()
     {
-        return hero;
-    }
-    
-    @Override
-    public void finishLevel()
-    {
-    }
-    
-    @Override
-    public void scheduleReset()
-    {
-    }
-    
-    @Override
-    public void spawnEntity(Entity ent)
-    {
-        spawnQueue.add(ent);
-    }
-    
-    @Override
-    public void increaseSouls(int score)
-    {
-    }
-    
-    @Override
-    public int getWidth()
-    {
-        return data.getWidth();
-    }
-    
-    @Override
-    public int getHeight()
-    {
-        return data.getHeight();
+        return time;
     }
 
     // -------------------------------------------------------------------------
-    // tick
+    // controlling
     // -------------------------------------------------------------------------
     
+    @Override
     public void tick()
     {
+        time += Game.SECS_PER_TICK;
         Game.get().tickTiles();
 
-        // doing entity logic first
-        entities.values().removeIf(entity -> 
-        {
-            entity.tick();
-            return entity.getHealth().shouldDespawn();
-        });
-        
-        if(!spawnQueue.isEmpty())
-        {
-            spawnQueue.forEach(ent -> entities.put(entityCounter++, ent));
-            spawnQueue.clear();
-        }
+        tickEntities();
         
-        if(hero.getX() > data.getWidth() * Tile.SIZE)
-        {
-            hero.setPosition(heroSpawnX, heroSpawnY);
-            // calculate new camera position
-            cameraX = -getViewX(hero.getCenterX());
-            cameraY = -getViewY(hero.getCenterY());
-            oldCameraX = cameraX;
-            oldCameraY = cameraY;
-        }
-        else
+        if(getHero().getX() > data.getWidth() * Tile.SIZE)
         {
-            // calculate new camera position
-            oldCameraX = cameraX;
-            oldCameraY = cameraY;
-            cameraX = -getViewX(hero.getCenterX());
-            cameraY = -getViewY(hero.getCenterY());
+            getHero().setPosition(heroSpawnX, heroSpawnY);
         }
-
-        // entity spawn layer after camera update
-        int startX = (int) (-cameraX / Tile.SIZE);
-        int startY = (int) (-cameraY / Tile.SIZE);
-        int endX = Math.min((int) Math.ceil((-cameraX + Shader.getViewWidth()) / Tile.SIZE), data.getWidth());
-        int endY = Math.min((int) Math.ceil((-cameraY + Shader.getViewHeight()) / Tile.SIZE), data.getWidth());
-        data.forEachEntity((x, y, tile) -> 
-        {
-            if(tile > 0)
-            {
-                data.deactivateEntity(x, y);
-
-                Entity ent = EntityBuilder.fromId(tile, this, Utils.toCoord(x), Utils.toCoord(y));
-                if(ent != null)
-                {
-                    entities.put(entityCounter++, ent);
-                }
-            }
-        }, startX, endX, startY, endY);
     }   
-    
-    private float getViewX(float x) 
-    {
-        x -= Shader.getViewWidth() >> 1;
-        if(x < 0)
-        {
-            return 0;
-        }
-        float max = data.getWidth() * Tile.SIZE - Shader.getViewWidth();
-        if(x > max)
-        {
-            return max;
-        }
-        return x;
-    }
-    
-    private float getViewY(float y) 
-    {
-        y -= Shader.getViewHeight() >> 1;
-        if(y < 0)
-        {
-            return 0;
-        }
-        float max = data.getHeight() * Tile.SIZE - Shader.getViewHeight();
-        if(y > max)
-        {
-            return max;
-        }
-        return y;
-    }
-    
-    @Override
-    public void updateTile(int layer, int x, int y)
-    {
-        if(layer > data.getBackgroundIndex())
-        {
-            layer--;
-        }
-        meshes[layer][x / meshSize][y / meshSize].clear();
-    }
-    
-    @Override
-    public void updateTile(int x, int y)
-    {
-        updateTile(data.getBackgroundIndex(), x, y);
-    }
-    
-    private void drawMesh(int l, int tl, int mx, int my)
-    {
-        TextureRenderer tr = meshes[l][mx][my];
-        if(!tr.isBuilt())
-        {
-            int tsx = mx * meshSize;
-            int tsy = my * meshSize;
-            int tex = Math.min(tsx + meshSize, data.getWidth());
-            int tey = Math.min(tsy + meshSize, data.getHeight());
-            for(int x = tsx; x < tex; x++)
-            {
-                for(int y = tsy; y < tey; y++)
-                {
-                    Tile t = Game.get().getTile(data.getTile(tl, x, y));
-                    if(t.shouldRender(x, y, this))
-                    {
-                        float minX = x * Tile.SIZE + t.getOffsetX();
-                        float minY = y * Tile.SIZE + t.getOffsetY();
-                        tr.addRectangle(minX, minY, 
-                                minX + t.getWidth(), minY + t.getHeight(), 
-                                t.getTextureMinX(x, y, this) + Level.ERROR, t.getTextureMinY(x, y, this) + Level.ERROR,
-                                t.getTextureMaxX(x, y, this) - Level.ERROR, t.getTextureMaxY(x, y, this) - Level.ERROR);
-                    }
-                }
-            }
-            tr.build();
-        }
-        meshes[l][mx][my].draw();
-    }
-    
-    public void renderTick(float lag)
-    {
-        float camX = Utils.interpolate(oldCameraX, cameraX, lag);
-        float camY = Utils.interpolate(oldCameraY, cameraY, lag);
-        
-        //System.out.println("CAM " + hero.getCenterX()+ " " + hero.getCenterY());
-        //System.out.println("CAM " + oldCameraX + " " + oldCameraY + " " + cameraX + " " + cameraY);
-
-        Shader.translateTo(camX, camY);
-        Shader.updateMatrix();
-
-        int startX = (int) (-camX / (meshSize * Tile.SIZE));
-        int startY = (int) (-camY / (meshSize * Tile.SIZE));
-        int endX = (int) Math.ceil((-camX + Shader.getViewWidth()) / (meshSize * Tile.SIZE));
-        int endY = (int) Math.ceil((-camY + Shader.getViewHeight()) / (meshSize * Tile.SIZE));
-
-        startX = Math.min(Math.max(startX, 0), meshWidth);
-        startY = Math.min(Math.max(startY, 0), meshHeight);
-        endX = Math.min(Math.max(endX, 0), meshWidth);
-        endY = Math.min(Math.max(endY, 0), meshHeight);
-
-        // background
-        Shader.setColorEnabled(false);
-        Shader.setTextureEnabled(true);
-        Shader.setBlendingEnabled(true);
-        Level.TILES.bind();
-
-        int fromLayer = 0;
-        int toLayer = data.getBackgroundIndex() + 1;
-
-        for(int l = fromLayer; l < toLayer; l++)
-        {
-            for(int mx = startX; mx < endX; mx++)
-            {
-                for(int my = startY; my < endY; my++)
-                {
-                    drawMesh(l, l, mx, my);
-                }
-            }
-        }
-
-        // entities
-
-        entities.values().forEach(entity -> 
-        {
-            entity.renderTick(lag);
-        });
-        
-        // foreground
-        Shader.setColorEnabled(false);
-        Shader.setTextureEnabled(true);
-        Shader.setBlendingEnabled(true);
-        Level.TILES.bind();
-
-        fromLayer = toLayer + 1;
-        toLayer = data.getLayers();
-
-        for(int l = fromLayer; l < toLayer; l++)
-        {
-            for(int mx = startX; mx < endX; mx++)
-            {
-                for(int my = startY; my < endY; my++)
-                {
-                    drawMesh(l - 1, l, mx, my);
-                }
-            }
-        }
-    }
-    
-    // -------------------------------------------------------------------------
-    // collision box, interaction layer
-    // -------------------------------------------------------------------------
-    
-    private Tile getInteractionTile(int x, int y)
-    {
-        int i = data.getInteractionTile(x, y);
-        if(i == -1)
-        {
-            return Game.FALLBACK_TILE;
-        }
-        return Game.get().getTile(i);
-    }
-    
-    private CollisionObject getMovementBox(int x, int y)
-    {
-        int i = data.getInteractionTile(x, y);
-        if(i == -1)
-        {
-            return CollisionObject.NULL_BOX;
-        }
-        return Game.get().getTile(i).getMovementBox(x, y, this).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
-    }
-    
-    @Override
-    public List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not)
-    {
-        List<CollisionObject> boxes;
-        if(not != null)
-        {
-            boxes = getEntitiesCollidingWith(not, box).stream().map(ent -> ent.getBox()).collect(Collectors.toList());
-        }
-        else
-        {
-            boxes = new LinkedList<>();
-        }
-        int startX = Utils.toBlock(box.getMinX());
-        int endX = Utils.toBlock(box.getMaxX());
-        int startY = Utils.toBlock(box.getMinY());
-        int endY = Utils.toBlock(box.getMaxY());
-        
-        for(int x = startX; x <= endX; x++)
-        {
-            for(int y = startY; y <= endY; y++)
-            {
-                CollisionObject cb = getMovementBox(x, y);
-                if(cb.mayCollide(box) && cb != CollisionObject.NULL_BOX)
-                {
-                    boxes.add(cb.copy());
-                }
-            }
-        }
-        return boxes;
-    }
-    
-    private CollisionObject getCollisionBox(int x, int y)
-    {
-        int i = data.getInteractionTile(x, y);
-        if(i == -1)
-        {
-            return CollisionObject.NULL_BOX;
-        }
-        Tile tile = Game.get().getTile(i);
-        return tile.getCollisionBox(x, y, this).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
-    }
-    
-    @Override
-    public List<Location> getCollisionBoxesAt(CollisionObject cb)
-    {
-        LinkedList<Location> boxes = new LinkedList<>();
-        int startX = Utils.toBlock(cb.getMinX());
-        int endX = Utils.toBlock(cb.getMaxX());
-        int startY = Utils.toBlock(cb.getMinY());
-        int endY = Utils.toBlock(cb.getMaxY());
-        
-        for(int x = startX; x <= endX; x++)
-        {
-            for(int y = startY; y <= endY; y++)
-            {
-                if(getCollisionBox(x, y).isColliding(cb))
-                {
-                    boxes.add(new Location(getInteractionTile(x, y), this, x, y));
-                }
-            }
-        }
-        return boxes;
-    }
-    
-    @Override
-    public List<Entity> getEntitiesCollidingWith(Entity not, CollisionObject cb)
-    {
-        return entities.values().stream().filter(ent -> ent != not && ent.getBox().isColliding(cb)).collect(Collectors.toList());
-    }
-
-    @Override
-    public void callEvent(String name, Consumer<Script> before, Consumer<Script> after)
-    {
-    }
-
-    @Override
-    public void callEvent(String name)
-    {
-    }
-}
+}

+ 8 - 0
src/me/hammerle/supersnuvi/rendering/IRenderer.java

@@ -0,0 +1,8 @@
+package me.hammerle.supersnuvi.rendering;
+
+import me.hammerle.snuviengine.api.Shader;
+
+public interface IRenderer<E>
+{
+    public void renderTick(Shader sh, float lag, E e);
+}

+ 470 - 0
src/me/hammerle/supersnuvi/rendering/LevelRenderer.java

@@ -0,0 +1,470 @@
+package me.hammerle.supersnuvi.rendering;
+
+import java.util.ArrayList;
+import me.hammerle.snuviengine.api.Shader;
+import me.hammerle.snuviengine.api.Texture;
+import me.hammerle.snuviengine.api.TextureRenderer;
+import me.hammerle.snuviengine.util.Rectangle;
+import me.hammerle.supersnuvi.Game;
+import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.gamelogic.Level;
+import me.hammerle.supersnuvi.gamelogic.LevelData;
+import me.hammerle.supersnuvi.tiles.Tile;
+import me.hammerle.supersnuvi.util.Utils;
+
+public class LevelRenderer implements IRenderer<Level>
+{
+    public final static float ERROR = 1f / 65536f;
+    public final static Texture TILES = new Texture("resources/tiles.png");
+    private final static Texture GUI = new Texture("resources/gui.png");
+    private final static TextureRenderer GUI_RENDERER = new TextureRenderer(60);
+    private final static int MESH_SIZE = 16;
+    
+    private float cameraX = 0.0f;
+    private float cameraY = 0.0f;
+    
+    private Level lastRenderedLevel = null;
+    private int meshLayers = 0;
+    private int meshWidth = 0;
+    private int meshHeight = 0;
+    private TextureRenderer[][][] meshes = null;
+    
+    @Override
+    public void renderTick(Shader sh, float lag, Level level)
+    {
+        updateCamera(sh, lag, level);
+        if(lastRenderedLevel != level)
+        {
+            updateMeshes(level);
+            lastRenderedLevel = level;
+        }
+
+        renderTilesAndEntities(sh, lag, level);
+        renderOverlay(sh, level);
+    }
+
+    private float getView(int screenSize, int levelSize, float pos) 
+    {
+        pos -= screenSize >> 1;
+        if(pos < 0)
+        {
+            return 0;
+        }
+        float max = levelSize * Tile.SIZE - screenSize;
+        if(pos > max)
+        {
+            return max;
+        }
+        return pos;
+    }
+    
+    private void updateCamera(Shader sh, float lag, Level level)
+    {
+        Entity hero = level.getHero();
+        
+        int levelWidth = level.getData().getWidth();
+        int levelHeight = level.getData().getHeight();
+        
+        int screenWidth = sh.getViewWidth();
+        int screenHeight = sh.getViewHeight();
+        
+        float halfHeroWidth = hero.getWidth() * 0.5f;
+        float halfHeroHeight = hero.getHeight() * 0.5f;
+        
+        float oldCamX = -getView(screenWidth, levelWidth, hero.getLastX() + halfHeroWidth);
+        float oldCamY = -getView(screenHeight, levelHeight, hero.getLastY() + halfHeroHeight);
+        float camX = -getView(screenWidth, levelWidth, hero.getX() + halfHeroWidth);
+        float camY = -getView(screenHeight, levelHeight, hero.getY() + halfHeroHeight);
+
+        cameraX = Utils.interpolate(oldCamX, camX, lag);
+        cameraY = Utils.interpolate(oldCamY, camY, lag);
+
+        sh.translateTo(cameraX, cameraY);
+        sh.updateMatrix();
+    }
+    
+    private void updateMeshes(Level level)
+    {
+        if(meshes != null)
+        {
+            for(int l = 0; l < meshLayers; l++)
+            {
+                for(int mx = 0; mx < meshWidth; mx++)
+                {
+                    for(int my = 0; my < meshHeight; my++)
+                    {
+                        meshes[l][mx][my].delete();
+                    }
+                }
+            }
+        }
+        
+        meshLayers = level.getData().getLayers() - 1;
+        meshWidth = (int) Math.ceil((double) level.getData().getWidth() / MESH_SIZE);
+        meshHeight = (int) Math.ceil((double) level.getData().getHeight() / MESH_SIZE);
+        meshes = new TextureRenderer[meshLayers][meshWidth][meshHeight];
+        for(int l = 0; l < meshLayers; l++)
+        {
+            for(int mx = 0; mx < meshWidth; mx++)
+            {
+                for(int my = 0; my < meshHeight; my++)
+                {
+                    meshes[l][mx][my] = new TextureRenderer(MESH_SIZE * MESH_SIZE * 2, false);
+                }
+            }
+        }
+    }
+    
+    private void drawMesh(Level level, int l, int tl, int mx, int my)
+    {
+        TextureRenderer tr = meshes[l][mx][my];
+        if(!tr.isBuilt())
+        {
+            LevelData data = level.getData();
+            int tsx = mx * MESH_SIZE;
+            int tsy = my * MESH_SIZE;
+            int tex = Math.min(tsx + MESH_SIZE, data.getWidth());
+            int tey = Math.min(tsy + MESH_SIZE, data.getHeight());
+            for(int x = tsx; x < tex; x++)
+            {
+                for(int y = tsy; y < tey; y++)
+                {
+                    Tile t = Game.get().getTile(data.getTile(tl, x, y));
+                    if(t.shouldRender(x, y, level))
+                    {
+                        float minX = x * Tile.SIZE + t.getOffsetX();
+                        float minY = y * Tile.SIZE + t.getOffsetY();
+                        tr.addRectangle(minX, minY, 
+                                minX + t.getWidth(), minY + t.getHeight(), 
+                                t.getTextureMinX(x, y, level) + ERROR, t.getTextureMinY(x, y, level) + ERROR,
+                                t.getTextureMaxX(x, y, level) - ERROR, t.getTextureMaxY(x, y, level) - ERROR);
+                    }
+                }
+            }
+            tr.build();
+        }
+        tr.draw();
+    }
+    
+    private void renderTilesAndEntities(Shader sh, float lag, Level level)
+    {
+        int startX = (int) (-cameraX / (MESH_SIZE * Tile.SIZE));
+        int startY = (int) (-cameraY / (MESH_SIZE * Tile.SIZE));
+        int endX = (int) Math.ceil((-cameraX + sh.getViewWidth()) / (MESH_SIZE * Tile.SIZE));
+        int endY = (int) Math.ceil((-cameraY + sh.getViewHeight()) / (MESH_SIZE * Tile.SIZE));
+
+        startX = Math.min(Math.max(startX, 0), meshWidth);
+        startY = Math.min(Math.max(startY, 0), meshHeight);
+        endX = Math.min(Math.max(endX, 0), meshWidth);
+        endY = Math.min(Math.max(endY, 0), meshHeight);
+
+        // clear meshes which need an update
+        if(level.getTileUpdater().shouldUpdateAll())
+        {
+            if(meshes != null)
+            {
+                for(int l = 0; l < meshLayers; l++)
+                {
+                    for(int mx = 0; mx < meshWidth; mx++)
+                    {
+                        for(int my = 0; my < meshHeight; my++)
+                        {
+                            meshes[l][mx][my].clear();
+                        }
+                    }
+                }
+            }
+        }
+        else
+        {
+            level.getTileUpdater().forEach((layer, x, y) -> 
+            {
+                if(layer == level.getData().getBackgroundIndex() + 1)
+                {
+                    // do not update changes on entity layer
+                    return;
+                }
+                if(layer > level.getData().getBackgroundIndex())
+                {
+                    layer--;
+                }
+                meshes[layer][x / MESH_SIZE][y / MESH_SIZE].clear();
+            });
+        }
+        level.getTileUpdater().clear();
+        
+        // background
+        sh.setColorEnabled(false);
+        sh.setTextureEnabled(true);
+        sh.setBlendingEnabled(true);
+        TILES.bind();
+        
+        LevelData data = level.getData();
+
+        int fromLayer = 0;
+        int toLayer = level.getData().getBackgroundIndex() + 1;
+
+        for(int l = fromLayer; l < toLayer; l++)
+        {
+            for(int mx = startX; mx < endX; mx++)
+            {
+                for(int my = startY; my < endY; my++)
+                {
+                    drawMesh(level, l, l, mx, my);
+                }
+            }
+        }
+
+        // entities
+        level.forEachEntity(entity -> entity.renderTick(lag));
+
+        // foreground
+        sh.setColorEnabled(false);
+        sh.setTextureEnabled(true);
+        sh.setBlendingEnabled(true);
+        TILES.bind();
+
+        fromLayer = toLayer + 1;
+        toLayer = level.getData().getLayers();
+
+        for(int l = fromLayer; l < toLayer; l++)
+        {
+            for(int mx = startX; mx < endX; mx++)
+            {
+                for(int my = startY; my < endY; my++)
+                {
+                    drawMesh(level, l - 1, l, mx, my);
+                }
+            }
+        }
+    }
+    
+    public static String formatBottles(int bottles, int maxBottles)
+    {
+        char[] c = new char[5];
+        if(bottles <= 9)
+        {
+            c[0] = '0';
+            c[1] = (char) (bottles + '0');
+        }
+        else if(bottles > 99)
+        {
+            c[0] = 'X';
+            c[1] = 'X';
+        }
+        else
+        {
+            c[0] = (char) ((bottles / 10) + '0');
+            c[1] = (char) ((bottles % 10) + '0');
+        }
+        c[2] = '/';
+        
+        int currentMaxSouls = Math.max(bottles, maxBottles);
+        if(currentMaxSouls <= 9)
+        {
+            c[3] = '0';
+            c[4] = (char) (currentMaxSouls + '0');
+        }
+        else if(currentMaxSouls > 99)
+        {
+            c[3] = 'X';
+            c[4] = 'X';
+        }
+        else
+        {
+            c[3] = (char) ((currentMaxSouls / 10) + '0');
+            c[4] = (char) ((currentMaxSouls % 10) + '0');
+        }
+        return new String(c);
+    }
+    
+    public static String formatTime(float time)
+    {
+        if(time == -1.0f)
+        {
+            return "-----";
+        }
+        else if(time >= 999.9f)
+        {
+            return "999.9";
+        }
+        return String.format("%05.1f", time);
+    }   
+    
+    private String[] split(String s)
+    {
+        ArrayList<String> list = new ArrayList<>();
+        int old = 0;
+        int index = 0;
+        while(index < s.length())
+        {
+            switch(s.charAt(index))
+            {
+                case '\n':
+                    list.add(s.substring(old, index));
+                    list.add("\n");
+                    old = index + 1;
+                    break;
+                case ' ':
+                    list.add(s.substring(old, index));
+                    old = index + 1;
+                    break;
+            }
+            index++;
+        }
+        if(old < s.length())
+        {
+            list.add(s.substring(old, index));
+        }
+        return list.toArray(new String[list.size()]);
+    }
+    
+    private void renderMessage(Shader sh, Level level)
+    {
+        String message = level.getMessage();
+        if(message != null)
+        {
+            float lineWidth = sh.getFontRenderer().getWidth();
+            String[] messageParts = split(message);
+            int index = 0;
+            ArrayList<StringBuilder> list = new ArrayList<>();
+            list.add(new StringBuilder());
+            float currentWidth = 0;
+
+            float w = sh.getViewWidth() - 26;
+            for(String s : messageParts)
+            {
+                if(s.equals("\n"))
+                {
+                    currentWidth = w;
+                    continue;
+                }
+                Rectangle rec = sh.getFontRenderer().getSize(s);
+                // + lineWidth for the space
+                if(currentWidth + rec.getWidth() + lineWidth < w)
+                {
+                    currentWidth += rec.getWidth();
+
+                    StringBuilder sb = list.get(index);
+                    if(sb.length() == 0)
+                    {
+                        sb.append(s);
+                    }
+                    else
+                    {
+                        sb.append(" ");
+                        sb.append(s);
+
+                        currentWidth += lineWidth;
+                    }
+                }
+                else
+                {
+                    StringBuilder sb = new StringBuilder();
+                    list.add(sb);
+                    index++;
+                    sb.append(s);
+                    currentWidth = rec.getWidth();
+                }
+            }
+            float height = list.size() * sh.getFontRenderer().getHeight();
+
+            sh.setColorEnabled(true);
+            sh.setTextureEnabled(false);
+            float messageY = sh.getViewHeight() - height - 26;
+            sh.getColorRenderer().drawRectangle(0.0f, messageY, sh.getViewWidth(), sh.getViewHeight(), 0x77000000);
+            messageY += 13;
+            sh.setTextureEnabled(true);
+            for(StringBuilder sb : list)
+            {
+                messageY = sh.getFontRenderer().drawString(13.0f, messageY, sb.toString());
+            }
+            sh.setColorEnabled(false);
+        }
+    }
+    
+    private void renderOverlay(Shader sh, Level level)
+    {
+        // menu rendering
+        sh.translateTo(0.0f, 0.0f);
+        sh.updateMatrix();
+
+        // grey background of clock and bottles
+        float lineHeight = sh.getFontRenderer().getHeight();
+        float lineWidth = sh.getFontRenderer().getWidth();
+        sh.setColorEnabled(true);
+        sh.setTextureEnabled(false);
+        sh.getColorRenderer().drawRectangle(0.0f, 0.0f, (lineWidth * 6.0f) + 10.0f, (lineHeight * 2.0f + 10.0f), 0x77000000);
+        sh.setTextureEnabled(true);
+        float y = 5.0f;
+        y = sh.getFontRenderer().drawString(13.0f, y, formatBottles(level.getCurrentBottles(), level.getMaxBottles()));
+        sh.getFontRenderer().drawString(13.0f, y, formatTime(level.getTime()));
+        sh.setColorEnabled(false);
+
+        // draw messages
+        renderMessage(sh, level);
+
+        GUI.bind();
+        GUI_RENDERER.clear();
+
+        int scale = sh.getViewScale();
+
+        // bottles
+        switch(scale)
+        {
+            case 1: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.0f, 0.046875f, 0.01171875f, 0.068359375f); break;
+            case 2: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.01171875f, 0.046875f, 0.037109375f, 0.0859375f); break;
+            default: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.037109375f, 0.046875f, 0.06640625f, 0.10546875f); break;
+        }
+
+        // clock
+        switch(scale)
+        {
+            case 1: GUI_RENDERER.addRectangle(4.0f, y, 13.0f, y + 9.0f, 0.0f, 0.265625f, 0.017578125f, 0.283203125f); break;
+            case 2: GUI_RENDERER.addRectangle(4.5f, y, 13.0f, y + 8.5f, 0.017578125f, 0.265625f, 0.05078125f, 0.298828125f); break;
+            default: GUI_RENDERER.addRectangle(4.666666666f, y, 13.0f, y + 8.333333333f, 0.05078125f, 0.265625f, 0.099609375f, 0.314453125f); break;
+        }
+
+        float w = sh.getViewWidth();
+
+        // gui background 
+        GUI_RENDERER.addRectangle(w - 111.0f, 0.0f, w - 44.0f, 24.0f, 0.0f, 0.0f, 0.130859375f, 0.046875f);
+        GUI_RENDERER.addRectangle(w - 44.0f, 0.0f, w - 18.0f, 16.0f, 0.130859375f, 0.0f, 0.181640625f, 0.03125f);
+        GUI_RENDERER.addRectangle(w - 76.0f, 24.0f, w - 45.0f, 57.0f, 0.068359375f, 0.046875f, 0.12890625f, 0.111328125f);
+
+        // health mirror    
+        int healthFrame = (int) (level.getHero().getHealth().getHealthPercent() * 7);
+        float leftMirror = (7 - healthFrame) * 0.0625f;
+        GUI_RENDERER.addRectangle(w - 39.0f, 8.0f, w - 7.0f, 46.0f, leftMirror, 0.15625f, leftMirror + 0.0625f, 0.23046875f);
+
+        // energy
+        float energy = level.getHero().getEnergy().getEnergyPercent();
+        float fullEnd =  w - 109.0f + 64.0f * energy;
+        GUI_RENDERER.addRectangle(w - 109.0f, 13.0f, fullEnd, 21.0f, 0.0f, 0.140625f, 0.125f * energy, 0.15625f);
+        GUI_RENDERER.addRectangle(fullEnd, 13.0f, w - 45.0f, 21.0f, 0.125f * energy, 0.125f, 0.125f, 0.140625f);
+
+        // gui foreground
+        GUI_RENDERER.addRectangle(w - 49.0f, 0.0f, w, 64.0f, 0.201171875f, 0.0f, 0.296875f, 0.125f);
+        GUI_RENDERER.addRectangle(w - 109.0f, 15.0f, w - 106.0f, 18.0f, 0.15625f, 0.03125f, 0.162109375f, 0.037109375f);
+        GUI_RENDERER.addRectangle(w - 97.0f, 15.0f, w - 92.0f, 20.0f, 0.1796875f, 0.03125f, 0.189453125f, 0.041015625f);
+
+        // health number overlay
+        GUI_RENDERER.addRectangle(w - 30.0f, 53.0f, w - 12.0f, 62.0f, leftMirror, 0.23828125f, leftMirror + 0.03515625f, 0.255859375f);
+
+        GUI_RENDERER.build();
+        GUI_RENDERER.draw();
+
+        // dynamic clock hand
+        sh.setColorEnabled(true);
+        sh.setTextureEnabled(false);
+        switch(scale)
+        {
+            case 1: sh.translateTo(8.5f, y + 4.5f); break;
+            case 2: sh.translateTo(8.75f, y + 4.25f); break;
+            default: sh.translateTo(8.8333333333f, y + 4.16666667f); break;
+        }
+        sh.rotate(-level.getTime() * 72.0f);
+        sh.updateMatrix();
+        sh.getColorRenderer().drawRectangle(-0.5f / scale, -0.5f / scale, 0.5f / scale, 4.0f - 0.5f * scale, 0xFF000000);
+    }
+}

+ 49 - 0
src/me/hammerle/supersnuvi/rendering/TileUpdater.java

@@ -0,0 +1,49 @@
+package me.hammerle.supersnuvi.rendering;
+
+import java.util.LinkedList;
+
+public class TileUpdater
+{
+    private static class Update
+    {
+        private final int l;
+        private final int x;
+        private final int y;
+        
+        public Update(int l, int x, int y)
+        {
+            this.l = l;
+            this.x = x;
+            this.y = y;
+        }
+    }
+    
+    private final LinkedList<Update> updates = new LinkedList<>();
+    private boolean updateAll = false;
+
+    public void add(int layer, int x, int y)
+    {
+        updates.add(new Update(layer, x, y));
+    }
+    
+    public void forEach(UpdateConsumer uc)
+    {
+        updates.forEach((up) -> uc.accept(up.l, up.x, up.y));
+    }
+    
+    public void updateAll()
+    {
+        updateAll = true;
+    }
+    
+    public boolean shouldUpdateAll()
+    {
+        return updateAll;
+    }
+    
+    public void clear()
+    {
+        updates.clear();
+        updateAll = false;
+    }
+}

+ 7 - 0
src/me/hammerle/supersnuvi/rendering/UpdateConsumer.java

@@ -0,0 +1,7 @@
+package me.hammerle.supersnuvi.rendering;
+
+@FunctionalInterface
+public interface UpdateConsumer
+{
+    public void accept(int layer, int x, int y);
+}

+ 5 - 5
src/me/hammerle/supersnuvi/tiles/BaseTile.java

@@ -1,6 +1,6 @@
 package me.hammerle.supersnuvi.tiles;
 
-import me.hammerle.supersnuvi.gamelogic.ILevel;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class BaseTile extends Tile
 {
@@ -18,25 +18,25 @@ public class BaseTile extends Tile
     }
     
     @Override
-    public float getTextureMinX(int x, int y, ILevel l)
+    public float getTextureMinX(int x, int y, Level l)
     {
         return tMinX;
     }
     
     @Override
-    public float getTextureMaxX(int x, int y, ILevel l)
+    public float getTextureMaxX(int x, int y, Level l)
     {
         return tMaxX;
     }
     
     @Override
-    public float getTextureMinY(int x, int y, ILevel l)
+    public float getTextureMinY(int x, int y, Level l)
     {
         return tMinY;
     }
     
     @Override
-    public float getTextureMaxY(int x, int y, ILevel l)
+    public float getTextureMaxY(int x, int y, Level l)
     {
         return tMaxY;
     }

+ 9 - 9
src/me/hammerle/supersnuvi/tiles/BottledSoulTile.java

@@ -1,12 +1,12 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.BlockDataStorage;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.SoundUtils;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class BottledSoulTile extends BaseTile
 {
@@ -21,17 +21,17 @@ public class BottledSoulTile extends BaseTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         if(ent.getItemCollector().canCollect())
         {
             if(states.add(x, y, l))
             {
-                l.updateTile(x, y);
+                l.getTileUpdater().add(l.getData().getBackgroundIndex(), x, y);
                 if(ent.getItemCollector().isHero())
                 {
                     SoundUtils.playSound(SoundUtils.Sound.COLLECT);
-                    ent.getLevel().increaseSouls(score);
+                    l.addBottles(score);
                 }
                 ent.getHealth().addHealthPercent(0.143f * score);
             }
@@ -39,7 +39,7 @@ public class BottledSoulTile extends BaseTile
     }
     
     @Override
-    public CollisionObject getCollisionBox(int x, int y, ILevel l) 
+    public CollisionObject getCollisionBox(int x, int y, Level l) 
     {
         if(states.contains(x, y, l))
         {
@@ -49,25 +49,25 @@ public class BottledSoulTile extends BaseTile
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l) 
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l) 
     {
         return false;
     }
     
     @Override
-    public boolean shouldRender(int x, int y, ILevel l) 
+    public boolean shouldRender(int x, int y, Level l) 
     {
         return !states.contains(x, y, l);
     }
     
     @Override
-    public void reset(ILevel l) 
+    public void reset(Level l) 
     {
         states.clear(l);
     } 
     
     @Override
-    public void reset(int x, int y, ILevel l)
+    public void reset(int x, int y, Level l)
     {
         states.clear(x, y, l);
     }

+ 8 - 8
src/me/hammerle/supersnuvi/tiles/CrumblingStoneTile.java

@@ -2,13 +2,13 @@ package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.BlockDataStorage;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.SoundUtils;
 import me.hammerle.supersnuvi.util.Utils;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class CrumblingStoneTile extends BaseTile
 {
@@ -22,7 +22,7 @@ public class CrumblingStoneTile extends BaseTile
     }
 
     @Override
-    public CollisionObject getCollisionBox(int x, int y, ILevel l) 
+    public CollisionObject getCollisionBox(int x, int y, Level l) 
     {
         if(states.contains(x, y, l))
         {
@@ -32,7 +32,7 @@ public class CrumblingStoneTile extends BaseTile
     }
 
     @Override
-    public CollisionObject getMovementBox(int x, int y, ILevel l) 
+    public CollisionObject getMovementBox(int x, int y, Level l) 
     {
         if(states.contains(x, y, l))
         {
@@ -42,14 +42,14 @@ public class CrumblingStoneTile extends BaseTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         super.onEntityCollide(ent, x, y, face, l);
         if(face == Face.UP)
         {
             if(states.add(x, y, l))
             {
-                ent.getLevel().updateTile(x, y);         
+                l.getTileUpdater().add(l.getData().getBackgroundIndex(), x, y);      
                 l.spawnEntity(EntityBuilder.buildCrumblingStone(l, Utils.toCoord(x), Utils.toCoord(y)));
                 SoundUtils.playSound(SoundUtils.Sound.STONE_CRUMBLING);
             }
@@ -57,19 +57,19 @@ public class CrumblingStoneTile extends BaseTile
     }
 
     @Override
-    public boolean shouldRender(int x, int y, ILevel l)
+    public boolean shouldRender(int x, int y, Level l)
     {
         return !states.contains(x, y, l);
     }
 
     @Override
-    public void reset(ILevel l) 
+    public void reset(Level l) 
     {
         states.clear(l);
     } 
 
     @Override
-    public void reset(int x, int y, ILevel l)
+    public void reset(int x, int y, Level l)
     {
         states.clear(x, y, l);
     }

+ 3 - 3
src/me/hammerle/supersnuvi/tiles/GoalTile.java

@@ -1,9 +1,9 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class GoalTile extends BaseTile
 {
@@ -14,11 +14,11 @@ public class GoalTile extends BaseTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         if(ent.getItemCollector().isHero())
         {
-            ent.getLevel().finishLevel();
+            l.finishLevel();
         }
     }
 

+ 9 - 9
src/me/hammerle/supersnuvi/tiles/HeadHitOnceTile.java

@@ -1,9 +1,9 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.BlockDataStorage;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class HeadHitOnceTile extends BaseBoxTile
 {
@@ -24,35 +24,35 @@ public class HeadHitOnceTile extends BaseBoxTile
     }
 
     @Override
-    public float getTextureMinX(int x, int y, ILevel l)
+    public float getTextureMinX(int x, int y, Level l)
     {
         return states.contains(x, y, l) ? tMinX2 : super.getTextureMinX(x, y, l);
     }
 
     @Override
-    public float getTextureMinY(int x, int y, ILevel l)
+    public float getTextureMinY(int x, int y, Level l)
     {
         return states.contains(x, y, l) ? tMinY2 : super.getTextureMinY(x, y, l);
     }
 
     @Override
-    public float getTextureMaxX(int x, int y, ILevel l)
+    public float getTextureMaxX(int x, int y, Level l)
     {
         return states.contains(x, y, l) ? tMaxX2 : super.getTextureMaxX(x, y, l);
     }
 
     @Override
-    public float getTextureMaxY(int x, int y, ILevel l)
+    public float getTextureMaxY(int x, int y, Level l)
     {
         return states.contains(x, y, l) ? tMaxY2 : super.getTextureMaxY(x, y, l); 
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l)
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l)
     {
         if(face == Face.DOWN && ent.getMotionY() < 0 && states.add(x, y, l))
         {
-            l.updateTile(x, y);
+            l.getTileUpdater().add(l.getData().getBackgroundIndex(), x, y);
             l.callEvent("tile_hit", (sc) -> 
             {
                 sc.setVar("tile_x", (double) x);
@@ -66,13 +66,13 @@ public class HeadHitOnceTile extends BaseBoxTile
     }
     
     @Override
-    public void reset(ILevel l) 
+    public void reset(Level l) 
     {
         states.clear(l);
     } 
     
     @Override
-    public void reset(int x, int y, ILevel l)
+    public void reset(int x, int y, Level l)
     {
         states.clear(x, y, l);
     }

+ 2 - 2
src/me/hammerle/supersnuvi/tiles/HeadHitTile.java

@@ -1,8 +1,8 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class HeadHitTile extends BaseBoxTile
 {
@@ -12,7 +12,7 @@ public class HeadHitTile extends BaseBoxTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l)
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l)
     {
         if(face == Face.DOWN && ent.getMotionY() < 0)
         {

+ 3 - 3
src/me/hammerle/supersnuvi/tiles/InteractTile.java

@@ -2,9 +2,9 @@ package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.Keys;
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class InteractTile extends BaseTile
 {
@@ -16,13 +16,13 @@ public class InteractTile extends BaseTile
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return false;
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l)
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l)
     {
         if(ent.getItemCollector().isHero() && Keys.UP.getTime() == 1 && face == Face.DOWN)
         {

+ 2 - 2
src/me/hammerle/supersnuvi/tiles/KillTile.java

@@ -1,9 +1,9 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class KillTile extends BaseTile
 {
@@ -14,7 +14,7 @@ public class KillTile extends BaseTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         super.onEntityCollide(ent, x, y, face, l);
         ent.getHealth().addHealth(-14.3f);

+ 4 - 4
src/me/hammerle/supersnuvi/tiles/Location.java

@@ -1,15 +1,15 @@
 package me.hammerle.supersnuvi.tiles;
 
-import me.hammerle.supersnuvi.gamelogic.ILevel;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class Location 
 {
     private final Tile tile;
-    private final ILevel l;
+    private final Level l;
     private final int x;
     private final int y;
     
-    public Location(Tile tile, ILevel l, int x, int y)
+    public Location(Tile tile, Level l, int x, int y)
     {
         this.tile = tile;
         this.l = l;
@@ -22,7 +22,7 @@ public class Location
         return tile;
     }
     
-    public ILevel getLevel() 
+    public Level getLevel() 
     {
         return l;
     }

+ 3 - 3
src/me/hammerle/supersnuvi/tiles/NullTile.java

@@ -1,6 +1,6 @@
 package me.hammerle.supersnuvi.tiles;
 
-import me.hammerle.supersnuvi.gamelogic.ILevel;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class NullTile extends Tile
 {
@@ -9,13 +9,13 @@ public class NullTile extends Tile
     }
 
     @Override
-    public boolean shouldRender(int x, int y, ILevel l)
+    public boolean shouldRender(int x, int y, Level l)
     {
         return false;
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return false;
     }

+ 3 - 3
src/me/hammerle/supersnuvi/tiles/PressureTile.java

@@ -1,10 +1,10 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class PressureTile extends BaseTile
 {
@@ -41,13 +41,13 @@ public class PressureTile extends BaseTile
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return false;
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l)
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l)
     {
         if(face == Face.UP)
         {

+ 2 - 2
src/me/hammerle/supersnuvi/tiles/RampTile.java

@@ -1,7 +1,7 @@
 package me.hammerle.supersnuvi.tiles;
 
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.CollisionLine;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class RampTile extends BaseTile
 {
@@ -15,7 +15,7 @@ public class RampTile extends BaseTile
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return false;
     }

+ 2 - 2
src/me/hammerle/supersnuvi/tiles/SlipperyTile.java

@@ -1,8 +1,8 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class SlipperyTile extends BaseBoxTile
 {
@@ -12,7 +12,7 @@ public class SlipperyTile extends BaseBoxTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         if(face == Face.UP)
         {

+ 4 - 4
src/me/hammerle/supersnuvi/tiles/SpikeTile.java

@@ -2,14 +2,14 @@ package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
-import me.hammerle.supersnuvi.gamelogic.Level;
+import me.hammerle.supersnuvi.rendering.LevelRenderer;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class SpikeTile extends BaseTile
 {
-    private final static Texture.Animation SPIKES = Level.TILES.addAnimation(96, 32, 
+    private final static Texture.Animation SPIKES = LevelRenderer.TILES.addAnimation(96, 32, 
             "resources/spike_trap/spike_trap_frame1.png", "resources/spike_trap/spike_trap_frame2.png",
             "resources/spike_trap/spike_trap_frame3.png", "resources/spike_trap/spike_trap_frame4.png",
             "resources/spike_trap/spike_trap_frame5.png");
@@ -26,7 +26,7 @@ public class SpikeTile extends BaseTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         super.onEntityCollide(ent, x, y, face, l);
         if(frame >= 1)

+ 3 - 3
src/me/hammerle/supersnuvi/tiles/StartTile.java

@@ -1,17 +1,17 @@
 package me.hammerle.supersnuvi.tiles;
 
-import me.hammerle.supersnuvi.gamelogic.ILevel;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class StartTile extends Tile
 {
     @Override
-    public boolean shouldRender(int x, int y, ILevel l)
+    public boolean shouldRender(int x, int y, Level l)
     {
         return false;
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return false;
     }

+ 12 - 12
src/me/hammerle/supersnuvi/tiles/Tile.java

@@ -1,9 +1,9 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public abstract class Tile 
 {
@@ -66,7 +66,7 @@ public abstract class Tile
         return this;
     }
     
-    public CollisionObject getCollisionBox(int x, int y, ILevel l)
+    public CollisionObject getCollisionBox(int x, int y, Level l)
     {
         if(collisionBox != null)
         {
@@ -75,12 +75,12 @@ public abstract class Tile
         return CollisionObject.NULL_BOX.reset();
     }
     
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return true;
     }
     
-    public CollisionObject getMovementBox(int x, int y, ILevel l)
+    public CollisionObject getMovementBox(int x, int y, Level l)
     {
         if(movementCollision != null)
         {
@@ -89,7 +89,7 @@ public abstract class Tile
         return CollisionObject.NULL_BOX.reset();
     }
     
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l)
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l)
     {
         
     }
@@ -98,11 +98,11 @@ public abstract class Tile
     {
     }
     
-    public void reset(ILevel l)
+    public void reset(Level l)
     {
     }
     
-    public void reset(int x, int y, ILevel l)
+    public void reset(int x, int y, Level l)
     {
     }
     
@@ -111,22 +111,22 @@ public abstract class Tile
         return 0;
     }
     
-    public float getTextureMinX(int x, int y, ILevel l)
+    public float getTextureMinX(int x, int y, Level l)
     {
         return 0.0f;
     }
     
-    public float getTextureMaxX(int x, int y, ILevel l)
+    public float getTextureMaxX(int x, int y, Level l)
     {
         return 0.0625f;
     }
     
-    public float getTextureMinY(int x, int y, ILevel l)
+    public float getTextureMinY(int x, int y, Level l)
     {
         return 0.0f;
     }
     
-    public float getTextureMaxY(int x, int y, ILevel l)
+    public float getTextureMaxY(int x, int y, Level l)
     {
         return 0.0625f;
     }
@@ -151,7 +151,7 @@ public abstract class Tile
         return Tile.SIZE;
     }
     
-    public boolean shouldRender(int x, int y, ILevel l)
+    public boolean shouldRender(int x, int y, Level l)
     {
         return true;
     }

+ 2 - 2
src/me/hammerle/supersnuvi/tiles/TrampolinTile.java

@@ -1,9 +1,9 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.SoundUtils;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class TrampolinTile extends BaseBoxTile
 {
@@ -13,7 +13,7 @@ public class TrampolinTile extends BaseBoxTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         if(face == Face.UP)
         {

+ 5 - 5
src/me/hammerle/supersnuvi/tiles/WaterTile.java

@@ -2,15 +2,15 @@ package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.snuviengine.api.Texture.Animation;
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
-import me.hammerle.supersnuvi.gamelogic.Level;
+import me.hammerle.supersnuvi.rendering.LevelRenderer;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.Face;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class WaterTile extends BaseTile
 {
-    private final static Animation WATER = Level.TILES.addAnimation(160, 0, 
+    private final static Animation WATER = LevelRenderer.TILES.addAnimation(160, 0, 
             "resources/water/water_frame1.png", "resources/water/water_frame2.png",
             "resources/water/water_frame3.png", "resources/water/water_frame4.png",
             "resources/water/water_frame5.png", "resources/water/water_frame6.png",
@@ -28,7 +28,7 @@ public class WaterTile extends BaseTile
     }
 
     @Override
-    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l) 
+    public void onEntityCollide(Entity ent, int x, int y, Face face, Level l) 
     {
         super.onEntityCollide(ent, x, y, face, l);
         ent.getMovement().setInWater(true);
@@ -39,7 +39,7 @@ public class WaterTile extends BaseTile
     }
 
     @Override
-    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    public boolean shouldAiUseCollisionBox(int x, int y, Level l)
     {
         return false;
     }

+ 7 - 7
src/me/hammerle/supersnuvi/util/BlockDataStorage.java

@@ -2,7 +2,7 @@ package me.hammerle.supersnuvi.util;
 
 import java.util.HashSet;
 import java.util.Objects;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
+import me.hammerle.supersnuvi.gamelogic.Level;
 
 public class BlockDataStorage
 {
@@ -10,9 +10,9 @@ public class BlockDataStorage
     {
         private int x;
         private int y;
-        private ILevel level;
+        private Level level;
         
-        private Data(int x, int y, ILevel level)
+        private Data(int x, int y, Level level)
         {
             this.x = x;
             this.y = y;
@@ -66,12 +66,12 @@ public class BlockDataStorage
         
     }
     
-    public boolean add(int x, int y, ILevel level)
+    public boolean add(int x, int y, Level level)
     {
         return data.add(new Data(x, y, level));
     }
     
-    public boolean contains(int x, int y, ILevel level)
+    public boolean contains(int x, int y, Level level)
     {
         containCache.x = x;
         containCache.y = y;
@@ -79,12 +79,12 @@ public class BlockDataStorage
         return data.contains(containCache);
     }
     
-    public void clear(ILevel level)
+    public void clear(Level level)
     {
         data.removeIf(d -> d.level == level);
     }
     
-    public void clear(int x, int y, ILevel level)
+    public void clear(int x, int y, Level level)
     {
         data.removeIf(d -> d.level == level && x == d.x && y == d.y);
     }