Procházet zdrojové kódy

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 před 5 roky
rodič
revize
0ce879b152
43 změnil soubory, kde provedl 1058 přidání a 1270 odebrání
  1. binární
      lib/SnuviEngine.jar
  2. binární
      resources/tiles.png
  3. binární
      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ární
lib/SnuviEngine.jar


binární
resources/tiles.png


binární
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);
     }