From 619eff6f61872be9aa2573df016acb7f6da6b447 Mon Sep 17 00:00:00 2001 From: TheBigEye Date: Mon, 30 Aug 2021 16:24:17 -0300 Subject: [PATCH 1/3] Unrelated changes --- src/minicraft/core/Game.java | 1 + src/minicraft/core/Network.java | 2 +- src/minicraft/core/Renderer.java | 2 +- src/minicraft/core/io/ConsoleReader.java | 407 ++++++++--------- src/minicraft/entity/Arrow.java | 7 +- src/minicraft/entity/Boat.java | 33 +- src/minicraft/entity/Entity.java | 424 ++++++++---------- src/minicraft/entity/furniture/Chest.java | 11 +- .../entity/furniture/DungeonChest.java | 8 +- src/minicraft/entity/furniture/Furniture.java | 48 +- src/minicraft/entity/mob/Player.java | 71 +-- src/minicraft/gfx/Font.java | 2 +- src/minicraft/item/Inventory.java | 18 +- src/minicraft/item/PowerGloveItem.java | 18 - src/minicraft/level/Level.java | 40 +- src/minicraft/level/tile/LawnTile.java | 10 +- src/minicraft/level/tile/TreeTile.java | 10 +- .../level/tile/farming/OrangeCropTile.java | 5 + src/minicraft/screen/CraftingDisplay.java | 2 +- src/minicraft/screen/TitleDisplay.java | 5 + src/resources/textures/gui.png | Bin 29837 -> 23318 bytes src/resources/textures/legacy/gui_legacy.png | Bin 20924 -> 15810 bytes 22 files changed, 524 insertions(+), 600 deletions(-) create mode 100644 src/minicraft/level/tile/farming/OrangeCropTile.java diff --git a/src/minicraft/core/Game.java b/src/minicraft/core/Game.java index 5ad703da..ad679d56 100644 --- a/src/minicraft/core/Game.java +++ b/src/minicraft/core/Game.java @@ -141,6 +141,7 @@ public static boolean isConnectedClient() { public static MinicraftServer server = null; + /** Checks if you are a host and the game is a server */ public static boolean isValidServer() { return ISONLINE && ISHOST && server != null; diff --git a/src/minicraft/core/Network.java b/src/minicraft/core/Network.java index a38ac513..6a2246f7 100644 --- a/src/minicraft/core/Network.java +++ b/src/minicraft/core/Network.java @@ -21,7 +21,7 @@ private Network() { private static final Random random = new Random(); - static boolean autoclient = false; // used in the initScreen method; jumps to multiplayer menu as client + static boolean autoclient = false; // Used in the initScreen method; jumps to multiplayer menu as client private static VersionInfo latestVersion = null; diff --git a/src/minicraft/core/Renderer.java b/src/minicraft/core/Renderer.java index c7b76a44..af46ac81 100644 --- a/src/minicraft/core/Renderer.java +++ b/src/minicraft/core/Renderer.java @@ -120,7 +120,7 @@ static void initScreen() { /** Renders the current screen. Called in game loop, a bit after tick(). */ public static void render() { if (!HAS_GUI || screen == null) - return; // no point in this if there's no gui... :P + return; // No point in this if there's no gui... :P if (readyToRenderGameplay) { if (isValidServer()) { diff --git a/src/minicraft/core/io/ConsoleReader.java b/src/minicraft/core/io/ConsoleReader.java index 330fa8bc..f147a047 100644 --- a/src/minicraft/core/io/ConsoleReader.java +++ b/src/minicraft/core/io/ConsoleReader.java @@ -23,58 +23,49 @@ import minicraft.screen.WorldSelectDisplay; public class ConsoleReader extends Thread { - + private enum Config { PLAYERCAP { - public String getValue() { - return String.valueOf(Game.server.getPlayerCap()); - } - + public String getValue() { return String.valueOf(Game.server.getPlayerCap()); } + public boolean setValue(String val) { try { Game.server.setPlayerCap(Integer.parseInt(val)); return true; } catch (NumberFormatException ex) { - System.out.println("\"" + val + "\" is not a valid number."); + System.out.println("\""+val+"\" is not a valid number."); } return false; } }, - + AUTOSAVE { - public String getValue() { - return String.valueOf(Settings.get("autosave")); - } - + public String getValue() { return String.valueOf(Settings.get("autosave")); } + public boolean setValue(String val) { Settings.set("autosave", Boolean.parseBoolean(val)); return true; } }; - + public abstract String getValue(); - public abstract boolean setValue(String val); - + public static final Config[] values = Config.values(); } - + private enum Command { - HELP("--all | [cmd]", "describes the function of each command. Specify a command name to read more about how to use it.", - "no arguments: prints a list of all available commands, with a short description of each.", - "cmd: a command name. will print the short description of that command, along with usage details such as what parameters/arguments it uses, and what function each argument has, and what the defualt behavior is if a given argument is ommitted.", - "--all: prints the long description of all the commands.", "Usage symbol meanings:", "\t| = OR; specifies two possible choices for a given argument.", - "\t[] = Optional; the arguments within may be specified, but they are not required.", "\t<> = Required; you must include the arguments within for the command to work.", - "Note that the usage symbols may be nested, so a <> inside a [] is only required if you do whatever else is inside the [].") { + HELP + ("--all | [cmd]", "describes the function of each command. Specify a command name to read more about how to use it.", "no arguments: prints a list of all available commands, with a short description of each.", "cmd: a command name. will print the short description of that command, along with usage details such as what parameters/arguments it uses, and what function each argument has, and what the defualt behavior is if a given argument is ommitted.", "--all: prints the long description of all the commands.", "Usage symbol meanings:", "\t| = OR; specifies two possible choices for a given argument.", "\t[] = Optional; the arguments within may be specified, but they are not required.", "\t<> = Required; you must include the arguments within for the command to work.", "Note that the usage symbols may be nested, so a <> inside a [] is only required if you do whatever else is inside the [].") { public void run(String[] args) { if (args.length == 0) { System.out.println("Available commands:"); - for (Command cmd : Command.values) + for (Command cmd: Command.values) System.out.println(cmd.getGeneralHelp()); - + return; } - + Command query = ConsoleReader.getCommandByName(args[0]); // prints its own error message if the command wasn't found. if (query != null) System.out.println(query.getDetailedHelp()); @@ -93,29 +84,28 @@ public void run(String[] args) { } }, - CONFIG("[option_name [value]]", "change various server settings.", "no arguments: displays all config options and their current values", - "option_name: displays the current value of that option", "option_name value:, will set the option to the specified value, provided it is a valid value for that option.") { - + CONFIG + ("[option_name [value]]", "change various server settings.", "no arguments: displays all config options and their current values", "option_name: displays the current value of that option", "option_name value:, will set the option to the specified value, provided it is a valid value for that option.") { + public void run(String[] args) { if (args.length == 0) { - for (Config c : Config.values) - System.out.println("\t" + c.name() + " = " + c.getValue()); + for (Config c: Config.values) + System.out.println("\t"+c.name() + " = " + c.getValue()); } else { Config configOption = null; try { configOption = Enum.valueOf(Config.class, args[0].toUpperCase(Localization.getSelectedLocale())); } catch (IllegalArgumentException ex) { - System.out.println("\"" + args[0] + "\" is not a valid config option. run \"config\" for a list of the available config options."); + System.out.println("\""+args[0]+"\" is not a valid config option. run \"config\" for a list of the available config options."); } - if (configOption == null) - return; - if (args.length > 1) { // we want to set the config option. - if (args.length > 2) - System.out.println("Note: Additional arguments (more than two) will be ignored."); + if (configOption == null) return; + if (args.length > 1) { // We want to set the config option. + if (args.length > 2) System.out.println("Note: Additional arguments (more than two) will be ignored."); boolean set = configOption.setValue(args[1]); if (set) { - System.out.println(configOption.name() + " set successfully."); - /// HERE is where we save the modified config options. + System.out.println(configOption.name()+" set successfully."); + + // HERE is where we save the modified config options. new Save(WorldSelectDisplay.getWorldName(), Game.server); new Save(); } else @@ -124,33 +114,36 @@ public void run(String[] args) { } } }, - - STOP(null, "close the server.") { + + STOP + (null, "close the server.") { public void run(String[] args) { System.out.println("Shutting down server..."); + Game.server.saveWorld(); Game.server.endConnection(); } }, - - RESTART(null, "restart the server.", "closes the server, then starts it back up again.") { + + RESTART + (null, "restart the server.", "closes the server, then starts it back up again.") { public void run(String[] args) { - Command.STOP.run(null); // shuts down the server. + Command.STOP.run(null); // Shuts down the server. try { - Thread.sleep(500); // give the computer some time to, uh, recuperate? idk, I think it's a good idea. - } catch (InterruptedException ignored) { - } - Network.startMultiplayerServer(); // start the server back up. + Thread.sleep(500); // Give the computer some time to, uh, recuperate? idk, I think it's a good idea. + } catch (InterruptedException ignored) {} + Network.startMultiplayerServer(); // Start the server back up. } }, - - SAVE(null, "Save the world to file.") { + + SAVE + (null, "Save the world to file.") { public void run(String[] args) { Game.server.saveWorld(); - System.out.println("World Saved."); } }, - - GAMEMODE("", "change the server gamemode.", "mode: one of the following: c(reative), su(rvivial), t(imed) / score, h(ardcore)") { + + GAMEMODE + ("", "change the server gamemode.", "mode: one of the following: c(reative), su(rvivial), t(imed) / score, h(ardcore)") { public void run(String[] args) { if (args.length != 1) { System.out.println("Incorrect number of arguments. Please specify the game mode in one word:"); @@ -159,52 +152,49 @@ public void run(String[] args) { } switch (args[0].toLowerCase()) { - case "s": - case "survival": - Settings.set("mode", "Survival"); - break; - - case "c": - case "creative": - Settings.set("mode", "Creative"); - break; - - case "h": - case "hardcore": - Settings.set("mode", "Hardcore"); - break; - - case "t": - case "timed": - case "score": - Settings.set("mode", "Score"); - break; - - default: - System.out.println(args[0] + " is not a valid game mode."); - printHelp(this); - break; + case "s": + case "survival": + Settings.set("mode", "Survival"); + break; + case "c": + case "creative": + Settings.set("mode", "Creative"); + break; + case "h": + case "hardcore": + Settings.set("mode", "Hardcore"); + break; + case "t": + case "timed": + case "score": + Settings.set("mode", "Score"); + break; + default: { + System.out.println(args[0] + " is not a valid game mode."); + printHelp(this); + break; + } } - + Game.server.updateGameVars(); } }, - - TIME("[timeString]", "sets or prints the time of day.", "no arguments: prints the current time of day, in ticks.", - "timeString: sets the time of day to the given value; it can be a number, in which case it is a tick count from 0 to 64000 or so, or one of the following strings: Morning, Day, Evening, Night. the time of day will be set to the beginning of the given time period.") { + + TIME + ("[timeString]", "sets or prints the time of day." , "no arguments: prints the current time of day, in ticks.", "timeString: sets the time of day to the given value; it can be a number, in which case it is a tick count from 0 to 64000 or so, or one of the following strings: Morning, Day, Evening, Night. the time of day will be set to the beginning of the given time period.") { public void run(String[] args) { if (args.length == 0) { - System.out.println("Time of day is: " + Updater.tickCount + " (" + Updater.getTime() + ")"); + System.out.println("Time of day is: " + Updater.tickCount + " ("+ Updater.getTime()+")"); return; } - + int targetTicks = -1; - + if (args[0].length() > 0) { try { String firstLetter = String.valueOf(args[0].charAt(0)).toUpperCase(); String remainder = args[0].substring(1).toLowerCase(); - Updater.Time time = Enum.valueOf(Updater.Time.class, firstLetter + remainder); + Updater.Time time = Enum.valueOf(Updater.Time.class, firstLetter+remainder); targetTicks = time.tickTime; } catch (IllegalArgumentException iaex) { try { @@ -213,7 +203,7 @@ public void run(String[] args) { } } } - + if (targetTicks >= 0) { Updater.setTime(targetTicks); Game.server.updateGameVars(); @@ -223,8 +213,9 @@ public void run(String[] args) { } } }, - - MSG("[username] ", "make a message appear on other players' screens.", "w/o username: sends to all players,", "with username: sends to that player only.") { + + MSG + ("[username] ", "make a message appear on other players' screens.", "w/o username: sends to all players,", "with username: sends to that player only.") { public void run(String[] args) { if (args.length == 0) { System.out.println("Please specify a message to send."); @@ -237,23 +228,15 @@ public void run(String[] args) { Game.server.broadcastNotification(args[0], 50); return; } - - String message = args[args.length - 1]; - for (MinicraftServerThread clientThread : Game.server.getAssociatedThreads(usernames.toArray(new String[usernames.size()]), true)) + + String message = args[args.length-1]; + for (MinicraftServerThread clientThread: Game.server.getAssociatedThreads(usernames.toArray(new String[usernames.size()]), true)) clientThread.sendNotification(message, 50); } }, - - TP(" ", "teleports a player to a given location in the world.", - "the first player name is the player that will be teleported. the second argument can be either another player, or a set of world coordinates.", - "if the second argument is a player name, then the first player will be teleported to the second player, possibly traversing different levels.", - "if world coordinates are specified, an x and y coordinate are required. A level depth may optionally be specified to go to a different level; if not specified, the current level is assumed.", - "the symbol \"~\" may be used in place of an x or y coordinate, or a level, to mean the current player position on that axis. additionally, an offset may be specified by writing it like so: \"~-3 ~\". this means 3 tiles to the left of the current player position.") { /// future - /// usage: - /// " - /// - /// | - /// " + + TP + (" ", "teleports a player to a given location in the world.", "the first player name is the player that will be teleported. the second argument can be either another player, or a set of world coordinates.", "if the second argument is a player name, then the first player will be teleported to the second player, possibly traversing different levels.", "if world coordinates are specified, an x and y coordinate are required. A level depth may optionally be specified to go to a different level; if not specified, the current level is assumed.", "the symbol \"~\" may be used in place of an x or y coordinate, or a level, to mean the current player position on that axis. additionally, an offset may be specified by writing it like so: \"~-3 ~\". this means 3 tiles to the left of the current player position.") { /// future usage: " | " public void run(String[] args) { if (args.length == 0) { System.out.println("You must specify a username, and coordinates or another username to teleport to."); @@ -265,15 +248,15 @@ public void run(String[] args) { System.out.println("Could not find player with username \"" + args[0] + "\""); return; } - + int xt, yt; Level level = clientThread.getClient().getLevel(); - + if (args.length > 2) { try { xt = getCoordinate(args[1], clientThread.getClient().x >> 4); yt = getCoordinate(args[2], clientThread.getClient().y >> 4); - + if (args.length == 4) { try { int lvl = getCoordinate(args[3], (level != null ? level.depth : 0)); @@ -292,13 +275,13 @@ public void run(String[] args) { return; } } else { - // user specified the username of another player to tp to. + // User specified the username of another player to tp to. MinicraftServerThread destClientThread = Game.server.getAssociatedThread(args[1]); if (destClientThread == null) { System.out.println("Could not find player with username \"" + args[0] + "\" for tp destination."); return; } - + RemotePlayer rp = destClientThread.getClient(); if (rp == null) { System.out.println("Client no longer exists..."); @@ -308,9 +291,9 @@ public void run(String[] args) { yt = rp.y >> 4; level = rp.getLevel(); } - + if (xt >= 0 && yt >= 0 && level != null && xt < level.w && yt < level.h) { - // perform teleport + // Perform teleport RemotePlayer playerToMove = clientThread.getClient(); if (playerToMove == null) { System.out.println("Can't perform teleport; Client no longer exists..."); @@ -321,12 +304,13 @@ public void run(String[] args) { return; } Level pLevel = playerToMove.getLevel(); - int nx = xt * 16 + 8; - int ny = yt * 16 + 8; + int nx = xt*16+8; + int ny = yt*16+8; if (pLevel == null || pLevel.depth != level.depth) { playerToMove.remove(); level.add(playerToMove, nx, ny); - } else { + } + else { int oldxt = playerToMove.x >> 4; int oldyt = playerToMove.y >> 4; playerToMove.x = nx; @@ -335,25 +319,20 @@ public void run(String[] args) { playerToMove.updatePlayers(oldxt, oldyt); playerToMove.updateSyncArea(oldxt, oldyt); } - - System.out.println("Teleported player " + playerToMove.getUsername() + " to tile coordinates " + xt + "," + yt + ", on level " + level.depth); + + System.out.println("Teleported player " + playerToMove.getUsername() + " to tile coordinates " + xt +"," + yt + ", on level " + level.depth); } else { System.out.println("Could not perform teleport; Coordinates are not valid..."); } } }, - - PING("", "Pings all the clients, and prints a message when each responds.") { + + PING ("", "Pings all the clients, and prints a message when each responds.") { @Override - public void run(String[] args) { - Game.server.pingClients(); - } + public void run(String[] args) { Game.server.pingClients(); } }, - - KILL(" | @[!] >", "Kills the specified entities.", "Specifying only a playername will kill that player.", - "In the second form, use @all to refer to all entities, @entity to refer to all non-mob entities, @mob to refer to only mob entities, and @player to refer to all players.", - "the \"!\" reverses the effect.", "@_ level will target all matching entities for that level.", - "using a playername and radius will target all matching entities within the given radius of the player, the radius being a number of tiles.") { + + KILL (" | @[!] >", "Kills the specified entities.", "Specifying only a playername will kill that player.", "In the second form, use @all to refer to all entities, @entity to refer to all non-mob entities, @mob to refer to only mob entities, and @player to refer to all players.", "the \"!\" reverses the effect.", "@_ level will target all matching entities for that level.", "using a playername and radius will target all matching entities within the given radius of the player, the radius being a number of tiles.") { @Override public void run(String[] args) { List entities = targetEntities(args); @@ -361,92 +340,80 @@ public void run(String[] args) { printHelp(this); return; } - + int count = entities.size(); - for (Entity e : entities) + for (Entity e: entities) e.die(); - + System.out.println("Removed " + count + " entities."); } }; - + private String generalHelp, detailedHelp, usage; - + Command(String usage, String general, String... specific) { String name = this.name().toLowerCase(); String sep = " - "; - + generalHelp = name + sep + general; - + this.usage = usage == null ? name : name + " " + usage; - if (usage != null) - usage = sep + "Usage: " + name + " " + usage; - else - usage = ""; - + if (usage != null) usage = sep+"Usage: "+name+" "+usage; + else usage = ""; + detailedHelp = name + usage + sep + general; if (specific != null && specific.length > 0) - detailedHelp += System.lineSeparator() + "\t" + String.join(System.lineSeparator() + "\t", specific); + detailedHelp += System.lineSeparator()+"\t"+String.join(System.lineSeparator()+"\t", specific); } - + public abstract void run(String[] args); - - public String getUsage() { - return usage; - } - - public String getGeneralHelp() { - return generalHelp; - } - - public String getDetailedHelp() { - return detailedHelp; - } - + + public String getUsage() { return usage; } + public String getGeneralHelp() { return generalHelp; } + public String getDetailedHelp() { return detailedHelp; } + public static void printHelp(Command cmd) { System.out.println("Usage: " + cmd.getUsage()); System.out.println("Type \"help " + cmd + "\" for more info."); } - + private static int getCoordinate(String coord, int baseline) throws NumberFormatException { if (coord.contains("~")) { - if (coord.equals("~")) - return baseline; - else - return Integer.parseInt(coord.replace("~", "")) + baseline; + if(coord.equals("~")) return baseline; + else return Integer.parseInt(coord.replace("~", "")) + baseline; } else return Integer.parseInt(coord); } - + @Nullable private static List targetEntities(String[] args) { List matches = new ArrayList<>(); - + if (args.length == 0) { System.out.println("Cannot target entities without arguments."); return null; } - + if (args.length == 1) { - // must be player name + // Must be player name MinicraftServerThread thread = Game.server.getAssociatedThread(args[0]); if (thread != null) matches.add(thread.getClient()); return matches; } - - // must specify @_ as first argument - + + // Must specify @_ as first argument + if (!args[0].startsWith("@")) { System.out.println("Invalid entity targeting format. Please read help."); return null; } - + String target = args[0].substring(1).toLowerCase(Localization.getSelectedLocale()); // cut off "@" List allEntities = new ArrayList<>(); - + if (args.length == 2) { - // specified @_ level + // Specified @_ level try { allEntities.addAll(Arrays.asList(Game.levels[new Integer(args[1])].getEntityArray())); } catch (NumberFormatException ex) { @@ -457,8 +424,8 @@ private static List targetEntities(String[] args) { return null; } } - - if (args.length == 3) { + + if(args.length == 3) { // @_ playername radius MinicraftServerThread thread = Game.server.getAssociatedThread(args[1]); RemotePlayer rp = thread == null ? null : thread.getClient(); @@ -466,114 +433,110 @@ private static List targetEntities(String[] args) { System.out.println("Invalid entity targeting format: Remote player does not exist: " + args[1]); return null; } - + try { int radius = Integer.valueOf(args[2]); - allEntities.addAll(rp.getLevel().getEntitiesInRect(new Rectangle(rp.x, rp.y, radius * 2, radius * 2, Rectangle.CENTER_DIMS))); + allEntities.addAll(rp.getLevel().getEntitiesInRect(new Rectangle(rp.x, rp.y, radius*2, radius*2, Rectangle.CENTER_DIMS))); allEntities.remove(rp); } catch (NumberFormatException ex) { System.out.println("Invalid entity targeting format: Specified radius is not an integer: " + args[2]); return null; } } - + boolean invert = false; if (target.startsWith("!")) { invert = true; target = target.substring(1); } - + List remainingEntities = new ArrayList<>(allEntities); switch (target) { - case "all": - break; // target all entities - - case "entity": // target only non-mobs - allEntities.removeIf(entity -> entity instanceof Mob); + case "all": break; // Target all entities + + case "entity": // Target only non-mobs + allEntities.removeIf(entity -> entity instanceof Mob); break; - - case "mob": // target only mobs - allEntities.removeIf(entity -> !(entity instanceof Mob)); + + case "mob": // Target only mobs + allEntities.removeIf(entity -> !(entity instanceof Mob)); break; - - case "player": // target only players - allEntities.removeIf(entity -> !(entity instanceof Player)); + + case "player": // Target only players + allEntities.removeIf(entity -> !(entity instanceof Player)); break; - - default: - System.out.println("Invalid entity targeting format: @_ argument is not valid: @" + target); - return null; + + default: + System.out.println("Invalid entity targeting format: @_ argument is not valid: @" + target); + return null; } - + remainingEntities.removeAll(allEntities); - + if (invert) return remainingEntities; - + return allEntities; } - + public static final Command[] values = Command.values(); } - + private boolean shouldRun; - + public ConsoleReader() { super("ConsoleReader"); shouldRun = true; } - - @SuppressWarnings("resource") + public void run() { Scanner stdin = new Scanner(System.in); try { - Thread.sleep(500); // this is to let it get past the debug statements at world load, and any - // others, maybe, if not in debug mode. - } catch (InterruptedException ignored) { - } + Thread.sleep(500); // this is to let it get past the debug statements at world load, and any others, maybe, if not in debug mode. + } catch (InterruptedException ignored) {} System.out.println("Type \"help\" for a list of commands..."); - - while (shouldRun/* && stdin.hasNext() */) { + + while (shouldRun/* && stdin.hasNext()*/) { + System.out.println(); System.out.print("Enter a command: "); + String command = stdin.nextLine().trim(); - if (command.length() == 0) - continue; + if (command.length() == 0) continue; List parsed = new ArrayList<>(Arrays.asList(command.split(" "))); int lastIdx = -1; for (int i = 0; i < parsed.size(); i++) { if (parsed.get(i).contains("\"")) { - if (lastIdx >= 0) { // closing a quoted String - while (i > lastIdx) { // join the words together - parsed.set(lastIdx, parsed.get(lastIdx) + " " + parsed.remove(lastIdx + 1)); + if (lastIdx >= 0) { // Closing a quoted String + while (i > lastIdx) { // Join the words together + parsed.set(lastIdx, parsed.get(lastIdx) + " " + parsed.remove(lastIdx+1)); i--; } - lastIdx = -1; // reset the "last quote" variable. - } else // start the quoted String - lastIdx = i; // set the "last quote" variable. - - parsed.set(i, parsed.get(i).replaceFirst("\"", "")); // remove the parsed quote character from the - // string. - i--; // so that this string can be parsed again, in case there is another quote. + lastIdx = -1; // Reset the "last quote" variable. + } else // Start the quoted String + lastIdx = i; // Set the "last quote" variable. + + parsed.set(i, parsed.get(i).replaceFirst("\"", "")); // Remove the parsed quote character from the string. + i--; // So that this string can be parsed again, in case there is another quote. } } - // if (Game.debug) System.out.println("Parsed command: " + parsed.toString()); - - Command cmd = getCommandByName(parsed.remove(0)); // will print its own error message if not found. + //if (Game.debug) System.out.println("Parsed command: " + parsed.toString()); + + Command cmd = getCommandByName(parsed.remove(0)); // Will print its own error message if not found. if (cmd == null) Command.HELP.run(new String[0]); else if (Game.isValidServer() || cmd == Command.HELP) cmd.run(parsed.toArray(new String[parsed.size()])); else System.out.println("No server running."); - - if (cmd == Command.STOP) - shouldRun = false; + + if (cmd == Command.STOP) shouldRun = false; } - + + stdin.close(); Game.quit(); } - + private static Command getCommandByName(String name) { Command cmd = null; try { @@ -581,7 +544,7 @@ private static Command getCommandByName(String name) { } catch (IllegalArgumentException ex) { System.out.println("Unknown command: \"" + name + "\""); } - + return cmd; } -} +} \ No newline at end of file diff --git a/src/minicraft/entity/Arrow.java b/src/minicraft/entity/Arrow.java index d1eba49a..09cfdb1b 100644 --- a/src/minicraft/entity/Arrow.java +++ b/src/minicraft/entity/Arrow.java @@ -48,18 +48,17 @@ public String getData() { @Override public void tick() { if (x < 0 || x>>4 > level.w || y < 0 || y>>4 > level.h) { - remove(); // remove when out of bounds + remove(); // Remove when out of bounds return; } x += dir.getX() * speed; y += dir.getY() * speed; - // TODO I think I can just use the xr yr vars, and the normal system with touchedBy(entity) to detect collisions instead. List entitylist = level.getEntitiesInRect(new Rectangle(x, y, 0, 0, Rectangle.CENTER_DIMS)); boolean criticalHit = random.nextInt(11) < 9; - for (int i = 0; i < entitylist.size(); i++) { - Entity hit = entitylist.get(i); + + for (Entity hit : entitylist) { if (hit instanceof Mob && hit != owner) { Mob mob = (Mob) hit; diff --git a/src/minicraft/entity/Boat.java b/src/minicraft/entity/Boat.java index 5bc07fcb..9f2730d5 100644 --- a/src/minicraft/entity/Boat.java +++ b/src/minicraft/entity/Boat.java @@ -3,8 +3,11 @@ import java.util.List; import java.util.Random; +import org.jetbrains.annotations.Nullable; + import minicraft.core.Game; import minicraft.core.Updater; +import minicraft.core.io.Sound; import minicraft.entity.mob.Player; import minicraft.entity.mob.RemotePlayer; import minicraft.entity.particle.SplashParticle; @@ -12,6 +15,7 @@ import minicraft.gfx.Screen; import minicraft.gfx.Sprite; import minicraft.item.BoatItem; +import minicraft.item.Item; import minicraft.item.PowerGloveItem; public class Boat extends Entity { @@ -121,18 +125,23 @@ public boolean blocks(Entity e) { return true; } - public void take(Player player) { - remove(); // remove this from the world - if (!Game.ISONLINE) { - if (!Game.isMode("creative") && player.activeItem != null && !(player.activeItem instanceof PowerGloveItem)) - player.getInventory().add(0, player.activeItem); // put whatever item the player is holding into their inventory (should never be a power glove, since it is put in a taken out again all in the same frame). - player.activeItem = new BoatItem("Boat"); // make this the player's current item. - } else if (Game.isValidServer() && player instanceof RemotePlayer) - Game.server.getAssociatedThread((RemotePlayer) player).updatePlayerActiveItem(new BoatItem("Boat")); - else - System.out.println("WARNING: undefined behavior; online game was not server and ticked furniture: " + this + "; and/or player in online game found that isn't a RemotePlayer: " + player); - - // if (Game.debug) System.out.println("set active item of player " + player + " to " + player.activeItem + "; picked up furniture: " + this); + public boolean interact(Player player, @Nullable Item item, Direction attackDir) { + if (item instanceof PowerGloveItem) { + Sound.Mob_generic_hurt.play(); + if (!Game.ISONLINE) { + remove(); + if (!Game.isMode("creative") && player.activeItem != null && !(player.activeItem instanceof PowerGloveItem)) + player.getInventory().add(0, player.activeItem); // put whatever item the player is holding into their inventory + player.activeItem = new BoatItem("Boat"); // make this the player's current item. + return true; + } else if (Game.isValidServer() && player instanceof RemotePlayer) { + remove(); + Game.server.getAssociatedThread((RemotePlayer) player).updatePlayerActiveItem(new BoatItem("Boat")); + return true; + } else + System.out.println("WARNING: undefined behavior; online game was not server and ticked furniture: " + this + "; and/or player in online game found that isn't a RemotePlayer: " + player); + } + return false; } public boolean move(double xa, double ya) { diff --git a/src/minicraft/entity/Entity.java b/src/minicraft/entity/Entity.java index b190ca13..9eb5dc9a 100644 --- a/src/minicraft/entity/Entity.java +++ b/src/minicraft/entity/Entity.java @@ -39,7 +39,7 @@ public abstract class Entity implements Tickable { public int x, y; // x, y entity coordinates on the map private int xr, yr; // x, y radius of entity private boolean removed; // Determines if the entity is removed from it's level; checked in Level.java - protected Level level; // the level that the entity is on + public Level level; // the level that the entity is on public int col; // current color. public int eid; /// this is intended for multiplayer, but I think it could be helpful in single player, too. certainly won't harm anything, I think... as long as finding a valid id doesn't take long... @@ -133,167 +133,136 @@ public int getLightRadius() { protected void touchedBy(Entity entity) { } - /** Item interact */ + /** + * Interacts with the entity this method is called on + * @param player The player attacking + * @param item The item the player attacked with + * @param attackDir The direction to interact + * @return If the interaction was successful + */ public boolean interact(Player player, @Nullable Item item, Direction attackDir) { - if (item != null) - return item.interact(player, this, attackDir); return false; } - /** - * Moves an entity horizontally and vertically. Returns whether entity was - * unimpeded in it's movement. - */ - public boolean move(int xa, int ya) { - if (Updater.saving || (xa == 0 && ya == 0)) - return true; // pretend that it kept moving - - boolean stopped = true; // used to check if the entity has BEEN stopped, COMPLETELY; below checks for a - // lack of collision. - if (move2(xa, 0)) - stopped = false; // becomes false if horizontal movement was successful. - if (move2(0, ya)) - stopped = false; // becomes false if vertical movement was successful. + /** Moves an entity horizontally and vertically. Returns whether entity was unimpeded in it's movement. */ + public boolean move(int xd, int yd) { + if (Updater.saving || (xd == 0 && yd == 0)) return true; // Pretend that it kept moving + + boolean stopped = true; // Used to check if the entity has BEEN stopped, COMPLETELY; below checks for a lack of collision. + if (move2(xd, 0)) stopped = false; // Becomes false if horizontal movement was successful. + if (move2(0, yd)) stopped = false; // Becomes false if vertical movement was successful. if (!stopped) { - int xt = x >> 4; // the x tile coordinate that the entity is standing on. - int yt = y >> 4; // the y tile coordinate that the entity is standing on. - level.getTile(xt, yt).steppedOn(level, xt, yt, this); // Calls the steppedOn() method in a tile's class. - // (used for tiles like sand (footprints) or lava - // (burning)) + int xt = x >> 4; // The x tile coordinate that the entity is standing on. + int yt = y >> 4; // The y tile coordinate that the entity is standing on. + level.getTile(xt, yt).steppedOn(level, xt, yt, this); // Calls the steppedOn() method in a tile's class. (used for tiles like sand (footprints) or lava (burning)) } - + return !stopped; } /** - * Moves the entity a long only one direction. If xa != 0 then ya should be 0. - * If xa = 0 then ya should be != 0. Will throw exception otherwise. - * - * @param xa Horizontal velocity. - * @param ya Vertical velocity. + * Moves the entity a long only one direction. + * If xd != 0 then ya should be 0. + * If xd = 0 then ya should be != 0. + * Will throw exception otherwise. + * @param xd Horizontal move. + * @param yd Vertical move. * @return true if the move was successful, false if not. */ - protected boolean move2(int xa, int ya) { - if (xa == 0 && ya == 0) - return true; // was not stopped - - boolean interact = true;// !Game.isValidClient() || this instanceof ClientTickable; - - // gets the tile coordinate of each direction from the sprite... - int xto0 = ((x) - xr) >> 4; // to the left - int yto0 = ((y) - yr) >> 4; // above - int xto1 = ((x) + xr) >> 4; // to the right - int yto1 = ((y) + yr) >> 4; // below - - // gets same as above, but after movement. - int xt0 = ((x + xa) - xr) >> 4; - int yt0 = ((y + ya) - yr) >> 4; - int xt1 = ((x + xa) + xr) >> 4; - int yt1 = ((y + ya) + yr) >> 4; - - // boolean blocked = false; // if the next tile can block you. - for (int yt = yt0; yt <= yt1; yt++) { // cycles through y's of tile after movement - for (int xt = xt0; xt <= xt1; xt++) { // cycles through x's of tile after movement - if (xt >= xto0 && xt <= xto1 && yt >= yto0 && yt <= yto1) - continue; // skip this position if this entity's sprite is touching it - // tile positions that make it here are the ones that the entity will be in, but - // are not in now. + protected boolean move2(int xd, int yd) { + if (xd == 0 && yd == 0) return true; // Was not stopped + + boolean interact = true;//!Game.isValidClient() || this instanceof ClientTickable; + + // Gets the tile coordinate of each direction from the sprite... + int xto0 = ((x) - xr) >> 4; // To the left + int yto0 = ((y) - yr) >> 4; // Above + int xto1 = ((x) + xr) >> 4; // To the right + int yto1 = ((y) + yr) >> 4; // Below + + // Gets same as above, but after movement. + int xt0 = ((x + xd) - xr) >> 4; + int yt0 = ((y + yd) - yr) >> 4; + int xt1 = ((x + xd) + xr) >> 4; + int yt1 = ((y + yd) + yr) >> 4; + + //boolean blocked = false; // If the next tile can block you. + for (int yt = yt0; yt <= yt1; yt++) { // Cycles through y's of tile after movement + for (int xt = xt0; xt <= xt1; xt++) { // Cycles through x's of tile after movement + if (xt >= xto0 && xt <= xto1 && yt >= yto0 && yt <= yto1) continue; // Skip this position if this entity's sprite is touching it + // Tile positions that make it here are the ones that the entity will be in, but are not in now. if (interact) level.getTile(xt, yt).bumpedInto(level, xt, yt, this); // Used in tiles like cactus - if (!level.getTile(xt, yt).mayPass(level, xt, yt, this)) { // if the entity can't pass this tile... - // blocked = true; // then the entity is blocked + if (!level.getTile(xt, yt).mayPass(level, xt, yt, this)) { // If the entity can't pass this tile... + //blocked = true; // Then the entity is blocked return false; } } } - - // these lists are named as if the entity has already moved-- it hasn't, though. - List wasInside = level.getEntitiesInRect(getBounds()); // gets all of the entities that are inside this - // entity (aka: colliding) before moving. - + + // These lists are named as if the entity has already moved-- it hasn't, though. + List wasInside = level.getEntitiesInRect(getBounds()); // Gets all of the entities that are inside this entity (aka: colliding) before moving. + int xr = this.xr, yr = this.yr; if (Game.isValidClient() && this instanceof Player) { xr++; yr++; } - List isInside = level - .getEntitiesInRect(new Rectangle(x + xa, y + ya, xr * 2, yr * 2, Rectangle.CENTER_DIMS)); // gets the entities that this entity will touch once moved. - for (int i = 0; interact && i < isInside.size(); i++) { - /// cycles through entities about to be touched, and calls touchedBy(this) for each of them. - Entity e = isInside.get(i); - if (e == this) - continue; // touching yourself doesn't count. - if (e instanceof Player) { - if (!(this instanceof Player)) - touchedBy(e); - } else - e.touchedBy(this); // call the method. ("touch" the entity) + List isInside = level.getEntitiesInRect(new Rectangle(x+xd, y+yd, xr*2, yr*2, Rectangle.CENTER_DIMS)); // Gets the entities that this entity will touch once moved. + if (interact) { + for (Entity e : isInside) { + /// Cycles through entities about to be touched, and calls touchedBy(this) for each of them. + if (e == this) continue; // Touching yourself doesn't count. + + if (e instanceof Player) { + if (!(this instanceof Player)) + touchedBy(e); + } else + e.touchedBy(this); // Call the method. ("touch" the entity) + } } - - isInside.removeAll(wasInside); // remove all the entities that this one is already touching before moving. - for (int i = 0; i < isInside.size(); i++) { - Entity e = isInside.get(i); - - if (e == this) - continue; // can't interact with yourself - - if (e.blocks(this)) - return false; // if the entity prevents this one from movement, don't move. + + isInside.removeAll(wasInside); // Remove all the entities that this one is already touching before moving. + for (Entity e : isInside) { + if (e == this) continue; // Can't interact with yourself + if (e.blocks(this)) return false; // If the entity prevents this one from movement, don't move. } - - // finally, the entity moves! - x += xa; - y += ya; - + + // Finally, the entity moves! + x += xd; + y += yd; + return true; // the move was successful. } - /** - * This exists as a way to signify that the entity has been removed through - * player action and/or world action; basically, it's actually gone, not just - * removed from a level because it's out of range or something. Calls to this - * method are used to, say, drop items. - */ - public void die() { - remove(); - } - + /** This exists as a way to signify that the entity has been removed through player action and/or world action; basically, it's actually gone, not just removed from a level because it's out of range or something. Calls to this method are used to, say, drop items. */ + public void die() { remove(); } + /** Removes the entity from the level. */ public void remove() { - if (removed && !(this instanceof ItemEntity)) // apparently this happens fairly often with item entities. + if (removed && !(this instanceof ItemEntity)) // Apparently this happens fairly often with item entities. System.out.println("Note: remove() called on removed entity: " + this); - + removed = true; - + if (level == null) System.out.println("Note: remove() called on entity with no level reference: " + getClass()); else level.remove(this); } - - /** - * This should ONLY be called by the Level class. To properly remove an entity - * from a level, use level.remove(entity) - */ + + /** This should ONLY be called by the Level class. To properly remove an entity from a level, use level.remove(entity) */ public void remove(Level level) { if (level != this.level) { - if (Game.debug) - System.out.println("Tried to remove entity " + this + " from level it is not in: " + level - + "; in level " + this.level); + if(Game.debug) System.out.println("Tried to remove entity " + this + " from level it is not in: " + level + "; in level " + this.level); } else { - removed = true; // should already be set. + removed = true; // Should already be set. this.level = null; } } - - - - /** - * This should ONLY be called by the Level class. To properly add an entity to a - * level, use level.add(entity) - */ + /** This should ONLY be called by the Level class. To properly add an entity to a level, use level.add(entity) */ public void setLevel(Level level, int x, int y) { if (level == null) { System.out.println("Tried to set level of entity " + this + " to a null level; Should use remove(level)"); @@ -301,216 +270,175 @@ public void setLevel(Level level, int x, int y) { } else if (level != this.level && Game.isValidServer() && this.level != null) { Game.server.broadcastEntityRemoval(this, this.level, !(this instanceof Player)); } - + this.level = level; removed = false; this.x = x; this.y = y; - + if (eid < 0) eid = Network.generateUniqueEntityId(); } - + public boolean isWithin(int tileRadius, Entity other) { - if (level == null || other.getLevel() == null) - return false; - if (level.depth != other.getLevel().depth) - return false; // obviously, if they are on different levels, they can't be next to each other. - - double distance = Math.abs(Math.hypot(x - other.x, y - other.y)); // calculate the distance between the two - // entities, in entity coordinates. - - return Math.round(distance) >> 4 <= tileRadius; // compare the distance (converted to tile units) with the specified radius. + if (level == null || other.getLevel() == null) return false; + if (level.depth != other.getLevel().depth) return false; // Obviously, if they are on different levels, they can't be next to each other. + + double distance = Math.abs(Math.hypot(x - other.x, y - other.y)); // Calculate the distance between the two entities, in entity coordinates. + + return Math.round(distance) >> 4 <= tileRadius; // Compare the distance (converted to tile units) with the specified radius. } - + /** * Returns the closest player to this entity. - * * @return the closest player. */ - protected Player getClosestPlayer() { + protected Player getClosestPlayer() { return getClosestPlayer(true); } - + /** - * Returns the closes player to this entity. If this is called on a player it - * can return itself. - * + * Returns the closes player to this entity. + * If this is called on a player it can return itself. * @param returnSelf determines if the method can return itself. * @return The closest player to this entity. */ protected Player getClosestPlayer(boolean returnSelf) { if (this instanceof Player && returnSelf) return (Player) this; - - if (level == null) - return null; - + + if (level == null) return null; + return level.getClosestPlayer(x, y); } - + /** - * I think this is used to update a entity over a network. The server will send - * a correction of this entity's state which will then be updated. - * + * I think this is used to update a entity over a network. + * The server will send a correction of this entity's state + * which will then be updated. * @param deltas A string representation of the new entity state. */ public final void update(String deltas) { - for (String field : deltas.split(";")) { - String fieldName = field.substring(0, field.indexOf(",")); - String val = field.substring(field.indexOf(",") + 1); - updateField(fieldName, val); + for (String field: deltas.split(";")) { + String fieldName = field.substring(0, field.indexOf(",")); + String val = field.substring(field.indexOf(",")+1); + updateField(fieldName, val); } - - if (Game.isValidClient() && this instanceof MobAi) { + + if(Game.isValidClient() && this instanceof MobAi) { lastUpdate = System.nanoTime(); } } - + /** - * Updates one of the entity's fields based on a string pair. Used to parse data - * from a server. - * + * Updates one of the entity's fields based on a string pair. + * Used to parse data from a server. * @param fieldName Which variable is being updated. - * @param val The new value. + * @param val The new value. * @return true if a variable was updated, false if not. */ protected boolean updateField(String fieldName, String val) { switch (fieldName) { - case "eid": - eid = Integer.parseInt(val); - return true; - case "x": - x = Integer.parseInt(val); - return true; - case "y": - y = Integer.parseInt(val); - return true; - case "level": - if (val.equals("null")) - return true; // this means no level. - Level newLvl = World.levels[Integer.parseInt(val)]; - if (newLvl != null && level != null) { - if (newLvl.depth == level.depth) - return true; - level.remove(this); - newLvl.add(this); - } - return true; + case "eid": eid = Integer.parseInt(val); return true; + case "x": x = Integer.parseInt(val); return true; + case "y": y = Integer.parseInt(val); return true; + case "level": + if (val.equals("null")) return true; // This means no level. + Level newLvl = World.levels[Integer.parseInt(val)]; + if (newLvl != null && level != null) { + if (newLvl.depth == level.depth) return true; + level.remove(this); + newLvl.add(this); + } + return true; } return false; } - - /// I think I'll make these "getUpdates()" methods be an established thing, that - /// returns all the things that can change that you need to account for when - /// updating entities across a server. - /// by extension, the update() method should always account for all the - /// variables specified here. + + /// I think I'll make these "getUpdates()" methods be an established thing, that returns all the things that can change that you need to account for when updating entities across a server. + /// By extension, the update() method should always account for all the variables specified here. /** - * Converts this entity to a string representation which can be sent to a server - * or client. - * + * Converts this entity to a string representation which can be sent to + * a server or client. * @return Networking string representation of this entity. */ protected String getUpdateString() { - return "x," + x + ";" + "y," + y + ";" + "level," + (level == null ? "null" : World.lvlIdx(level.depth)); + return "x," + x + ";" + + "y," + y + ";" + + "level," + (level == null?"null":World.lvlIdx(level.depth)); } - + /** * Returns a string representation of this entity. - * - * @param fetchAll true if all variables should be returned, false if only the - * ones who have changed should be returned. + * @param fetchAll true if all variables should be returned, false if only the ones who have changed should be returned. * @return Networking string representation of this entity. */ public final String getUpdates(boolean fetchAll) { if (accessedUpdates) { - if (fetchAll) - return prevUpdates; - else - return curDeltas; - } else { - if (fetchAll) - return getUpdateString(); - else - return getUpdates(); + if (fetchAll) return prevUpdates; + else return curDeltas; + } + else { + if (fetchAll) return getUpdateString(); + else return getUpdates(); } } - + /** * Determines what has been updated and only return that. - * - * @return String representation of all the variables which has changed since - * last time. + * @return String representation of all the variables which has changed since last time. */ public final String getUpdates() { - // if the updates have already been fetched and written, but not flushed, then - // just return those. - if (accessedUpdates) - return curDeltas; - else - accessedUpdates = true; // after this they count as accessed. - - /// first, get the current string of values, which includes any subclasses. + // If the updates have already been fetched and written, but not flushed, then just return those. + if (accessedUpdates) return curDeltas; + else accessedUpdates = true; // After this they count as accessed. + + /// First, get the current string of values, which includes any subclasses. String updates = getUpdateString(); - + if (prevUpdates.length() == 0) { - // if there were no values saved last call, our job is easy. But this is only the case the first time this is run. - prevUpdates = curDeltas = updates; // set the update field for next time - return updates; // and we're done! + // If there were no values saved last call, our job is easy. But this is only the case the first time this is run. + prevUpdates = curDeltas = updates; // Set the update field for next time + return updates; // And we're done! } - - /// if we did have updates last time, then save them as an array, before - /// overwriting the update field for next time. + + /// If we did have updates last time, then save them as an array, before overwriting the update field for next time. String[] curUpdates = updates.split(";"); String[] prevUpdates = this.prevUpdates.split(";"); this.prevUpdates = updates; - - /// now, we have the current values, and the previous values, as arrays of - /// key-value pairs sep. by commas. Now, the goal is to separate which are - /// actually *updates*, meaning they are different from last time. - + + /// Now, we have the current values, and the previous values, as arrays of key-value pairs sep. by commas. Now, the goal is to separate which are actually *updates*, meaning they are different from last time. + StringBuilder deltas = new StringBuilder(); - for (int i = 0; i < curUpdates.length; i++) { // b/c the string always contains the same number of pairs (and - // the same keys, in the same order), the indexes of cur and - // prev updates will be the same. - /// loop though each of the updates this call. If it is different from the last - /// one, then add it to the list. - if (!curUpdates[i].equals(prevUpdates[i])) { + for (int i = 0; i < curUpdates.length; i++) { // b/c the string always contains the same number of pairs (and the same keys, in the same order), the indexes of cur and prev updates will be the same. + /// Loop though each of the updates this call. If it is different from the last one, then add it to the list. + if(!curUpdates[i].equals(prevUpdates[i])) { deltas.append(curUpdates[i]).append(";"); } } - + curDeltas = deltas.toString(); - - if (curDeltas.length() > 0) - curDeltas = curDeltas.substring(0, curDeltas.length() - 1); // cuts off extra ";" - + + if (curDeltas.length() > 0) curDeltas = curDeltas.substring(0, curDeltas.length() -1); // Cuts off extra ";" + return curDeltas; } - - /// this marks the entity as having a new state to fetch. - public void flushUpdates() { - accessedUpdates = false; - } - - public String toString() { - return getClass().getSimpleName() + getDataPrints(); - } - + + /// This marks the entity as having a new state to fetch. + public void flushUpdates() { accessedUpdates = false; } + + public String toString() { return getClass().getSimpleName() + getDataPrints(); } protected List getDataPrints() { List prints = new ArrayList<>(); prints.add("eid=" + eid); return prints; } - + @Override public final boolean equals(Object other) { return other instanceof Entity && hashCode() == other.hashCode(); } - + @Override - public final int hashCode() { - return eid; - } + public final int hashCode() { return eid; } } diff --git a/src/minicraft/entity/furniture/Chest.java b/src/minicraft/entity/furniture/Chest.java index 5cc7ceb9..9673ae82 100644 --- a/src/minicraft/entity/furniture/Chest.java +++ b/src/minicraft/entity/furniture/Chest.java @@ -3,7 +3,10 @@ import java.io.IOException; import java.util.List; +import org.jetbrains.annotations.Nullable; + import minicraft.core.Game; +import minicraft.entity.Direction; import minicraft.entity.ItemHolder; import minicraft.entity.mob.Player; import minicraft.gfx.Sprite; @@ -61,20 +64,12 @@ public void populateInvRandom(String lootTable, int depth) { } } - @Override - public void take(Player player) { - if (inventory.invSize() == 0) - super.take(player); - } - - /* @Override public boolean interact(Player player, @Nullable Item item, Direction attackDir) { if (inventory.invSize() == 0) return super.interact(player, item, attackDir); return false; } - */ @Override protected String getUpdateString() { diff --git a/src/minicraft/entity/furniture/DungeonChest.java b/src/minicraft/entity/furniture/DungeonChest.java index d5777dbb..aab741d8 100644 --- a/src/minicraft/entity/furniture/DungeonChest.java +++ b/src/minicraft/entity/furniture/DungeonChest.java @@ -122,15 +122,15 @@ public boolean interact(Player player, @Nullable Item item, Direction attackDir) return super.interact(player, item, attackDir); return false; } - + @Override protected String getUpdateString() { String updates = super.getUpdateString() + ";"; updates += "isLocked,"+isLocked; - + return updates; } - + @Override protected boolean updateField(String field, String val) { if(super.updateField(field, val)) return true; @@ -139,7 +139,7 @@ protected boolean updateField(String field, String val) { isLocked = Boolean.parseBoolean(val); return true; } - + return false; } } \ No newline at end of file diff --git a/src/minicraft/entity/furniture/Furniture.java b/src/minicraft/entity/furniture/Furniture.java index 77eb776a..c071b701 100644 --- a/src/minicraft/entity/furniture/Furniture.java +++ b/src/minicraft/entity/furniture/Furniture.java @@ -1,6 +1,9 @@ package minicraft.entity.furniture; +import org.jetbrains.annotations.Nullable; + import minicraft.core.Game; +import minicraft.core.io.Sound; import minicraft.entity.Direction; import minicraft.entity.Entity; import minicraft.entity.mob.Player; @@ -8,6 +11,7 @@ import minicraft.gfx.Screen; import minicraft.gfx.Sprite; import minicraft.item.FurnitureItem; +import minicraft.item.Item; import minicraft.item.PowerGloveItem; /** @@ -89,6 +93,31 @@ protected void touchedBy(Entity entity) { if (entity instanceof Player) tryPush((Player) entity); } + + + /** + * Used in PowerGloveItem.java to let the user pick up furniture. + * @param player The player picking up the furniture. + */ + @Override + public boolean interact(Player player, @Nullable Item item, Direction attackDir) { + if (item instanceof PowerGloveItem) { + Sound.Mob_generic_hurt.play(); + if (!Game.ISONLINE) { + remove(); + if (!Game.isMode("creative") && player.activeItem != null && !(player.activeItem instanceof PowerGloveItem)) + player.getInventory().add(0, player.activeItem); // put whatever item the player is holding into their inventory + player.activeItem = new FurnitureItem(this); // make this the player's current item. + return true; + } else if (Game.isValidServer() && player instanceof RemotePlayer) { + remove(); + Game.server.getAssociatedThread((RemotePlayer) player).updatePlayerActiveItem(new FurnitureItem(this)); + return true; + } else + System.out.println("WARNING: undefined behavior; online game was not server and ticked furniture: " + this + "; and/or player in online game found that isn't a RemotePlayer: " + player); + } + return false; + } /** * Tries to let the player push this furniture. @@ -105,25 +134,6 @@ public void tryPush(Player player) { } } - /** - * Used in PowerGloveItem.java to let the user pick up furniture. - * - * @param player The player picking up the furniture. - */ - public void take(Player player) { - remove(); // remove this from the world - if (!Game.ISONLINE) { - if (!Game.isMode("creative") && player.activeItem != null && !(player.activeItem instanceof PowerGloveItem)) - player.getInventory().add(0, player.activeItem); // put whatever item the player is holding into their inventory (should never be a power glove, since it is put in a taken out again all in the same frame). - player.activeItem = new FurnitureItem(this); // make this the player's current item. - } else if (Game.isValidServer() && player instanceof RemotePlayer) - Game.server.getAssociatedThread((RemotePlayer) player).updatePlayerActiveItem(new FurnitureItem(this)); - else - System.out.println("WARNING: undefined behavior; online game was not server and ticked furniture: " + this + "; and/or player in online game found that isn't a RemotePlayer: " + player); - - // if (Game.debug) System.out.println("set active item of player " + player + " to " + player.activeItem + "; picked up furniture: " + this); - } - @Override public boolean canWool() { return true; diff --git a/src/minicraft/entity/mob/Player.java b/src/minicraft/entity/mob/Player.java index a003245d..6bc73a39 100644 --- a/src/minicraft/entity/mob/Player.java +++ b/src/minicraft/entity/mob/Player.java @@ -1033,55 +1033,66 @@ public void render(Screen screen) { curSprite.renderRow(0, screen, xo, yo, -1, shirtColor); } + // Renders slashes: - if (attackTime > 0) { switch (attackDir) { - case UP: // If currently attacking upwards... - screen.render(xo + 0, yo - 4, 3 + 2 * 32, 0, 3); // Render left half-slash - screen.render(xo + 8, yo - 4, 3 + 2 * 32, 1, 3); // Render right half-slash (mirror of left). - if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { // If the player had an item when they last attacked... - attackItem.sprite.render(screen, xo + 4, yo - 4, 1); // Then render the icon of the item, mirrored - } - break; - case LEFT: // Attacking to the left... (Same as above) - screen.render(xo - 4, yo, 4 + 2 * 32, 1, 3); - screen.render(xo - 4, yo + 8, 4 + 2 * 32, 3, 3); - if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { - attackItem.sprite.render(screen, xo - 4, yo + 4, 1); - } - break; - case RIGHT: // Attacking to the right (Same as above) - screen.render(xo + 8 + 4, yo, 4 + 2 * 32, 0, 3); - screen.render(xo + 8 + 4, yo + 8, 4 + 2 * 32, 2, 3); - if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { - attackItem.sprite.render(screen, xo + 8 + 4, yo + 4); - } - break; - case DOWN: // Attacking downwards (Same as above) - screen.render(xo + 0, yo + 8 + 4, 3 + 2 * 32, 2, 3); - screen.render(xo + 8, yo + 8 + 4, 3 + 2 * 32, 3, 3); - if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { - attackItem.sprite.render(screen, xo + 4, yo + 8 + 4); - } - break; + + case UP: // if currently attacking upwards... + screen.render(xo + 0, yo - 4, 3 + 2 * 32, 0, 3); //render left half-slash + screen.render(xo + 8, yo - 4, 3 + 2 * 32, 1, 3); //render right half-slash (mirror of left). + if (attackItem != null) { // if the player had an item when they last attacked... + attackItem.sprite.render(screen, xo + 4, yo - 4, 1); // then render the icon of the item, mirrored + } + break; + + case LEFT: // Attacking to the left... (Same as above) + screen.render(xo - 4, yo, 4 + 2 * 32, 1, 3); + screen.render(xo - 4, yo + 8, 4 + 2 * 32, 3, 3); + if (attackItem != null) { + attackItem.sprite.render(screen, xo - 4, yo + 4, 1); + } + break; + + case RIGHT: // Attacking to the right (Same as above) + screen.render(xo + 8 + 4, yo, 4 + 2 * 32, 0, 3); + screen.render(xo + 8 + 4, yo + 8, 4 + 2 * 32, 2, 3); + if (attackItem != null) { + attackItem.sprite.render(screen, xo + 8 + 4, yo + 4); + } + break; + + case DOWN: // Attacking downwards (Same as above) + screen.render(xo + 0, yo + 8 + 4, 3 + 2 * 32, 2, 3); + screen.render(xo + 8, yo + 8 + 4, 3 + 2 * 32, 3, 3); + if (attackItem != null) { + attackItem.sprite.render(screen, xo + 4, yo + 8 + 4); + } + break; + + case NONE: + break; } } - + if (isFishing) { switch (dir) { case UP: screen.render(xo + 4, yo - 4, fishingLevel + 11 * 32, 1); break; + case LEFT: screen.render(xo - 4, yo + 4, fishingLevel + 11 * 32, 1); break; + case RIGHT: screen.render(xo + 8 + 4, yo + 4, fishingLevel + 11 * 32, 0); break; + case DOWN: screen.render(xo + 4, yo + 8 + 4, fishingLevel + 11 * 32, 0); break; + case NONE: break; } diff --git a/src/minicraft/gfx/Font.java b/src/minicraft/gfx/Font.java index 004990b7..7c76c356 100644 --- a/src/minicraft/gfx/Font.java +++ b/src/minicraft/gfx/Font.java @@ -12,7 +12,7 @@ public class Font { private static String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"+ "6789.,!?'\"-+=/\\%()<>:;^@ÁÉÍÓÚÑ¿¡"+ - "ÃÊÇÔÕĞÇÜİÖŞÆØÅ"; + "ÃÊÇÔÕĞÇÜİÖŞÆØÅŰŐ[]#|{}_"; /* * The order of the letters in the chars string is represented in the order that * they appear in the sprite-sheet. diff --git a/src/minicraft/item/Inventory.java b/src/minicraft/item/Inventory.java index 8e064c92..8c6a4f9d 100644 --- a/src/minicraft/item/Inventory.java +++ b/src/minicraft/item/Inventory.java @@ -9,8 +9,8 @@ import minicraft.entity.furniture.Furniture; public class Inventory { - private Random random = new Random(); - private List items = new ArrayList<>(); // the list of items that is in the inventory. + private final Random random = new Random(); + private final List items = new ArrayList<>(); // The list of items that is in the inventory. /** * Returns all the items which are in this inventory. @@ -18,9 +18,7 @@ public class Inventory { * @return ArrayList containing all the items in the inventory. */ public List getItems() { - List newItems = new ArrayList<>(); - newItems.addAll(items); - return newItems; + return new ArrayList<>(items); } public void clearInv() { @@ -31,10 +29,20 @@ public int invSize() { return items.size(); } + /** + * Get one item in this inventory. + * @param idx The index of the item in the inventory's item array. + * @return The specified item. + */ public Item get(int idx) { return items.get(idx); } + /** + * Remove an item in this inventory. + * @param idx The index of the item in the inventory's item array. + * @return The removed item. + */ public Item remove(int idx) { return items.remove(idx); } diff --git a/src/minicraft/item/PowerGloveItem.java b/src/minicraft/item/PowerGloveItem.java index 9fcb5541..938c9dda 100644 --- a/src/minicraft/item/PowerGloveItem.java +++ b/src/minicraft/item/PowerGloveItem.java @@ -1,10 +1,5 @@ package minicraft.item; -import minicraft.entity.Boat; -import minicraft.entity.Direction; -import minicraft.entity.Entity; -import minicraft.entity.furniture.Furniture; -import minicraft.entity.mob.Player; import minicraft.gfx.Sprite; public class PowerGloveItem extends Item { @@ -13,19 +8,6 @@ public PowerGloveItem() { super("Power Glove", new Sprite(0, 12, 0)); } - public boolean interact(Player player, Entity entity, Direction attackDir) { - if (entity instanceof Furniture) { // If the power glove is used on a piece of furniture... - Furniture f = (Furniture) entity; - f.take(player); // Takes (picks up) the furniture - return true; - } else if (entity instanceof Boat) { - Boat b = (Boat) entity; - b.take(player); - return true; - } - return false; // method returns false if we were not given a furniture entity. - } - public PowerGloveItem clone() { return new PowerGloveItem(); } diff --git a/src/minicraft/level/Level.java b/src/minicraft/level/Level.java index 0ce3d86e..44aa5b1c 100644 --- a/src/minicraft/level/Level.java +++ b/src/minicraft/level/Level.java @@ -184,8 +184,7 @@ public Level(int w, int h, long seed, int level, Level parentLevel, boolean make return; } - if (Game.debug) - System.out.println("Making level " + level + "..."); + if(Game.debug) System.out.println("Making level "+level+"..."); maps = LevelGen.createAndValidateMap(w, h, level); if (maps == null) { @@ -204,19 +203,20 @@ public Level(int w, int h, long seed, int level, Level parentLevel, boolean make if (parentLevel != null) { // If the level above this one is not null (aka, if this isn't the sky level) - for (int y = 0; y < h; y++) { // loop through height - for (int x = 0; x < w; x++) { // loop through width + for (int y = 0; y < h; y++) { // Loop through height + for (int x = 0; x < w; x++) { // Loop through width if (parentLevel.getTile(x, y) == Tiles.get("Stairs Down")) { // If the tile in the level above the current one is a stairs down then... - if (level == -4) /// make the obsidian wall formation around the stair in the dungeon level + if (level == -4) /// Make the obsidian wall formation around the stair in the dungeon level Structure.dungeonGate.draw(this, x, y); - else if (level == 0) { // surface - if (Game.debug) System.out.println("Setting tiles around "+x+","+y+" to hard rock"); - setAreaTiles(x, y, 1, Tiles.get("Hard Rock"), 0); // surround the sky stairs with hard rock + else if (level == 0) { // Surface + if (Game.debug) System.out.println("Setting tiles around "+ x +","+ y +" to hard rock"); + setAreaTiles(x, y, 1, Tiles.get("Hard Rock"), 0); // Surround the sky stairs with hard rock } - else // any other level, the up-stairs should have dirt on all sides. + else // Any other level, the up-stairs should have dirt on all sides. setAreaTiles(x, y, 1, Tiles.get("dirt"), 0); - setTile(x, y, Tiles.get("Stairs Up")); // set a stairs up tile in the same position on the current level + + setTile(x, y, Tiles.get("Stairs Up")); // Set a stairs up tile in the same position on the current level } } } @@ -270,7 +270,7 @@ private void checkAirWizard(boolean check) { if (e instanceof AirWizard) found = true; for (Entity e: entities) - if(e instanceof AirWizard) + if (e instanceof AirWizard) found = true; } @@ -291,24 +291,26 @@ private void checkChestCount(boolean check) { int numChests = 0; - if(check) { - for(Entity e: entitiesToAdd) - if(e instanceof DungeonChest) + if (check) { + for (Entity e: entitiesToAdd) + if (e instanceof DungeonChest) numChests++; - for(Entity e: entities) - if(e instanceof DungeonChest) + for (Entity e: entities) + if (e instanceof DungeonChest) numChests++; if (Game.debug) System.out.println("Found " + numChests + " chests."); } - /// make DungeonChests! + /// Make DungeonChests! for (int i = numChests; i < 10 * (w / 128); i++) { DungeonChest d = new DungeonChest(true); boolean addedchest = false; - while(!addedchest) { // keep running until we successfully add a DungeonChest - //pick a random tile: + while(!addedchest) { // Keep running until we successfully add a DungeonChest + + // Pick a random tile: int x2 = random.nextInt(16 * w) / 16; int y2 = random.nextInt(16 * h) / 16; + if (getTile(x2, y2) == Tiles.get("Obsidian")) { boolean xaxis = random.nextBoolean(); if (xaxis) { diff --git a/src/minicraft/level/tile/LawnTile.java b/src/minicraft/level/tile/LawnTile.java index 07abeb11..ea9c21f8 100644 --- a/src/minicraft/level/tile/LawnTile.java +++ b/src/minicraft/level/tile/LawnTile.java @@ -58,7 +58,11 @@ public boolean interact(Level level, int x, int y, Player player, Item item, Dir if (player.payStamina(2 - tool.level) && tool.payDurability()) { level.setTile(x, y, Tiles.get("grass")); Sound.Tile_generic_hurt.play(); - level.dropItem(x * 16 + 8, y * 16 + 8, Items.get("Seeds")); + + if (random.nextInt(3) == 1) { // 28% chance to drop Seeds + level.dropItem(x * 16 + 8, y * 16 + 8, Items.get("Seeds")); + } + return true; } } @@ -68,7 +72,9 @@ public boolean interact(Level level, int x, int y, Player player, Item item, Dir public boolean hurt(Level level, int x, int y, Mob source, int dmg, Direction attackDir) { - level.dropItem(x * 16 + 8, y * 16 + 8, 0, 1, Items.get("Seeds")); + if (random.nextInt(6) == 1) { // 20% chance to drop sky seeds + level.dropItem(x * 16 + 8, y * 16 + 8, 0, 1, Items.get("Seeds")); + } level.setTile(x, y, Tiles.get("grass")); return true; diff --git a/src/minicraft/level/tile/TreeTile.java b/src/minicraft/level/tile/TreeTile.java index 1257518e..9ea975cc 100644 --- a/src/minicraft/level/tile/TreeTile.java +++ b/src/minicraft/level/tile/TreeTile.java @@ -79,8 +79,8 @@ public boolean hurt(Level level, int x, int y, Mob source, int dmg, Direction at @Override public boolean interact(Level level, int xt, int yt, Player player, Item item, Direction attackDir) { - if (Game.isMode("creative")) - return false; // go directly to hurt method + if (Game.isMode("Creative")) + return false; // Go directly to hurt method if (item instanceof ToolItem) { ToolItem tool = (ToolItem) item; if (tool.type == ToolType.Axe) { @@ -107,9 +107,9 @@ public void hurt(Level level, int x, int y, int dmg) { level.add(new TextParticle("" + dmg, x * 16 + 8, y * 16 + 8, Color.RED)); if (damage >= treeHealth) { - level.dropItem(x * 16 + 8, y * 16 + 8, 1, 2, Items.get("Wood")); - level.dropItem(x * 16 + 8, y * 16 + 8, 1, 2, Items.get("Acorn")); - level.setTile(x, y, Tiles.get("grass")); + level.dropItem(x * 16 + 8, y * 16 + 8, 1, 3, Items.get("Wood")); + level.dropItem(x * 16 + 8, y * 16 + 8, 0, 2, Items.get("Acorn")); + level.setTile(x, y, Tiles.get("Grass")); } else { level.setData(x, y, damage); } diff --git a/src/minicraft/level/tile/farming/OrangeCropTile.java b/src/minicraft/level/tile/farming/OrangeCropTile.java new file mode 100644 index 00000000..f9e6b8e4 --- /dev/null +++ b/src/minicraft/level/tile/farming/OrangeCropTile.java @@ -0,0 +1,5 @@ +package minicraft.level.tile.farming; + +public class OrangeCropTile { + +} diff --git a/src/minicraft/screen/CraftingDisplay.java b/src/minicraft/screen/CraftingDisplay.java index b0a99389..75441410 100644 --- a/src/minicraft/screen/CraftingDisplay.java +++ b/src/minicraft/screen/CraftingDisplay.java @@ -77,7 +77,7 @@ private ItemListing[] getCurItemCosts() { HashMap costMap = recipes[recipeMenu.getSelection()].getCosts(); for(String itemName: costMap.keySet()) { Item cost = Items.get(itemName); - costList.add(new ItemListing(cost, costMap.get(itemName)+"/"+player.getInventory().count(cost))); + costList.add(new ItemListing(cost, player.getInventory().count(cost) + "/" + costMap.get(itemName))); } return costList.toArray(new ItemListing[costList.size()]); diff --git a/src/minicraft/screen/TitleDisplay.java b/src/minicraft/screen/TitleDisplay.java index 16fdf51d..2437ae0f 100644 --- a/src/minicraft/screen/TitleDisplay.java +++ b/src/minicraft/screen/TitleDisplay.java @@ -305,6 +305,11 @@ public void render(Screen screen) { "Test == InDev!", "Story? yes!", "Mod on phase B", + + "Axes: good against plants!", + "Picks: good against rocks!", + "Shovels: good against dirt!", + "Swords: good against mobs!", // What's that? "Infinite terrain? What's that?", diff --git a/src/resources/textures/gui.png b/src/resources/textures/gui.png index cafd780886bc278ef8efb4c0c5b730e313a67a49..e00144c18301ebde0a13642389d67777a511d44c 100644 GIT binary patch literal 23318 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%0DY=?D1*h~D?6#LB4O0{l$(OT?06841%6 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 delta 19174 zcmb@tbzD@@`|nGKNQsIFLo3nl5V8C1O%jGNTqXN=p5>9 zzrWwP=bZaG_x^SNfEgAuYwx|*UeEeG@8`3Bdbj4H|8T(t6uA|oCEj@$AT#ijpY)!c zm*UDKt4pg(U`P7XmgZD{f~k$uN|W{S{$45yNN#)~yW))f_91$t`uL|mA6F%%<#I_G z_wnrT)FqwvOG zr)U0zSm)51K=A_wbk=|;-47J!5>`ZTu3c@hC9GzGu~KA%jnhpMI^Sx$>(ms47_Ecm1-Jv9CCj5&fpPC@i_uK4pmP0|Y4WPU9P5R8 z+geAx+E20CoF4`_JE%yt5d39X(cD;`%u3C4z5!6saBWG70F)@HMiw~IkOK9sPKEbTkn8oXl80o2n^yuSP8AY#O0N-Wk^sdSGAP?c#KzITP# z5GHruYofD1wk=Q26)SvvBT+}cmXObMgkb}c0RXG+E29^r9(BSzrO%oJFJ{6c*d+a z3fN;Ar5UzJC|G1uLO>JZ!`+=G3-0a;_bU~jrf+tsihFQt_zI8S+f%CEX0I}8Y2THE z^tpd;Q-Q3?5b)cm@tCvCV5bLToGNq`R8-KuO1&NCw>n<`JRZJc&VJ6QZ3Vl}Dq;ws zDIpTR^ttt^uBetu*LVLq*vwVVYqi#o2fzxo`@YSJFsqittfI$g<7E3F>t?JV$L1nD z$ccOF3UDN^ zTFI!b(rF}Pn%|PO$mM0u%$%wF8}t2?-Wyj~`PF!)_ddm@hPUJC_BevP<5K3GL7P{d zN)?8OUzkPA{4+F>OwA}NK@G75tUAOpZ5UH#U{t(N&&fd3#ktxE4YcBi&T;2Po7Ra8 z)Bbs*?0^yxEK2_O6?XxYlc|N!z&|jT z#QUy@-59t#y<-EZJH_{Ii2OjB7%pj=?fu#)OY4%or*@Xkm`)8p)T{Ewql|;b50POt zS8FzMLS9TE<7_UreBryv123J_I7^C1D~+pmhue?PlpaJRsZx~M(};Nw)*zT#+|C>d z&pX7t0GO-a$5g}{*L2j!`>v8+{Cm~gtp)OTw`P=ZYzrIlF}1EM-G)A-dAigB@nlE& z*^;nHu}YA$$4VBW{xwP$_2gu$^tFk3sT7f?JZqmyN{*)gB(1=H+5J_nU1px5pJxno zy+o>Z|DycEPHpcVrYrPnQcR|U;AziUH~pL)S@CYh3>pOGI%+K$B* z1$A1tSM&q%l@hlv@Dty@&skzdbG>Q{Y<{cmFjrInYtYvJa|X>&q$tLk{M?XKM9y@f z#va}$SNMzdDRsfFYugqR)V9OyyUHli!Se$NQIb?Ww@{Bu3M1Ol9x~%gw*uQ~KMVZ< zAu!NDoOgrTt=v6ryI4FaRLLkgIcAAk?~R&D7!|u0F6Y4Vq&@2;N@UkdoThf2aLaqU z`Z|FBq<+MVOr(b`Lf^(*WT0hl>~pu`LlLjj!=ufw{*j@_wU(1pb}h)KR$2~cS49N%1!Wsby9a1PG&0d?*yei71k4N2TQ9QtF#XMJHkva=P=#xDb|*&BpIIgw3d-)Xpv{DF}{ zl`llMX~Newh|P$*r#PKB*u{j&$2jXPSrtA!gb10;z(|gIwfxs-1#Q<6%x!aG8fe4` zZr@oPlPSH1Q+a=%K0N&EzO)0KRp=}u?kSay=5{0c{Si}QE~))33`#d2yNTnnft>1@ z-mr>SrBUelGkZm%vozI$c;_mZ{R&trtw(QRNYu>b207?l+~CsQjsTr1&D(>();-67f1os9J3~ijAa^fdFg2A0t4h zTD&vrw0Uv-Lx<|p$zAN2r+g%LF{Y6mipD@l_1=8B+;C?p4E3MJ!>JDdt%KZCyg#wn z81AM4oe*8x$1B5!Xg5)|8!fZUx#>=075a&ovTT&wA;!8TlaUM@Eg)XRDx3+NNF)x_K=H8~a@wQ~%2E z(|xtgKRir`5$gmChZ<8@B8E>Cec$oT%bclOdOLYu@<>ux6jhu|onDvoemCO#_~_8V zCGan9)Txe$K);mL-QxHNCNpO1)k{Mc9%KPgM^caK?%@cFUjaD>5HD2ybe!7q&P3C@ zaA1T)#IK*_Y`EE+TFg!cvVGX1IQ5Os!-7|sz(H=BeqQMKOx81Cwl*a*r1}P`i<(lx z7g)J6Z@>AFLalqf&1upgxL*A!h~P56Dy&CiJnQ*hsAKy9+*)tl{>~<1rlfa3v^}#t z68%y?Utug$$c}Xt5Sg#a74y1sI_nSFD%P`0DViQpZ4tMXr9k*?*e*iRVarTwa0Q*drBOim9r*v&WVf2;mhm*%%TqJY!(NTGCoPF{^9fJ z?d|o8K%Hq_{faD+3VCVu>+6$wf10di_pOzYcP9HH`fZ}VhOQcgFAznn_ql#y2e=8} ziUkYnH3)uhmf}P_FERI~E|(qhQOWpQ6nim-&u~*YQ~pluYCOvYFx|~@f9&@DeN+tO zS+?!QM#qudDPUjLF%dAiM>%ZxZgGv%(Y|#ENKj+yxC5HQwZv)VUO}_Vx zhE)-5o?*rJ#fum71hIkP;o+aQK4yKCtMFXsI5=*gfeXED*mbXFVfNK>^=XYn)!tLV z+pb@oE$KwOyUpN;n&;-TquhRtjZyiM0L#lXKs=g_gd3>Pj+fOKdDaE@%4yJAbvFgy zBSXwo#^bO=xK(=?j*f7A%%s}q(br3<#@1-4SF9nMxYR~+zBQSVM^9pEqouOHP8k>^ z!?XQN-?Av2$a{Ay>bk`=<*MN>^>#679SSD3`(78!Vb3|58*WCu8FZpe>CCKW*w7R5 zFb@@`22@uheh8DuhWBYZpn|U{Cl+fTZE%ZCZL<-9#r`JMo3kurTE(DR~WHC#{2v+|l?H zAtjBYpPvcHO9C))WrZ~ME;(a!<)Wtzo8#QhRj|#oUgq$}ipIM#hc&P1HL0vOF0L4Zn#u3s4n9yo%1>7yJaZ z%T$UkD}6)l6Oek;uWYsCRSFO2@CBog4n<8ay@0*fKrL)CUU`r)jVLcT=I2|m z)C>_%%@95Vzx(Td@8*B+<$pQGZQC@f7ZLoPnTdmNS5`dwosIdN4FTlGHTUkiBJz9= zK+t06V0umwI=fPO>Ipb`F=P|zmKhNZtu+-YoYcVs-8==c*>UOm``oH+_7>wB<}0Y zE(PqAj%UvQIxX)5&buUD`HEXyFnIzzu2ZaEAz_iEn;C^Hd8?!nPufnsn5yP0xje!AV|P6r44-w&$fz{!N;GpF+3!*^A8>Cf);v-J~!;HOD_08mo18=}9< zX%Hsz&hl_{%#H)fiq!MavLs%oDH$l%Y~Os5B`t;3WhwHWX34-XQTJ)RyWm>KOau?Q zKv2R_ihv_@U{?{nU{Oo#%o6G|!C^+6_BpE{EgLrSsXobT41GOdt^rN1=K_D62J5OI zBh6fZi%x-Ym`=GbE~uj#NJok0rG)hJ)2t!Te25?2Q%~98su9=!AchR=ofrEcmLh(4 zJma;!1gdgy;}w~99-nuswrNJ(T&qjQsjSBowod5)t~x}1+j~W(TXa`b0m-*gF}QKW z6a7St_CkLMtVM^O6%Ocn!!}orpCq-|XI4!~QC!eATfxHZc zgJ-HTo&uYUX4>fqa0pb>iJ+8KBLsqo(h+Uv1AoUQZe~Lbj8%5-zuVrCz5nBG*kGqW zf_X>3bV2=aLi82Z)tRc_!LXN6RAAXHQuZKM7r1Co7WyEu=&|wt|y|s)f0+f!W&!G>iKkCx6`^IISaJpgKj@V?w6B0u+OAnKh!ZdxG1%r6|pq zEMh-pehi0Bl8lfFi69*Hp60#kT#=iTMJ3VOsTL2E%fZTLf}s*pA1kf%u zt6JY@YqLMWx{8Z?1nm5Fe}d(CdDBIsdQ`xnx4HBjFVXq13$xZ_)zERRNx98J2oY^| zGA&lT`xs0i04VW&12y+XbDbsWF^%uj0vY)mdvA%5J~Za%kH*2U`UbaY>EtG9@VD?y zhd17_8E2B`Wyb}{`q5I;(ftA4aaMDdZri+AwJuMkeGJW_iNn;EKfmU^KtznBgq*y< zb1YJgMY9xP&+mR_n4}u47}FK{lrmE2qWG}@eyzg_02jsL*Py-~_ikq2W7oSKxp$4} z?P$qTgO$&!K&lwXos@ zEFJ*V>?e`l1;s%xFUB`Goc+F-k!Wz4wiZhPcyoSZbodG+F7lF(unyi&lMH!UpV;0v z8`xzX0w2;{p3x0xZ~0>q9UcK!8^B2mNsF%BZ(M4R?CCi-vZ}B)&y!6{Go$IS_#al! z=WJ+oyt~S_9!-6hiYtGR?l@hhYXt(P4_@bcDUM^S6!o)xUnz2lVVdNp-$#TFT8nOg zMel?BvjEh`_+yJK--1DsmJWf~dMKGu1CK3+iz8B*%e620jqaLBa8kp!n61jKg z>q#;V!E9{ z_uKYXz05VfLelve2XOnHmJ*_0GAG;=kjm@p;X1(sh&o(dj4zDW5R<>COBFRnnb7A_UL>9rlAKgB)I1itZSZO?@;Fj+2p|_Qde4alEiU-8hI?%WlB^9@M zsAK3`=XE+R2j6cws2H3uNH5cCu)AY#_><#`b`~%zQL0^jV>wx{b5k=aT1xGAX$)zz znQyQ^=zb}0Xy(B?g~mkWOj&EYz*pW;nCYc>i?jzcH4EO3BR?|a`Kqmoe`W~hSw*WS zcDTyO+09O@-tFXH2>9L33ShgnsM5OD2*z8Njin4qcnj#3WZ}=hl!xGD^K9abn;b58 z85hTaXI$*Q=*iSgK}z=z^(iVTxq40(C|iNKE^3*bgJas>`@4$6P9c*2k3-ONQJ==2 zrQgu_rWWiwZe6axX@eujqq)3JKfxOvO)`=~O`#@PqQ3MNSb#-_YvLqDmub`+ zl9P&dEXxkh6Z1N+GsMHIEwTGzw>dL%eOv*EM!OsXE<4f3P^vt6$7PHYG~9CudwL|y zRodb_64MaN4Xvg2_3khBnKCkd4&5*7`o0Q~V!2fiF7G$}2Ev(e+59!pG?`TVw_n~| z_K9gT$VEL=0uBxCk1N( zpkTMkpzy>Ye4aPxebCmxp+uSD-t`(BO1F%@0DIZ06dns%BsSHJcsdpn-p(#u#}Iyo zEkN&rk!TwcHb*W_Jt!+iUt}_Ky=!=1vYUcoBd3xkg$TOn{Y@;ZMIGvU^VYYZ;(F&> zN534^Yja)+!6N!)#&edZWR(dW0(x=sRZZ&(wG>XMaNT%xd#br{O@ zlg^pzoFcEu83m$ShG1%bR8%L!jYE6pwq6ht7h^l%mzJqPFbykxYSV0ByEq|f%Yn*8 znah~?4Q z{l7Q8YQA8<&GI}{u>@9A8EMGsGw*&u zkK;$mD<1=v#4-E}xi<5*Bf=*`wS34BPl`eJNZe4&2i&4$-dB!mr26R@HbD3gCUhzv z^0fd0ZDBi$ec(osjO#!5G}*H&KLXP=zn!YH;;srd$+7n4q?5PZ#%&U3@7OjYYtnTb z=Mw!D!vSq=CVH5WFx;N+_nr}-ek~5Z7yxXd2XQ-m>n^M9lXwInN%ZxN3H9|= z%M5!%lDB!Bdv0u?q!wjkEcl$WXIoDR&E-R&<1$*5%d4&55vDa;ENOxFTn~jzH%Vx8 zo&`Y7f{4tj7R+i4U~~JNhn~`=h8i{Sg%8pFb+B>{lA z(Rd{C7N&_>M zy#e=@Z9_KNr5}K?l&yG=ulLf$O<`7M{_dIK9r@Wvi54RE*y;At7`7Zbi0PXq?nl!f z*9R@ptsK(!)6D0K{9ES52S}I0ZtAW66y<+-mbaSFM5T_Gs0vPaBE6%m!-biWhya5) z^f-dy1{7nZLRmM81nFe;omI7%7zr!M4vOn5UmeWj0ZfeRT!;|DkgAiSOFe*Aa>T7YKZ{%$6#XAE`8zCrxk{k99O5B5IW8W>4)^Mf*ue2BIolI zx?t0=27ngzq(UFBj+swc6^KZFj`59u$y47*7E;;9Q-0mWdU}~0_N`2}=H(EnM#KM& zGY{%i_otZ?;#sNTxIn{df@bnFXGzDHT4-V=qA3@DGl~-z&XpwnVd~dO!_ZM~t=Wme z9SBKfEN9uCj!-Djt$?HaXFxpgZe z#-WkHo-+&_1c5@dklVcl`${b7-1AD+C7p~ZONIAYEEFpvuUMv?)eRqA(!AVrQPwaD{)746PuBlgdyz+IP1g$Wb=Zg$>p^IQH!M~1N zYJ-yGAG|o82c9J=_n60M7q;H;-<7G?z$4CfqdS%r=F3mH0H==V z+|yt(>Z2u}lZjV_3Yc127I*mFdcn;`MqN^3Bxy}X%!P^s7)Y21BdUnP9!D$3eBlqs!Ar6Atf-cj z7vg@K&ge3_0mww#w~{l~;`q>ynaK>nMcW%I??WRVVNjBm$SD zj+bA8`~NZrD=0jIJ4eO;hXNS+myf0Y69gUuivKMQ(utQ8?i`3RM|{b?y0a>LTlrN= zmmX9&>GvMKv`_!-Fz#CnR#%NKp>gorz9)3iBImQ8D8Vde+;eEo6=o~Fh3Kv~+vOzE z=>R9FW2xFqDZ_nEqTUf7u$`|xO5?MAp7D>Kc%A~;3)el-q4PHH3vEN+vu~)S`@6!y&V5dz+PVw` zf5%lMuwdJ;<5N+Uhp zk9fM4u@YP<8)F=uh!pCdherKJ+S?6by24Aepu7t)&i>*!eoRqWUq`m{F~J8qobD;`w>w@QW4qNpO3 z&qDi+3`5Lkdo`maH6u`^t%&gOslyeZJJ)3Ds{>k6^Gku*`@4${i>U_4c16p6I0EJ} zE)yDT%nbR{NxF){*hEd0>Ts^D9}gPdX|!GMw^-fXTs7N%c4EdI;#XA`q25=5j=;c`6?5+x+TBso$8^1`C6c>!%Xq$ zu$wqUFyC#_b8Gt-;WHsOn|T;=Em~~4SOA}1tJ0ttc#f^MgyRZk@!2P#9)pSOlb)2Y zF9uEnFN_ENl8Xb?q8vY>41JDd=G~m2d8`w&<4QzEVFCh1GUPMUWhMJwjjy;jg1i(gg$qsPZJL7BuoAyp zRP$Hp%gE=v8sJ4&jzlPLTIto>s5ks^iWni-wjLF^nDQ*PKM$i+Ge4E2rcWw;xy@Qs z`7e>4BAYJPE7Yzu$au+p(34*lD_f%lQTvz9{ z_#Y{FPky(7LCJxNqdD+?CfbZKf81l1;#=s7I^Cg&JqFPZk#kBkiFR4hjr-GzJO4SF zOg#+2F~`8Ma5Qf_{QXv*GtR>kbl;R%Veo_JsN~tGbjJZJxi9ZrQb-^@-%!6p8F0(D zhcM^dzBambmIb}aCqQ?jdFcRiZZhwQcT669oDmvH0YG|32B&Vi0$vdX5$g_`r8wN4 z&6IZBomIwa4s^}eSg2-T)V_x7HEm^1G7}lJh*{d7^BeyRt{oJw;S392#S8ri+78B` z{_^Gz_N!B~Ij?3S(9|gc{b;+=$NKoVlESJKL`Y|CEwC(!ETI8L)Q2zK=Ct~LY4uIf zJt2je0Nk%3c(Z0kN$AsqZf|t-G<;$FGNPz$hF1Egi7Z)5yf(>x7Yknl^YinKImi|y zklb9?sL8Cm$D0$m&-&eTnUj5+lHXXysW`*C1&|wo-6nN7D^dN>W> zSDkT(e`~Lv3x*Ar;Gb8Em$yNaM!R-P;#GynTKp=Uw2Bn;;#OM@+6kPQ)%aWm@K{+` zeri0>p(H!eSB)uc_gwoWx_u-s-47>r5LD)X-L4Z5cRv4#mqOG5@`%PGE4(bJ0+V~{ zxOaqht^a9fFdZ(c6J(^z+Z>|_dlTJd1&rYFmpN-+zDykKUasa-lYCzEpwX_QOc6>flrlv6$NR zUQ_9Z)&dYB^w;MtHI7>q>PKkxhTESFr@K<}70b3H72$xEsRBYuK@fK+a7lu);(tOK z2jCxkSO(7(s|&o=M_B(DNEbLZ_<{d2NGwj>|AB`8!z=!yOhB2#DDfXe@*j-?sxL-~ z|LBkZU>}G7Lx%x^rpc}+vk6S6FKiVC#P#b1Tl8g;8G@%C4*_)WaD$VNTd|;sn6^xp zY6m%qaTJM$W@}uWwl2f+d>t_&0lc>9kpG-d)<0Er`TyhT0{^G;{XY#r@D^^cr>o59 z_r*4Q!oLVJ(0({iZT_t^Gvy21X&52jEC{s>TE4SbKcCjQng>5TH?;t``s!+)Tezf*CL{60sT-MaNMs}~fA>}RS+$e7n zlRtBW=C##;%cNh+^h5PQe z%2?)pQAoX^uD%W1j4>9(uuwm-p-K7zH<1v@bNkzPesyX+9eWp0%yZqZU2iK{4|TMK z{SiREy$3xu`L+#Xd5{TVy7_aTK>!?{63Ho=xjX5kvdyTGdgqh5opT8?a|WwWt)C!<>RmpCkhjI1!P zmmkMg^Sz&Squ}|y2~}9!lS9zjFZj=5EpV6#6l$*rGzs%ypC3E1( z`9@HC-W`#`wee;F0pC|(BSr{yuJF8SlunuHO~$)pN6XNZhPfASu?6T{IZ9j_}KnHD46we+9m;SH`_={>4A zl4|7le+{QC2}u`kMWP)7EF%7SoRQV%An}Q`o9~P|2l&Y|p1?_4iZS8i7JBXaOD`+h zB7Cl~^h~Y-Pucs=BN>QW47<;3C#e&;zh|6F)^D|b>>aqxG<4CVUrtCu$ujxOx`XJb z1ax>EI@Fbxh}vDb`Re6m{#|^b!LO4QNifKfp2i@3FK&@~eRgeUrYm=MCDy^5Sn~xCX&m zb1_)0=9YgXDbK$8Fs5QTG+t1Hl&q=`8$HROzrl}Bgeyo1=FkvE01NOir__xqt)Tcc zypa3Sae<81AMq6vtNmZ4dWK(u!V3@KZ#J3OdoG1XmxnOL>bGHX_6+DTt5i_~c#5sW z(9DL+>7Mbs%>A+_BbUe2&l~0CAmPWvI)13}V`1m*Lp-lXmt!hD55vtNl6Z7=A`Mq;Zz9CvpfzGKWtao)wKCW{pcnVujdB@1Q(I$!EiiLpuKdY(5 z1D65_w{_9o^aHx!m=D>XM)q)TE|i2Un@|Df&^Fg@Nx0qkJ74-Fiiv*Cs6+R*Q~nQL zt{+Nl3vR!*TpTL>Ueuz0gmuUhETh@yS+LB6k9*s3cK1giKepWG_w4o-@W5Q(bD%dN z4d+juwBITGR33&DQic@1BtG#0tGxjUxPiickE$+PlHkYnm7gT;`v2};vvPW9Z3Vsd zJWH~DC+3a%NE3L|6^XKxphh0u>gra6-u%IM)Nn_CJ}O{?-&xl95*)O==Z`ZwE*6co znT4YLo6fWEx3fKV%lp0nPxE6!h$K!xoO7;&bg#b$OYgrQZndt}}tFg=nj$jl?j;b^mPQSUDlLQ@W)3Po!s#cC;-afj8UYiv*@H zTHipKeIHX1II3U!0|KIv*A7@m%)i?&#YsZuA?nzVDUo270aP7SG0(NteYk~({5HJK zhaN7UXq7%?+TFnFmVNZPQb|YbJ)}&tNWtO^VUfQ3icPPctu%u~vVXeplU2Om;`YX1 z_s0hb-lu8%e|_gPe7__aG17K`D6CZiR|S3!Vz=MS7#K7YUkEAbi3+~624{cGrXXi5 z?Urdj5P6oT3}9LYInj0yGd@e;=ZN_q#)4EmgMwD=%=spZ*!eeZH6N}QA{RGD>pf_O zQ7sp}f5xG&_O+|CtZG$t>Ku+1{$aDD-@gsvYgtnz8Ddo%ba#Kcqo&sEZpT9`ck4_C z*4pKD=NSBQWi7m&w?rS8uLWWg_{381m|rRbX?$k|s#znn3V3u2q_+v)58MokXSJA}Q9h!Dx<&nJx0dQJ)MOO8pOx*lqquAt6hVaGd#)axL! z9Fpx$?6P@{7c7VU5l7Y*PlEQ3!`4M6#WSgRoPfh&V!QLb&R0~Le1k%6cQyyh(S<-i z%Btco^CS|i(pUycDeQYlOG87;zFq1{>ZX3Gmm4mtbY&D?=hX7$RGDw z$GBHD+awXw{%Hk9$@-f{)#woX-eGXyB2H4r?Q)3iOHZ3D@)x4KTc$9g+a3h;4T%Z;noh~ z$&s@i$^u7Wm!Eil?L4BpM$Z2oPfUi~y=}x=y~FSQ+koSQ-Dxjwg1xctVNcOLXYa=? zj29cwUkKpTl)tHcP4LftZHuoQi!92GQj8xf+b0fOA2c(|WML)XrIVmQB?YY{w81vn zUv=NBR$`fLgIu&N90-3?R7I+W`GnU9eN&{<6~N5KJJwwp@y*r z5=iUovxDeqix|9MJP$V`tiR!$VaFAnp%yWpwjR}FaZ#HS;Qg4(Sp@EV zM-y>reAEbz585sazSJK29)ybfJrT8*rcDrM4j}bH4^YF%2e5vPF2d$N(O z1VP!MZ{Eq!;?c`XGIBkV#8VKJxoGEo&l0^nvQjx zp8m80b6_-|Vh>kTRQy@yhyG$H{}|5*4ZW*g0++E<(jpvd;)jAZ=y55%mV6${for9& z0>?8iSyOr7obs$otHUhZrRzorq@*a8N0AwFF*iW%eY#q1!oRo6L9GAo1%tr6;V)-m z1kT@tuh!!&U6quSPUiXk-3-Jsjb@?Qrh(k2;=j!y3kI5j{w3gHaNeJm4lDJd7R8|_p=al{{02^@3D?6a|9D*MAG8^D9B=?PeI13t$e<>Pxl!OOX?&SFWa!gudW)2 z2X1PRQyzINru0MofYG6rJ@DzEl7%MI>9MIAQ4tZBY$3du{TRfEX7O~u)7~|ao4ZFH z?ypTd(3&n?QkA8IrwiZTKusR8Pgl8oTX>o=Apc;6c^bAK4t?6yv`+n>gKDS1Oe zHB)IwlL#Je3OjsgX=O!fCXPHnUo;R|jq95?ryRhJm#vZ}U%!Not%qJ6jb?#y6|!aWDV&|L#+rH2XF3fuPw^SX5?HMvtHjXDRWbl*F=+IVxou1g0%XzVK##)|g+=;GS-drsqi@!%bmk71Dh6mLT*Y2-hyc|_2*K5!Q+4SHXvA7}sUzhl1 zkW28OO2dA8-n)}K`r72)wV#kLcPoT|aWXHwMkcZgLvG%kR&*jKtq-jBKO>cUM-u;ZLATAaOryS-Mr( zvgJ})PSuhipJ_M*?HwN=?tdJOiyzex1-gL#C!Y132#yEVd21!tAS8PC*JCZxIPxvm zH0T&Xr0jv!85*lnxj*eZGt7p0mEbcqUw~9a*zQi8s4vQttwpv{?SgWe)7B!1h0984SLJgK+dE_Zs0*@r}-uI@l`GM=6h#b z9x^N{BYSuMIcht71`-QB`@^fPtkUCSr>)ntMzD(8pnC#gU({*q^d$DLcFp8UZUN)P zlL$Lwh2m1`%re&|l@T|}_(lNg)^Wuqv;>tO_Cmj!a*P`la8jrA#mcM0y*gJ{8sr~% zMnK0)C(wifZB->iij8OQpBxJgjHd9&MN;s{DyP0t03VZ3Bc$YG+YP(DZ@^Znq$fOL($IE za832m?YeOzDD~X90<+&;1h#;0s>cz!J4rx+8br&< z(z)KNQI&PTGGD4NTNC`Q%S41FDtxJ&Gl(I84-e$ z8~S{I&Tm)db-KOsuMT6mRm36^l#mka>*Nw^ub=J@dVB}rhV8CS?3_A~^izHMo=7Pd zjx=TEYp$6ttx;aTP-Tvm$Wl=Zh9HJwLWf7!;G+daSpypci#9hG9rqbJ4xrJ52M$V5 zm%E}|AWGw(ci)q2iqybs=SAJcqIXEXVjhvX*GqvE@+VJrbazI9yAw6Nu)ht%3Cxl;O`Ytbt%6||I)xc-sqKWJ z6nSA&=*fgXqx3v~f{K3v(P%L5x$>F(-NpW2p6M=l1O8%B2ETpghlYxYQCJtLEh$i2 zb4Co4!0Xs(U5`_`nKAHI=Czu7HC2Gw#(XI#%fZC~D_YLM6wBoruz>MZXP)!1u(E14 zEctn#t}63&aaUD#fq#_l%`x=vzUsD7GY<|-|Ad`V96IYJD452i?)`;Kr;n&ngZSge znb)fG=3-5WNu^O(#Cj#%+B=g}3_y`0jri%@G z`kxJH%ka|nxim5-VTJMmQm8xp_@zV z7Pe&4S>OwRjd{t!NMhz;;=_1#Zcme3ZgpFh6Gj~M{hf}ZmmJO@F5jJXxbX+kZcoxkr zN9}29RYFeEkhx93`F9 zCFkDY(%HY(`<%S7E~(Uh_Vpm?s$c!8 zRfMjV8b2G5&$VFSvGN%un7jKEQ>;oHtqk9B?a_}t*l|P0t?~-11j7!wT33$A0tMI*=l)(_Eao!efdT9>`# zmg0GR{R4Sb7F4RDh55eyIw9cSRnT|Wyu6Oo8Un%(yFFSzKMV16>Qf~+)W8YM!_Nd1 zXeu_A&lxcc>!Fv&56z37MH}IA-JsDhS;SYiqZEA)yKoWh*ZY%ZI!l|EXa+H$P*$Bl zMJ>pJ4_hPt*)cgM@VGf@tNIw0EjYmEb&e9BtzH^LTQ)0I7+I& z!c59Xjz5=FSZmEb_nkjff`YsLniBNt4^cP1a0RvBWN78s8|!JdDI1Z)r@QL|@fm)x zqh6*AJ`r{Ez6%l+P}|j_on~9&W8DUjD8!UwV=^M=_Ku62Y?zErq%DA%LH<|rAR#2v z!Cf-V5Og-5Kej1p@$nXc_0;vs&4^ttV_IDMO!FJUe7{HA4EB@X)SCTHVAgF=*Xnow zTT+XTS8V%?)^gN5`uZs=-9jKsN3S9%mG|)eTO>cD#RqjXE~rRJBtwHTF5|F}3kxdp zRKjB824vUW08?;^)lUjgYsUoU^_MoE&elktS)OM=ZBE|)M~HtO7?B9(F}vA7YVAa_ zzneSEg{QW7S*5t$t6L2z4^QEHYZckOV1>(-hQoIU-8E|v4Ho}Yx<+!X?GFQ9yd0|a zYg`pA+jGl2B5Sv=Eb~b5x|;>w59c>tHBhr?fDb{_A*5Aa+7M#ZvHSEd(a(QMKaZ9| z_>;ZX>mxT_oR<5%!5BPpI%t~U!9zLG}LJr$62*I*vv$! zb|NOx%}i0L7}r`etdvV5_auzVSjp_6;YT+jjO)m>Qi~XdrkP<~6GBby#+ctE9aCA8 z=|ZNAus>USPJ6aLI=z3s=Y8JyJiqh&&hvhr_j{fNf=M|hnRHY&V5DdW5ZX(RP%_=? zqW{c|TJh@0bW@`%Q#fVI4HYvP-dT4_YhP3nu2nQ7FYycR_|)tg-eBhHkU^SH9P>HG zt;F2tmRl@~!Ugh;Z+hln+^?VP8&BaAMwtX3pym+sU5+~|nCb4rVKdzBT>7}jsrIIn z!Qp|p5^wA~rx`Gs#t>lM7sr&n6Yh_ld1OE6NJqOYVu8Y#g{N^63yXP+nO98~&f*?q zq4$S3vsQA|k6;A_;Adb3e37jF6EsR+4P@V71}uTxCp#Fm04FeY-{zrBpE_`{dlj(; z5*d5Fj9%>AwInWvW}wA-m;PGkj~M=-(>yEgNEg^aQ~tIFQq2(P_V0?n-@J7x+?|qM zFNHK>OhK`K@iiFDxF5n?-Cq;8d$R+iLC&~tT&xLdK$e%z0mffTo8REvXTE*>|Nj3e zA^*P&x1Ww%s=c^yk&NjA*n=A)ZL%}tL=XQCv8Nq$dAXr}9ui1Pc6Apl+o1_-*Prbz>cH%wjpz}yhFd}(cTQ#e zHWLmeY;W~$;;gPuC8n3#!0-kt)UoVpv2rz*p5@x1%r-bSQwG&(?)=!lH+qascgY2(|D_g&tN53mxFoIF^PX`XgEfoM>j+Y z05e5!(_IjbrKylr?NUPjf0)k=d;Eoqa0PVRw;4OQ@R z0=zK#Cq_9+18OYkc`DOp(ikUFbTHs2BWhbxLpB7~ z-0zrR3)wS$K5G8ub)~LDP-#!^ilv2uNCQ02hz#D5-#d{mVK<1yn1f;O8Vdm z{z@UJf}V@pW@^i8WKKucg)g&9YFyugatSGd3yd=3D^jpclr%Wo!AX!#)yte-SA-ak zuLT7hAj?yxr+&5hSEqjU_^fD`i+N;PVQRToP^2G;2R>d9O8nV^A|^F_6b57?p0>KJ z6;(mcr+H+reX0S-tbBQ5*cv*~_NVo5mf~a5h5%*VeVs3C6iWt#YMTkSj`!iKWtOMW z9&0U8<0i{u#;3aW|E%C1UhF;vJB%x~aOQaBVxApF{jS))+3!Zw-Ak$l7+uDis|pzk z#5G*_9Ib9uq9@b=30NvC1D;`$%tugrqxARTc2Kv2f~w#Qv8h7BPO)_gLfx^OcG_3) zfGY7$$d9c<(Emuw=AJ{UtF{MS){yG1IK3q&xx1}6WynX)kuR5>3dP2EqwTQ{w;oqU z2^7sN-;$wJ?7uBT@S_p(xnW_RM#IpRyZ0(+JZ+^(R#dNfx_L05dQ7by{oR4^o6Qx0 zWBvD-wpR`7Qh(@6|DI`Bu zX9Usti_Kxp`ZD7e#Ib@m=M}Z`ct#!i`|s2JGVKkbX;Ar0X!m53mf2!*j`SqFy<`(- zqmJ;#i(}G_jkqAM(M?qH#lr1m=fI%>@?~9|^rTWG1r@ Date: Mon, 30 Aug 2021 16:25:35 -0300 Subject: [PATCH 2/3] Editable books (It does not work : ( ) --- src/minicraft/item/BookItem.java | 38 ++-- src/minicraft/saveload/Load.java | 61 ++++-- src/minicraft/saveload/Save.java | 48 ++++- src/minicraft/screen/BookDisplay.java | 175 +++++++++--------- src/minicraft/screen/BookEditableDisplay.java | 113 +++++++++++ 5 files changed, 314 insertions(+), 121 deletions(-) create mode 100644 src/minicraft/screen/BookEditableDisplay.java diff --git a/src/minicraft/item/BookItem.java b/src/minicraft/item/BookItem.java index 117bdcbd..af43b2e1 100644 --- a/src/minicraft/item/BookItem.java +++ b/src/minicraft/item/BookItem.java @@ -10,44 +10,52 @@ import minicraft.level.tile.Tile; import minicraft.screen.BookData; import minicraft.screen.BookDisplay; +import minicraft.screen.BookEditableDisplay; public class BookItem extends Item { protected static ArrayList getAllInstances() { ArrayList items = new ArrayList(); - items.add(new BookItem("Book", new Sprite(0, 8, 0), null)); + //items.add(new BookItem("Book", new Sprite(0, 8, 0), null)); items.add(new BookItem("Antidious", new Sprite(1, 8, 0), BookData.antVenomBook, true)); items.add(new BookItem("AlAzif", new Sprite(0, 28, 0), BookData.AlAzif, true)); + items.add(new BookItem("Editable Book", new Sprite(0, 8, 0), "type here", false, true)); return items; } - protected String book; // TODO this is not saved yet; it could be, for editable books. + protected String text; // TODO this is not saved yet; it could be, for editable books. public final boolean hasTitlePage; + public final boolean editable; private Sprite sprite; - private BookItem(String title, Sprite sprite, String book) { - this(title, sprite, book, false); - } - - private BookItem(String title, Sprite sprite, String book, boolean hasTitlePage) { + private BookItem(String title, Sprite sprite, String text, boolean hasTitlePage) { this(title, sprite, text, hasTitlePage, false); } + + private BookItem(String title, Sprite sprite, String text, boolean hasTitlePage, boolean editable) { super(title, sprite); - this.book = book; + this.text = text; this.hasTitlePage = hasTitlePage; this.sprite = sprite; + this.editable = editable; } + public boolean interactOn(Tile tile, Level level, int xt, int yt, Player player, Direction attackDir) { - Game.setMenu(new BookDisplay(book, hasTitlePage)); - // level.add(new Cthulhu(1), player.x, player.y); + Game.setMenu(editable ? new BookEditableDisplay(this) : new BookDisplay(text, hasTitlePage)); return true; } @Override - public boolean interactsWithWorld() { - return false; - } - + public boolean interactsWithWorld() { return false; } + public BookItem clone() { - return new BookItem(getName(), sprite, book, hasTitlePage); + return new BookItem(getName(), sprite, text, hasTitlePage, editable); + } + + public void setText(String text) { + this.text = text; + } + + public String getText() { + return this.text; } } diff --git a/src/minicraft/saveload/Load.java b/src/minicraft/saveload/Load.java index a2445cc5..52c51f29 100644 --- a/src/minicraft/saveload/Load.java +++ b/src/minicraft/saveload/Load.java @@ -80,6 +80,7 @@ import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; import minicraft.item.ArmorItem; +import minicraft.item.BookItem; import minicraft.item.Inventory; import minicraft.item.Item; import minicraft.item.Items; @@ -603,17 +604,53 @@ public void loadInventory(Inventory inventory, List data) { int count = Integer.parseInt(curData[1]); - if (newItem instanceof StackableItem) { - ((StackableItem) newItem).count = count; + if(newItem instanceof StackableItem) { + ((StackableItem)newItem).count = count; inventory.add(newItem); - } else inventory.add(newItem, count); + } else + inventory.add(newItem, count); } else { Item toAdd = Items.get(item); - inventory.add(toAdd); + if (toAdd instanceof BookItem) { + if (item.contains(";")) { + try { + // tmpData is used so that loadBook (or more accurately, loadFromFile) doesn't overwrite the other items in the inventory + ArrayList tmpData = new ArrayList<>(data); + String text = loadBook("BookData", Integer.parseInt(item.split(";")[1])); + data = tmpData; + + // find our "fake" returns and replace them with "true" ones for the in-game book text + text = text.replace("\\n", "\n"); + + ((BookItem) toAdd).setText(text); + inventory.add(toAdd); + } catch (Exception e) { + // if the data doesn't exist or the index isn't an integer + System.out.println("WARNING: Bad data for book"); + } + } else { + // it's not editable, so we can just add it + inventory.add(toAdd); + } + } else { + inventory.add(toAdd); + } } } } + private String loadBook(String filename, int idx) { + String file; + try { + file = loadFromFile(location + filename + extension, false); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + // split the file at returns and get the right one + return file.split("\n")[idx]; + } + private void loadEntities(String filename) { LoadingDisplay.setMessage("Entities"); loadFromFile(location + filename + extension); @@ -637,18 +674,16 @@ public static Entity loadEntity(String entityData, boolean isLocalSave) { if(isLocalSave) System.out.println("Warning: Assuming version of save file is current while loading entity: " + entityData); return Load.loadEntity(entityData, Game.VERSION, isLocalSave); } - @SuppressWarnings({ "unused", "rawtypes" }) + @Nullable public static Entity loadEntity(String entityData, Version worldVer, boolean isLocalSave) { entityData = entityData.trim(); if (entityData.length() == 0) return null; - - List info = new ArrayList<>(); // this gets everything inside the "[...]" after the entity name. - //System.out.println("Loading entity:" + entityData); - String[] stuff = entityData.substring(entityData.indexOf("[") + 1, entityData.indexOf("]")).split(":"); - info.addAll(Arrays.asList(stuff)); - - String entityName = entityData.substring(0, entityData.indexOf("[")); // this gets the text before "[", which is the entity name. + + String[] stuff = entityData.substring(entityData.indexOf("[") + 1, entityData.indexOf("]")).split(":"); // This gets everything inside the "[...]" after the entity name. + List info = new ArrayList<>(Arrays.asList(stuff)); + + String entityName = entityData.substring(0, entityData.indexOf("[")); // This gets the text before "[", which is the entity name. if (entityName.equals("Player") && Game.debug && Game.isValidClient()) System.out.println("CLIENT WARNING: Loading regular player: " + entityData); @@ -859,7 +894,7 @@ private static Entity getEntity(String string, int moblvl) { case "Knight": return new Knight(moblvl); case "OldGolem": return new OldGolem(moblvl); case "Snake": return new Snake(moblvl); - case "Cthulhu": return new EyeQueen(moblvl); + case "Cthulhu": return new EyeQueen(moblvl); // Check... case "EyeQueen": return new EyeQueen(moblvl); case "EyeQueenPhase2": return new EyeQueenPhase2(moblvl); case "EyeQueenPhase3": return new EyeQueenPhase3(moblvl); diff --git a/src/minicraft/saveload/Save.java b/src/minicraft/saveload/Save.java index 0a4086eb..448b9b5e 100644 --- a/src/minicraft/saveload/Save.java +++ b/src/minicraft/saveload/Save.java @@ -32,8 +32,10 @@ import minicraft.entity.mob.boss.AirWizard; import minicraft.entity.particle.Particle; import minicraft.entity.particle.TextParticle; +import minicraft.item.BookItem; import minicraft.item.Inventory; import minicraft.item.Item; +import minicraft.item.Items; import minicraft.item.PotionType; import minicraft.network.MinicraftServer; import minicraft.screen.LoadingDisplay; @@ -88,6 +90,7 @@ public Save(String worldname) { if (!Game.isValidServer()) { // this must be waited for on a server. writePlayer("Player", Game.player); writeInventory("Inventory", Game.player); + writeBooks("BookData", Game.player); } writeEntities("Entities"); @@ -119,6 +122,7 @@ public Save(Player player, boolean writePlayer) { if(writePlayer) { writePlayer("Player", player); writeInventory("Inventory", player); + writeBooks("BookData", player); } } @@ -274,6 +278,9 @@ private void writeInventory(String filename, Player player) { } public static void writeInventory(Player player, List data) { data.clear(); + + int numBooks = 0; + if(player.activeItem != null) { data.add(player.activeItem.getData()); } @@ -281,7 +288,46 @@ public static void writeInventory(Player player, List data) { Inventory inventory = player.getInventory(); for(int i = 0; i < inventory.invSize(); i++) { - data.add(inventory.get(i).getData()); + Item item = inventory.get(i); + if (item.equals(Items.get("Editable Book"))) { + data.add(item.getData() + ";" + numBooks); + numBooks++; + } else { + data.add(item.getData()); + } + } + } + + private void writeBooks(String filename, Player player) { + writeBooks(player, data); + String[] stringData = data.toArray(new String[]{}); + try { + writeToFile(location + filename + extension, stringData, false); + } catch (IOException e) { + e.printStackTrace(); + } + data.clear(); + } + public static void writeBooks(Player player, List data) { + data.clear(); + + Inventory inventory = player.getInventory(); + + if (player.activeItem != null && player.activeItem.equals(Items.get("Editable Book"))) { + // make sure to include the book in the player's hand + inventory.add(player.activeItem); + } + + for (int i = 0; i < inventory.invSize(); i++) { + // TODO: makes this just check the book's 'editable' property instead of checking for a particular name + if (inventory.get(i).equals(Items.get("Editable Book"))) { + String text = ((BookItem)inventory.get(i)).getText(); + + // make sure we can save it, since returns are used to separate the books from each other in the file + text = text.replace("\n", "\\n"); + + data.add(text); + } } } diff --git a/src/minicraft/screen/BookDisplay.java b/src/minicraft/screen/BookDisplay.java index e03d6672..4357419b 100644 --- a/src/minicraft/screen/BookDisplay.java +++ b/src/minicraft/screen/BookDisplay.java @@ -1,110 +1,101 @@ package minicraft.screen; -import java.util.ArrayList; -import java.util.Arrays; - import minicraft.core.Game; +import minicraft.core.Renderer; import minicraft.core.io.InputHandler; -import minicraft.core.io.Localization; import minicraft.gfx.Color; import minicraft.gfx.Font; +import minicraft.gfx.FontStyle; import minicraft.gfx.Point; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteSheet; import minicraft.screen.entry.StringEntry; -public class BookDisplay extends Display { - - // null characters "\0" denote page breaks. - private static final String defaultBook = " \n \0" + "There is nothing of use here." + "\0 \0" - + "Still nothing... :P"; - - private static final int spacing = 3; - private static final int minX = 15, maxX = 15 + 8 * 32, minY = 8 * 5, maxY = 8 * 5 + 8 * 16; - - private String[][] lines; - protected int page; - - private final boolean hasTitle; - private final boolean showPageCount; - private final int pageOffset; - - public BookDisplay(String book) { - this(book, false); - } - - public BookDisplay(String book, boolean hasTitle) {// this(book, hasTitle, !hasTitle); } - // public BookDisplay(String book, boolean hasTitle, boolean hideCountIfOnePage) - // { - page = 0; - if (book == null) { - book = defaultBook; - hasTitle = false; - } - book = Localization.getLocalized(book); - this.hasTitle = hasTitle; +import org.jetbrains.annotations.NotNull; - ArrayList pages = new ArrayList<>(); - String[] splitContents = book.split("\0"); - for (String content : splitContents) { - String[] remainder = { content }; - while (remainder[remainder.length - 1].length() > 0) { - remainder = Font.getLines(remainder[remainder.length - 1], maxX - minX, maxY - minY, spacing, true); - pages.add(Arrays.copyOf(remainder, remainder.length - 1)); // removes the last element of remainder, which is the leftover. - } - } - - lines = pages.toArray(new String[pages.size()][]); - - showPageCount = hasTitle || lines.length != 1; - pageOffset = showPageCount ? 1 : 0; - - Menu.Builder builder = new Menu.Builder(true, spacing, RelPos.CENTER); - - Menu pageCount = builder // the small rect for the title - .setPositioning(new Point(Screen.w / 2, 0), RelPos.BOTTOM) - .setEntries(StringEntry.useLines(Color.BLACK, "Page", hasTitle ? "Title" : "1/" + lines.length)) - .setSelection(1).createMenu(); - - builder.setPositioning(new Point(Screen.w / 2, pageCount.getBounds().getBottom() + spacing), RelPos.BOTTOM) - .setSize(maxX - minX + SpriteSheet.boxWidth * 2, maxY - minY + SpriteSheet.boxWidth * 2) - .setShouldRender(false); - - menus = new Menu[lines.length + pageOffset]; - if (showPageCount) - menus[0] = pageCount; - for (int i = 0; i < lines.length; i++) { - menus[i + pageOffset] = builder.setEntries(StringEntry.useLines(Color.WHITE, lines[i])).createMenu(); +public class BookDisplay extends Display { + + private static final int DISPLAY_WIDTH = 256; + private static final int DISPLAY_HEIGHT = 128; + + static final int TEXT_AREA_WIDTH = DISPLAY_WIDTH - 12; + static final int TEXT_AREA_HEIGHT = DISPLAY_HEIGHT - 24; + static final int LINE_SPACING = 2; + + private final Menu.Builder builder; + + int page; + String[] pages; + + private final int pageNumberDisplayIdx; + + private final boolean hasTitlePage; + + public BookDisplay(@NotNull String text) { this(text, false); } + public BookDisplay(@NotNull String book, boolean hasTitlePage) { + this.hasTitlePage = hasTitlePage; + + pages = book.split("\0", -1); + + pageNumberDisplayIdx = pages.length; + + menus = new Menu[pages.length + (hasTitlePage ? 2 : 1)]; + + builder = new Menu.Builder(false, 3, RelPos.CENTER).setSize(DISPLAY_WIDTH, DISPLAY_HEIGHT).setPositioning(new Point(Renderer.WIDTH/2, Renderer.HEIGHT/2), RelPos.CENTER).setFrame(443, 3, 443); + + for (int p = 0; p < pages.length; p++) { + menus[p] = builder.createMenu(); } - - menus[page + pageOffset].shouldRender = true; + + menus[pageNumberDisplayIdx] = updatePageNumber(); + + page = 0; } - - protected void turnPage(int dir) { - if (page + dir >= 0 && page + dir < lines.length) { - menus[page + pageOffset].shouldRender = false; - page += dir; - if (showPageCount) - menus[0].updateSelectedEntry(new StringEntry( - page == 0 && hasTitle ? "Title" : (page + 1) + "/" + lines.length, Color.BLACK)); - menus[page + pageOffset].shouldRender = true; - } + + protected int getPageCount() { + return pages.length; } - + + private Menu updatePageNumber() { + return builder + .setSize(64, 24) + .setPositioning(new Point(48, 28), RelPos.CENTER) + .setEntries(new StringEntry( + (page + (hasTitlePage ? 0 : 1)) + "/" + (getPageCount() - (hasTitlePage ? 1 : 0)), + Color.BLACK + )) + .createMenu(); + } + @Override public void tick(InputHandler input) { - if (input.getKey("menu").clicked || input.getKey("exit").clicked) - Game.exitMenu(); // this is what closes the book; TODO if books were editable, I would probably remake the book here with the edited pages. + if (input.getKey("cursor-left").clicked) turnPage(-1); + if (input.getKey("cursor-right").clicked) turnPage(1); + if (input.getKey("exit").clicked) + Game.exitMenu(); + } + + void turnPage(int direction) { + if (!(page + direction < 0 || page + direction > getPageCount() - 1)) { + page += direction; + } - if (input.getKey("cursor-left").clicked) - turnPage(-1); // this is what turns the page back - //if (input.getKey("cursor-left").clicked) - //Sound.GUI_PageUp.play(); - - if (input.getKey("cursor-right").clicked) - turnPage(1); // this is what turns the page forward - //if (input.getKey("cursor-right").clicked) - //Sound.GUI_PageUp.play(); - + menus[pageNumberDisplayIdx] = updatePageNumber(); + } + + @Override + public void render(Screen screen) { + FontStyle fontStyle; + + if (hasTitlePage && page == 0) { + fontStyle = new FontStyle(Color.WHITE).setShadowType(Color.BLACK, true); + // if(Game.debug) System.out.println("showing title"); + } else { + menus[pageNumberDisplayIdx].render(screen); + fontStyle = new FontStyle(Color.BLACK).setXPos(24).setYPos(44); + } + + menus[page].render(screen); + + Font.drawParagraph(pages[page], screen, TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT, fontStyle, LINE_SPACING); } -} +} \ No newline at end of file diff --git a/src/minicraft/screen/BookEditableDisplay.java b/src/minicraft/screen/BookEditableDisplay.java new file mode 100644 index 00000000..c08b1c36 --- /dev/null +++ b/src/minicraft/screen/BookEditableDisplay.java @@ -0,0 +1,113 @@ +package minicraft.screen; + +import java.util.Arrays; + +import minicraft.core.Game; +import minicraft.core.io.InputHandler; +import minicraft.gfx.Font; +import minicraft.item.BookItem; + +public class BookEditableDisplay extends BookDisplay { + + private static final int MAX_PAGE_COUNT = 30; + + private BookItem bookItem; + + private int pageCount; + + private static String extendPageCount(String bookText) { + int pageCount = bookText.split("\0", -1).length; + if(pageCount >= MAX_PAGE_COUNT) return bookText; + String[] extra = new String[MAX_PAGE_COUNT - pageCount]; + Arrays.fill(extra, "\0"); + return bookText + String.join("", extra); + } + + public BookEditableDisplay(BookItem bookItem) { + super(extendPageCount(bookItem.getText()), bookItem.hasTitlePage); + + this.bookItem = bookItem; + + this.pageCount = bookItem.getText().split("\0").length; + turnPage(-1); // just updates the page count + } + + @Override + protected int getPageCount() { + return Math.min(MAX_PAGE_COUNT, Math.max(pageCount, page+1)+1); + } + + /*private void addChar(char character) { + if (pages[page].length() + 1 < 270) { + pages[page] += character; + } else if (page < 28) { + turnPage(1); + addChar(character); + } + } + + private void removeChar() { + if (pages[page].length() - 1 >= 0) { + pages[page] = pages[page].substring(0, pages[page].length()-1); + } + }*/ + + @Override + public void tick(InputHandler input) { + if (input.getKey("exit").clicked) { + bookItem.setText(String.join("\0", Arrays.copyOfRange(pages, 0, pageCount))); + Game.exitMenu(); + return; + } + + if (input.getKey("cursor-left").clicked) turnPage(-1); + if (input.getKey("cursor-right").clicked) turnPage(1); + + String pageText = input.addKeyTyped(pages[page], null); + if(input.getKey("enter").clicked) + pageText += "\n"; // allow newlines + + final int oldLen = pages[page].length(); + final int newLen = pageText.length(); + + if(oldLen == newLen) return; + + // determine if this last change can fit on the current page + String[] lines = Font.getLines(pageText, TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT, LINE_SPACING, true); + boolean hasOverflow = lines[lines.length-1].length() > 0; + // check if change occurred, and there is enough space on the page to write it + if(newLen > oldLen && hasOverflow) { + // change does not fit; attempt to move it to next page + if(page >= MAX_PAGE_COUNT - 1 || page < pageCount-1) + return; // change does not fit because max page limit reached or there is text on the next page + + // recreate this page with the last word removed + String prevPage = pageText.substring(0, pageText.length() - lines[lines.length-1].length()); + pages[page] = prevPage; // save the page without the overflow text + turnPage(1); // move to next page + pages[page] = lines[lines.length-1]; // write the overflow to the next page + } + else { + // change fits on page + pages[page] = pageText; + if(oldLen == 0 && pageCount <= page) + pageCount = page + 1; // text has been set, bring page count to current page + else if(newLen == 0 && pageCount == page + 1 && page > 0) { + // last non-blank page has been made blank, lower page count until non-blank page found + do pageCount--; + while(pageCount > 1 && pages[pageCount-1].length() == 0); + } + } + } + + /*private String getBookText() { + + String bookToSave = ""; + + for (String currentPage: pages) { + bookToSave += currentPage + "\0"; + } + + return bookToSave; + }*/ +} From 1f3d38609d460bdfe9e38f7fda607e576e6f92f2 Mon Sep 17 00:00:00 2001 From: TheBigEye Date: Fri, 10 Sep 2021 10:28:49 -0300 Subject: [PATCH 3/3] Delete editable books (lot bugs :( ) --- src/minicraft/item/BookItem.java | 38 ++-- src/minicraft/screen/BookDisplay.java | 175 +++++++++--------- src/minicraft/screen/BookEditableDisplay.java | 113 ----------- 3 files changed, 107 insertions(+), 219 deletions(-) delete mode 100644 src/minicraft/screen/BookEditableDisplay.java diff --git a/src/minicraft/item/BookItem.java b/src/minicraft/item/BookItem.java index af43b2e1..117bdcbd 100644 --- a/src/minicraft/item/BookItem.java +++ b/src/minicraft/item/BookItem.java @@ -10,52 +10,44 @@ import minicraft.level.tile.Tile; import minicraft.screen.BookData; import minicraft.screen.BookDisplay; -import minicraft.screen.BookEditableDisplay; public class BookItem extends Item { protected static ArrayList getAllInstances() { ArrayList items = new ArrayList(); - //items.add(new BookItem("Book", new Sprite(0, 8, 0), null)); + items.add(new BookItem("Book", new Sprite(0, 8, 0), null)); items.add(new BookItem("Antidious", new Sprite(1, 8, 0), BookData.antVenomBook, true)); items.add(new BookItem("AlAzif", new Sprite(0, 28, 0), BookData.AlAzif, true)); - items.add(new BookItem("Editable Book", new Sprite(0, 8, 0), "type here", false, true)); return items; } - protected String text; // TODO this is not saved yet; it could be, for editable books. + protected String book; // TODO this is not saved yet; it could be, for editable books. public final boolean hasTitlePage; - public final boolean editable; private Sprite sprite; - private BookItem(String title, Sprite sprite, String text, boolean hasTitlePage) { this(title, sprite, text, hasTitlePage, false); } - - private BookItem(String title, Sprite sprite, String text, boolean hasTitlePage, boolean editable) { + private BookItem(String title, Sprite sprite, String book) { + this(title, sprite, book, false); + } + + private BookItem(String title, Sprite sprite, String book, boolean hasTitlePage) { super(title, sprite); - this.text = text; + this.book = book; this.hasTitlePage = hasTitlePage; this.sprite = sprite; - this.editable = editable; } - public boolean interactOn(Tile tile, Level level, int xt, int yt, Player player, Direction attackDir) { - Game.setMenu(editable ? new BookEditableDisplay(this) : new BookDisplay(text, hasTitlePage)); + Game.setMenu(new BookDisplay(book, hasTitlePage)); + // level.add(new Cthulhu(1), player.x, player.y); return true; } @Override - public boolean interactsWithWorld() { return false; } - - public BookItem clone() { - return new BookItem(getName(), sprite, text, hasTitlePage, editable); + public boolean interactsWithWorld() { + return false; } - - public void setText(String text) { - this.text = text; - } - - public String getText() { - return this.text; + + public BookItem clone() { + return new BookItem(getName(), sprite, book, hasTitlePage); } } diff --git a/src/minicraft/screen/BookDisplay.java b/src/minicraft/screen/BookDisplay.java index 4357419b..e03d6672 100644 --- a/src/minicraft/screen/BookDisplay.java +++ b/src/minicraft/screen/BookDisplay.java @@ -1,101 +1,110 @@ package minicraft.screen; +import java.util.ArrayList; +import java.util.Arrays; + import minicraft.core.Game; -import minicraft.core.Renderer; import minicraft.core.io.InputHandler; +import minicraft.core.io.Localization; import minicraft.gfx.Color; import minicraft.gfx.Font; -import minicraft.gfx.FontStyle; import minicraft.gfx.Point; import minicraft.gfx.Screen; +import minicraft.gfx.SpriteSheet; import minicraft.screen.entry.StringEntry; -import org.jetbrains.annotations.NotNull; - public class BookDisplay extends Display { - - private static final int DISPLAY_WIDTH = 256; - private static final int DISPLAY_HEIGHT = 128; - - static final int TEXT_AREA_WIDTH = DISPLAY_WIDTH - 12; - static final int TEXT_AREA_HEIGHT = DISPLAY_HEIGHT - 24; - static final int LINE_SPACING = 2; - - private final Menu.Builder builder; - - int page; - String[] pages; - - private final int pageNumberDisplayIdx; - - private final boolean hasTitlePage; - - public BookDisplay(@NotNull String text) { this(text, false); } - public BookDisplay(@NotNull String book, boolean hasTitlePage) { - this.hasTitlePage = hasTitlePage; - - pages = book.split("\0", -1); - - pageNumberDisplayIdx = pages.length; - - menus = new Menu[pages.length + (hasTitlePage ? 2 : 1)]; - - builder = new Menu.Builder(false, 3, RelPos.CENTER).setSize(DISPLAY_WIDTH, DISPLAY_HEIGHT).setPositioning(new Point(Renderer.WIDTH/2, Renderer.HEIGHT/2), RelPos.CENTER).setFrame(443, 3, 443); - - for (int p = 0; p < pages.length; p++) { - menus[p] = builder.createMenu(); - } - - menus[pageNumberDisplayIdx] = updatePageNumber(); - - page = 0; - } - - protected int getPageCount() { - return pages.length; - } - - private Menu updatePageNumber() { - return builder - .setSize(64, 24) - .setPositioning(new Point(48, 28), RelPos.CENTER) - .setEntries(new StringEntry( - (page + (hasTitlePage ? 0 : 1)) + "/" + (getPageCount() - (hasTitlePage ? 1 : 0)), - Color.BLACK - )) - .createMenu(); + + // null characters "\0" denote page breaks. + private static final String defaultBook = " \n \0" + "There is nothing of use here." + "\0 \0" + + "Still nothing... :P"; + + private static final int spacing = 3; + private static final int minX = 15, maxX = 15 + 8 * 32, minY = 8 * 5, maxY = 8 * 5 + 8 * 16; + + private String[][] lines; + protected int page; + + private final boolean hasTitle; + private final boolean showPageCount; + private final int pageOffset; + + public BookDisplay(String book) { + this(book, false); } - - @Override - public void tick(InputHandler input) { - if (input.getKey("cursor-left").clicked) turnPage(-1); - if (input.getKey("cursor-right").clicked) turnPage(1); - if (input.getKey("exit").clicked) - Game.exitMenu(); + + public BookDisplay(String book, boolean hasTitle) {// this(book, hasTitle, !hasTitle); } + // public BookDisplay(String book, boolean hasTitle, boolean hideCountIfOnePage) + // { + page = 0; + if (book == null) { + book = defaultBook; + hasTitle = false; + } + book = Localization.getLocalized(book); + this.hasTitle = hasTitle; + + ArrayList pages = new ArrayList<>(); + String[] splitContents = book.split("\0"); + for (String content : splitContents) { + String[] remainder = { content }; + while (remainder[remainder.length - 1].length() > 0) { + remainder = Font.getLines(remainder[remainder.length - 1], maxX - minX, maxY - minY, spacing, true); + pages.add(Arrays.copyOf(remainder, remainder.length - 1)); // removes the last element of remainder, which is the leftover. + } + } + + lines = pages.toArray(new String[pages.size()][]); + + showPageCount = hasTitle || lines.length != 1; + pageOffset = showPageCount ? 1 : 0; + + Menu.Builder builder = new Menu.Builder(true, spacing, RelPos.CENTER); + + Menu pageCount = builder // the small rect for the title + .setPositioning(new Point(Screen.w / 2, 0), RelPos.BOTTOM) + .setEntries(StringEntry.useLines(Color.BLACK, "Page", hasTitle ? "Title" : "1/" + lines.length)) + .setSelection(1).createMenu(); + + builder.setPositioning(new Point(Screen.w / 2, pageCount.getBounds().getBottom() + spacing), RelPos.BOTTOM) + .setSize(maxX - minX + SpriteSheet.boxWidth * 2, maxY - minY + SpriteSheet.boxWidth * 2) + .setShouldRender(false); + + menus = new Menu[lines.length + pageOffset]; + if (showPageCount) + menus[0] = pageCount; + for (int i = 0; i < lines.length; i++) { + menus[i + pageOffset] = builder.setEntries(StringEntry.useLines(Color.WHITE, lines[i])).createMenu(); + } + + menus[page + pageOffset].shouldRender = true; } - - void turnPage(int direction) { - if (!(page + direction < 0 || page + direction > getPageCount() - 1)) { - page += direction; + + protected void turnPage(int dir) { + if (page + dir >= 0 && page + dir < lines.length) { + menus[page + pageOffset].shouldRender = false; + page += dir; + if (showPageCount) + menus[0].updateSelectedEntry(new StringEntry( + page == 0 && hasTitle ? "Title" : (page + 1) + "/" + lines.length, Color.BLACK)); + menus[page + pageOffset].shouldRender = true; } - - menus[pageNumberDisplayIdx] = updatePageNumber(); } - + @Override - public void render(Screen screen) { - FontStyle fontStyle; - - if (hasTitlePage && page == 0) { - fontStyle = new FontStyle(Color.WHITE).setShadowType(Color.BLACK, true); - // if(Game.debug) System.out.println("showing title"); - } else { - menus[pageNumberDisplayIdx].render(screen); - fontStyle = new FontStyle(Color.BLACK).setXPos(24).setYPos(44); - } - - menus[page].render(screen); + public void tick(InputHandler input) { + if (input.getKey("menu").clicked || input.getKey("exit").clicked) + Game.exitMenu(); // this is what closes the book; TODO if books were editable, I would probably remake the book here with the edited pages. - Font.drawParagraph(pages[page], screen, TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT, fontStyle, LINE_SPACING); + if (input.getKey("cursor-left").clicked) + turnPage(-1); // this is what turns the page back + //if (input.getKey("cursor-left").clicked) + //Sound.GUI_PageUp.play(); + + if (input.getKey("cursor-right").clicked) + turnPage(1); // this is what turns the page forward + //if (input.getKey("cursor-right").clicked) + //Sound.GUI_PageUp.play(); + } -} \ No newline at end of file +} diff --git a/src/minicraft/screen/BookEditableDisplay.java b/src/minicraft/screen/BookEditableDisplay.java deleted file mode 100644 index c08b1c36..00000000 --- a/src/minicraft/screen/BookEditableDisplay.java +++ /dev/null @@ -1,113 +0,0 @@ -package minicraft.screen; - -import java.util.Arrays; - -import minicraft.core.Game; -import minicraft.core.io.InputHandler; -import minicraft.gfx.Font; -import minicraft.item.BookItem; - -public class BookEditableDisplay extends BookDisplay { - - private static final int MAX_PAGE_COUNT = 30; - - private BookItem bookItem; - - private int pageCount; - - private static String extendPageCount(String bookText) { - int pageCount = bookText.split("\0", -1).length; - if(pageCount >= MAX_PAGE_COUNT) return bookText; - String[] extra = new String[MAX_PAGE_COUNT - pageCount]; - Arrays.fill(extra, "\0"); - return bookText + String.join("", extra); - } - - public BookEditableDisplay(BookItem bookItem) { - super(extendPageCount(bookItem.getText()), bookItem.hasTitlePage); - - this.bookItem = bookItem; - - this.pageCount = bookItem.getText().split("\0").length; - turnPage(-1); // just updates the page count - } - - @Override - protected int getPageCount() { - return Math.min(MAX_PAGE_COUNT, Math.max(pageCount, page+1)+1); - } - - /*private void addChar(char character) { - if (pages[page].length() + 1 < 270) { - pages[page] += character; - } else if (page < 28) { - turnPage(1); - addChar(character); - } - } - - private void removeChar() { - if (pages[page].length() - 1 >= 0) { - pages[page] = pages[page].substring(0, pages[page].length()-1); - } - }*/ - - @Override - public void tick(InputHandler input) { - if (input.getKey("exit").clicked) { - bookItem.setText(String.join("\0", Arrays.copyOfRange(pages, 0, pageCount))); - Game.exitMenu(); - return; - } - - if (input.getKey("cursor-left").clicked) turnPage(-1); - if (input.getKey("cursor-right").clicked) turnPage(1); - - String pageText = input.addKeyTyped(pages[page], null); - if(input.getKey("enter").clicked) - pageText += "\n"; // allow newlines - - final int oldLen = pages[page].length(); - final int newLen = pageText.length(); - - if(oldLen == newLen) return; - - // determine if this last change can fit on the current page - String[] lines = Font.getLines(pageText, TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT, LINE_SPACING, true); - boolean hasOverflow = lines[lines.length-1].length() > 0; - // check if change occurred, and there is enough space on the page to write it - if(newLen > oldLen && hasOverflow) { - // change does not fit; attempt to move it to next page - if(page >= MAX_PAGE_COUNT - 1 || page < pageCount-1) - return; // change does not fit because max page limit reached or there is text on the next page - - // recreate this page with the last word removed - String prevPage = pageText.substring(0, pageText.length() - lines[lines.length-1].length()); - pages[page] = prevPage; // save the page without the overflow text - turnPage(1); // move to next page - pages[page] = lines[lines.length-1]; // write the overflow to the next page - } - else { - // change fits on page - pages[page] = pageText; - if(oldLen == 0 && pageCount <= page) - pageCount = page + 1; // text has been set, bring page count to current page - else if(newLen == 0 && pageCount == page + 1 && page > 0) { - // last non-blank page has been made blank, lower page count until non-blank page found - do pageCount--; - while(pageCount > 1 && pages[pageCount-1].length() == 0); - } - } - } - - /*private String getBookText() { - - String bookToSave = ""; - - for (String currentPage: pages) { - bookToSave += currentPage + "\0"; - } - - return bookToSave; - }*/ -}