package com.isteinvids.untrusted.level;
import com.isteinvids.untrusted.LevelRenderPanel;
import com.isteinvids.untrusted.UntrustedLua;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import static java.lang.String.valueOf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.JOptionPane;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaError;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.jse.JsePlatform;
/**
*
* @author EmirRhouni
*/
public final class LevelManager {
public static final String[] levels = new String[]{
"level_0",
"level_1",
"level_2",
"level_3"
};
//If there's any of these words in the editable area, the script won't run
public static final String[] blacklistWords = new String[]{
"validateAtLeastXObjects", "validateExactlyXManyObjects"
};
private final Globals globals;
private final int width = 32, height = 32;
public int backgroundColour = 0x111111;
public Player player = new Player(new Position(0, 0));
public Map<String, Tile> blockMapping = new ConcurrentHashMap();
public Map<Position, Integer> squareColours = new ConcurrentHashMap();
public Map<Position, String> tiles = new ConcurrentHashMap();
public Map<String, Entity> entities = new ConcurrentHashMap();
public List<String> playerInventory = new CopyOnWriteArrayList<String>();
public String[] prevPlayerInventory = new String[]{};
public Map<String, Item> itemMapping = new ConcurrentHashMap();
//evemts. ex: tickevent, playerontile
public Map<Event, String> events = new ConcurrentHashMap();
public String lastSuccessfulScript;
public String originalScript;
public int levelIndex = 0;
public String status = "";
public String errorText = "";
private boolean playerCodeRunning = false;
private boolean input = true;
public Runnable phoneCallback;
public LevelManager() {
this.globals = JsePlatform.standardGlobals();
this.globals.get("math").set("rand", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue arg1, LuaValue arg2) {
int min = arg1.toint();
int max = arg2.toint();
Random rand = new Random();
int randomNum = rand.nextInt((max - min) + 1) + min;
return LuaValue.valueOf(randomNum);
}
});
this.globals.get("string").set("char", new OneArgFunction() {
@Override
public LuaValue call(LuaValue arg1) {
int charint = arg1.toint();
return LuaValue.valueOf(Character.toString((char) charint));
}
});
// UntrustedLua.showComputerPane(false);
prevPlayerInventory = new String[]{"computer"};
levelIndex++;//for testing purposes
loadLevel();
}
public void addEntityEvent(String event, String entity, Runnable runn) {
events.put(new EntityEvent(entity, runn), event);
}
public void addTilePropertyEvent(String event, String tile, Runnable runn) {
events.put(new TileEvent(tile, runn), event);
}
/**
* TickEvent, PlayerOnTileEvent
*
* @param event
*/
public void runEntityEvent(String event) {
for (Map.Entry<Event, String> ent : events.entrySet()) {
if (ent.getKey().getClass() == EntityEvent.class) {
if (ent.getValue().equals(event)) {
ent.getKey().run();
}
}
}
}
public boolean isPlayerCodeRunning() {
return playerCodeRunning;
}
public void runTileEvent(String tile, String event) {
for (Map.Entry<Event, String> ent : events.entrySet()) {
if (ent.getKey().getClass() == TileEvent.class) {
if (((TileEvent) ent.getKey()).tileName.equals(tile)) {
if (ent.getValue().equals(event)) {
ent.getKey().run();
}
}
}
}
}
public void loadLevel() {
lastSuccessfulScript = originalScript = loadLevelFile(levels[levelIndex]);
UntrustedLua.textArea.setText(originalScript);
UntrustedLua.textArea.setCaretPosition(0);
initLevelProperties();
runScript(originalScript, true);
}
private String loadLevelFile(String level) {
try {
String res = "/levels/" + level + ".lua";
String ret = "";
String line;
InputStream levelStream = UntrustedLua.class.getResourceAsStream(res);
if (levelStream == null) {
return "";
}
BufferedReader br = new BufferedReader(new InputStreamReader(levelStream));
while ((line = br.readLine()) != null) {
ret += line + "\n";
}
br.close();
return ret;
} catch (IOException ex) {
}
return "";
}
public void setInput(boolean input) {
this.input = input;
}
public Entity createEntity(String name, Entity entity) {
if (!entities.containsKey(name)) {
entities.put(name, entity);
return entity;
}
return null;
}
public Entity getEntity(String name) {
return entities.get(name);
}
public void resetLevelProperties() {
input = true;
status = "";
errorText = "";
phoneCallback = null;
playerInventory.clear();
playerInventory.addAll(Arrays.asList(prevPlayerInventory));
itemMapping.clear();
squareColours.clear();
blockMapping.clear();
entities.clear();
tiles.clear();
events.clear();
backgroundColour = 0x111111;
player = null;
}
private void initLevelProperties() {
resetLevelProperties();
blockMapping.put("air", new Tile(' ', 0x000000, false));
blockMapping.put("block", new Tile('#', 0xFFFFFF, true));
blockMapping.put("exit", new Tile('k', 0xFFFF00, false));
addItemMapping("computer", new Item('\u2318', 0xFFFFFF));
addItemMapping("phone", new Item('\u260E', 0xFFFFFF));
// if (levelIndex != 0) {
// addItemToInventory("computer");
// }
UntrustedLua.showComputerPane(playerInventory.contains("computer"));
addTilePropertyEvent("onTileCollision", "exit", new Runnable() {
@Override
public void run() {
LuaValue exitRet = callScriptFunc(LuaMain.functions.EXIT);
if (exitRet.isboolean() && exitRet.toboolean()) {
prevPlayerInventory = playerInventory.toArray(new String[0]);
levelIndex++;
loadLevel();
}
}
});
for (int i = 0; i <= getWidth(); i++) {
for (int j = 0; j <= getHeight(); j++) {
setTile(i, j, "air");
}
}
}
//TODO: validate method
public boolean isTileAlsoAnItem(String name) {
if (blockMapping.containsKey(name) && playerInventory.contains(name)) {
return true;
}
return false;
}
public void addItemMapping(final String name, final Item item) {
if (!itemMapping.containsKey(name)) {
itemMapping.put(name, item);
}
if (!blockMapping.containsKey(name)) {
blockMapping.put(name, new Tile(item.symbol, item.colour, false));
}
addTilePropertyEvent("onTileCollision", name, new Runnable() {
@Override
public void run() {
if (playerInventory.contains(name)) {
setLevelStatus("You already have " + name + "!");
return;
}
addItemToInventory(name);
// playerInventory.put(status, null)
}
});
}
public void addItemToInventory(String name) {
if (itemMapping.containsKey(name)) {
if (!playerInventory.contains(name)) {
playerInventory.add(name);
if (name.equals("computer")) {
UntrustedLua.showComputerPane(true);
}
}
}
}
public void removeItemFromInventory(String name) {
if (playerInventory.contains(name)) {
playerInventory.remove(name);
}
if (name.equals("computer")) {
UntrustedLua.showComputerPane(false);
}
}
private boolean scriptContainsBadWord(String script) {
boolean insideEditable = false;
for (String ln : script.split("\n")) {
if (ln.contains(UntrustedLua.END)) {
insideEditable = false;
}
if (ln.contains(UntrustedLua.START)) {
insideEditable = true;
}
if (insideEditable) {
for (String badWord : blacklistWords) {
if (ln.contains(badWord)) {
System.out.println(ln);
return true;
}
}
}
}
return false;
}
private String[] getAllBadWords(String script) {
List<String> badStrings = new ArrayList();
boolean insideEditable = false;
for (String ln : script.split("\n")) {
if (ln.contains(UntrustedLua.END)) {
insideEditable = false;
}
if (ln.contains(UntrustedLua.START)) {
insideEditable = true;
}
if (insideEditable) {
for (String badWord : blacklistWords) {
if (ln.contains(badWord)) {
badStrings.add(badWord);
}
}
}
}
return badStrings.toArray(new String[0]);
}
public void runScript(String script, boolean isOriginalScript) {
try {
initLevelProperties();
if (!isOriginalScript) {
if (scriptContainsBadWord(script)) {
resetLevelProperties();
setLevelStatus("Bad word");
String words = Arrays.toString(getAllBadWords(script));
JOptionPane.showMessageDialog(UntrustedLua.mainFrame, "Blacklisted word/s in script:\n" + words);
return;
}
}
globals.load(new LuaLevel(this));
globals.load(new LuaPlayer(this));
globals.load(new LuaMain(this));
globals.load(script).invoke();
callScriptFunc(LuaMain.functions.INIT);
globals.load(new LuaLevel(this));
globals.load(new LuaPlayer(this));
callScriptFunc(LuaMain.functions.VALIDATE);
lastSuccessfulScript = script;
} catch (LuaError ex) {
ex.printStackTrace();
if (lastSuccessfulScript != null) {
runScript(lastSuccessfulScript, lastSuccessfulScript.equals(originalScript));
}
JOptionPane.showMessageDialog(UntrustedLua.mainFrame, ex.getMessage());
}
}
public void setLevelStatus(String status) {
this.status = status;
}
public void resetLevelStatus() {
this.status = "";
}
public void setErrorText(String errorText) {
this.errorText = errorText;
}
public void resetErrorText() {
this.errorText = "";
}
public LuaValue callScriptFunc(LuaMain.functions func) {
return globals.get("Main").get(func.functionName).invoke().arg(1);
}
public void update(LevelRenderPanel renderPanel) {
String newpos = "";
boolean tick = false;
boolean playerMove = false;
if (player != null) {
if (input) {
if (renderPanel.getInput().isKeyPressed(KeyEvent.VK_W)) {
newpos = "up";
playerMove = true;
}
if (renderPanel.getInput().isKeyPressed(KeyEvent.VK_S)) {
newpos = "down";
playerMove = true;
}
if (renderPanel.getInput().isKeyPressed(KeyEvent.VK_A)) {
newpos = "left";
playerMove = true;
}
if (renderPanel.getInput().isKeyPressed(KeyEvent.VK_D)) {
newpos = "right";
playerMove = true;
}
}
tick = playerMove;
}
if (input) {
if (renderPanel.getInput().isKeyPressed(KeyEvent.VK_Q)) {
if (playerInventory.contains("phone")) {
if (phoneCallback != null) {
phoneCallback.run();
}
}
}
if (renderPanel.getInput().isKeyPressed(KeyEvent.VK_E)) {
tick = true;
}
}
if (tick) {
runEntityEvent("behavior");
}
if (player != null) {
if (playerMove) {
movePosition(player.position, newpos, true);
}
}
}
private boolean movePosition(Position pos, String direction) {
return this.movePosition(pos, direction, false);
}
private boolean movePosition(Position pos, String direction, boolean doEvents) {
boolean success = false;
int newx = pos.x, newy = pos.y;
if (direction.equals("left")) {
newx--;
}
if (direction.equals("right")) {
newx++;
}
if (direction.equals("up")) {
newy--;
}
if (direction.equals("down")) {
newy++;
}
if (!getTile(newx, newy).equals("none")) {
String ent = getEntity(newx, newy);
Tile tile = blockMapping.get(getTile(newx, newy));
boolean collide = tile.isCollidable();
if (player != null) {
if (newx == player.position.x && newy == player.position.y) {
collide = true;
}
}
if (!ent.equals("none") && entities.containsKey(ent)) {
if (entities.get(ent).collidable) {
collide = true;
}
if (doEvents) {
runEntityEvent("onEntityCollision");
}
}
if (!collide) {
pos.setPosition(newx, newy);
success = true;
}
if (doEvents) {
runTileEvent(getTile(newx, newy), "onTileCollision");
}
if (itemMapping.containsKey(getTile(newx, newy))) {
tiles.put(new Position(newx, newy), "air");
}
}
return success;
}
public void addTileProperty(String name, Tile tile) {
if (!blockMapping.containsKey(name)) {
blockMapping.put(name, tile);
}
}
public void movePlayer(String direction) {
if (player == null) {
return;
}
movePosition(player.position, direction);
}
public void moveToward(Position from, Position to) {
// var target = obj.findNearest(type);
int leftDist = from.x - to.x;
int upDist = from.y - to.y;
String direction;
if (upDist == 0 && leftDist == 0) {
return;
}
if (upDist > 0 && upDist >= leftDist) {
direction = "up";
} else if (upDist < 0 && upDist < leftDist) {
direction = "down";
} else if (leftDist > 0 && leftDist >= upDist) {
direction = "left";
} else {
direction = "right";
}
movePosition(from, direction);
}
public void moveEntity(String direction, String entName) {
if (entities.containsKey(entName)) {
Entity ent = entities.get(entName);
movePosition(ent.position, direction);
}
}
public void validateAtLeastXObjects(int num, String tile) {
int i = 0;
for (Map.Entry<Position, String> tls : tiles.entrySet()) {
if (tls.getValue().equals(tile)) {
i++;
}
}
if (i >= num) {
return;
}
System.out.println("ERROR ERROR, FOUND " + i + " TILES (" + tile + "), NEEDS AT LEAST " + num);
resetLevelProperties();
errorText = "FOUND " + i + " TILES (" + tile + "), NEEDS AT LEAST " + num;
}
public void validateExactlyXManyObjects(int num, String tile) {
int i = 0;
for (Map.Entry<Position, String> tls : tiles.entrySet()) {
if (tls.getValue().equals(tile)) {
i++;
}
}
if (i == num) {
return;
}
System.out.println("ERROR ERROR, FOUND " + i + " TILES (" + tile + "), NEEDS " + num);
resetLevelProperties();
errorText = "FOUND " + i + " TILES (" + tile + "), NEEDS " + num;
}
public String getTile(int x, int y) {
Position pos = new Position(x, y);
String ret = tiles.get(pos);
return ret == null ? "none" : ret;
}
public String getEntity(int x, int y) {
for (Map.Entry<String, Entity> ents : entities.entrySet()) {
if (ents.getValue().position.equals(new Position(x, y))) {
return ents.getKey();
}
}
return "none";
}
public void setTile(int x, int y, String bl) {
Position pos = new Position(x, y);
if (blockMapping.containsKey(bl)) {
if (!tiles.containsKey(pos) || tiles.get(pos).equals("air")) {
tiles.put(pos, bl);
}
}
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public Runnable luaFuncToRunnable(final LuaFunction func) {
return this.luaFuncToRunnable(func, null);
}
public Runnable luaFuncToRunnable(final LuaFunction func, final Varargs args) {
return new Runnable() {
@Override
public void run() {
func.invoke(args == null ? LuaValue.NONE : args);
}
};
}
public static interface Event {
public void run();
}
public static class EntityEvent implements Event {
public String entityName;
public Runnable runnable;
public EntityEvent(String entityName, Runnable runnable) {
this.entityName = entityName;
this.runnable = runnable;
}
@Override
public void run() {
runnable.run();
}
}
public static class TileEvent implements Event {
public Runnable runnable;
public String tileName;
public TileEvent(String tileName, Runnable runnable) {
this.tileName = tileName;
this.runnable = runnable;
}
@Override
public void run() {
runnable.run();
}
}
}