Răsfoiți Sursa

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 ani în urmă
părinte
comite
c580ae9e2e

BIN
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);
+}
+

BIN
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


BIN
lib/SnuviEngine.jar


BIN
lib/SnuviScriptRecoded.jar


BIN
resources/tiles.png


BIN
resources/tiles.xcf


+ 4 - 1
slot1.txt

@@ -1,5 +1,5 @@
 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.01-Parabola.map=true
 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.bottles=13
 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.KeyBinding;
 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.StartScreenLevel;
 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.util.SoundUtils;
+import me.hammerle.supersnuvi.util.Utils;
 
 public class Game extends Engine
 {
@@ -35,7 +40,7 @@ public class Game extends Engine
     public static final NullTile FALLBACK_TILE = new NullTile();
     
     // tiles
-    private final Tile[] registeredTiles = new Tile[70];
+    private final Tile[] registeredTiles = new Tile[71];
     
     // levels
     private Level currentLevel = null;
@@ -43,6 +48,11 @@ public class Game extends Engine
     private int levelIndex = 0;
     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
     private final SimpleConfig config = new SimpleConfig("options.txt", true);
     private final SimpleConfig[] saveSlots = new SimpleConfig[] 
@@ -66,25 +76,26 @@ public class Game extends Engine
     {
         instance = this;
         registerTiles();
+        addSnuviCommands();
         
         File[] files = new File("./levels").listFiles();
         Arrays.sort(files, (o1, o2) -> o1.compareTo(o2));
         ArrayList<Level> levelList = new ArrayList<>();
         for(File file : files)
         {
-            if(file.isFile())
+            if(file.isFile() && file.getName().endsWith(".map"))
             {
                 Level l = new Level(file);
                 levelList.add(l);
             }
         }
-        levels = levelList.toArray(new Level[levelList.size()]);
+        levels = levelList.toArray(new Level[levelList.size()]); 
     }
     
     @Override
     public void init()
     {
-        setMaxFps(120);
+        setMaxFps(60);
         setNanosPerTick(MS_PER_TICK * 1_000_000);
     }
     
@@ -100,6 +111,8 @@ public class Game extends Engine
             SoundUtils.playSound(SoundUtils.Sound.SONG_1);
             SoundUtils.stopSound(SoundUtils.Sound.MENU_MUSIC);
             
+            snuviScheduler.setActiveLevel(currentLevel);
+            snuviScheduler.tick(currentLevel);
             currentLevel.tick();
             
             // doing that here to prevent concurent modification
@@ -230,13 +243,13 @@ public class Game extends Engine
                                 }
                                 optionsDirty = true;
                                 break;
-                            case 15: // save options
+                            case 9: // save options
                                 Keys.write(config);
                                 config.set("sound", sound);
                                 config.save();
                                 optionsDirty = false;
                                 break;
-                            case 16: // go back
+                            case 10: // go back
                                 screen = 0;
                                 break;
                             default: // rebind keys
@@ -390,8 +403,8 @@ public class Game extends Engine
                 
                 float left = width * 0.2f;
                 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.setColorEnabled(true);
@@ -429,18 +442,6 @@ public class Game extends Engine
                 y = fr.drawString(secLeft, y, getKeyName(Keys.ESCAPE)); 
                 fr.drawString(left, y, "K: 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)
                 {
                     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));
             }
         }
+        
+        // test interact tile
+        registeredTiles[70] = new InteractTile(0.5625f, 0.0f, 0.625f, 0.0625f);
     }
     
     public Tile getTile(int id)
@@ -688,4 +692,60 @@ public class Game extends Engine
     {
         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 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 = 
     {
         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)
@@ -57,13 +49,6 @@ public class Keys
         KeyHandler.rebind(RUN, config.getInt("key.run", GLFW_KEY_LEFT_SHIFT));
         KeyHandler.rebind(ESCAPE, config.getInt("key.escape", GLFW_KEY_ESCAPE));
         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)
@@ -77,12 +62,5 @@ public class Keys
         config.set("key.run", RUN.getKey());
         config.set("key.escape", ESCAPE.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;
     }
     
+    private float smoothMove(float current, float max)
+    {
+        return current + (max - current) * 0.5f;
+    }
+    
     @Override
     public void tick() 
     {

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

@@ -1,6 +1,8 @@
 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;
@@ -25,4 +27,7 @@ public interface ILevel
     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);
 }

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

@@ -1,15 +1,20 @@
 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.tiles.Location;
@@ -59,6 +64,11 @@ public final class Level implements ILevel
     
     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)
     {
         this.data = new LevelData(f);
@@ -131,7 +141,18 @@ public final class Level implements ILevel
             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); 
     }
 
     // -------------------------------------------------------------------------
@@ -179,16 +200,6 @@ public final class Level implements ILevel
     
     public boolean resetLevel()
     {
-        /*boolean dead = false;
-        if(!done) // hero just died
-        {
-            tries--;
-            if(tries <= 0)
-            {
-                tries = 7;
-                dead = true;
-            }
-        }*/
         Game.get().resetTiles(this);
         data.activateEntities();
         souls = 0;
@@ -210,6 +221,8 @@ public final class Level implements ILevel
                 }
             }
         }
+        
+        callEvent("level_reset");
         return false;
     }
     
@@ -260,11 +273,6 @@ public final class Level implements ILevel
         return souls;
     }
     
-    public int getMaxBottles()
-    {
-        return maxSouls;
-    }
-    
     public LevelData getData()
     {
         return data;
@@ -295,6 +303,17 @@ public final class Level implements ILevel
     {
         if(worldLoaded)
         {
+            if(!messages.isEmpty())
+            {
+                if(Keys.ENTER.getTime() == 1)
+                {
+                    messages.removeFirst();
+                }
+                removeMessageLock = messages.isEmpty();
+                return;
+            }
+            removeMessageLock = false;
+            
             time += Game.SECS_PER_TICK;
             Game.get().tickTiles();
             
@@ -357,20 +376,22 @@ public final class Level implements ILevel
             c[1] = (char) ((bottles % 10) + '0');
         }
         c[2] = '/';
-        if(maxSouls <= 9)
+        
+        int currentMaxSouls = Math.max(bottles, maxSouls);
+        if(currentMaxSouls <= 9)
         {
             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[4] = 'X';
         }
         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);
     }
@@ -421,6 +442,11 @@ public final class Level implements ILevel
     @Override
     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())
         {
             layer--;
@@ -464,10 +490,61 @@ public final class Level implements ILevel
         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)
     {
         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 camY = Utils.interpolate(oldCameraY, cameraY, lag);
             
@@ -506,10 +583,10 @@ public final class Level implements ILevel
             
             // entities
             
-            entities.values().forEach(entity -> 
+            for(Entity entity : entities.values())
             {
                 entity.renderTick(lag);
-            });
+            }
             
             // foreground
             Shader.setColorEnabled(false);
@@ -547,7 +624,74 @@ public final class Level implements ILevel
             Shader.getFontRenderer().drawString(13.0f, y, formatTime(time));
             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_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;
             }
             
+            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);
@@ -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());
     }
+    
+    @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.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 me.hammerle.supersnuvi.Game;
 import me.hammerle.supersnuvi.entity.Entity;
 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());
     }
+
+    @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);
     } 
     
+    @Override
+    public void reset(int x, int y, ILevel l)
+    {
+        states.clear(x, y, l);
+    }
+    
     @Override
     public int getBottleScore()
     {

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

@@ -67,4 +67,10 @@ public class CrumblingStoneTile extends BaseTile
     {
         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.gamelogic.ILevel;
-import me.hammerle.supersnuvi.gamelogic.ILevel;
 import me.hammerle.supersnuvi.util.Face;
 
 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()
     {
         return 0;

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

@@ -78,4 +78,9 @@ public class BlockDataStorage
     {
         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);
+    }
 }