Jelajahi Sumber

removed outdated snuvi text dependency; added snuvi script recoded dependency; added parser, scheduler, logger; several super snuvi related commands and events; removed old outdated key bindings; added base interact tile; raise max bottles if necessary; added message renderer; added test scripts for each level

Kajetan Johannes Hammerle 6 tahun lalu
induk
melakukan
c580ae9e2e

TEMPAT SAMPAH
levels/00-Tech_Demo.map


+ 45 - 0
levels/00-Tech_Demo.snuvi

@@ -0,0 +1,45 @@
+event.load("level_reset");
+event.load("tile_interact");
+
+@main
+wait();
+
+if(event == "level_reset")
+{
+    onLevelReset();
+}
+
+if(event == "tile_interact")
+{
+    onInteract(tile_x, tile_y);
+}
+
+goto(@main);
+
+function onLevelReset()
+{
+    hero = entity.getHero();
+    x = tile.toTileCoord(entity.getX(hero));
+    y = tile.toTileCoord(entity.getY(hero));
+
+    y -= 1;
+
+    x = tile.toLevelCoord(x);
+    y = tile.toLevelCoord(y);
+
+    entity.teleport(hero, x, y);
+
+    level.setTile(level.getBackgroundIndex(), 0, 12, 70);
+    level.setTile(level.getBackgroundIndex(), 4, 12, -1);
+}
+
+function onInteract(x, y)
+{
+    level.setTile(level.getBackgroundIndex(), 4, 12, 6);
+    newLine = text.fromCode(10);
+
+    message = text.concat("Hi, ein neuer Block ist erschienen.", newLine, "Hi, ein neuer Block ist erschienen. Hi, ein neuer Block ist erschienen. Hi, ein neuer Block ist erschienen. Hi, ein neuer Block ist erschienen. Hi, ein neuer Block ist erschienen. Hi, ein neuer Block ist erschienen. Hi, ein neuer Block ist erschienen.");
+
+    level.addMessage(message);
+}
+

TEMPAT SAMPAH
levels/01-Tech_Demo.map


+ 10 - 0
levels/01-Tech_Demo.snuvi

@@ -0,0 +1,10 @@
+print("Hi");
+event.load("level_reset");
+
+@main
+wait();
+
+print(event, 2);
+
+goto(@main);
+

+ 0 - 0
levels/02-Parabola.snuvi


+ 0 - 0
levels/03-Higher_Ground.snuvi


+ 0 - 0
levels/04-Out_of_Reach.snuvi


+ 0 - 0
levels/05-Like_Ice.snuvi


TEMPAT SAMPAH
lib/SnuviEngine.jar


TEMPAT SAMPAH
lib/SnuviScriptRecoded.jar


TEMPAT SAMPAH
resources/tiles.png


TEMPAT SAMPAH
resources/tiles.xcf


+ 4 - 1
slot1.txt

@@ -1,5 +1,5 @@
 level.00-Tech_Demo.map=true
 level.00-Tech_Demo.map=true
-level.00-Tech_Demo.map.bottles=9
+level.00-Tech_Demo.map.bottles=18
 level.00-Tech_Demo.map.time=1.5374999999999965
 level.00-Tech_Demo.map.time=1.5374999999999965
 level.01-Parabola.map=true
 level.01-Parabola.map=true
 level.01-Parabola.map.bottles=4
 level.01-Parabola.map.bottles=4
@@ -16,3 +16,6 @@ level.02-Parabola.map.time=15.950043
 level.04-Like_Ice.map=true
 level.04-Like_Ice.map=true
 level.04-Like_Ice.map.bottles=13
 level.04-Like_Ice.map.bottles=13
 level.04-Like_Ice.map.time=20.69997
 level.04-Like_Ice.map.time=20.69997
+level.04-Out_of_Reach.map=true
+level.04-Out_of_Reach.map.bottles=8
+level.04-Out_of_Reach.map.time=36.54973

+ 80 - 20
src/me/hammerle/supersnuvi/Game.java

@@ -8,11 +8,16 @@ import me.hammerle.snuviengine.api.Engine;
 import me.hammerle.snuviengine.api.FontRenderer;
 import me.hammerle.snuviengine.api.FontRenderer;
 import me.hammerle.snuviengine.api.KeyBinding;
 import me.hammerle.snuviengine.api.KeyBinding;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Shader;
+import me.hammerle.snuviscript.code.SnuviParser;
+import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.gamelogic.StartScreenLevel;
 import me.hammerle.supersnuvi.gamelogic.StartScreenLevel;
 import me.hammerle.supersnuvi.savegame.SimpleConfig;
 import me.hammerle.supersnuvi.savegame.SimpleConfig;
+import me.hammerle.supersnuvi.snuviscript.SnuviLogger;
+import me.hammerle.supersnuvi.snuviscript.SnuviScheduler;
 import me.hammerle.supersnuvi.tiles.*;
 import me.hammerle.supersnuvi.tiles.*;
 import me.hammerle.supersnuvi.util.SoundUtils;
 import me.hammerle.supersnuvi.util.SoundUtils;
+import me.hammerle.supersnuvi.util.Utils;
 
 
 public class Game extends Engine
 public class Game extends Engine
 {
 {
@@ -35,7 +40,7 @@ public class Game extends Engine
     public static final NullTile FALLBACK_TILE = new NullTile();
     public static final NullTile FALLBACK_TILE = new NullTile();
     
     
     // tiles
     // tiles
-    private final Tile[] registeredTiles = new Tile[70];
+    private final Tile[] registeredTiles = new Tile[71];
     
     
     // levels
     // levels
     private Level currentLevel = null;
     private Level currentLevel = null;
@@ -43,6 +48,11 @@ public class Game extends Engine
     private int levelIndex = 0;
     private int levelIndex = 0;
     private final StartScreenLevel startScreenLevel = new StartScreenLevel();
     private final StartScreenLevel startScreenLevel = new StartScreenLevel();
     
     
+    // scripting
+    private final SnuviLogger snuviLogger = new SnuviLogger();
+    private final SnuviScheduler snuviScheduler = new SnuviScheduler();
+    private final SnuviParser snuviParser = new SnuviParser(snuviLogger, snuviScheduler);
+    
     // config and savegames
     // config and savegames
     private final SimpleConfig config = new SimpleConfig("options.txt", true);
     private final SimpleConfig config = new SimpleConfig("options.txt", true);
     private final SimpleConfig[] saveSlots = new SimpleConfig[] 
     private final SimpleConfig[] saveSlots = new SimpleConfig[] 
@@ -66,25 +76,26 @@ public class Game extends Engine
     {
     {
         instance = this;
         instance = this;
         registerTiles();
         registerTiles();
+        addSnuviCommands();
         
         
         File[] files = new File("./levels").listFiles();
         File[] files = new File("./levels").listFiles();
         Arrays.sort(files, (o1, o2) -> o1.compareTo(o2));
         Arrays.sort(files, (o1, o2) -> o1.compareTo(o2));
         ArrayList<Level> levelList = new ArrayList<>();
         ArrayList<Level> levelList = new ArrayList<>();
         for(File file : files)
         for(File file : files)
         {
         {
-            if(file.isFile())
+            if(file.isFile() && file.getName().endsWith(".map"))
             {
             {
                 Level l = new Level(file);
                 Level l = new Level(file);
                 levelList.add(l);
                 levelList.add(l);
             }
             }
         }
         }
-        levels = levelList.toArray(new Level[levelList.size()]);
+        levels = levelList.toArray(new Level[levelList.size()]); 
     }
     }
     
     
     @Override
     @Override
     public void init()
     public void init()
     {
     {
-        setMaxFps(120);
+        setMaxFps(60);
         setNanosPerTick(MS_PER_TICK * 1_000_000);
         setNanosPerTick(MS_PER_TICK * 1_000_000);
     }
     }
     
     
@@ -100,6 +111,8 @@ public class Game extends Engine
             SoundUtils.playSound(SoundUtils.Sound.SONG_1);
             SoundUtils.playSound(SoundUtils.Sound.SONG_1);
             SoundUtils.stopSound(SoundUtils.Sound.MENU_MUSIC);
             SoundUtils.stopSound(SoundUtils.Sound.MENU_MUSIC);
             
             
+            snuviScheduler.setActiveLevel(currentLevel);
+            snuviScheduler.tick(currentLevel);
             currentLevel.tick();
             currentLevel.tick();
             
             
             // doing that here to prevent concurent modification
             // doing that here to prevent concurent modification
@@ -230,13 +243,13 @@ public class Game extends Engine
                                 }
                                 }
                                 optionsDirty = true;
                                 optionsDirty = true;
                                 break;
                                 break;
-                            case 15: // save options
+                            case 9: // save options
                                 Keys.write(config);
                                 Keys.write(config);
                                 config.set("sound", sound);
                                 config.set("sound", sound);
                                 config.save();
                                 config.save();
                                 optionsDirty = false;
                                 optionsDirty = false;
                                 break;
                                 break;
-                            case 16: // go back
+                            case 10: // go back
                                 screen = 0;
                                 screen = 0;
                                 break;
                                 break;
                             default: // rebind keys
                             default: // rebind keys
@@ -390,8 +403,8 @@ public class Game extends Engine
                 
                 
                 float left = width * 0.2f;
                 float left = width * 0.2f;
                 float right = width * 0.8f;
                 float right = width * 0.8f;
-                float top = (height - line * 21.0f) * 0.5f;
-                float bottom = top + line * 21.0f;
+                float top = (height - line * 15.0f) * 0.5f;
+                float bottom = top + line * 15.0f;
                 
                 
                 Shader.setTextureEnabled(false);
                 Shader.setTextureEnabled(false);
                 Shader.setColorEnabled(true);
                 Shader.setColorEnabled(true);
@@ -429,18 +442,6 @@ public class Game extends Engine
                 y = fr.drawString(secLeft, y, getKeyName(Keys.ESCAPE)); 
                 y = fr.drawString(secLeft, y, getKeyName(Keys.ESCAPE)); 
                 fr.drawString(left, y, "K: Enter");
                 fr.drawString(left, y, "K: Enter");
                 y = fr.drawString(secLeft, y, getKeyName(Keys.ENTER)); 
                 y = fr.drawString(secLeft, y, getKeyName(Keys.ENTER)); 
-                fr.drawString(left, y, "K: Combat");
-                y = fr.drawString(secLeft, y, getKeyName(Keys.COMBAT)); 
-                fr.drawString(left, y, "K: Switch Face");
-                y = fr.drawString(secLeft, y, getKeyName(Keys.COMBAT_SWITCH_FACE)); 
-                fr.drawString(left, y, "K: Dash/Dodge");
-                y = fr.drawString(secLeft, y, getKeyName(Keys.COMBAT_DASH)); 
-                fr.drawString(left, y, "K: Dash/Dodge");
-                y = fr.drawString(secLeft, y, getKeyName(Keys.COMBAT_DODGE)); 
-                fr.drawString(left, y, "K: Block");
-                y = fr.drawString(secLeft, y, getKeyName(Keys.COMBAT_BLOCK)); 
-                fr.drawString(left, y, "K: Attack");
-                y = fr.drawString(secLeft, y, getKeyName(Keys.COMBAT_ATTACK)); 
                 if(optionsDirty)
                 if(optionsDirty)
                 {
                 {
                     y = fr.drawString(left, y, true, "&cSave");
                     y = fr.drawString(left, y, true, "&cSave");
@@ -622,6 +623,9 @@ public class Game extends Engine
                 registeredTiles[45 + 5 * y + x] = new BaseTile(0.0625f * x, 0.25f + 0.0625f * y, 0.0625f * (x + 1), 0.25f + 0.0625f * (y + 1));
                 registeredTiles[45 + 5 * y + x] = new BaseTile(0.0625f * x, 0.25f + 0.0625f * y, 0.0625f * (x + 1), 0.25f + 0.0625f * (y + 1));
             }
             }
         }
         }
+        
+        // test interact tile
+        registeredTiles[70] = new InteractTile(0.5625f, 0.0f, 0.625f, 0.0625f);
     }
     }
     
     
     public Tile getTile(int id)
     public Tile getTile(int id)
@@ -688,4 +692,60 @@ public class Game extends Engine
     {
     {
         SoundUtils.closeSounds();
         SoundUtils.closeSounds();
     }
     }
+    
+    public SnuviParser getParser()
+    {
+        return snuviParser;
+    }
+    
+    public Level getCurrentLevel()
+    {
+        return currentLevel;
+    }
+    
+    public void setCurrentLevel(Level l)
+    {
+        snuviScheduler.setActiveLevel(l);
+        currentLevel = l;
+    }
+    
+    private void addSnuviCommands()
+    {
+        snuviParser.registerFunction("level.getwidth", (sc, in) -> (double) currentLevel.getWidth());
+        snuviParser.registerFunction("level.getheight", (sc, in) -> (double) currentLevel.getHeight());
+        snuviParser.registerFunction("level.getlayers", (sc, in) -> (double) currentLevel.getData().getLayers());
+        snuviParser.registerFunction("level.getbackgroundindex", (sc, in) -> (double) currentLevel.getData().getBackgroundIndex());
+        snuviParser.registerFunction("level.settile", (sc, in) -> 
+        {
+            int layer = in[0].getInt(sc);
+            int x = in[1].getInt(sc);
+            int y = in[2].getInt(sc);
+            int tile = in[3].getInt(sc);
+            currentLevel.getData().setTile(layer, x, y, tile);
+            currentLevel.updateTile(layer, x, y);
+            Game.get().getTile(tile).reset(x, y, currentLevel);
+            return Void.TYPE;
+        });
+        snuviParser.registerFunction("level.addmessage", (sc, in) -> 
+        {
+            currentLevel.addMessage(in[0].getString(sc));
+            return Void.TYPE;
+        });
+        
+        snuviParser.registerFunction("tile.totilecoord", (sc, in) -> (double) Utils.toBlock(in[0].getFloat(sc)));
+        snuviParser.registerFunction("tile.tolevelcoord", (sc, in) -> (double) Utils.toCoord(in[0].getInt(sc)));
+        
+        snuviParser.registerFunction("entity.gethero", (sc, in) -> currentLevel.getHero());
+        snuviParser.registerFunction("entity.getx", (sc, in) -> (double) ((Entity) in[0].get(sc)).getX());
+        snuviParser.registerFunction("entity.gety", (sc, in) -> (double) ((Entity) in[0].get(sc)).getY());
+        snuviParser.registerFunction("entity.teleport", (sc, in) -> 
+        {
+            Entity ent = (Entity) in[0].get(sc);
+            float x = in[1].getFloat(sc);
+            float y = in[2].getFloat(sc);
+            ent.setPosition(x, y);
+            ent.setPosition(x, y);
+            return Void.TYPE;
+        });
+    }
 }
 }

+ 1 - 23
src/me/hammerle/supersnuvi/Keys.java

@@ -17,18 +17,10 @@ public class Keys
     public final static KeyBinding ESCAPE = KeyHandler.register(GLFW_KEY_ESCAPE);
     public final static KeyBinding ESCAPE = KeyHandler.register(GLFW_KEY_ESCAPE);
     public final static KeyBinding ENTER = KeyHandler.register(GLFW_KEY_ENTER);
     public final static KeyBinding ENTER = KeyHandler.register(GLFW_KEY_ENTER);
     
     
-    public final static KeyBinding COMBAT = KeyHandler.register(GLFW_KEY_A);
-    public final static KeyBinding COMBAT_SWITCH_FACE = KeyHandler.register(GLFW_KEY_S);
-    public final static KeyBinding COMBAT_DASH = KeyHandler.register(GLFW_KEY_D);
-    public final static KeyBinding COMBAT_DODGE = KeyHandler.register(GLFW_KEY_F);
-    public final static KeyBinding COMBAT_BLOCK = KeyHandler.register(GLFW_KEY_G);
-    public final static KeyBinding COMBAT_ATTACK = KeyHandler.register(GLFW_KEY_H);
-    
     private final static KeyBinding[] KEYS = 
     private final static KeyBinding[] KEYS = 
     {
     {
         UP, DOWN, LEFT, RIGHT, 
         UP, DOWN, LEFT, RIGHT, 
-        JUMP, RUN, ESCAPE, ENTER, 
-        COMBAT, COMBAT_SWITCH_FACE, COMBAT_DASH, COMBAT_DODGE, COMBAT_BLOCK, COMBAT_ATTACK
+        JUMP, RUN, ESCAPE, ENTER
     };
     };
     
     
     public static void rebind(KeyBinding binding)
     public static void rebind(KeyBinding binding)
@@ -57,13 +49,6 @@ public class Keys
         KeyHandler.rebind(RUN, config.getInt("key.run", GLFW_KEY_LEFT_SHIFT));
         KeyHandler.rebind(RUN, config.getInt("key.run", GLFW_KEY_LEFT_SHIFT));
         KeyHandler.rebind(ESCAPE, config.getInt("key.escape", GLFW_KEY_ESCAPE));
         KeyHandler.rebind(ESCAPE, config.getInt("key.escape", GLFW_KEY_ESCAPE));
         KeyHandler.rebind(ENTER, config.getInt("key.enter", GLFW_KEY_ENTER));
         KeyHandler.rebind(ENTER, config.getInt("key.enter", GLFW_KEY_ENTER));
-        
-        KeyHandler.rebind(COMBAT, config.getInt("key.combat", GLFW_KEY_A));
-        KeyHandler.rebind(COMBAT_SWITCH_FACE, config.getInt("key.combat.switchface", GLFW_KEY_S));
-        KeyHandler.rebind(COMBAT_DASH, config.getInt("key.combat.dash", GLFW_KEY_D));
-        KeyHandler.rebind(COMBAT_DODGE, config.getInt("key.combat.dodge", GLFW_KEY_F));
-        KeyHandler.rebind(COMBAT_BLOCK, config.getInt("key.combat.block", GLFW_KEY_G));
-        KeyHandler.rebind(COMBAT_ATTACK, config.getInt("key.combat.attack", GLFW_KEY_H));
     }
     }
     
     
     public static void write(SimpleConfig config)
     public static void write(SimpleConfig config)
@@ -77,12 +62,5 @@ public class Keys
         config.set("key.run", RUN.getKey());
         config.set("key.run", RUN.getKey());
         config.set("key.escape", ESCAPE.getKey());
         config.set("key.escape", ESCAPE.getKey());
         config.set("key.enter", ENTER.getKey());
         config.set("key.enter", ENTER.getKey());
-
-        config.set("key.combat", COMBAT.getKey());
-        config.set("key.combat.switchface", COMBAT_SWITCH_FACE.getKey());
-        config.set("key.combat.dash", COMBAT_DASH.getKey());
-        config.set("key.combat.dodge", COMBAT_DODGE.getKey());
-        config.set("key.combat.block", COMBAT_BLOCK.getKey());
-        config.set("key.combat.attack", COMBAT_ATTACK.getKey());
     }
     }
 }
 }

+ 5 - 0
src/me/hammerle/supersnuvi/entity/components/ai/HumanController.java

@@ -70,6 +70,11 @@ public class HumanController extends Controller
         return deathFrame < 17;
         return deathFrame < 17;
     }
     }
     
     
+    private float smoothMove(float current, float max)
+    {
+        return current + (max - current) * 0.5f;
+    }
+    
     @Override
     @Override
     public void tick() 
     public void tick() 
     {
     {

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

@@ -1,6 +1,8 @@
 package me.hammerle.supersnuvi.gamelogic;
 package me.hammerle.supersnuvi.gamelogic;
 
 
 import java.util.List;
 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.entity.Entity;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.util.CollisionObject;
 import me.hammerle.supersnuvi.util.CollisionObject;
@@ -25,4 +27,7 @@ public interface ILevel
     public List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not);
     public List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not);
     public List<Location> getCollisionBoxesAt(CollisionObject cb);
     public List<Location> getCollisionBoxesAt(CollisionObject cb);
     public List<Entity> getEntitiesCollidingWith(Entity not, 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);
 }
 }

+ 192 - 23
src/me/hammerle/supersnuvi/gamelogic/Level.java

@@ -1,15 +1,20 @@
 package me.hammerle.supersnuvi.gamelogic;
 package me.hammerle.supersnuvi.gamelogic;
 
 
 import java.io.File;
 import java.io.File;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.List;
 import java.util.TreeSet;
 import java.util.TreeSet;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.snuviengine.api.TextureRenderer;
 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.Game;
+import me.hammerle.supersnuvi.Keys;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.Location;
@@ -59,6 +64,11 @@ public final class Level implements ILevel
     
     
     private TreeSet<Point> spawns = new TreeSet<>();
     private TreeSet<Point> spawns = new TreeSet<>();
     
     
+    private Script levelScript = null;
+    private final LinkedList<String> messages = new LinkedList<>();
+    private float lastFrameLag = 0;
+    private boolean removeMessageLock = false;
+    
     public Level(File f)
     public Level(File f)
     {
     {
         this.data = new LevelData(f);
         this.data = new LevelData(f);
@@ -131,7 +141,18 @@ public final class Level implements ILevel
             spawns.add(new Point(5, 5));
             spawns.add(new Point(5, 5));
         }
         }
 
 
+        // make sure hero is spawned before any script starts
         resetLevel();
         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); 
     }
     }
 
 
     // -------------------------------------------------------------------------
     // -------------------------------------------------------------------------
@@ -179,16 +200,6 @@ public final class Level implements ILevel
     
     
     public boolean resetLevel()
     public boolean resetLevel()
     {
     {
-        /*boolean dead = false;
-        if(!done) // hero just died
-        {
-            tries--;
-            if(tries <= 0)
-            {
-                tries = 7;
-                dead = true;
-            }
-        }*/
         Game.get().resetTiles(this);
         Game.get().resetTiles(this);
         data.activateEntities();
         data.activateEntities();
         souls = 0;
         souls = 0;
@@ -210,6 +221,8 @@ public final class Level implements ILevel
                 }
                 }
             }
             }
         }
         }
+        
+        callEvent("level_reset");
         return false;
         return false;
     }
     }
     
     
@@ -260,11 +273,6 @@ public final class Level implements ILevel
         return souls;
         return souls;
     }
     }
     
     
-    public int getMaxBottles()
-    {
-        return maxSouls;
-    }
-    
     public LevelData getData()
     public LevelData getData()
     {
     {
         return data;
         return data;
@@ -295,6 +303,17 @@ public final class Level implements ILevel
     {
     {
         if(worldLoaded)
         if(worldLoaded)
         {
         {
+            if(!messages.isEmpty())
+            {
+                if(Keys.ENTER.getTime() == 1)
+                {
+                    messages.removeFirst();
+                }
+                removeMessageLock = messages.isEmpty();
+                return;
+            }
+            removeMessageLock = false;
+            
             time += Game.SECS_PER_TICK;
             time += Game.SECS_PER_TICK;
             Game.get().tickTiles();
             Game.get().tickTiles();
             
             
@@ -357,20 +376,22 @@ public final class Level implements ILevel
             c[1] = (char) ((bottles % 10) + '0');
             c[1] = (char) ((bottles % 10) + '0');
         }
         }
         c[2] = '/';
         c[2] = '/';
-        if(maxSouls <= 9)
+        
+        int currentMaxSouls = Math.max(bottles, maxSouls);
+        if(currentMaxSouls <= 9)
         {
         {
             c[3] = '0';
             c[3] = '0';
-            c[4] = (char) (maxSouls + '0');
+            c[4] = (char) (currentMaxSouls + '0');
         }
         }
-        else if(maxSouls > 99)
+        else if(currentMaxSouls > 99)
         {
         {
             c[3] = 'X';
             c[3] = 'X';
             c[4] = 'X';
             c[4] = 'X';
         }
         }
         else
         else
         {
         {
-            c[3] = (char) ((maxSouls / 10) + '0');
-            c[4] = (char) ((maxSouls % 10) + '0');
+            c[3] = (char) ((currentMaxSouls / 10) + '0');
+            c[4] = (char) ((currentMaxSouls % 10) + '0');
         }
         }
         return new String(c);
         return new String(c);
     }
     }
@@ -421,6 +442,11 @@ public final class Level implements ILevel
     @Override
     @Override
     public void updateTile(int layer, int x, int y)
     public void updateTile(int layer, int x, int y)
     {
     {
+        if(layer == data.getBackgroundIndex() + 1)
+        {
+            // do not update changes on entity layer
+            return;
+        }
         if(layer > data.getBackgroundIndex())
         if(layer > data.getBackgroundIndex())
         {
         {
             layer--;
             layer--;
@@ -464,10 +490,61 @@ public final class Level implements ILevel
         meshes[l][mx][my].draw();
         meshes[l][mx][my].draw();
     }
     }
     
     
+    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()]);
+    }
+    
     public void renderTick(float lag)
     public void renderTick(float lag)
     {
     {
         if(worldLoaded)
         if(worldLoaded)
         {
         {
+            if(messages.isEmpty())
+            {
+                if(removeMessageLock)
+                {
+                    if(lag > lastFrameLag)
+                    {
+                        removeMessageLock = false;
+                    }
+                    else
+                    {
+                        lag = lastFrameLag;
+                    }
+                }
+                else
+                {
+                    lastFrameLag = lag;
+                }
+            }
+            else
+            {
+                lag = lastFrameLag;
+            }
+            
             float camX = Utils.interpolate(oldCameraX, cameraX, lag);
             float camX = Utils.interpolate(oldCameraX, cameraX, lag);
             float camY = Utils.interpolate(oldCameraY, cameraY, lag);
             float camY = Utils.interpolate(oldCameraY, cameraY, lag);
             
             
@@ -506,10 +583,10 @@ public final class Level implements ILevel
             
             
             // entities
             // entities
             
             
-            entities.values().forEach(entity -> 
+            for(Entity entity : entities.values())
             {
             {
                 entity.renderTick(lag);
                 entity.renderTick(lag);
-            });
+            }
             
             
             // foreground
             // foreground
             Shader.setColorEnabled(false);
             Shader.setColorEnabled(false);
@@ -547,7 +624,74 @@ public final class Level implements ILevel
             Shader.getFontRenderer().drawString(13.0f, y, formatTime(time));
             Shader.getFontRenderer().drawString(13.0f, y, formatTime(time));
             Shader.setColorEnabled(false);
             Shader.setColorEnabled(false);
             
             
-            float w = Shader.getViewWidth();
+            // draw messages
+            if(!messages.isEmpty())
+            {
+                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)
+                {
+                    messageY = Shader.getFontRenderer().drawString(13.0f, messageY, sb.toString());
+                }
+                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.bind();
             GUI_RENDERER.clear();
             GUI_RENDERER.clear();
@@ -570,6 +714,8 @@ public final class Level implements ILevel
                 default: GUI_RENDERER.addRectangle(4.666666666f, y, 13.0f, y + 8.333333333f, 0.05078125f, 0.265625f, 0.099609375f, 0.314453125f); 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 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 - 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 - 44.0f, 0.0f, w - 18.0f, 16.0f, 0.130859375f, 0.0f, 0.181640625f, 0.03125f);
@@ -705,4 +851,27 @@ public final class Level implements ILevel
     {
     {
         return entities.values().stream().filter(ent -> ent != not && ent.getBox().isColliding(cb)).collect(Collectors.toList());
         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)
+    {
+        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)
+    {
+        callEvent(name, null, null);
+    }
+    
+    public void addMessage(String message)
+    {
+        messages.add(message);
+    }
 }
 }

+ 12 - 0
src/me/hammerle/supersnuvi/gamelogic/StartScreenLevel.java

@@ -4,9 +4,11 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.List;
 import java.util.Random;
 import java.util.Random;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.Shader;
 import me.hammerle.snuviengine.api.TextureRenderer;
 import me.hammerle.snuviengine.api.TextureRenderer;
+import me.hammerle.snuviscript.code.Script;
 import me.hammerle.supersnuvi.Game;
 import me.hammerle.supersnuvi.Game;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
 import me.hammerle.supersnuvi.entity.EntityBuilder;
@@ -670,4 +672,14 @@ public final class StartScreenLevel implements ILevel
     {
     {
         return entities.values().stream().filter(ent -> ent != not && ent.getBox().isColliding(cb)).collect(Collectors.toList());
         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)
+    {
+    }
 }
 }

+ 48 - 0
src/me/hammerle/supersnuvi/snuviscript/SnuviLogger.java

@@ -0,0 +1,48 @@
+package me.hammerle.supersnuvi.snuviscript;
+
+import me.hammerle.snuviscript.code.ISnuviLogger;
+import me.hammerle.snuviscript.code.Script;
+
+public class SnuviLogger implements ISnuviLogger
+{
+    private void printListElement(String name, String content)
+    {
+        System.out.print(" - ");
+        System.out.print(name);
+        System.out.print(": ");
+        System.out.println(content);
+    }
+    
+    @Override
+    public void print(String message, Exception ex, String function, String scriptname, Script sc, int line) 
+    {
+        if(scriptname != null)
+        {
+            if(sc != null)
+            {
+                System.out.println("error in '" + scriptname + "' " + sc.getId());
+            }
+            else
+            {
+                System.out.println("error in '" + scriptname + "'");
+            }
+        }
+        if(message != null)
+        {
+            printListElement("message", message);
+        }
+        if(ex != null)
+        {
+            printListElement("exception", ex.getClass().getSimpleName());
+            printListElement("exception", ex.getMessage());
+        }
+        if(function != null)
+        {
+            printListElement("function", function);
+        }
+        if(line != -1)
+        {
+            printListElement("line", String.valueOf(line));
+        }
+    }
+}

+ 85 - 0
src/me/hammerle/supersnuvi/snuviscript/SnuviScheduler.java

@@ -0,0 +1,85 @@
+package me.hammerle.supersnuvi.snuviscript;
+
+import java.util.ArrayList;
+import me.hammerle.snuviscript.code.ISnuviScheduler;
+import me.hammerle.supersnuvi.gamelogic.Level;
+
+public class SnuviScheduler implements ISnuviScheduler
+{
+    private static class Task
+    {
+        private final Runnable r;
+        private long ticks;
+        private final Level owner;
+        
+        private Task(Runnable r, long ticks, Level owner)
+        {
+            this.r = r;
+            this.ticks = ticks;
+            this.owner = owner;
+        }
+        
+        private boolean tick(Level current)
+        {
+            if(current != owner && owner != null)
+            {
+                return false;
+            }
+            ticks--;
+            if(ticks <= 0)
+            {
+                r.run();
+                return true;
+            }
+            return false;
+        }
+    }
+    
+    private int id = 0;
+    private final ArrayList<Task> queue = new ArrayList<>();
+    
+    private Level currentLevel = null;
+    
+    public void setActiveLevel(Level l)
+    {
+        currentLevel = l;
+    }
+    
+    @Override
+    public int scheduleTask(Runnable r, long delay)
+    { 
+        queue.add(new Task(r, Math.max(delay, 1), currentLevel));
+        return id++;
+    }
+    
+    @Override
+    public int scheduleTask(Runnable r)
+    { 
+        return scheduleTask(r, 1);
+    }
+    
+    public void tick(Level current)
+    {
+        int i = 0;
+        while(i < queue.size())
+        {
+            if(queue.get(i).tick(current))
+            {
+                if(i + 1 == queue.size())
+                {
+                    // last element
+                    queue.remove(i);
+                }
+                else
+                {
+                    // swap last
+                    queue.set(i, queue.get(queue.size() - 1));
+                    queue.remove(queue.size() - 1);
+                    // current element is a new one, skip i++
+                    continue;
+                }
+            }
+            i++;
+        }
+    }    
+}

+ 6 - 0
src/me/hammerle/supersnuvi/tiles/BottledSoulTile.java

@@ -66,6 +66,12 @@ public class BottledSoulTile extends BaseTile
         states.clear(l);
         states.clear(l);
     } 
     } 
     
     
+    @Override
+    public void reset(int x, int y, ILevel l)
+    {
+        states.clear(x, y, l);
+    }
+    
     @Override
     @Override
     public int getBottleScore()
     public int getBottleScore()
     {
     {

+ 6 - 0
src/me/hammerle/supersnuvi/tiles/CrumblingStoneTile.java

@@ -67,4 +67,10 @@ public class CrumblingStoneTile extends BaseTile
     {
     {
         states.clear(l);
         states.clear(l);
     } 
     } 
+
+    @Override
+    public void reset(int x, int y, ILevel l)
+    {
+        states.clear(x, y, l);
+    }
 }
 }

+ 36 - 0
src/me/hammerle/supersnuvi/tiles/InteractTile.java

@@ -0,0 +1,36 @@
+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;
+
+public class InteractTile extends BaseTile
+{
+    public InteractTile(float tMinX, float tMinY, float tMaxX, float tMaxY)
+    {
+        super(tMinX, tMinY, tMaxX, tMaxY);
+        super.setCollisionBox(CollisionObject.DEFAULT_TILE_BOX);
+        super.setMovementBox(CollisionObject.NULL_BOX);
+    }
+
+    @Override
+    public boolean shouldAiUseCollisionBox(int x, int y, ILevel l)
+    {
+        return false;
+    }
+
+    @Override
+    public void onEntityCollide(Entity ent, int x, int y, Face face, ILevel l)
+    {
+        if(ent.getItemCollector().isHero() && Keys.UP.getTime() == 1 && face == Face.DOWN)
+        {
+            l.callEvent("tile_interact", (sc) -> 
+            {
+                sc.setVar("tile_x", (double) x);
+                sc.setVar("tile_y", (double) y);
+            }, null);
+        }
+    }
+}

+ 0 - 1
src/me/hammerle/supersnuvi/tiles/SlipperyTile.java

@@ -2,7 +2,6 @@ package me.hammerle.supersnuvi.tiles;
 
 
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.gamelogic.ILevel;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.Face;
 
 
 public class SlipperyTile extends BaseBoxTile
 public class SlipperyTile extends BaseBoxTile

+ 4 - 0
src/me/hammerle/supersnuvi/tiles/Tile.java

@@ -101,6 +101,10 @@ public abstract class Tile
     {
     {
     }
     }
     
     
+    public void reset(int x, int y, ILevel l)
+    {
+    }
+    
     public int getBottleScore()
     public int getBottleScore()
     {
     {
         return 0;
         return 0;

+ 5 - 0
src/me/hammerle/supersnuvi/util/BlockDataStorage.java

@@ -78,4 +78,9 @@ public class BlockDataStorage
     {
     {
         data.removeIf(d -> d.level == level);
         data.removeIf(d -> d.level == level);
     }
     }
+    
+    public void clear(int x, int y, ILevel level)
+    {
+        data.removeIf(d -> d.level == level && x == d.x && y == d.y);
+    }
 }
 }