--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ * Feb-2013 Michael Zucchi - modernised java
+ * Mar-2013 Michael Zucchi - changed server protocol
+ */
+package duskz.server.entityz;
+
+import duskz.server.*;
+import duskz.protocol.DuskProtocol;
+import duskz.server.entity.Mob;
+import duskz.server.entity.Merchant;
+import duskz.server.entity.Sign;
+import duskz.server.entity.Item;
+import duskz.server.entity.Prop;
+import duskz.server.entity.DuskObject;
+import duskz.server.entity.Equipment;
+import duskz.server.entity.PlayerMerchant;
+import duskz.server.entity.LivingThing;
+import duskz.server.entity.TileMap;
+import java.io.*;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+public class CommandsWork implements DuskProtocol {
+
+ public static String parseCommand(LivingThing lt, DuskEngine game, String cmdline) throws Exception {
+ if (cmdline == null) {
+ return null;
+ }
+ if (lt == null) {
+ return null;
+ }
+ if (game == null) {
+ return null;
+ }
+
+ String cmd = null;
+ String args = null;
+
+ int intIndex = cmdline.indexOf(" ");
+ if (intIndex == -1) {
+ cmd = cmdline.toLowerCase();
+ } else {
+ cmd = cmdline.substring(0, intIndex).toLowerCase();
+ args = cmdline.substring(intIndex + 1).trim();
+ }
+
+ if (cmd.length() < 1) {
+ return "huh?";
+ }
+
+ lt.isAlwaysCommands = true;
+ boolean blnFoundScriptedCommand = false;
+ /*
+ ** Don't try to find a scripted command if they are doing a tell
+ */
+ if (cmd.substring(0, 1) != "/") {
+ try {
+ Script script = new Script("commands/" + cmd, game, false);
+ script.varVariables.addVariable("trigger", lt);
+ if (args != null) {
+ script.runScript(args);
+ } else {
+ script.runScript();
+ }
+ script.close();
+ blnFoundScriptedCommand = true;
+ } catch (Exception e) {
+ blnFoundScriptedCommand = false;
+ }
+ if (!lt.isAlwaysCommands) {
+ return null;
+ }
+ }
+ if (lt.privs > 2) {
+ if (cmdline.startsWith(">")) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ cmdline = cmdline.substring(1);
+ if (cmdline.equalsIgnoreCase("s")) {
+ if (game.blnSavingGame) {
+ return "Game already being saved, please wait.";
+ }
+ game.saveMap();
+ return "Game settings saved";
+ }
+
+ String strMapLog = "shortmap_redraw";
+ try (PrintStream psMap = new PrintStream(new FileOutputStream(strMapLog, true), true)) {
+ game.changeMap(lt, lt.x, lt.y, Short.parseShort(cmdline));
+ psMap.println("changetile " + lt.x + " " + lt.y + " " + Short.parseShort(cmdline));
+ return null;
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While " + lt.name + " tried to >" + cmdline, e);
+ return null;
+ }
+ }
+ if (cmdline.startsWith("<")) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ cmdline = cmdline.substring(1);
+ if (cmdline.equals("s")) {
+ if (game.blnSavingGame) {
+ return "Game already being saved, please wait.";
+ }
+ game.saveMap();
+ return "Game settings saved";
+ } else if (cmdline.equalsIgnoreCase("merchant")) {
+ if (lt.overMerchant() != null) {
+ return "There's already a merchant there.";
+ }
+ if (lt.overPlayerMerchant() != null) {
+ return "There's already a merchant there.";
+ }
+ Merchant mrcStore = new Merchant(game);
+ mrcStore.x = lt.x;
+ mrcStore.y = lt.y;
+ //game.vctMerchants.add(mrcStore);
+ //game.blnMerchantListChanged = true;
+ game.addDuskObject(lt.map, mrcStore);
+ return null;
+ } else if (cmdline.toLowerCase().startsWith("prop ")) {
+ if (cmdline.length() == 5) {
+ return "Add what prop?";
+ }
+ cmdline = cmdline.substring(5);
+ Prop prpStore = game.getProp(cmdline);
+ if (prpStore != null) {
+ prpStore.x = lt.x;
+ prpStore.y = lt.y;
+ //game.vctProps.addElement(prpStore);
+ //game.blnPropListChanged = true;
+ game.addDuskObject(lt.map, prpStore);
+ }
+ return null;
+ } else if (cmdline.startsWith("sign ")) {
+ if (cmdline.length() == 5) {
+ return "What should the sign say?";
+ }
+ Sign sgnStore = new Sign(game, "sign", cmdline.substring(5), lt.x, lt.y, game.getID());
+ //game.vctSigns.add(sgnStore);
+ //game.blnSignListChanged = true;
+ game.addDuskObject(lt.map, sgnStore);
+ return null;
+ }
+ Item itmStore = game.getItem(cmdline);
+ if (itmStore != null) {
+ itmStore.x = lt.x;
+ itmStore.y = lt.y;
+ //game.vctItems.add(itmStore);
+ game.addDuskObject(lt.map, itmStore);
+ return null;
+ }
+ try {
+ Mob mob = new Mob(cmdline, lt.x, lt.y, game);
+ // TODO: this previously didn't call addDuskObject - bug or intentional?
+ //game.vctMobs.addElement(mob);
+ //game.blnMobListChanged = true;
+ game.addDuskObject(lt.map, mob);
+
+ mob.changeLocBypass(lt.map, lt.x, lt.y);
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While creating mob \"" + cmdline + "\"", e);
+ }
+ return null;
+ }
+ }
+
+ // Remap shortcuts
+ if (cmd.startsWith(";")) {
+ args = cmdline.substring(1).trim();
+ cmd = "gossip";
+ }
+ if (cmd.startsWith(":")) {
+ args = cmdline.substring(1).trim();
+ cmd = "clan";
+ }
+ if (cmd.startsWith("'")) {
+ args = cmdline.substring(1).trim();
+ cmd = "say";
+ }
+ if (cmd.startsWith(".")) {
+ args = cmdline.substring(1).trim();
+ cmd = "emote";
+ }
+ if (cmd.startsWith("/")) {
+ args = cmdline.substring(1).trim();
+ cmd = "tell";
+ }
+
+ switch (cmd) {
+
+ case "addmember": {
+ if (lt.privs != 1)
+ return "Huh?";
+ if (args == null) {
+ return "Add who?";
+ }
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world";
+ }
+ if (lt.battle != null) {
+ return "Not while you're fighting!";
+ }
+ if (thnStore.battle != null) {
+ thnStore.chatMessage(lt.name + " has invited you to join their clan, but you are in the middle of a battle");
+ return "They're in the middle of a battle. They have been notified that you tried to clan them.";
+ }
+ lt.chatMessage("You have invited " + thnStore.name + " to join the clan " + lt.clan + ".");
+
+ if (true)
+ // FIXME: protocol implementation
+ throw new RuntimeException("cannot ask questions yet");
+ // FIXME: this looks dodgy
+ // FIXME: move to livingthing
+
+ /*
+ // thnStore.halt();
+ thnStore.stillThere(); // This puts something in the buffer
+ thnStore.stillThere(); // Have to do this twice to ensure that thnStore is out of
+ // its read loop
+ lt.connectionThread.sleep(500); // wait for the "notdead" response to get back from client.
+ try {
+ // Empty out the BufferedReader for the answer
+ // while (thnStore.instream.ready()) {
+ // thnStore.instream.read();
+ // }
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While " + lt.name + " was trying to addmember " + thnStore.name, e);
+ }
+ thnStore.chatMessage(lt.name + " has invited you to join the clan " + lt.clan + ". If you accept, type yes.");
+ try {
+ if (thnStore.instream.readLine().equalsIgnoreCase("yes")) {
+ thnStore.clan = lt.clan;
+ if (thnStore.privs == 1) {
+ thnStore.privs = 0;
+ }
+ thnStore.chatMessage("You have been added to the clan, " + lt.clan + "");
+ thnStore.proceed();
+ game.removeDuskObject(thnStore);
+ game.addDuskObject(thnStore);
+ return thnStore.name + " has accepted your invitation.";
+ }
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While reading the answer to " + lt.name + "'s attempt to addmember " + thnStore.name, e);
+ }
+ thnStore.proceed();
+ */
+ return thnStore.name + " has declined your invitation.";
+ }
+ case "kick": {
+ if (lt.privs != 1)
+ return "Huh?";
+ if (args == null) {
+ return "Kick who?";
+ }
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ if (!thnStore.clan.equalsIgnoreCase(lt.clan)) {
+ return "They're not in your clan.";
+ }
+ thnStore.chatMessage("You have been kicked out of " + lt.clan + ".");
+ thnStore.clan = "none";
+ game.removeDuskObject(thnStore);
+ game.addDuskObject(thnStore.map, thnStore);
+ return thnStore.name + " has been kicked out of your clan.";
+ }
+// if (lt.privs > 2) {
+// if (lt.privs > 4) {
+ case "makegod": {
+ if (lt.privs <= 4)
+ return "Huh?";
+
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ if (args == null) {
+ return "Make who a god?";
+ }
+ int iSPloc = args.indexOf(" ");
+ if (iSPloc < 0) {
+ return "Make them what level of a god?";
+ }
+ String sName = args.substring(0, iSPloc).trim();
+ int level = Integer.parseInt(args.substring(iSPloc).trim());
+
+ LivingThing thnStore = game.getPlayer(sName);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ int oldLevel = thnStore.privs;
+ thnStore.privs = level;
+ thnStore.isSaveNeeded = true;
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":Changed " + thnStore.name + "'s priveledges from " + oldLevel + " to " + level + ".");
+ return thnStore.name + "'s priveledges have been set to " + level + ".";
+ }
+ case "reloadprefs": {
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.loadPrefs();
+ return "Preferences reloaded";
+ }
+ case "resizemap":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.resizeMap(lt.map, lt.x + 1, lt.y + 1);
+ return "Map resized";
+ case "shutdown":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.ALWAYS, lt.name + " has shut down the server.");
+ game.chatMessage("The server is going down.", "default");
+ game.blnShuttingDown = true;
+ for (LivingThing thnStore : game.playersByName.values()) {
+ try {
+ if (thnStore != lt) {
+ thnStore.close();
+ }
+ } catch (Exception e) {
+ if (thnStore != null) {
+ game.log.printError("parseCommand():While trying to close " + thnStore.name + " for " + lt.name + "'s shutdown request", e);
+ } else {
+ game.log.printError("parseCommand():While trying to close a null player for " + lt.name + "'s shutdown request", e);
+ }
+ }
+ }
+ lt.isSaveNeeded = true;
+ lt.savePlayer();
+ System.gc();
+ System.exit(0);
+ return null;
+ case "stop":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.blnShuttingDown = true;
+ return "Stopped accepting incoming socket connections.";
+ case "start":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.blnShuttingDown = false;
+ return "Started accepting incoming connections";
+ case "filteron":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.blnIPF = true;
+ return "Started filtering duplicate IP addressess of socket connections.";
+ case "filteroff":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.blnIPF = false;
+ return "Stopped filtering duplicate IP addressess of socket connections.";
+ case "floodlimit":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ if (args == null) {
+ return "What value do you want the floodlimit to have?";
+ }
+ try {
+ game.floodLimit = (long) Integer.parseInt(args);
+ return "Set floodlimit to " + args + " milliseconds.";
+ } catch (Exception e) {
+ game.log.printError("parseCommand():Invalid value \"" + args + "\" for floodlimit.", e);
+ return "Invalid value \"" + args + "\" for floodlimit.";
+ }
+ case "ipban": {
+ if (lt.privs <= 4)
+ return "Huh?";
+ String strBlockedIP;
+ if (args == null) {
+ return "Whos IP address do you wish to ban?";
+ }
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ String strIP = thnStore.getAddress();
+ int i = strIP.indexOf("/");
+ strIP = strIP.substring(i + 1, strIP.length());
+ // FIXME: better i/o
+ try (RandomAccessFile rafBannedIP = new RandomAccessFile("conf/blockedIP", "rw")) {
+ strBlockedIP = rafBannedIP.readLine();
+ while (strBlockedIP != null) {
+ if (strIP.indexOf(strBlockedIP) != -1) {
+ //rafBannedIP.close();
+ return "Already blocked.";
+ }
+ strBlockedIP = rafBannedIP.readLine();
+ }
+ rafBannedIP.seek(rafBannedIP.length());
+ rafBannedIP.writeBytes(strIP + "\n");
+ } catch (IOException ex) {
+ game.log.printError("parseCommand():When " + lt.name + " tried to ban " + thnStore + "'s IP address", ex);
+ }
+ return thnStore.name + "'s IP address has been blocked.";
+ }
+ case "loglevel":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ if (args == null) {
+ return "Logging level is currently " + game.log.getLogLevel();
+ }
+ try {
+ int level = Integer.parseInt(args);
+ game.log.setLogLevel(level);
+ return "Logging level is now " + game.log.getLogLevel();
+ } catch (Exception e) {
+ game.log.printError("parseCommand():Invalid value \"" + args + "\" for loglevel.", e);
+ return "Invalid value \"" + args + "\" for loglevel.";
+ }
+ case "gc":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "Starting garbage collection.");
+ System.gc();
+ game.log.printMessage(Log.INFO, "Finished garbage collection.");
+ return "Finished garbage collection.";
+ case "finalize":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "Starting finalization.");
+ System.runFinalization();
+ game.log.printMessage(Log.INFO, "Finished finalization.");
+ return "Finished finalization.";
+ case "cleanup":
+ if (lt.privs <= 4)
+ return "Huh?";
+ game.cleanup();
+ return "Finished cleanup.";
+ case "save":
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (game.blnSavingGame) {
+ return "Game already being saved, please wait.";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.saveMap();
+ return "Game settings saved";
+ case "backup":
+ if (lt.privs <= 2)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.backupMap();
+ return "Game settings backed up";
+ case "list": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "What do you want to list?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ cmdline = cmdline.substring(5);
+ String filename = null;
+ String title = null;
+ if (args.equals("items")) {
+ filename = "defItems";
+ title = "Items:\n";
+ } else if (args.equals("conf")) {
+ filename = "conf";
+ title = "Conf files:\n";
+ } else if (args.equals("mobs")) {
+ filename = "defMobs";
+ title = "Mobiles:\n";
+ } else if (args.equals("commands")) {
+ filename = "commands";
+ title = "Custom commands:\n";
+ } else if (args.equals("races")) {
+ filename = "defRaces";
+ title = "Races:\n";
+ } else if (args.equals("pets")) {
+ filename = "defPets";
+ title = "Pets:\n";
+ } else if (args.equals("factions")) {
+ filename = "factions";
+ title = "Factions:\n";
+ } else if (args.equals("conditions")) {
+ filename = "defConditions";
+ title = "Conditions:\n";
+ } else if (args.equals("help")) {
+ filename = "helpFiles";
+ title = "Help Files:\n";
+ } else if (args.equals("scripts")) {
+ filename = "scripts";
+ title = "Scripts:\n";
+ } else if (args.equals("spell groups")) {
+ filename = "defSpellGroups";
+ title = "Spell Groups:\n";
+ } else if (args.equals("spells")) {
+ filename = "defSpells";
+ title = "Spells:\n";
+ } else if (args.equals("props")) {
+ filename = "defProps";
+ title = "Props:\n";
+ } else if (args.equals("move actions")) {
+ filename = "defMoveActions";
+ title = "Move Action Scripts:\n";
+ } else if (args.equals("can move")) {
+ filename = "defCanMoveScripts";
+ title = "Can Move Scripts:\n";
+ } else if (args.equals("can see")) {
+ filename = "defCanSeeScripts";
+ title = "Can See Scripts:\n";
+ } else if (args.equals("tile actions")) {
+ filename = "defTileActions";
+ title = "Tile Action Scripts:\n";
+ } else if (args.equals("tile move")) {
+ filename = "defTileScripts";
+ title = "Can Move Tile Scripts:\n";
+ } else if (args.equals("tile see")) {
+ filename = "defTileSeeScripts";
+ title = "Tile See Scripts:\n";
+ }
+ if (filename != null) {
+ File filList = new File(filename);
+ String strResult[] = filList.list();
+ StringBuilder sb = new StringBuilder();
+ //strBuff.append("").append((char) 20).append(strTitle).append("\n");
+ for (int i = 0; i < strResult.length; i++) {
+ // Only output files that do not end in .dsko
+ if (strResult[i].indexOf(".dsko") == -1) {
+ sb.append(strResult[i]).append("\n");
+ }
+ }
+ //strBuff.append("--EOF--\n");
+ //lt.send(strBuff.toString());
+ lt.viewText(title, false, sb.toString());
+ return null;
+ }
+ return "You can't list that.";
+ }
+ case "view": {
+ if (lt.privs <= 2)
+ return "Huh?";
+
+ if (args == null) {
+ return "What do you want to view?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ if (args.indexOf("..") != -1) {
+ return "You don't have permission to access that file.";
+ }
+ String filename = null;
+ boolean blnUser = false;
+ boolean blnPet = false;
+ if (args.startsWith("item ")) {
+ args = args.toLowerCase();
+ filename = "defItems/" + args.substring(5);
+ } else if (args.startsWith("conf ")) {
+ filename = "conf/" + args.substring(5);
+ } else if (args.startsWith("mob ")) {
+ filename = "defMobs/" + args.substring(4);
+ } else if (args.startsWith("command ")) {
+ filename = "commands/" + args.substring(8);
+ } else if (args.startsWith("race ")) {
+ filename = "defRaces/" + args.substring(5);
+ } else if (args.startsWith("pet ")) {
+ filename = "defPets/" + args.substring(5);
+ } else if (args.startsWith("faction")) {
+ return "You cannot view faction files.";
+ } else if (args.startsWith("condition ")) {
+ filename = "defConditions/" + args.substring(10);
+ } else if (args.startsWith("help ")) {
+ filename = "helpFiles/" + args.substring(5);
+ } else if (args.startsWith("script ")) {
+ filename = "scripts/" + args.substring(7);
+ } else if (args.startsWith("spell group ")) {
+ filename = "defSpellGroups/" + args.substring(12);
+ } else if (args.startsWith("spell ")) {
+ filename = "defSpells/" + args.substring(6);
+ } else if (args.startsWith("prop ")) {
+ filename = "defProps/" + args.substring(5);
+ } else if (args.startsWith("move action ")) {
+ filename = "defMoveActions/" + args.substring(12);
+ } else if (args.startsWith("can move ")) {
+ filename = "defCanMoveScripts/" + args.substring(9);
+ } else if (args.startsWith("can see ")) {
+ filename = "defCanSeeScripts/" + args.substring(8);
+ } else if (args.startsWith("tile action ")) {
+ filename = "defTileActions/" + args.substring(12);
+ } else if (args.startsWith("tile move ")) {
+ filename = "defTileScripts/" + args.substring(10);
+ } else if (args.startsWith("tile see ")) {
+ filename = "defTileSeeScripts/" + args.substring(9);
+ } else if (args.startsWith("user ")) {
+ if (lt.privs < 5) {
+ return "You don't have enough privelages to edit a user's file.";
+ }
+ blnUser = true;
+ filename = "users/" + args.substring(5);
+ } else if (args.startsWith("pet ")) {
+ if (lt.privs < 5) {
+ return "You don't have enough privelages to edit a user's pet file.";
+ }
+ blnPet = true;
+ filename = "pets/" + args.substring(4);
+ }
+ File filView = new File(filename);
+ if (!filView.exists()) {
+ if (blnUser) {
+ return "There is no player named \"" + filView.getName() + "\".";
+ }
+ if (blnPet) {
+ return "The player named \"" + filView.getName() + "\" does not have a pet.";
+ }
+ //lt.send((char) 18 + args + "\n--EOF--\n");
+ lt.viewText(args, true, null);
+ return null;
+ }
+ RandomAccessFile rafView = null;
+ StringBuilder sb = new StringBuilder();
+ try {
+ rafView = new RandomAccessFile(filView, "rw");
+ if (blnUser) {
+ rafView.readLine(); //Skip over users' password
+ }
+ String strStore2 = rafView.readLine();
+ //sb.append((char) 18 + args + "\n");
+ while (strStore2 != null) {
+ sb.append(strStore2 + "\n");
+ strStore2 = rafView.readLine();
+ }
+ //sb.append("--EOF--\n");
+ lt.viewText(args, true, sb.toString());
+ } catch (Exception e) {
+ game.log.printError("parseCommand():Reading file for " + filView.getName(), e);
+ }
+ try {
+ rafView.close();
+ } catch (Exception e) {
+ game.log.printError("parseCommand():Closing file after " + filView.getName(), e);
+ }
+ return null;
+ }
+ case "submit": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "What do you want to submit?";
+ }
+ if ((lt.privs < 4) && (!args.startsWith("mob "))) {
+ return "You are not allowed to submit files.";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ if (args.indexOf("..") != -1) {
+ return "You don't have permission to access that file.";
+ }
+
+ if (true)
+ return "Developer hasn't implemented submit yet";
+
+ // FIXME: implement submit, just use some submit message protocol
+
+ boolean compile = false;
+ boolean blnUser = false;
+ boolean blnPet = false;
+ String strFileName = null;
+ if (args.startsWith("item ") && (lt.privs > 3)) {
+ args = args.toLowerCase();
+ strFileName = "defItems/" + args.substring(5);
+ } else if (args.startsWith("conf ") && (lt.privs > 3)) {
+ strFileName = "conf/" + args.substring(5);
+ } else if (args.startsWith("mob ")) {
+ strFileName = "defMobs/" + args.substring(4);
+ } else if (args.startsWith("command ") && (lt.privs > 3)) {
+ strFileName = "commands/" + args.substring(8);
+ compile = true;
+ } else if (args.startsWith("race ") && (lt.privs > 3)) {
+ strFileName = "defRaces/" + args.substring(5);
+ } else if (args.startsWith("pet ") && (lt.privs > 3)) {
+ strFileName = "defPets/" + args.substring(4);
+ } else if (args.startsWith("faction") && (lt.privs > 3)) {
+ return "You cannot submit faction files.";
+ } else if (args.startsWith("condition ") && (lt.privs > 3)) {
+ strFileName = "defConditions/" + args.substring(10);
+ } else if (args.startsWith("help ") && (lt.privs > 3)) {
+ strFileName = "helpFiles/" + args.substring(5);
+ } else if (args.startsWith("script ") && (lt.privs > 3)) {
+ strFileName = "scripts/" + args.substring(7);
+ compile = true;
+ } else if (args.startsWith("spell group ") && (lt.privs > 3)) {
+ strFileName = "defSpellGroups/" + args.substring(12);
+ compile = true;
+ } else if (args.startsWith("spell ") && (lt.privs > 3)) {
+ strFileName = "defSpells/" + args.substring(6);
+ } else if (args.startsWith("prop ")) {
+ strFileName = "defProps/" + args.substring(5);
+ } else if (args.startsWith("move action ") && (lt.privs > 3)) {
+ strFileName = "defMoveActions/" + args.substring(12);
+ compile = true;
+ } else if (args.startsWith("can move ") && (lt.privs > 3)) {
+ strFileName = "defCanMoveScripts/" + args.substring(9);
+ compile = true;
+ } else if (args.startsWith("can see ") && (lt.privs > 3)) {
+ strFileName = "defCanSeeScripts/" + args.substring(8);
+ compile = true;
+ } else if (args.startsWith("tile action ") && (lt.privs > 3)) {
+ strFileName = "defTileActions/" + args.substring(12);
+ compile = true;
+ } else if (args.startsWith("tile move ") && (lt.privs > 3)) {
+ strFileName = "defTileScripts/" + args.substring(10);
+ compile = true;
+ } else if (args.startsWith("tile see ") && (lt.privs > 3)) {
+ strFileName = "defTileSeeScripts/" + args.substring(9);
+ compile = true;
+ } else if (args.startsWith("user ")) {
+ if (lt.privs < 5) {
+ return "You don't have enough privelages to submit a user's file.";
+ }
+ if (game.getPlayer(args.substring(5)) != null) {
+ return "You cannot submit a file for an active user.";
+ }
+ blnUser = true;
+ strFileName = "users/" + args.substring(5);
+ } else if (args.startsWith("pet ")) {
+ if (lt.privs < 5) {
+ return "You don't have enough privelages to submit a user's pet file.";
+ }
+ if (game.getPlayer(args.substring(4)) != null) {
+ return "You cannot submit a pet file for an active user.";
+ }
+ blnPet = true;
+ strFileName = "pets/" + args.substring(4);
+ }
+ if (strFileName == null) {
+ return "Cannot submit " + args;
+ }
+ File filView = null;
+ try {
+ filView = new File(strFileName);
+ } catch (Exception e) {
+ return "Cannot submit " + args + " (" + strFileName + ")";
+ }
+ RandomAccessFile rafView = null;
+ try {
+ if (blnUser) {
+ /*
+ Read in the user's password before deleting the file
+ */
+ rafView = new RandomAccessFile(filView, "r");
+ cmdline = rafView.readLine();
+ }
+ if (filView.exists()) {
+ filView.delete();
+ }
+ rafView = new RandomAccessFile(filView, "rw");
+ if (blnUser) {
+ /*
+ Write out the password for user files
+ */
+ rafView.writeBytes(cmdline + "\n");
+ }
+ /**
+ * FIXME: from message
+ */
+ /*
+ cmdline = lt.instream.readLine();
+ while (!cmdline.equals("--EOF--")) {
+ rafView.writeBytes(cmdline + "\n");
+ cmdline = lt.instream.readLine();
+ }*/
+ rafView.close();
+ if (compile) {
+ Script scrStore = new Script(filView.getPath(), game, true);
+ scrStore.close();
+ }
+ if (blnUser || blnPet) {
+ /*
+ Delete the .backup file for users and pets
+ */
+ filView = new File(strFileName + ".backup");
+ if (filView.exists()) {
+ filView.delete();
+ }
+ }
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While submitting file " + args + " (" + filView.getName() + ")", e);
+ try {
+ rafView.close();
+ } catch (Exception e2) {
+ game.log.printError("parseCommand():While closing file " + args + " (" + filView.getName() + ") after failed submit", e);
+ }
+ return "Error while trying to submit " + args + " (" + filView.getName() + ").";
+ }
+ return "Submit of " + args + " was successful.";
+ }
+ case "delete": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "What do you want to delete?";
+ }
+ if (lt.privs < 4) {
+ return "You are not allowed to delete files.";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ if (args.indexOf("..") != -1) {
+ return "You don't have permission to access that file.";
+ }
+ String filename = null;
+ String strReturn = null;
+ if (args.startsWith("item ")) {
+ filename = "defItems/" + args.substring(5);
+ strReturn = "item " + args.substring(5);
+ } else if (args.startsWith("conf ")) {
+ filename = "conf/" + args.substring(5);
+ strReturn = "conf " + args.substring(5);
+ } else if (args.startsWith("mob ")) {
+ filename = "defMobs/" + args.substring(4);
+ strReturn = "mob " + args.substring(4);
+ } else if (args.startsWith("command ")) {
+ filename = "commands/" + args.substring(8);
+ strReturn = "command " + args.substring(8);
+ } else if (args.startsWith("race ")) {
+ filename = "defRaces/" + args.substring(5);
+ strReturn = "race " + args.substring(5);
+ } else if (args.startsWith("pet ")) {
+ filename = "defPets/" + args.substring(4);
+ strReturn = "pet " + args.substring(4);
+ } else if (args.startsWith("faction")) {
+ return "You cannot delete faction files.";
+ } else if (args.startsWith("condition ")) {
+ filename = "defConditions/" + args.substring(8);
+ strReturn = "condition " + args.substring(8);
+ } else if (args.startsWith("help ")) {
+ filename = "helpFiles/" + args.substring(5);
+ strReturn = "help " + args.substring(5);
+ } else if (args.startsWith("script ")) {
+ filename = "scripts/" + args.substring(7);
+ strReturn = "script " + args.substring(7);
+ } else if (args.startsWith("spell group ")) {
+ filename = "defSpellGroups/" + args.substring(12);
+ strReturn = "spell group " + args.substring(12);
+ } else if (args.startsWith("spell ")) {
+ filename = "defSpells/" + args.substring(6);
+ strReturn = "spell " + args.substring(6);
+ } else if (args.startsWith("prop ")) {
+ filename = "defProps/" + args.substring(5);
+ strReturn = "prop " + args.substring(5);
+ } else if (args.startsWith("move action ")) {
+ filename = "defMoveActions/" + args.substring(12);
+ strReturn = "move action " + args.substring(12);
+ } else if (args.startsWith("can move ")) {
+ filename = "defCanMoveScripts/" + args.substring(9);
+ strReturn = "can move " + args.substring(9);
+ } else if (args.startsWith("can see ")) {
+ filename = "defCanSeeScripts/" + args.substring(8);
+ strReturn = "can see " + args.substring(8);
+ } else if (args.startsWith("tile action ")) {
+ filename = "defTileActions/" + args.substring(12);
+ strReturn = "tile action " + args.substring(12);
+ } else if (args.startsWith("tile move ")) {
+ filename = "defTileScripts/" + args.substring(10);
+ strReturn = "tile move " + args.substring(10);
+ } else if (args.startsWith("tile see ")) {
+ filename = "defTileSeeScripts/" + args.substring(9);
+ strReturn = "tile see " + args.substring(9);
+ }
+ File filDelete = null;
+ if (filename != null) {
+ filDelete = new File(filename);
+ if (filDelete.exists()) {
+ filDelete.delete();
+ filDelete = new File(filename + ".dsko");
+ if (filDelete.exists()) {
+ filDelete.delete();
+ strReturn += " and the associated .dsko file.";
+ }
+ } else {
+ return strReturn + " does not exist.";
+ }
+ return "Deleted " + strReturn;
+ }
+ return "You cannot delete that.";
+ }
+ case "clanleader": {
+ if (lt.privs <= 2)
+ return "Huh?";
+
+ if (args == null) {
+ return "Clanleader who?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ LivingThing thnStore = game.getPlayer(args.substring(0, args.indexOf(' ')));
+ if (thnStore == null) {
+ return "They're not in this world";
+ }
+ if (args.length() < thnStore.name.length() + 2) {
+ return "What clan?";
+ }
+ if (thnStore.privs > 1) {
+ return "You can't clanleader them.";
+ }
+ args = args.substring(thnStore.name.length() + 1);
+ thnStore.clan = args;
+ if (args.equals("none")) {
+ thnStore.privs = 0;
+ thnStore.chatMessage("You are now clanless.");
+ } else {
+ thnStore.privs = 1;
+ thnStore.chatMessage("You are now a member of the " + args + " clan.");
+ }
+ game.removeDuskObject(thnStore);
+ game.addDuskObject(thnStore.map, thnStore);
+ return thnStore.name + " is now a leader of the " + args + " clan.";
+ }
+ case "boot": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "Boot who?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ if (thnStore.privs >= lt.privs) {
+ thnStore.chatMessage(lt.name + " attempted to boot you.");
+ return "You do not have high enough privelages to boot them.";
+ }
+ thnStore.chatMessage("You have been booted.");
+ thnStore.close();
+ return null;
+ }
+ case "hardkill": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "HardKill who?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ if (thnStore.privs >= lt.privs) {
+ thnStore.chatMessage(lt.name + " attempted to HardKill you.");
+ return "You do not have high enough privelages to HardKill them.";
+ }
+ thnStore.closeNoMsgPlayer();
+ return null;
+ }
+ case "nochannel": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "nochannel who for how long?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ LivingThing thnStore;
+ int duration;
+ try {
+ thnStore = game.getPlayer(args.substring(0, args.indexOf(" ")));
+ duration = Integer.parseInt(args.substring(args.indexOf(" ") + 1));
+ } catch (Exception e) {
+ game.log.printError("parseCommand():When " + lt.name + " tried to nochannel " + args, e);
+ return "nochannel who for how long?";
+ }
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ if (thnStore.privs >= lt.privs) {
+ thnStore.chatMessage(lt.name + " attempted to nochannel you.");
+ return "You do not have high enough privelages to nochannel them.";
+ }
+ if (duration > game.noChannelMax) {
+ duration = game.noChannelMax;
+ }
+ thnStore.chatMessage("You have been nochanneled for " + duration + " seconds.");
+ thnStore.noChannel = duration;
+ return "You have nochanneled " + thnStore.name + " for " + duration + " seconds";
+ }
+ case "allowchannel": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "allowchannel who?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ thnStore.chatMessage("Your nochannel status has been removed.");
+ thnStore.noChannel = 0;
+ return thnStore.name + "'s nochannel status has been removed.";
+ }
+ case "gecho": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "G-Echo what?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ game.chatMessage(args, "default");
+ return null;
+ }
+ case "teleport": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "Teleport to where?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ int index = args.lastIndexOf('_');
+ char charSep = ' ';
+ if (index != -1) {
+ charSep = '_';
+ }
+ try {
+ String[] params = args.split(" ");
+ TileMap map;
+ int destX, destY;
+ if (params.length == 3) {
+ map = game.maps.get(params[0]);
+ destX = Integer.parseInt(params[1]);
+ destY = Integer.parseInt(params[2]);
+ } else {
+ map = lt.map;
+ destX = Integer.parseInt(params[0]);
+ destY = Integer.parseInt(params[1]);
+ }
+ if (lt.privs < 5 && destX >= map.getCols()) {
+ destX = map.getCols() - 1;
+ }
+ if (lt.privs < 5 && destY >= map.getRows()) {
+ destY = map.getRows() - 1;
+ }
+ if (destX < 0) {
+ destX = 0;
+ }
+ if (destY < 0) {
+ destY = 0;
+ }
+ lt.changeLocBypass(map, destX, destY);
+ } catch (Exception e) {
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "Teleport to where?";
+ } else {
+ // Hmm, i'm not sure what this does, teleport to another player?
+ // Whats with the weird range checking?
+ int destX = thnStore.x;
+ int destY = thnStore.y;
+ if (lt.privs < 5 && destX > 349) {
+ destX = 349;
+ }
+ if (lt.privs < 5 && destY > 349) {
+ destY = 349;
+ }
+ if (destX < 0) {
+ destX = 0;
+ }
+ if (destY < 0) {
+ destY = 0;
+ }
+ lt.changeLocBypass(thnStore.map, destX, destY);
+ }
+ }
+ return null;
+ }
+ case "remove": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "remove what?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ DuskObject objStore = lt.getLocalObject(args);
+ if (objStore != null) {
+ if (objStore.isLivingThing()) {
+ LivingThing thnStore = (LivingThing) objStore;
+ if (thnStore.isMob()) {
+ thnStore.close();
+ game.blnMobListChanged = true;
+ return "Object removed.";
+ } else {
+ return "You can't remove players/pets.";
+ }
+ } else if (objStore.isItem()) {
+ //game.vctItems.remove(objStore);
+ game.removeDuskObject(objStore);
+ return "Object removed.";
+ } else if (objStore.isSign()) {
+ //game.vctSigns.remove(objStore);
+ //game.blnSignListChanged = true;
+ game.removeDuskObject(objStore);
+ return "Object removed.";
+ } else if (objStore.isProp()) {
+ //game.vctProps.removeElement(objStore);
+ //game.blnPropListChanged = true;
+ game.removeDuskObject(objStore);
+ return "Object removed.";
+ } else if (objStore.isMerchant()) {
+ //game.vctMerchants.remove(objStore);
+ //game.blnMerchantListChanged = true;
+ game.removeDuskObject(objStore);
+ return "Object removed.";
+ }
+ }
+ return "You don't see that here.";
+ }
+ case "changeclan": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "ChangeClan who?";
+ }
+ LivingThing thnStore = game.getPlayer(args.substring(0, args.indexOf(' ')));
+ if (thnStore == null) {
+ return "They're not in this world";
+ }
+ if (cmdline.length() < thnStore.name.length() + 2) {
+ return "What clan?";
+ }
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ args = args.substring(thnStore.name.length() + 1);
+ thnStore.clan = args;
+ if (thnStore.privs == 1) {
+ thnStore.privs = 0;
+ }
+ if (args.equals("none")) {
+ thnStore.chatMessage("You are now a member of no clan.");
+ } else {
+ thnStore.chatMessage("You are now a member of the " + args + " clan.");
+ }
+ return thnStore.name + " has been added to the " + args + " clan";
+ }
+ case "madd": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "Madd what?";
+ }
+ Merchant mrcStore = lt.overMerchant();
+ if (mrcStore != null) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ mrcStore.items.add(args);
+ game.refreshEntities(lt);
+ } else {
+ if (lt.overPlayerMerchant() != null) {
+ return "You cannot add items to a player's merchant this way.";
+ }
+ return "You are not on a merchant.";
+ }
+ return null;
+ }
+ case "mremove": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ if (args == null) {
+ return "Mremove what?";
+ }
+ Merchant mrcStore = lt.overMerchant();
+ if (mrcStore != null) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+ mrcStore.items.remove(args);
+ game.refreshEntities(lt);
+ } else {
+ if (lt.overPlayerMerchant() != null) {
+ return "You cannot remove items from a player's merchant this way.";
+ }
+ return "You are not on a merchant.";
+ }
+ return null;
+ }
+ case "whoip": {
+ if (lt.privs <= 2)
+ return "Huh?";
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y);
+
+ int nPlayers = game.playersByName.size();
+ StringBuilder sb = new StringBuilder();
+
+ // TODO: this used to be atomic, does it need to be?
+
+ for (LivingThing thnStore : game.playersByName.values()) {
+ boolean hidden = false;
+ if (thnStore.privs > 2) {
+ if (thnStore.hasCondition("invis")) {
+ hidden = true;
+ }
+ }
+ if (hidden && (lt.privs < thnStore.privs)) {
+ nPlayers--;
+ }
+ }
+
+ lt.chatMessage("\tThere are " + nPlayers + " players online:");
+
+ for (LivingThing thnStore : game.playersByName.values()) {
+ boolean hidden = false;
+ boolean skip = false;
+ if (thnStore.privs > 2) {
+ if (thnStore.hasCondition("invis")) {
+ hidden = true;
+ }
+ }
+ if (hidden && (lt.privs < thnStore.privs)) {
+ skip = true;
+ }
+ if (!skip) {
+ String ip = thnStore.getAddress().toString();
+ while (ip.length() < 34) {
+ ip += " ";
+ }
+ sb.setLength(0);
+ sb.append(" ").append(ip);
+ sb.append(thnStore.getCharacterPoints()).append("cp ");
+ if (thnStore.privs == 1) {
+ sb.append("{Clan Leader}");
+ } else if (thnStore.privs == 3) {
+ sb.append("{God}");
+ } else if (thnStore.privs == 4) {
+ sb.append("{High God}");
+ } else if (thnStore.privs > 4) {
+ sb.append("{Master God}");
+ }
+ if (hidden) {
+ sb.append("{hidden}");
+ }
+ if (thnStore.noChannel != 0) {
+ sb.append("{nochanneled}");
+ }
+ if (!thnStore.clan.equals("none")) {
+ sb.append("<" + thnStore.clan + "> ");
+ }
+ sb.append(thnStore.name + "\n");
+ lt.chatMessage(sb.toString());
+ }
+ }
+
+ return null;
+ }
+ case "change": {
+ if (lt.battle != null) {
+ return "Not while you're fighting!";
+ }
+ if (args == null) {
+ return "Change what?";
+ }
+
+ if (true)
+ return "Race changing unimplemented";
+
+ if (args.equals("race")) {
+ if (lt.getCharacterPoints() > game.changeRaceCpLimit) {
+ return "You can no longer change your race.";
+ }
+ lt.unloadRace();
+
+ // FIXME: I'm not sure why this needs to clear messages here.
+/*
+ if (lt.isPet()) {
+
+ lt.getMaster().halt();
+ // lt.getMaster().stillThere(); // This puts something in the buffer
+ // lt.getMaster().thrConnection.sleep(750); // wait for it...
+ try {
+ // Empty out the BufferedReader for the answer
+ // while (lt.getMaster().instream.ready()) {
+ // lt.getMaster().instream.readLine();
+ // }
+ } catch (Exception e) {
+ game.log.printError("parseCommand():Trying to empty ready buffer of pet's master for change race.", e);
+ }
+ } else {
+ lt.halt();
+ // lt.stillThere(); // This puts something in the buffer
+ // lt.thrConnection.sleep(750); // wait for it...
+ try {
+ // Empty out the BufferedReader for the answer
+ // while (lt.instream.ready()) {
+ // lt.instream.readLine();
+ // }
+ } catch (Exception e) {
+ game.log.printError("parseCommand():Trying to empty ready buffer of player for change race.", e);
+ }
+ }
+ lt.loadRace();
+ if (lt.isPet()) {
+ lt.getMaster().proceed();
+ lt.getMaster().updateStats();
+ } else {
+ lt.proceed();
+ }
+ */
+ game.removeDuskObject(lt);
+ game.addDuskObject(lt.map, lt);
+ lt.updateStats();
+ return "Your race has been changed.";
+ }
+ }
+
+ I am here!
+
+ case "clan": {
+ if (!lt.isPlayer() && !lt.isMob()) {
+ return "Only players can use the gossip/clan/tell channels.";
+ }
+ if (lt.clan.equals("none")) {
+ return "You're not in a clan. Use gossip instead";
+ }
+ if (lt.noChannel != 0) {
+ return "You can't do that when nochanneled.";
+ }
+ if (args == null) {
+ return "Clan what?";
+ }
+ if (args.length() > game.messagecap) {
+ return "That message was too long.";
+ }
+ if (!args.equals("")) {
+ long lTemp = lt.lastMessageStamp;
+ lt.lastMessageStamp = System.currentTimeMillis();
+ if ((System.currentTimeMillis() - lTemp) < game.floodLimit) {
+ return "No flooding.";
+ }
+ game.chatMessage(lt.name + " clans: " + args, lt.clan, lt.name);
+ }
+ return null;
+ }
+
+ case "emote": {
+ if (args == null) {
+ return "Emote what?";
+ }
+ if (lt.noChannel != 0) {
+ return "You can't do that when nochanneled.";
+ }
+ if (args.length() > game.messagecap) {
+ return "That message was too long.";
+ }
+ if (!args.equals("")) {
+ long lTemp = lt.lastMessageStamp;
+ lt.lastMessageStamp = System.currentTimeMillis();
+ if ((System.currentTimeMillis() - lTemp) < game.floodLimit) {
+ return "No flooding.";
+ }
+ String strPerson = lt.name;
+ if (lt.privs > 2
+ && lt.hasCondition("invis")
+ && lt.hasCondition("invis2")) {
+ strPerson = "A god";
+ }
+ game.chatMessage(lt.map, strPerson + " " + args, lt.x, lt.y, lt.name);
+ }
+ return null;
+ }
+ case "tell": {
+ if (!lt.isPlayer()) {
+ return "Only players can use the gossip/clan/tell channels.";
+ }
+ if (args == null) {
+ return "Tell who what?";
+ }
+ if (lt.noChannel != 0) {
+ return "You can't do that when nochanneled.";
+ }
+ StringTokenizer tknStore = new StringTokenizer(args, " ");
+ String strStore2 = null;
+ try {
+ strStore2 = tknStore.nextToken();
+ } catch (Exception e) {
+ return "Tell who?";
+ }
+ LivingThing thnStore = game.getPlayer(strStore2);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ if (thnStore.privs > 2
+ && thnStore.hasCondition("invis")
+ && thnStore.hasCondition("invis2")) {
+ return "They're not in this world.";
+ }
+ if (thnStore.name.equalsIgnoreCase(lt.name)) {
+ return "Talking to yourself is not a good sign.";
+ }
+ String strIgnoreName = thnStore.name.toLowerCase();
+ if (lt.ignoreList.contains(strIgnoreName)) {
+ return "You can't do that while you are ignoring them.";
+ }
+ strIgnoreName = lt.name.toLowerCase();
+ if (thnStore.ignoreList.contains(strIgnoreName)) {
+ return "They did not get the message, they are ignoring you.";
+ }
+ args = args.substring(strStore2.length(), args.length()).trim();
+ if (args.length() > game.messagecap) {
+ return "That message was too long.";
+ }
+ if (args.length() == 0) {
+ return "Tell them what?";
+ }
+ long lTemp = lt.lastMessageStamp;
+ lt.lastMessageStamp = System.currentTimeMillis();
+ if ((System.currentTimeMillis() - lTemp) < game.floodLimit) {
+ return "No flooding.";
+ }
+ String strPerson = lt.name;
+ if (lt.privs > 2
+ && lt.hasCondition("invis")
+ && lt.hasCondition("invis2")) {
+ strPerson = "A god";
+ }
+ game.log.printMessage(Log.ALWAYS, lt.name + " tells " + thnStore.name + " : " + args);
+ thnStore.chatMessage(strPerson + " tells you: " + args);
+ return "You tell " + strStore2 + ": " + args;
+ }
+ case "who": {
+ int nPlayers = game.playersByName.size();
+ StringBuilder sb = new StringBuilder();
+
+ // TOOD: originally this was atomic on stream
+ // although 'atomic' is wrong since nobody else was atomic on stream
+
+ for (LivingThing thnStore : game.playersByName.values()) {
+ boolean hidden = false;
+ if (thnStore.privs > 2) {
+ if (thnStore.hasCondition("invis")) {
+ hidden = true;
+ }
+ }
+ if (hidden && (lt.privs < thnStore.privs)) {
+ nPlayers--;
+ }
+ if (lt.privs < 3 && !thnStore.isWorking) {
+ nPlayers--;
+ }
+ if (lt.privs < 3 && !thnStore.isReady) {
+ nPlayers--;
+ }
+ }
+
+ lt.chatMessage("\tThere are " + nPlayers + " players online:");
+
+ for (LivingThing thnStore : game.playersByName.values()) {
+ boolean hidden = false;
+ boolean skip = false;
+ if (thnStore.privs > 2) {
+ if (thnStore.hasCondition("invis")) {
+ hidden = true;
+ }
+ }
+ if (hidden && (lt.privs < thnStore.privs)) {
+ skip = true;
+ }
+ if (lt.privs < 3 && !thnStore.isWorking) {
+ skip = true;
+ }
+ if (lt.privs < 3 && !thnStore.isReady) {
+ skip = true;
+ }
+ System.out.println(" user " + thnStore.name + " skip " + skip);
+ if (!skip) {
+ sb.setLength(0);
+ sb.append("\t");
+ sb.append(thnStore.getCharacterPoints());
+ sb.append("cp ");
+ if (lt.privs > 2 && !thnStore.isWorking) {
+ sb.append("{* Not Working *}");
+ }
+ if (lt.privs > 2 && !thnStore.isReady) {
+ sb.append("{Entering the world}");
+ }
+ if (lt.privs > 2 && !thnStore.isSaveable) {
+ sb.append("{Loading/Saving}");
+ }
+ if (thnStore.privs == 1) {
+ sb.append("{Clan Leader}");
+ } else if (thnStore.privs == 3) {
+ sb.append("{God}");
+ } else if (thnStore.privs == 4) {
+ sb.append("{High God}");
+ } else if (thnStore.privs > 4) {
+ sb.append("{Master God}");
+ }
+ if (hidden) {
+ sb.append("{hidden}");
+ }
+ if (thnStore.noChannel != 0) {
+ sb.append("{nochanneled}");
+ }
+ if (thnStore.ignoreList.contains(lt.name.toLowerCase())) {
+ sb.append("{Ignoring you}");
+ }
+ if (lt.ignoreList.contains(thnStore.name.toLowerCase())) {
+ sb.append("{Ignored}");
+ }
+ if (!thnStore.clan.equals("none")) {
+ sb.append("<");
+ sb.append(thnStore.clan);
+ sb.append("> ");
+ }
+ if (thnStore.title == null
+ || thnStore.title.equals("none")) {
+ sb.append(thnStore.name);
+ //sb.append("\n");
+ } else {
+ sb.append(thnStore.name);
+ sb.append(" ");
+ sb.append(thnStore.title);
+ //sb.append("\n");
+ }
+ lt.chatMessage(sb.toString());
+ }
+ }
+ return "Who complete.";
+ }
+ //kill
+ case "order": {
+ if (args == null) {
+ return "Order who to do what?";
+ }
+ int intStore = args.indexOf(" ");
+ if (intStore == -1) {
+ return "Order them to do what?";
+ }
+ DuskObject objStore = lt.getLocalObject(args.substring(0, intStore));
+ if (objStore == null) {
+ return "You don't see that here.";
+ }
+ if (!objStore.isLivingThing()) {
+ return "You can't order that.";
+ }
+ LivingThing thnStore = (LivingThing) objStore;
+ if (thnStore.getCharmer() != lt) {
+ return "They don't take orders from you.";
+ }
+ args = args.substring(intStore + 1);
+ try {
+ thnStore.chatMessage(Commands.parseCommand(thnStore, game, args));
+// lt.chatMessage(Commands.parseCommand(thnStore, engGame, strArgs));
+ } catch (Exception e) {
+ game.log.printError("parseCommand():" + thnStore.name + ", while trying to follow the following order: \"" + args + "\"", e);
+ }
+ return null;
+ }
+
+ case "inv":
+ case "inventory": {
+ final String[] formats = {
+ "Wielded: %s",
+ "Arms: %s",
+ "Legs: %s",
+ "Torso: %s",
+ "Waist: %s",
+ "Neck: %s",
+ "Skull: %s",
+ "Eyes: %s",
+ "Hands: %s"};
+ lt.chatMessage("-Worn-");
+ for (int i = 0; i < formats.length; i++) {
+ Item item = lt.wornItems.getWorn(i);
+ if (item != null)
+ lt.chatMessage(String.format(formats[i], item.description));
+ }
+ lt.chatMessage("-Inventory-:");
+ for (LinkedList<Item> list : lt.itemList.values()) {
+ if (!list.isEmpty()) {
+ Item item = (Item) list.element();
+ lt.chatMessage(list.size() + " " + item.name);
+ }
+ }
+ return null;
+ }
+
+ case "help": {
+ File file;
+ String title;
+
+ // FIXME: was atomic
+ if (args == null) {
+ file = new File("help");
+ title = "Help";
+ } else {
+ if (args.indexOf("..") != -1) {
+ return "There is no help on that subject";
+ }
+ file = new File("helpFiles/" + args);
+ title = "Help on " + args;
+ }
+
+ try (RandomAccessFile helpFile = new RandomAccessFile(file, "r")) {
+ lt.chatMessage(title);
+ while ((cmdline = helpFile.readLine()) != null) {
+ lt.chatMessage(cmdline);
+ }
+ } catch (IOException e) {
+ game.log.printError("parseCommand():When " + lt.name + " tried to get help on " + args, e);
+ return "There is no help on that subject";
+ }
+ return null;
+ }
+
+ case "get": {
+ if (args == null) {
+ return "Get what?";
+ }
+ DuskObject objStore = lt.getLocalObject(args);
+ if (objStore == null) {
+ return "You don't see that here.";
+ }
+ if (!objStore.isItem()) {
+ return "You can't get that.";
+ }
+ Item itmStore = (Item) objStore;
+ if (Math.abs(lt.x - itmStore.x) + Math.abs(lt.y - itmStore.y) < 2) {
+ if (lt.privs > 2) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":gets " + args + ":" + lt.x + "," + lt.y);
+ }
+ lt.itemList.addElement(itmStore);
+ lt.updateItems();
+ game.removeDuskObject(itmStore);
+ } else {
+ return "That's too far away.";
+ }
+ itmStore.onGetItem(game, lt);
+ return null;
+ }
+
+ case "drop": {
+ if (args == null) {
+ return "Drop what?";
+ }
+ int intDot = args.indexOf(".");
+ int intNumToDrop = 1;
+ if (intDot != -1) {
+ try {
+ intNumToDrop = Integer.parseInt(args.substring(0, intDot));
+ } catch (NumberFormatException e) {
+ intNumToDrop = 1;
+ }
+ }
+ Item itmStore = lt.getItem(args);
+ if (itmStore != null) {
+ String strMessage = "You drop " + itmStore.name + ".";
+ if (intNumToDrop > 1) {
+ strMessage = "You drop " + intNumToDrop + " " + itmStore.name + ".";
+ }
+ if (itmStore.intCost == 0) {
+ strMessage = "A " + itmStore.name + " vanishes into thin air.";
+ if (intNumToDrop > 1) {
+ strMessage = intNumToDrop + " " + itmStore.name + " vanish into thin air.";
+ }
+ }
+ if (lt.privs > 2) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":drops " + args + ":" + lt.x + "," + lt.y);
+ }
+ for (int i = 0; i < intNumToDrop; i++) {
+ itmStore = lt.getItemAndRemove(itmStore.name);
+ itmStore.x = lt.x;
+ itmStore.y = lt.y;
+ if (itmStore.intCost != 0) {
+ //game.vctItems.add(itmStore);
+ game.addDuskObject(lt.map, itmStore);
+ lt.updateItems();
+ }
+ itmStore.onDropItem(game, lt);
+ }
+ return strMessage;
+ }
+ return "You don't have that.";
+ }
+
+ case "use": {
+ if (args == null) {
+ return "Use what?";
+ }
+ if (lt.battle == null) {
+ lt.useItem(args, -1);
+ } else {
+ lt.battle.addCommand(lt, "use " + args);
+ }
+ return null;
+ }
+ case "eat": {
+ if (args == null) {
+ return "Eat what?";
+ }
+ if (lt.battle == null) {
+ lt.useItem(args, Item.FOOD);
+ } else {
+ lt.battle.addCommand(lt, "eat " + args);
+ }
+ return null;
+ }
+ case "drink": {
+ if (args == null) {
+ return "Drink what?";
+ }
+ if (lt.battle == null) {
+ lt.useItem(args, Item.DRINK);
+ } else {
+ lt.battle.addCommand(lt, "drink " + args);
+ }
+ return null;
+ }
+
+ case "give": {
+ if (args == null) {
+ return "Give who what?";
+ }
+ StringTokenizer tknStore = new StringTokenizer(args, " ");
+ String strStore2 = null;
+ try {
+ strStore2 = tknStore.nextToken();
+ } catch (Exception e) {
+ return "Give who what?";
+ }
+ DuskObject objStore = lt.getLocalObject(strStore2);
+ if (objStore == null) {
+ return "You don't see them here.";
+ }
+ if (!objStore.isLivingThing()) {
+ return "You can't give to that.";
+ }
+ LivingThing thnStore = (LivingThing) objStore;
+ if ((lt.privs < 3) && (Math.abs(thnStore.x - lt.x) + Math.abs(thnStore.y - lt.y) > 1)) {
+ return "They're too far away.";
+ }
+ args = args.substring(strStore2.length() + 1);
+ if (lt.privs > 2) {
+ game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":gives " + args + " to " + strStore2 + ":" + lt.x + "," + lt.y);
+ }
+ if (args.startsWith("gp")) {
+ args = args.substring(3);
+ try {
+ int intStore = Integer.parseInt(args);
+ if (intStore < 0) {
+ return "You can't give negative money!";
+ }
+ if (intStore <= lt.cash) {
+ lt.cash -= intStore;
+ thnStore.cash += intStore;
+ lt.updateStats();
+ thnStore.updateStats();
+ thnStore.chatMessage(lt.name + " gives you " + intStore + "gp.");
+ return "You give " + thnStore.name + " " + intStore + "gp.";
+ } else {
+ lt.chatMessage("You don't have that much gp");
+ }
+ } catch (Exception e) {
+ return "That is not a valid amount of gp to give.";
+ }
+ }
+ int intDot = args.indexOf(".");
+ int intNumToGive = 1;
+ if (intDot != -1) {
+ try {
+ intNumToGive = Integer.parseInt(args.substring(0, intDot));
+ } catch (NumberFormatException e) {
+ intNumToGive = 1;
+ }
+ }
+ Item itmStore = lt.getItem(args);
+ if (itmStore != null) {
+ String strMessage = lt.name + " gives you ";
+ String strMessage2 = "You give " + thnStore.name + " ";
+ if (intNumToGive > 1) {
+ strMessage += intNumToGive + " ";
+ strMessage2 += intNumToGive + " ";
+ }
+ strMessage += itmStore.name + ".";
+ strMessage2 += itmStore.name + ".";
+ cmdline = itmStore.name;
+
+ while (intNumToGive > 0) {
+ itmStore = lt.getItemAndRemove(cmdline);
+ thnStore.itemList.addElement(itmStore);
+ intNumToGive--;
+
+ itmStore.onDropItem(game, lt);
+ itmStore.onGetItem(game, thnStore);
+ }
+
+ thnStore.chatMessage(strMessage);
+ thnStore.updateItems();
+ lt.updateItems();
+ return strMessage2;
+ }
+ return "You don't have that.";
+ }
+
+ case "buy": {
+ if (args == null) {
+ return "Buy what?";
+ }
+ int quantity;
+ try {
+ int i = args.indexOf(" ");
+ quantity = Integer.parseInt(args.substring(0, i));
+ args = args.substring(i + 1);
+ } catch (Exception e) {
+ return "How many of what do you want to buy?";
+ }
+ if (quantity > 100) {
+ quantity = 100;
+ } else if (quantity < 1) {
+ return "You can't buy less than one of something.";
+ }
+ PlayerMerchant pmrStore = lt.overPlayerMerchant();
+ if (pmrStore != null) {
+ long numItem = pmrStore.contains(args);
+ if (numItem > 0) {
+ Item itmStore = game.getItem(args);
+ if (itmStore != null) {
+ if (quantity > numItem) {
+ return "This merchant does not have that many.";
+ }
+ int intCost = (itmStore.intCost * 3) / 4;
+ if (lt.name.equalsIgnoreCase(pmrStore.strOwner)) {
+ intCost = 0;
+ }
+ if (intCost * quantity > lt.cash) {
+ return "You can't afford that";
+ } else {
+ lt.cash -= intCost * quantity;
+ pmrStore.cash += intCost * quantity;
+ itmStore = pmrStore.remove(args);
+ lt.itemList.addElement(itmStore);
+ for (int i = 1; i < quantity; i++) {
+ itmStore = pmrStore.remove(args);
+ lt.itemList.addElement(itmStore);
+ }
+ lt.updateItems();
+ lt.updateStats();
+ }
+ }
+ }
+ }
+
+ Merchant mrcStore = lt.overMerchant();
+ if (mrcStore == null) {
+ return "Buy from whom?";
+ }
+ if (lt.getFollowing() != null && lt.getFollowing().isPet()) {
+ if (args.startsWith(lt.getFollowing().name + ":")) {
+ args = args.substring(lt.getFollowing().name.length() + 1);
+ if (mrcStore.contains(args)) {
+ if (args.startsWith("train:")) {
+ args = args.substring(6);
+ mrcStore.train(args, quantity, lt.getFollowing());
+ lt.updateStats();
+ }
+ }
+ return null;
+ }
+ }
+ if (mrcStore.contains(args)) {
+ if (args.startsWith("train:")) {
+ args = args.substring(6);
+ mrcStore.train(args, quantity, lt);
+ lt.updateStats();
+ } else {
+ if (args.startsWith("pet")) {
+ mrcStore.pet(lt);
+ lt.updateStats();
+ } else {
+ Item itmStore = game.getItem(args);
+ if (itmStore != null) {
+ if (itmStore.intCost * quantity > lt.cash) {
+ return "You can't afford that";
+ } else {
+ lt.cash -= itmStore.intCost * quantity;
+ lt.itemList.addElement(itmStore);
+ for (int i = 1; i < quantity; i++) {
+ lt.itemList.addElement(game.getItem(args));
+ }
+ lt.updateItems();
+ lt.updateStats();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ case "sell": {
+ if (args == null) {
+ return "Sell what?";
+ }
+
+ PlayerMerchant pmrStore = lt.overPlayerMerchant();
+ if (pmrStore != null) {
+ if (lt.name.equalsIgnoreCase(pmrStore.strOwner)) {
+ int quantity = 1;
+ try {
+ int i = args.indexOf(" ");
+ quantity = Integer.parseInt(args.substring(0, i));
+ args = args.substring(i + 1);
+ } catch (Exception e) {
+ return "How many of what do you want to sell?";
+ }
+ Item itmStore = lt.getItem(args);
+ for (int i = 0; i < quantity; i++) {
+ itmStore = lt.getItemAndRemove(args);
+ if (itmStore != null) {
+ itmStore.onDropItem(game, lt);
+ pmrStore.add(itmStore);
+ lt.isSaveNeeded = true;
+ } else {
+ i = quantity;
+ }
+ }
+ lt.updateItems();
+ lt.updateStats();
+ return null;
+ }
+ return "You cannot sell items to this merchant.";
+ }
+
+ Merchant mrcStore = lt.overMerchant();
+ if (mrcStore == null) {
+ return "Sell that to whom?";
+ }
+ int quantity = 1;
+ try {
+ int i = args.indexOf(" ");
+ quantity = Integer.parseInt(args.substring(0, i));
+ args = args.substring(i + 1);
+ } catch (Exception e) {
+ return "How many of what do you want to sell?";
+ }
+ if (quantity > 100) {
+ quantity = 100;
+ }
+ Item itmStore = lt.getItem(args);
+ for (int i = 0; i < quantity; i++) {
+ itmStore = lt.getItemAndRemove(args);
+ if (itmStore != null) {
+ itmStore.onDropItem(game, lt);
+ lt.cash += (itmStore.intCost / 2);
+ lt.isSaveNeeded = true;
+ } else {
+ i = quantity;
+ }
+ }
+ lt.updateItems();
+ lt.updateStats();
+ return null;
+ }
+
+ case "cast": {
+ if (args == null) {
+ return "Cast what?";
+ }
+ if (lt.battle == null) {
+ lt.castSpell(args);
+ } else {
+ lt.battle.addCommand(lt, "cast " + args);;
+ }
+ return null;
+ }
+
+ case "follow": {
+ if (args == null) {
+ return "Follow who?";
+ }
+ if (lt.isSleeping) {
+ return "You can't do that while you're sleeping";
+ }
+ DuskObject objStore = lt.getLocalObject(args);
+ if (objStore == null) {
+ return "You don't see that here.";
+ }
+ if (objStore.isLivingThing()) {
+ LivingThing thnStore = (LivingThing) objStore;
+ if (lt.getMaster() != null && thnStore != lt.getMaster()) {
+ if (lt.isPet()) {
+ return "You can only follow your owner.";
+ }
+ return "You're already following someone. Leave them first.";
+ }
+ if (Math.abs(lt.x - thnStore.x) + Math.abs(lt.y - thnStore.y) > 1) {
+ return "They're too far away.";
+ }
+ if (thnStore == lt) {
+ return "You can't follow yourself.";
+ }
+ if (!thnStore.isPlayer() && !lt.isMob()) {
+ return "You can only follow players.";
+ }
+ if (thnStore.noFollow || (thnStore.isPet() && thnStore.getMaster().noFollow)) {
+ return "They won't let you follow them.";
+ }
+ if (lt.isPet()) {
+ thnStore.setFollowing(lt);
+ lt.setMaster(thnStore);
+ thnStore.updateStats();
+ lt.updateStats();
+ return "You are now following " + lt.getMaster().name + ".";
+ }
+ LivingThing thnStore2 = thnStore;
+ while (thnStore2 != null) {
+ if (lt == thnStore2) {
+ return "You're already in that group.";
+ }
+ thnStore2 = thnStore2.getMaster();
+ }
+ thnStore.chatMessage("You are now being followed by " + lt.name + ".");
+ while (thnStore.getFollowing() != null) {
+ thnStore = thnStore.getFollowing();
+ if (thnStore.isPlayer()) {
+ thnStore.chatMessage("You are now being followed by " + lt.name + ".");
+ }
+ }
+ thnStore.setFollowing(lt);
+ lt.setMaster(thnStore);
+ thnStore.updateStats();
+ lt.updateStats();
+ return "You are now following " + lt.getMaster().name + ".";
+ }
+ return "That's not something you can follow.";
+ }
+
+ case "unfollow": {
+ if (args == null) {
+ return "Unfollow who?";
+ }
+
+ // FIXME: implemente unfollow
+ if (true)
+ return "unfollow is not yet implemented";
+
+
+ LivingThing thnStore = lt.getFollowing();
+ if (thnStore != null && thnStore.isPet()) {
+ if (thnStore.name.equalsIgnoreCase(args)) {
+ /*
+ lt.halt();
+ lt.chatMessage("Do you really want to permanently erase your pet?");
+ try {
+ if (lt.instream.readLine().equalsIgnoreCase("yes")) {
+ lt.getFollowing().close();
+ File deleteme = new File("pets/" + lt.name.toLowerCase());
+ deleteme.delete();
+ deleteme = new File("pets/" + lt.name.toLowerCase() + ".backup");
+ deleteme.delete();
+ lt.getFollowing().close();
+ lt.setFollowing(lt.getFollowing().getFollowing());
+ lt.proceed();
+ return "Your pet has been erased.";
+ }
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While unfollowing pet for " + lt.name, e);
+ }
+ lt.proceed();
+ * */
+ return null;
+ }
+ thnStore = thnStore.getFollowing();
+ }
+ while (thnStore != null) {
+ if (thnStore.name.equalsIgnoreCase(args)) {
+ if (thnStore.isPet()) {
+ thnStore = thnStore.getMaster();
+ }
+ thnStore.removeFromGroup();
+ return null;
+ }
+ thnStore = thnStore.getFollowing();
+ }
+ return "They're not following you.";
+ }
+
+ case "stay": {
+ if (lt.isPet()) {
+ lt.removeFromGroup();
+ return Commands.parseCommand(lt, game, "emote sits down to await " + lt.getMaster().name + "'s return.");
+ }
+ return (Commands.parseCommand(lt, game, "emote stays like a good little puppy."));
+ }
+
+ case "leave": {
+ if (lt.isPet()) {
+ return "You cannot leave your master unless he unfollows you.";
+ }
+ lt.removeFromGroup();
+ return "You are now on your own.";
+ }
+
+ case "unclan": {
+ if (lt.clan.equals("none")) {
+ return "You're not in a clan.";
+ }
+ if (lt.battle != null) {
+ return "Wait until you're done battling.";
+ }
+ // FIXME: reimplement unclan
+ if (true)
+ return "unclan not implemented yet";
+ try {
+ /*
+ lt.halt();
+ lt.chatMessage("Are you sure you want to drop out of your clan? If so type yes.");
+ if (lt.instream.readLine().equalsIgnoreCase("yes")) {
+ lt.clan = "none";
+ if (lt.privs == 1) {
+ lt.privs = 0;
+ }
+ lt.proceed();
+ game.removeDuskObject(lt);
+ game.addDuskObject(lt);
+ return "You have been removed from your clan.";
+ }*/
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While " + lt.name + " was trying to dropout of their clan", e);
+ }
+ //lt.proceed();
+ return null;
+ }
+
+ case "description": {
+ if (args == null) {
+ lt.description = null;
+ return "Your description has been removed.";
+ }
+ lt.description = args;
+ return "Your description has been set to:" + lt.description;
+ }
+
+ case "title": {
+ if (!lt.isPlayer()) {
+ return "Only players may have titles.";
+ }
+ if (args == null) {
+ lt.title = null;
+ return "Your title has been removed.";
+ }
+ lt.title = args;
+ if (lt.title.length() > game.titlecap) {
+ lt.title = lt.title.substring(0, game.titlecap);
+ }
+ return "Your title has been set to:" + lt.title;
+ }
+
+ case "password": {
+ if (!lt.isPlayer()) {
+ return "Only players can change their password.";
+ }
+ return "password changing not re-implemented yet";
+ /*
+ try {
+ lt.halt();
+ lt.chatMessage("Enter your current password.");
+ String strOldPass = lt.instream.readLine();
+ if (!strOldPass.equals(lt.password)) {
+ lt.proceed();
+ return "Sorry, that is not your password.";
+ }
+ lt.chatMessage("Enter a new password.");
+ String strNewPass = lt.instream.readLine();
+ lt.chatMessage("Repeat that password.");
+ String strNewPassRepeat = lt.instream.readLine();
+ if (strNewPass == null) {
+ lt.proceed();
+ return "Not a valid password.";
+ }
+ if (!strNewPass.equals(strNewPassRepeat)) {
+ lt.proceed();
+ return "Sorry, those passwords do not match.";
+ }
+ lt.password = strNewPass;
+ lt.proceed();
+ return "Your password has now been changed.";
+ } catch (Exception e) {
+ game.log.printError("parseCommand():While " + lt.name + " was changing their password", e);
+ }
+ lt.proceed();
+ */
+ }
+
+ case "wear": {
+ if (args == null) {
+ return "Wear what?";
+ }
+ // FIXME: this should go on livingthing, but the interaction flow is messy
+ LinkedList<Item> qStore = lt.itemList.get(args);
+ if (qStore != null) {
+ Item itmStore = (Item) qStore.element();
+ int where = -1;
+
+ switch (itmStore.intType) {
+ case (1): {
+ where = Equipment.WIELD;
+ break;
+ }
+ case (2):
+ where = itmStore.intKind + Equipment.ARMS;
+ break;
+ default:
+ return "You can't wear that";
+ }
+
+ Item old = lt.wornItems.wear(where, itmStore);
+ if (old != null) {
+ lt.itemList.addElement(old);
+ lt.onUnwear(old);
+ }
+ lt.onWear(itmStore);
+
+ lt.itemList.removeElement(itmStore.name);
+ if (lt.isPet()) {
+ lt.getMaster().updateStats();
+ }
+ if (lt.isPlayer()) {
+ lt.updateStats();
+ }
+ lt.updateEquipment();
+ lt.updateItems();
+ return null;
+ }
+ return "You don't have that";
+ }
+ case "unwear": {
+ if (args == null) {
+ return "Unwear what?";
+ }
+ lt.unWear(args);
+ return null;
+ }
+ case "rement": {
+ if (args == null) {
+ return null;
+ }
+ long lngID = Long.parseLong(args);
+ lt.removeEntity(lngID);
+ return null;
+ }
+ case "audio": {
+ if (args == null) {
+ if (lt.audioon) {
+ return "Your audio is turned on.";
+ }
+ return "Your audio is turned off.";
+ } else if (args.equalsIgnoreCase("off")) {
+ lt.audioon = false;
+ return "Your audio has been turned off.";
+ } else if (args.equalsIgnoreCase("on")) {
+ lt.audioon = true;
+ return "Your audio has been turned on.";
+ }
+ }
+ case "color": {
+ if (args == null) {
+ if (lt.coloron) {
+ return "Your color is turned on.";
+ }
+ return "Your color is turned off.";
+ } else if (args.equalsIgnoreCase("off")) {
+ lt.coloron = false;
+ return "Your color has been turned off.";
+ } else if (args.equalsIgnoreCase("on")) {
+ lt.coloron = true;
+ return "Your color has been turned on.";
+ }
+ }
+ case "highlight": {
+ if (args == null) {
+ if (lt.highlight) {
+ return "Highlighting of enemies in battle is turned on.";
+ }
+ return "Highlighting of enemies in battle is turned off.";
+ } else if (args.equalsIgnoreCase("off")) {
+ lt.highlight = false;
+ lt.clearFlags();
+ return "Highlighting of enemies in battle has been turned off.";
+ } else if (args.equalsIgnoreCase("on")) {
+ lt.highlight = true;
+ return "Highlighting of enemies in battle has been turned on.";
+ }
+ }
+ case "popup": {
+ // FIXME: TBD
+ if (args == null) {
+ if (lt.popup) {
+ return "You have popup windows turned on.";
+ }
+ return "You have popup windows turned off.";
+ } else if (args.equalsIgnoreCase("off")) {
+ lt.popup = false;
+ return "You have turned popup windows off.";
+ } else if (args.equalsIgnoreCase("on")) {
+ lt.popup = true;
+ return "You have turned popup windows on.";
+ }
+ }
+ case "nofollow": {
+ if (args == null) {
+ if (lt.noFollow) {
+ return "You are not allowed to be followed.";
+ }
+ return "You can be followed.";
+ } else if (args.equalsIgnoreCase("off")) {
+ lt.noFollow = false;
+ return "You can now be followed.";
+ } else if (args.equalsIgnoreCase("on")) {
+ lt.noFollow = true;
+ return "You can no longer be followed.";
+ }
+ }
+ case "ignore": {
+ if (args == null) {
+ return "Ignore who?";
+ }
+ LivingThing thnStore = game.getPlayer(args);
+ if (thnStore == null) {
+ return "They're not in this world.";
+ }
+ String strIgnoreName = thnStore.name.toLowerCase();
+ if (lt.name.equalsIgnoreCase(strIgnoreName)) {
+ return "Trying to ignore yourself is not a good sign.";
+ }
+ if (thnStore.privs > 2) {
+ return "You cannot ignore a god.";
+ }
+ if (!lt.ignoreList.contains(strIgnoreName)) {
+ lt.ignoreList.add(strIgnoreName);
+ } else {
+ return "You are already ignoring them.";
+ }
+ return "You are now ignoring " + strIgnoreName;
+ }
+ case "unignore": {
+ if (args == null) {
+ return "UnIgnore who?";
+ }
+ String strIgnoreName = args.toLowerCase();
+ if (strIgnoreName == "all") {
+ lt.ignoreList.clear();
+ return "You are no longer ignoring anyone.";
+ }
+ if (lt.ignoreList.contains(strIgnoreName)) {
+ lt.ignoreList.remove(strIgnoreName);
+ } else {
+ return "You are not ignoring them.";
+ }
+ return "You are no longer ignoring " + strIgnoreName;
+ }
+ case "appletimages": {
+ //lt.updateAppletImages();
+ return "obsolete command";
+ }
+ case "applicationimages": {
+ //lt.updateApplicationImages();
+ return "obsolete command";
+ }
+ case "notdead": {
+ return null;
+ }
+ case "quit":
+ case "logout": {
+ if (lt.battle == null) {
+ lt.close();
+ return null;
+ }
+ return "You cannot quit in the middle of a fight.";
+ }
+ }
+ if (!blnFoundScriptedCommand) {
+ return "huh?";
+ }
+ return null;
+ }
+}
+CODE STATUS September 2013
+--------------------------
+
+After a very active development period the work simply stopped - so it
+is in a bit of a half-way state as you'd imagine. Other time
+pressures and interests meant I haven't had time to work on this for
+months but I hope to again one day in some shape or form.
+
+Essentially the code in duskz/server and duskz/server/entity is the
+original Dusk code which has been refactored a bit (badly), updated to
+newer Java and a new protocol (I think), but more or less shares the
+original features and data format. It's basically a dead branch.
+
+The code in dusk/server/entityz is mostly a complete re-write of the
+object class hierarchy, data i/o, map handling and a few other things.
+Unfortunately as it was in active development when it stopped it too
+isn't entirely in a good state either.
+
+I think the object heirarchy is pretty good, and the script system was
+"upgraded" to javascript but there is a ton of work left to make it
+usable. In hindsight i should've gone straight to a database
+(berkeleydb) backend as too much of the entity code is dealing with
+i/o.
+
+The original readme follows ...
+
README
------
+
This is the server implementation of DuskZ. A client is required
to access the game.
It is a fork and major overhaul of the Dusk 2.7.3 source code, released
circa 2000.
-This is currently in an alpha state and in very active development.
+This is currently in an alpha state.
... to be completed ...
--- /dev/null
+
+
+Well, write most of it ... but the big items are:
+
+commands
+Spells and Skills
+Shops
+
+save faction changes
+save player changes
+
+send player updates for changed things
+
+check all the bonus stats stuff, i think i did it a bit wrong
Thoughts
========
-Most of the global scripts are very simple and define global game
-policies. Some need to run very often.
-
-It is quite possible they could be removed - either provide fixed game policies,
-or converted into Java and loaded at runtime.
-
Certain game state is tracked by changing tiles! I think this
facility should be removed entirely and alternative mechanisms
provided.
Global per-game scripts
-----------------------
+Most of the global scripts are very simple and define global game
+policies. Some need to run very often.
+
+It is quite possible they could be removed - either provide fixed game policies,
+or converted into Java and loaded at runtime.
+
+
OnBoot
------
OnBattle
--------
-Only used for mobs. The script to run is defined by the mob file
-(finally, some indirection!)
+
+Only used for mobs. The script to run is defined by the mob file.
+These tweak the mob AI.
scripts/name
trigger=mob
"battlechat"
Send a message to battle chat window. unused.
+
+Values
+------
+
+Apart from constants and variables there are also a number of
+pre-defined values available to scripts. These are also used to
+implement arithmetic expressions.
+
++ value value
+- value value
+* value value
+/ value value
+ Binary arithmetic operators.
+
+rand
+ Return a random number between zero and 1.0 (Math.random()).
+
+thing attribute
+ Read accessor for an attribute of thing, as below.
+
+ cp
+ tp
+ cash
+ exp
+ locx
+ locy
+ hp
+ maxhp
+ mp
+ maxmp
+ stre
+ dext
+ inte
+ wisd
+ cons
+ dammod
+ ac
+ privs
+ skill name
+ Retrieve skill level
+ count name
+ Count of items named name in thing's inventory
+
--- /dev/null
+
+Existing class structure
+
+DuskObject
+ LivingThing (type=0)
+ *Pet (type=2)
+ Mob (type=1)
+
+ Item
+ Merchant
+ PlayerMerchant
+ Prop
+ Sign
+
+
+PlayerMerchant
+--------------
+
+Basically a cache that the owner can store things to for zero cost of transaction.
+
+
+State fields
+------------
+
+Fields and values that are saved and restored as part of the game state.
+
+Mob
+ name
+ originalX
+ originalY
+
+Sign
+ message
+ x
+ y
+
+Merchant
+ x
+ y
+ names of items
+ end (end marker)
+
+PlayerMerchant - is not saved. This appears to be a bug because items
+are removed from the player when they put them in the store.
+
+Props
+ x
+ y
+ name
+
+
+Global variables
+ name
+ type code
+ value
+
+
+Players are saved separately. TODO: document
+
+
+Engine Startup
+--------------
+
+Actions in the following order
+
+0. load preferences
+
+0.1 comple event scripts
+0.2 compile per-tile scripts
+
+1. load map(s)
+
+2. load map privs(*1)
+
+3. load map owner(*1)
+
+4. load merchants and the items each sells
+
+5. load mobs. First load the mob prototype by name and set the
+ location from the state file.
+
+6. load sign. State file includes full sign definition.
+
+7. load props. First load prop prototype and set the prop location.
+
+8. load global variables.
+
+9. trigger the onBoot script.
+
+10. start the engine thread.
+
+1 - I commented that out so need to study it further, seems to be
+ ownership of rectangular regions of the map. I think this can be
+ stored in memory more efficiently since it isn't needed very
+ often.
+
+Player Startup
+--------------
+
+0. init sockets and stream instances
+
+1. perform a bunch of global policy checks - maximum connections,
+ server shutting down, and blocked address checking. Doesn't belong
+ here.
+
+2. Start the player thread, which does more initialisation.
+
+Player thread
++++++++++++++
+
+0. Perform login/create authentication/checks. New code.
+
+1. Greet user
+
+2. Invoke onStart script for living thing.
+
+3. Initialise with prototype 'default' user.
+
+4. If it is an existing user, load user state.
+
+5. Load the pet for the user if not already set. Pets are stored
+ indexed by the owner name. This seems to be a cross-check of the
+ player state file because when that is loaded pets are also loaded.
+
+6. Load race which modifies base stats.
+
+7. Set pet location if set.
+
+8. If creating user, save now.
+
+9. Initialise client with:
+9.1 New map
+9.2 Set location (add player to game map)
+9.3 Updated player info
+9.4 Updated player stats
+9.5 Updated player items
+9.6 Updated player worn information
+9.7 Updated player actions
+
+10. Enter the command loop.
+
+Pet Startup
+-----------
+
+Pets are only added from the player load or by buying one.
+
+0. Load default pet as a prototype
+
+1. Load pet information
+
+2. If the pet has a race, load race modifiers
+
+3. Link to master.
+
+Mob Startup
+-----------
+
+Mobs are loaded at startup, scripts and gods can also create them.
+
+0. Load the mob prototype by name
+
+1. Override location
+
--- /dev/null
+
+This documents the JavaScript api and mechanisms.
+
+
+Public interfaces
++++++++++++++++++
+
+Game {
+ Item createItem(String type);
+ void removeItem(Item item);
+ Mobile createMob(String type);
+}
+
+Active {
+ // send dialogue to the player
+ chat(String v);
+ // emote an action
+ emote(String v);
+ setCondition(String name, int duration);
+ // get or change an attribute, XXX = INT, DEX, etc.
+ int getXXX();
+ int addXXX(int amount);
+ // Remove an item from inventory
+ removeItem(Item item);
+ // Add an item to inventory
+ addItem(Item item);
+
+ // Variable functions - persistent per-active state
+ int getInteger(String name, int def);
+ int getString(String name, String def);
+ int getDouble(String name, double def);
+
+ void setInteger(String name, int val);
+ void setString(String name, String val);
+ void setDouble(String name, double val);
+}
+
+Item {
+ // Name of item
+ String getName();
+ // How many uses left
+ int getUses();
+ // Alter uses
+ int addUses(int count);
+}
+
+// TODO: do i want conditions to have arbitrary persistent variables too?
+// possibly
+Condition {
+}
+
+Items
+=====
+
+onGet
+-----
+
+Scripts are stored at
+ onItem/itemname.drop
+
+Parameters
+ game - global game object
+ owner - current Active holding the object
+ item - item being dropped
+
+If provided, the script is executed after the object is taken from
+the map and added to the inventory.
+
+onDrop
+------
+
+Scripts are stored at
+ onItem/itemname.drop
+
+Parameters
+ game - global game object
+ owner - current Active holding the object
+ item - item being dropped
+
+If provided, the script OVERRIDES the default drop behaviour which is
+to leave the item at the current location. This is different from
+Dusk where the script was run after the default drop behaviour.
+
+onUse
+-----
+
+Scripts are stored at
+ onItem/itemname.use
+
+Parameters
+ game - global game object
+ owner - current Active holding the object
+ item - item being used
+
+If provided the script provides the on-use behaviour for the item in
+question. The script will only be called if Item.getUses() is
+infinite (-1) or greater than 0.
+
+Conditions
+==========
+
+onStart
+-------
+
+Scripts are stored at
+ onCondition/condition.start
+
+Parameters
+ game - global game object
+ owner - Active to which condition applies
+ condition - Condition object
+
+
+onOccurance
+-----------
+
+Scripts are stored at
+ onCondition/condition.tick
+
+Parameters
+ game - global game object
+ owner - Active to which condition applies
+ condition - Condition object
+
+
+onEnd
+-------
+
+Scripts are stored at
+ onCondition/condition.end
+
+Parameters
+ game - global game object
+ owner - Active to which condition applies
+ condition - Condition object
+
--- /dev/null
+
+This is a planning document for the new 'duskz' scripting system.
+
+Overview
+========
+
+The new scripting system will be using the ScriptEngine framework
+included in Java 6+.
+
+The goal will be to provide a super-set of the features previously
+available, with a more modern language.
+
+Security
+========
+
+JavaScript has far more facilities available than one would expect
+from a scripting language such as the ability to reflect into the
+system and access any public state.
+
+Whilst this allows for powerful scripts it is also an issue of
+security.
+
+Unfortunately fully securing the sandbox will be difficult, but the
+initial basic approach will be to rely on the security manager for
+high level control (files, network), and scripts will only have access
+to wrapper interfaces rather than the live game objects.
+
+Dessign
+=======
+
+The design has to take into account the way the script system works.
+For example in javascript object definitions within scripts persist
+between script invocations.
+
+For efficiency reasons it may be desirable to preload all scripts in
+some manner. Precompilation is also an option but has restircted use.
+
+Some obvious possibilities for the design:
+
+0. all behaviour of all objects loaded together
+1. all behaviour of one object in one file using toplevel functions
+2. behaviour in separate scripts using toplevel logic (as current
+script system)
+3. all behaviour of one object in one file using arguments (like a
+shell script)
+
+0. All behaviour global
+-----------------------
+
+Implemented by having a separate 'object' for each type of script.
+The object would follow some convention to implement class (no need
+for class inheritence), e.g. Mob's would have an onBattle function,
+Items would not (or perhaps they could?).
+
+Each item is loaded (on demand?) into the ScriptEngine, and methods
+invoked via an object of the same name as the script.
+
+Example
++++++++
+items/absinthe_brain
+var absinthe = {
+ onUse: function(thing) {
+ thing.emote('wails', 'oh, my head!');
+ if (thing.getInte() > 10)
+ thing.incrInte(-1);
+ thing.remove('absinthe');
+ },
+ onDrop: function(thing) {
+ thing.order("get absinthe");
+ thing.remove('absinthe');
+ }
+};
+
+Pros:
+o Everything is only compiled once
+o Can replace scripts when they are changed
+o Behaviour is all together in one place
+o Member variables can be used for session-persistent state.
+o Argument counts are checked at runtime.
+
+Cons:
+o Entire game logic is loaded into script engine at once - memory?
+o Can replace a different script when it is changed - security.
+o Member variables can be abused for session-persistent state.
+o Fair bit of scaffolding.
+o Arguments must be deined.
+
+Another possibility is to go even further and replace all object
+definitions with javascript objects instead:
+
+var absinthe = {
+ type: drink,
+ description: "a mug of green liquer flavoured with wormwood",
+ cost: 1,
+ image: 6,
+ ... etc
+};
+
+However, accessing field values is clumsy from Java.
+
+1. Behaviour in top-level functions
+-----------------------------------
+
+Example
++++++++
+items/absinthe_brain
+onUse = function(thing) {
+ thing.emote('wails: oh, my head!');
+ if (thing.getInte() > 10)
+ thing.incrInte(-1);
+ thing.remove('absinthe');
+};
+
+onDrop = function(thing) {
+ thing.order("get absinthe");
+ thing.remove('absinthe');
+};
+
+Pros:
+o Simpler syntax
+o Function names can be checked at runtime
+o Argument count is checked at runtime.
+
+Cons:
+o Requires running the script every time before use even if
+precompiled. Or many engines.
+o Arguments must be defined
+
+2. Behaviour in separate scripts
+---------------------------------
+
+This is like the existing system, a master file indicates which script
+is executed for every event on a given type of object.
+
+Example
++++++++
+script/getabsinthe:
+thing.emote('wails: oh, my head!');
+if (thing.getInte() > 10)
+ thing.incrInte(-1);
+ thing.remove('absinthe');
+}
+
+script/dropabsinthe:
+thing.order("get absinthe");
+thing.remove('absinthe');
+
+Pros:
+o Apart from syntax, identical to existing system
+o Can be precompiled
+o Small scripts faster to parse if passing every time
+o Requires no scaffolding in script
+
+Cons:
+o Unwieldly number of files to manage and setting to correlate
+o Logic spread across multiple files
+
+3. Object behaviour in single top-level script
+----------------------------------------------
+
+Here global variables are used to pass which behaviour is desired.
+
+Example
++++++++
+items/absinthe_brain:
+switch (script) {
+case 'onUse':
+ thing.emote('wails: oh, my head!');
+ if (thing.getInte() > 10)
+ thing.incrInte(-1);
+ thing.remove('absinthe');
+ }
+ break;
+case 'onDrop':
+ thing.order("get absinthe");
+ thing.remove('absinthe');
+ break;
+}
+
+Pros:
+o Scripts can be precompiled
+o Script execution relatively isolated and cannot be overriden by another script
+
+Cons:
+o Typos in case strings wont be detected at runtime
+o Requires some scaffolding.
+o Very little automatic checking.
+
+Conclusions
+-----------
+
+Although the object and pre-compiled nature of proposal 0 has some
+appeal, it relies too much on the implementation of the
+ScriptingEngine, and is the most memory intensive solution.
+
+Proposal 1 is appealing from the player perspective but seems a little
+clumsy to use in the host.
+
+Proposal 2 benefits from being "how the game already does it", but
+that probably doesn't mean too much. It's main drawback is the number
+of files and settings required.
+
+Proposal 3 seems a little too complicated and error prone although it
+tries to combine the benefits of a single-file behaviour and lower
+scaffolding of the individual script files.
+
+One thing I find odd is that the JavaScript script engine ... doesn't
+let you add new script functions! You can only add objects, which
+makes their use clumsy - i.e. there is no way to remove 'thing.'
+inside the scripts. It's probably worth supporting java language
+scripts too.
--- /dev/null
+
+Online game state is stored in state files which are semi-serialised
+versions of things.
+
+Format
+------
+
+The format is the same as other object files - pseudo-properties format,
+but each object is wrapped in a container.
+
+type=Class,objectname
+properties
+=end
+
+Class is the name of the class (relative to duskz.server.entity) this
+object represents.
+
+objectname is the name of the object prototype, e.g. name of the mob,
+item, etc.
+
+Saving process
+--------------
+
+1. Write envelope header (class,name)
+2. Write volatile state variables, by calling Thing.writeState()
+3. Write envelope footer
+
+Loading process
+---------------
+
+1. Read envelope header.
+2. Instantiate object of type
+3. Load object prototype using Thing.load(name)
+4. Read volatile state variables and set them via setProperty()
+5. Until envelope footer is encountered.
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.io;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * Convert a few basic files
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Convert {
+
+ public static void main(String[] args) throws IOException {
+ File src = new File("/home/notzed/src/DuskRPG/DuskFiles/Dusk2.7.3");
+ File dst = new File("/home/notzed/dusk/game");
+
+ PrintStream o = new PrintStream(new File(dst, "mobs.new"));
+
+ try (BufferedReader is = new BufferedReader(new FileReader(new File(src, "mobs")))) {
+ while (true) {
+ String m = is.readLine();
+
+ if (m == null || !m.trim().equals("mob2.3"))
+ break;
+
+ String name= is.readLine();
+ int x = Integer.valueOf(is.readLine());
+ int y = Integer.valueOf(is.readLine());
+
+ o.println("type.Mobile=" + name);
+ o.println("map=dusk");
+ o.println("x=" + x);
+ o.println("y=" + y);
+ o.println("=end");
+ }
+ }
+ o.close();
+ }
+}
import duskz.io.tiled.Image;
import duskz.io.tiled.Layer;
import duskz.io.tiled.Map;
-import duskz.io.tiled.Properties;
import duskz.io.tiled.Property;
import duskz.io.tiled.Tile;
import duskz.io.tiled.Tileset;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
// new File("/home/notzed/house.jar"));
//tiledToDusk(new File("/home/notzed/test-map.tmx"),
// new File("/home/notzed/test-map.bin"));
- tiledToDusk(new File("/home/notzed/dusk/maps/do-drop-inn.tmx"),
- new File("/home/notzed/src/DuskRPG/DuskFiles/DuskX/defMaps/do-drop-inn"));
+ //tiledToDusk(new File("/home/notzed/dusk/maps/dusk.tmx"),
+ // new File("/home/notzed/dusk/game/defMaps/dusk"));
+ //tiledToDusk(new File("/home/notzed/dusk/maps/do-drop-inn.tmx"),
+ // new File("/home/notzed/src/DuskRPG/DuskFiles/DuskX/defMaps/do-drop-inn"));
//testexport();
+ tiledToDusk(new File("/home/notzed/dusk/maps/main.tmx"),
+ new File("/home/notzed/dusk/game/defMaps/main"));
}
}
package duskz.server;
import duskz.protocol.DuskProtocol;
+import duskz.protocol.Wearing;
import duskz.server.entity.Mob;
import duskz.server.entity.Merchant;
import duskz.server.entity.Sign;
import duskz.server.entity.Item;
import duskz.server.entity.Prop;
import duskz.server.entity.DuskObject;
-import duskz.server.entity.Equipment;
import duskz.server.entity.PlayerMerchant;
import duskz.server.entity.LivingThing;
import duskz.server.entity.TileMap;
switch (itmStore.intType) {
case (1): {
- where = Equipment.WIELD;
+ where = Wearing.WIELD;
break;
}
case (2):
- where = itmStore.intKind + Equipment.ARMS;
+ where = itmStore.intKind + Wearing.ARMS;
break;
default:
return "You can't wear that";
}
}
+ @Deprecated
public synchronized long getID() {
nextid++;
return nextid;
}
+ @Deprecated
public void chatMessage(String msg, String from) {
from = from.toLowerCase();
log.printMessage(Log.ALWAYS, msg);
* @param strFrom
*/
// FIXME: move somewhere better
+ @Deprecated
public void chatMessage(TileMap map, String inMessage, int locx, int locy, String strFrom) {
strFrom = strFrom.toLowerCase();
LivingThing thnStore;
}
}
+ @Deprecated
public void chatMessage(String msg, String clan, String from) {
from = from.toLowerCase();
log.printMessage(Log.ALWAYS, msg);
}
// FIXME: move to livinghting?
+ // only ever called on a player source
+ @Deprecated
public void refreshEntities(LivingThing refresh) {
LinkedList<DuskObject> newEntities = new LinkedList<>();
}
// FIXME: move to map or livingthing?
+ @Deprecated
public void addEntity(TileMap map, DuskObject add) {
for (TileMap.MapData md : map.range(add.x, add.y, viewrange)) {
for (DuskObject o : md.entities) {
}
// FIXME: Move to map?
+ @Deprecated
void notifyRemoved(DuskObject remove) {
for (TileMap.MapData md : remove.map.range(remove.x, remove.y, viewrange)) {
for (DuskObject o : md.entities) {
if (!lt.map.inside(inLocX, inLocY))
return false;
- System.out.printf("can move to: %d,%d tid=%3d on map %s\n", inLocX, inLocY, lt.map.getTile(inLocX, inLocY), lt.map.name);
+ //System.out.printf("can move to: %d,%d tid=%3d on map %s\n", inLocX, inLocY, lt.map.getTile(inLocX, inLocY), lt.map.name);
for (DuskObject o : lt.map.getEntities(inLocX, inLocY, null)) {
if (o.isLivingThing()) {
}
}
intConfidence += relation * lt.getTotalPoints();
+ System.out.printf("mob '%s' can see player '%s' relation %f confidence=%d\n", mob.name, lt.name, relation, intConfidence);
+ System.out.printf(" player points total = %d armour %d damage %d component %f\n", lt.getTotalPoints(), lt.getArmorMod(), lt.getDamMod(), relation * lt.getTotalPoints());
}
if (lt.isMob()) {
double relation;
}
}
intConfidence += relation * lt.getTotalPoints();
+ System.out.printf("mob '%s' can see mob '%s' relation %f confidence=%d\n", mob.name, lt.name, relation, intConfidence);
+ System.out.printf(" player points total = %d armour %d damage %d component %f\n", lt.getTotalPoints(), lt.getArmorMod(), lt.getDamMod(), relation * lt.getTotalPoints());
}
}
}
return ID;
}
- /**
- * Convert to on-wire entity format
- *
- * @param eng
- * @return
- */
- public String toEntity() {
- StringBuilder sb = new StringBuilder();
-
- sb.append(name).append('\n');
- sb.append(getEntityType()).append('\n');
- sb.append(ID).append('\n');
- sb.append(x).append('\n');
- sb.append(y).append('\n');
- sb.append(getImage()).append('\n');
-
- return sb.toString();
- }
public EntityUpdateMessage toMessage(int msg) {
EntityUpdateMessage en = new EntityUpdateMessage();
import duskz.protocol.DuskMessage;
import duskz.protocol.ListMessage;
-import duskz.protocol.Wearing;
+import static duskz.protocol.Wearing.*;
/**
* Equipment contains all the Items a LivingThing is wearing.
*
* @author Tom Weingarten
*/
-public class Equipment implements Wearing {
+public class Equipment {
// Must match Wearing
public static final String[] USER_NAMES = {
import duskz.protocol.ListMessage;
import duskz.protocol.MapMessage;
import duskz.protocol.TransactionMessage;
+import duskz.protocol.Wearing;
import duskz.server.Battle;
import duskz.server.BlockedIPException;
import duskz.server.Commands;
//(The script can change this to false to stop it from happening)
//(This allows scripts to short-circuit in-game commands)
public boolean isAlwaysCommands = true;
- // File names for i/o
+ // File wornNames for i/o
File file, backup;
//ID data
public String password,
return null;
}
- @Override
- public String toEntity() {
- StringBuilder sb = new StringBuilder();
- if (isSleeping) {
- sb.append("<sleeping>");
- }
- if (isPlayer() && !clan.equals("none")) {
- sb.append('<');
- sb.append(clan);
- sb.append('>');
- }
- if (isPet() && hp < 0) {
- sb.append("<wounded>");
- }
- for (String s : flags) {
- sb.append('<');
- sb.append(s);
- sb.append('>');
- }
- sb.append(name).append('\n');
- sb.append(getEntityType()).append('\n');
- sb.append(ID).append('\n');
- sb.append(x).append('\n');
- sb.append(y).append('\n');
- sb.append(getImage()).append('\n');
- if (isPlayer()) {
- sb.append(imagestep).append('\n');
- }
- return sb.toString();
- }
-
@Override
public EntityUpdateMessage toMessage(int msg) {
EntityUpdateMessage en = super.toMessage(msg);
break;
// FIXME: do i need these 'old' versions?
case "wield":
- wornItems.wear(Equipment.WIELD, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.WIELD, game.getItem(in.readLine()));
break;
case "arms":
- wornItems.wear(Equipment.ARMS, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.ARMS, game.getItem(in.readLine()));
break;
case "legs":
- wornItems.wear(Equipment.LEGS, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.LEGS, game.getItem(in.readLine()));
break;
case "torso":
- wornItems.wear(Equipment.TORSO, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.TORSO, game.getItem(in.readLine()));
break;
case "waist":
- wornItems.wear(Equipment.WAIST, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.WAIST, game.getItem(in.readLine()));
break;
case "neck":
- wornItems.wear(Equipment.NECK, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.NECK, game.getItem(in.readLine()));
break;
case "skull":
- wornItems.wear(Equipment.SKULL, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.SKULL, game.getItem(in.readLine()));
break;
case "eyes":
- wornItems.wear(Equipment.EYES, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.EYES, game.getItem(in.readLine()));
break;
case "hands":
- wornItems.wear(Equipment.HANDS, game.getItem(in.readLine()));
+ wornItems.wear(Wearing.HANDS, game.getItem(in.readLine()));
break;
case "wield2":
- parseWear(in, Equipment.WIELD);
+ parseWear(in, Wearing.WIELD);
break;
case "arms2":
- parseWear(in, Equipment.ARMS);
+ parseWear(in, Wearing.ARMS);
break;
case "legs2":
- parseWear(in, Equipment.LEGS);
+ parseWear(in, Wearing.LEGS);
break;
case "torso2":
- parseWear(in, Equipment.TORSO);
+ parseWear(in, Wearing.TORSO);
break;
case "waist2":
- parseWear(in, Equipment.WAIST);
+ parseWear(in, Wearing.WAIST);
break;
case "neck2":
- parseWear(in, Equipment.NECK);
+ parseWear(in, Wearing.NECK);
break;
case "skull2":
- parseWear(in, Equipment.SKULL);
+ parseWear(in, Wearing.SKULL);
break;
case "eyes2":
- parseWear(in, Equipment.EYES);
+ parseWear(in, Wearing.EYES);
break;
case "hands2":
- parseWear(in, Equipment.HANDS);
+ parseWear(in, Wearing.HANDS);
break;
case "nofollow":
noFollow = true;
for (Condition cond : conditions) {
rafPlayerFile.writeBytes("condition\n" + cond.name + "\n" + cond.ticksPast + "\n" + cond.duration + "\n");
}
- for (int i = 0; i < Equipment.WEARING_COUNT; i++) {
+ for (int i = 0; i < Wearing.WEARING_COUNT; i++) {
Item item = wornItems.getWorn(i);
if (item != null) {
rafPlayerFile.writeBytes(Equipment.USER_NAMES[i] + "\n" + item.name + "\n" + item.lngDurability + "\n" + item.intUses + "\n");
}
public int getDamMod() {
- Item item = wornItems.getWorn(Equipment.WIELD);
+ Item item = wornItems.getWorn(Wearing.WIELD);
if (item != null)
return item.intMod;
return 100;
}
public int getRange() {
- Item item = wornItems.getWorn(Equipment.WIELD);
+ Item item = wornItems.getWorn(Wearing.WIELD);
if (item == null) {
return 1;
return;
}
- Item item = wornItems.damageItem(Equipment.WIELD, damage);
+ Item item = wornItems.damageItem(Wearing.WIELD, damage);
if (item != null) {
chatMessage("Your " + item.name + " breaks.");
onUnwear(item);
return;
}
- for (int i = Equipment.ARMS; i < Equipment.WEARING_COUNT; i++) {
- Item item = wornItems.damageItem(Equipment.WIELD, damage);
+ for (int i = Wearing.ARMS; i < Wearing.WEARING_COUNT; i++) {
+ Item item = wornItems.damageItem(Wearing.WIELD, damage);
if (item != null) {
chatMessage("Your " + item.name + " breaks.");
onUnwear(item);
if (index != -1) {
unwear(index);
} else if (strStore.equalsIgnoreCase("all")) {
- for (int i = 0; i < Equipment.WEARING_COUNT; i++) {
+ for (int i = 0; i < Wearing.WEARING_COUNT; i++) {
unwear(i);
}
} else {
*/
package duskz.server.entity;
+import duskz.protocol.Wearing;
import duskz.server.Condition;
import duskz.server.DuskEngine;
import duskz.server.Faction;
dblGroupRelation = Double.valueOf(in.readLine()).doubleValue();
break;
case "wield":
- parseWear(in, Equipment.WIELD);
+ parseWear(in, Wearing.WIELD);
break;
case "arms":
- parseWear(in, Equipment.ARMS);
+ parseWear(in, Wearing.ARMS);
break;
case "legs":
- parseWear(in, Equipment.LEGS);
+ parseWear(in, Wearing.LEGS);
break;
case "torso":
- parseWear(in, Equipment.TORSO);
+ parseWear(in, Wearing.TORSO);
break;
case "waist":
- parseWear(in, Equipment.WAIST);
+ parseWear(in, Wearing.WAIST);
break;
case "neck":
- parseWear(in, Equipment.NECK);
+ parseWear(in, Wearing.NECK);
break;
case "skull":
- parseWear(in, Equipment.SKULL);
+ parseWear(in, Wearing.SKULL);
break;
case "eyes":
- parseWear(in, Equipment.EYES);
+ parseWear(in, Wearing.EYES);
break;
case "hands":
- parseWear(in, Equipment.HANDS);
+ parseWear(in, Wearing.HANDS);
break;
case "faction":
String strFaction = in.readLine();
/**
* Implements an iterator which follows a 'looking' path
*
- * TODO: it should probably use Bresenhams line algorithm
*/
private class LookIterator implements Iterator<MapData> {
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+/**
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Ability {
+
+ public final String name;
+ private int level;
+
+ public Ability(String name) {
+ this.name = name;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+// fixme copyrihgts
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import duskz.protocol.Wearing;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+/**
+ * An active, 'living thing'. Active objects can move and participate in battles.
+ *
+ * Notes:
+ * the player update code needs to track if they were over a merchant
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public abstract class Active extends Thing {
+
+ /**
+ * Privilege level
+ */
+ private int privs;
+ /**
+ * Experience. Check: do mobs and pets need this?
+ */
+ private int exp;
+ /**
+ * Cash on hand
+ */
+ private long gold;
+ //
+ // TODO: make this part of the protocol?
+ //
+ public static final int STAT_HP = 0;
+ public static final int STAT_HPMAX = 1;
+ public static final int STAT_MP = 2;
+ public static final int STAT_MPMAX = 3;
+ public static final int STAT_STR = 4;
+ public static final int STAT_INT = 5;
+ public static final int STAT_DEX = 6;
+ public static final int STAT_CON = 7;
+ public static final int STAT_WIS = 8;
+ // These are pseudo-stats calculated dynamically
+ // Should always be at the end of fixed ones
+ public static final int STAT_DAMAGE = 9;
+ public static final int STAT_RANGE = 10;
+ public static final int STAT_ARC = 11;
+ public static final String[] stat_keys = {"hp", "hpmax", "mp", "mpmax", "str", "int", "dex", "con", "wis"};
+ /**
+ * Player stats. Keep this private (once mobile fixed up)
+ * Keyed by STAT_* constants.
+ */
+ protected int stats[] = new int[12];
+ protected int bonus[] = new int[12];
+ // object? player?
+ // I don' think mobs can have clans - factions are used instead, clan/race
+ // candiates for class beteen active and player/pet
+ String clan;
+ /**
+ * Race, mobs can't have races.
+ */
+ Race race;
+ Equipment wornItems = new Equipment();
+ Inventory inventory = new Inventory();
+ /**
+ * Sleeping state
+ */
+ private boolean sleeping;
+ /**
+ * Tracks actives following in a pack
+ */
+ protected Pack pack;
+ /**
+ * Does this leader allow following at the moment.
+ */
+ boolean canLead = true;
+ /**
+ * Allowed to move
+ */
+ protected boolean moveable = true;
+ /**
+ * "step" of image: basically last direction moved.
+ */
+ protected int imageStep;
+ /**
+ * Live conditions
+ */
+ private final ConditionList conditions = new ConditionList();
+ /**
+ * Arbitrary variables for script use.
+ */
+ protected final VariableList variables = new VariableList();
+ /**
+ * If fighting, current battle
+ */
+ protected Battle battle;
+ /**
+ * Damage done, accumulates until you lose, where it is used to give
+ * bonus experience to your the winners
+ */
+ int damageDone;
+ //
+ private final HashMap<String, Ability> skills = new HashMap<>();
+ // FIXME: SpellAbility subclasses Ability
+ private final HashMap<String, Ability> spells = new HashMap<>();
+ /**
+ * Pending moves, processed one at a time during movement tick
+ */
+ final private LinkedList<String> moveQueue = new LinkedList<>();
+ /**
+ * Pending commands, this is only used during battle
+ */
+ final private LinkedList<String[]> battleCommands = new LinkedList<>();
+ /**
+ * Tick of last state change, such as sleeping, etc.
+ */
+ int flagsChanged;
+ /**
+ * Tick of last condition change
+ */
+ int conditionsChanged;
+
+ Active(Game game) {
+ super(game);
+ }
+
+ Wearable parseWearable(String value) {
+ // format is name,durability,uses
+
+ System.out.println("parse wearable: " + value);
+
+ String[] args = value.split(",");
+
+ if (args[0].equals("none"))
+ return null;
+
+ Wearable w = (Wearable) game.createItem(args[0].toLowerCase());
+
+ if (w != null) {
+ w.durability = Long.valueOf(args[1]);
+ w.uses = Integer.valueOf(args[2]);
+ }
+ return w;
+ }
+
+ // TODO: this probably needs some 'exporter' on Thing
+ Holdable parseHoldable(String value) {
+ // format is name,durability,uses ... but durability is only used on Wearables
+ // FIXME: fix exporter ... not so easy, everything is an item, even weapons.
+
+ System.out.println("parse holdable: " + value);
+
+ String[] args = value.split(",");
+
+ if (args[0].equals("none"))
+ return null;
+
+ Holdable h = (Holdable) game.createItem(args[0].toLowerCase());
+
+ // blah ... nice eh?
+ if (h instanceof Wearable) {
+ ((Wearable) h).durability = Long.valueOf(args[1]);
+ }
+ h.uses = Integer.valueOf(args[2]);
+
+ return h;
+ }
+
+ /* *
+ * I/o stuff
+ */
+ @Override
+ void setProperty(String name, String value) {
+ if (variables.setProperty(name, value))
+ return;
+ if (conditions.setProperty(name, value))
+ return;
+ switch (name) {
+ case "hp":
+ stats[STAT_HP] = Integer.valueOf(value);
+ break;
+ case "maxhp":
+ stats[STAT_HPMAX] = Integer.valueOf(value);
+ break;
+ case "mp":
+ stats[STAT_MP] = Integer.valueOf(value);
+ break;
+ case "maxmp":
+ stats[STAT_MPMAX] = Integer.valueOf(value);
+ break;
+ case "str":
+ stats[STAT_STR] = Integer.valueOf(value);
+ break;
+ case "int":
+ stats[STAT_INT] = Integer.valueOf(value);
+ break;
+ case "dex":
+ stats[STAT_DEX] = Integer.valueOf(value);
+ break;
+ case "con":
+ stats[STAT_CON] = Integer.valueOf(value);
+ break;
+ case "wis":
+ stats[STAT_WIS] = Integer.valueOf(value);
+ break;
+ case "hpbon":
+ bonus[STAT_HP] = Integer.valueOf(value);
+ break;
+ case "maxhpbon":
+ bonus[STAT_HPMAX] = Integer.valueOf(value);
+ break;
+ case "mpbon":
+ bonus[STAT_MP] = Integer.valueOf(value);
+ break;
+ case "maxmpbon":
+ bonus[STAT_MPMAX] = Integer.valueOf(value);
+ break;
+ case "strbon":
+ bonus[STAT_STR] = Integer.valueOf(value);
+ break;
+ case "intbon":
+ bonus[STAT_INT] = Integer.valueOf(value);
+ break;
+ case "dexbon":
+ bonus[STAT_DEX] = Integer.valueOf(value);
+ break;
+ case "conbon":
+ bonus[STAT_CON] = Integer.valueOf(value);
+ break;
+ case "wisbon":
+ bonus[STAT_WIS] = Integer.valueOf(value);
+ break;
+ case "exp":
+ exp = Integer.valueOf(value);
+ break;
+ case "cash":
+ gold = Long.valueOf(value);
+ break;
+ case "race":
+ race = game.getRace(value);
+ break;
+ case "clan":
+ clan = value;
+ break;
+ case "wield":
+ wornItems.wear(Wearing.WIELD, parseWearable(value));
+ break;
+ case "arms":
+ wornItems.wear(Wearing.ARMS, parseWearable(value));
+ break;
+ case "legs":
+ wornItems.wear(Wearing.LEGS, parseWearable(value));
+ break;
+ case "torso":
+ wornItems.wear(Wearing.TORSO, parseWearable(value));
+ break;
+ case "waist":
+ wornItems.wear(Wearing.WAIST, parseWearable(value));
+ break;
+ case "neck":
+ wornItems.wear(Wearing.NECK, parseWearable(value));
+ break;
+ case "skull":
+ wornItems.wear(Wearing.SKULL, parseWearable(value));
+ break;
+ case "eyes":
+ wornItems.wear(Wearing.EYES, parseWearable(value));
+ break;
+ case "hands":
+ wornItems.wear(Wearing.HANDS, parseWearable(value));
+ break;
+ // FIXME: should it specify the item type?
+ case "item":
+ inventory.add(parseHoldable(value));
+ break;
+
+ //
+ // any more missing?
+ default:
+ super.setProperty(name, value);
+ break;
+ }
+ // FIXME: spells, skills, inventory?
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ super.writeProperties(out);
+ for (int i = 0; i < stat_keys.length; i++) {
+ writeProperty(out, stat_keys[i], stats[i]);
+ }
+ for (int i = 0; i < stat_keys.length; i++) {
+ writeProperty(out, stat_keys[i] + "bon", bonus[i]);
+ }
+ writeProperty(out, "exp", exp);
+ writeProperty(out, "gold", gold);
+ writeProperty(out, "clan", clan);
+ // FIXME: hack for mobile, race doesn't apply
+ for (int i = 0; i < Wearing.WEARING_COUNT; i++) {
+ Wearable w = wornItems.getWorn(i);
+ if (w != null) {
+ writeProperty(out, Wearing.wornNames[i], w.name + "," + w.durability + "," + w.uses);
+ }
+ }
+ if (race != null)
+ writeProperty(out, "race", race.name);
+ variables.writeProperties(out);
+ conditions.writeProperties(out);
+ }
+
+ public int getStat(int key) {
+ return stats[key] + race.stats[key] + bonus[key];
+ }
+
+ public void setStat(int key, int value) {
+ // TODO: range check?
+ stats[key] = value;
+ }
+
+ public void addStat(int key, int value) {
+ setStat(key, stats[key] + value);
+ }
+
+ public int getBonus(int key) {
+ return bonus[key];
+ }
+
+ public int getHP() {
+ return getStat(STAT_HP);
+ }
+
+ public int getHPMax() {
+ return getStat(STAT_HPMAX);
+ }
+
+ public int getMP() {
+ return getStat(STAT_MP);
+ }
+
+ public int getMPMax() {
+ return getStat(STAT_MPMAX);
+ }
+
+ public int getSTR() {
+ return getStat(STAT_STR);
+ }
+
+ public int getINT() {
+ return getStat(STAT_INT);
+ }
+
+ public int getDEX() {
+ return getStat(STAT_DEX);
+ }
+
+ public int getCON() {
+ return getStat(STAT_CON);
+ }
+
+ public int getWIS() {
+ return getStat(STAT_WIS);
+ }
+
+ public int getRange() {
+ Weapon item = (Weapon) wornItems.getWorn(Wearing.WIELD);
+
+ return item != null ? item.range : 1;
+ }
+
+ public int getRangeWithBonus() {
+ return getRange() + bonus[STAT_RANGE];
+ }
+
+ public long getGold() {
+ return gold;
+ }
+
+ /**
+ * Add or remove gold to/from the purse.
+ *
+ * If removing (amouunt < 0), the operation only proceeds if there
+ * is sufficient money available.
+ *
+ * @param amount
+ * @return true if the gold was added, false if removing the gold would lead
+ * to a deficit.
+ */
+ public boolean addGold(int amount) {
+ // FIXME: locks
+ if (gold + amount < 0)
+ return false;
+ gold += amount;
+ return true;
+ }
+
+ public int getExp() {
+ return exp;
+ }
+
+ public boolean addExp(int amount) {
+ if (exp + amount < 0)
+ return false;
+ // FIXME: locks? or is += atomic in java, hmm.
+ exp += amount;
+ return true;
+ }
+
+ public boolean isMoveable() {
+ return moveable;
+ }
+
+ public void setMoveable(boolean moveable) {
+ this.moveable = moveable;
+ }
+
+ public boolean isPlayer() {
+ return getType() == TYPE_PLAYER;
+ }
+
+ public boolean isFighting() {
+ return battle != null;
+ }
+
+ public boolean isSleeping() {
+ return sleeping;
+ }
+
+ public void setSleeping(boolean sleeping) {
+ if (this.sleeping != sleeping) {
+ this.sleeping = sleeping;
+ flagsChanged();
+ }
+ }
+
+ /**
+ * Whether the object is alive, e.g. connection, etc.
+ * not the same as player death, todo: rename it
+ *
+ * @return
+ */
+ public boolean isAlive() {
+ // FIXME: implement isAlive
+ return true;
+ //throw new UnsupportedOperationException();
+ }
+
+ public List<String> getActiveConditions() {
+ return conditions.getActiveConditions();
+ }
+
+ public int getBattleSide() {
+ if (battle != null)
+ return battle.getSide(this);
+ return 0;
+ }
+
+ public int getSkillLevel(String name) {
+ Ability a = skills.get(name);
+ if (a != null)
+ return a.getLevel();
+ return 0;
+ }
+
+ /**
+ * This thing caused damage to another player.
+ *
+ * This implements battle accounting and weapon damage
+ *
+ * @param amount
+ */
+ public void causedDamage(int amount) {
+ damageDone += amount;
+ weaponDamage(amount);
+ }
+
+ /**
+ * Invoked on attackor to age weapon
+ *
+ * @param amount
+ */
+ public void weaponDamage(int amount) {
+ Wearable item = wornItems.damageItem(Wearing.WIELD, amount);
+ if (item != null) {
+ chatMessage("Your " + item.name + " breaks.");
+ // FIXME: onUnwear(item);
+ //if (isPlayer()) {
+ // updateEquipment();
+ // updateStats();
+ //}
+ }
+ }
+
+ public void receivedDamage(int amount) {
+ setStat(STAT_HP, stats[STAT_HP] - amount);
+ armourDamage(amount);
+ }
+
+ public void armourDamage(int damage) {
+ int total = wornItems.armourCount();
+ if (total == 0) {
+ return;
+ }
+
+ for (int i = Wearing.ARMS; i < Wearing.WEARING_COUNT; i++) {
+ Wearable item = wornItems.damageItem(i, damage / total);
+ if (item != null) {
+ chatMessage("Your " + item.name + " breaks.");
+ //FIXME: onUnwear(item);
+ //updateStats();
+ //updateEquipment();
+ }
+ }
+ }
+
+ /**
+ * Process one queued move
+ */
+ public void moveTick() {
+ String dir;
+
+ synchronized (moveQueue) {
+ if (moveQueue.isEmpty())
+ return;
+
+ dir = moveQueue.removeFirst();
+ }
+
+ System.out.println("move " + name + " tick: " + dir);
+
+ switch (dir.charAt(0)) {
+ case 'n':
+ moveTo(x, y - 1, 0);
+ break;
+ case 's':
+ moveTo(x, y + 1, 2);
+ break;
+ case 'w':
+ moveTo(x - 1, y, 4);
+ break;
+ case 'e':
+ moveTo(x + 1, y, 6);
+ break;
+ }
+ }
+
+ /**
+ * Called every 'item type' tick. Something like every 250ms.
+ *
+ * @param tick
+ */
+ public void tick(int tick) {
+ // Every second
+ if (tick % 4 == 0) {
+ if (conditions.checkConditions(this, tick))
+ conditionsChanged = tick;
+ }
+ // Every 18 seconds
+ if (tick % 73 == 0) {
+ //if (lt.battle == null) {
+ int hpinc, mpinc;
+ if (isSleeping()) {
+ hpinc = 3 + getCON();
+ mpinc = 3 + getWIS();
+ } else {
+ hpinc = 1 + getCON() / 2;
+ mpinc = 1 + getWIS() / 2;
+ }
+ setStat(STAT_HP, Math.min(stats[STAT_HP] + hpinc, stats[STAT_HPMAX]));
+ setStat(STAT_MP, Math.min(stats[STAT_MP] + mpinc, stats[STAT_MPMAX]));
+ // lt.updateInfo();
+ // lt.savePlayer();
+ //}
+ }
+ // TODO: move tick
+ }
+
+ /**
+ * Called at the end of a turn to update visibility to this object/fire off
+ * updates, etc.
+ *
+ * @param tick
+ */
+ public void visibilityTick(int tick) {
+ }
+ int viewRange = 6;
+
+ boolean visibleRange(int tx, int ty) {
+ return Math.abs(x - tx) <= viewRange
+ && Math.abs(y - ty) <= viewRange;
+ }
+
+ /**
+ * Execute the canSeeActive script - can this active
+ * see the other at this time
+ *
+ * @param other
+ * @return
+ */
+ public boolean onCanSeeActive(Active other) {
+ System.out.println("not implemented: onCanSeeActive");
+ return true;
+ }
+
+ /**
+ * Execute the canMoveActive script - can this active
+ * occupy the same location as other
+ *
+ * @param blocking
+ * @return
+ */
+ public boolean onCanMoveActive(Active blocking) {
+ System.out.println("not implemented: onCanMoveActive");
+
+ return false;
+ }
+
+ /**
+ * Execute location and tile canSee scripts
+ *
+ * @param lx
+ * @param ly
+ * @return
+ */
+ public boolean onCanSee(int lx, int ly) {
+ return game.onLocationVisible(this, lx, ly, map.getTile(lx, ly));
+ }
+
+ /**
+ * Check if the player can see the location, uses a line of sight check
+ *
+ * @param tx
+ * @param ty
+ * @return
+ */
+ public boolean canSee(int tx, int ty) {
+ if (!visibleRange(tx, ty))
+ return false;
+ //if (!blnLineOfSight) {
+ // return true;
+ //}
+
+ for (TileMap.MapData md : map.look(x, y, tx, ty)) {
+ if (!onCanSee(md.x, md.y))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Can this active move to the given location.
+ *
+ * @param tx
+ * @param ty
+ * @return
+ */
+ public boolean canMoveTo(int tx, int ty) {
+
+ if (!this.map.inside(tx, ty))
+ return false;
+
+ //System.out.printf("can move to: %d,%d tid=%3d on map %s\n", tx, ty, this.map.getTile(tx, ty), this.map.name);
+
+ for (Thing o : this.map.getEntities(tx, ty, null)) {
+ if (o instanceof Active) {
+ Active a = (Active) o;
+ if (!onCanMoveActive(a))
+ return false;
+ }
+ }
+
+ return game.onLocationAble(this, tx, ty, map.getTile(tx, ty));
+ }
+
+ abstract public void send(DuskMessage msg);
+
+ public abstract void chatMessage(Active from, String clan, String msg);
+
+ /**
+ * Send a message to player.
+ *
+ * @param msg if null, nothing is sent.
+ */
+ void chatMessage(String msg) {
+ if (msg != null)
+ chatMessage(null, null, msg);
+ }
+
+ void chatBattle(String msg) {
+ // FIXME:P right code
+ chatMessage(null, null, msg);
+ }
+
+ /**
+ * Send a localised chat message from the given object
+ *
+ * @param msg
+ */
+ public void localisedChat(String msg) {
+ //log.printMessage(Log.ALWAYS, inMessage);
+
+ for (TileMap.MapData md : map.range(x, y, game.viewRange)) {
+ for (Thing o : md.entities) {
+ if (o.getType() == Thing.TYPE_PLAYER) {
+ Player p = (Player) o;
+
+ p.chatMessage(this, null, msg);
+ }
+ }
+ }
+ }
+
+ public List<Active> getPackMembers() {
+ // FIXME: threads
+ if (pack != null)
+ return pack.members;
+ else
+ return Arrays.asList(this);
+ }
+
+ public void travelTo(int destX, int destY, boolean goon) {
+ if (pack != null && !pack.isLeader(this)) {
+ chatMessage("You can't move while you're following someone.");
+ }
+ // TODO: verify logic
+ //if (master != null) {
+ // if (!isPet() || master.following == this) {
+ // return "You can't move while you're following someone.";
+ // }
+ //}
+ if (goon && !canMoveTo(destX, destY)) {
+ chatMessage("You can't move onto that location.");
+ return;
+ }
+ if (Math.abs(destX - x) > game.viewRange || Math.abs(destY - y) > game.viewRange) {
+ chatMessage("Too far away");
+ return;
+ }
+ synchronized (moveQueue) {
+ moveQueue.clear();
+
+ TileMap.MoveListener ml = new TileMap.MoveListener() {
+ @Override
+ public boolean canMoveto(TileMap.MapData md) {
+ return canMoveTo(md.x, md.y);
+ }
+ };
+ int mflags = goon ? 0 : TileMap.SKIP_END;
+
+ for (TileMap.MoveData md : map.move(x, y, destX, destY, mflags, ml)) {
+ moveQueue.add(md.direction);
+ }
+ }
+ }
+
+ public void enqueueMove(String dir) {
+ if (pack != null
+ && !pack.isLeader(this)) {
+ chatMessage("You can't move while following someone");
+ return;
+ }
+ synchronized (moveQueue) {
+ moveQueue.add(dir);
+ }
+ }
+
+ public boolean hasPendingMoves() {
+ synchronized (moveQueue) {
+ return !moveQueue.isEmpty();
+ }
+ }
+
+ public void clearMoveQueue() {
+ synchronized (moveQueue) {
+ moveQueue.clear();
+ }
+ }
+
+ /**
+ * Jump to a new location/map.
+ *
+ * No checks are performed on validity of jump
+ *
+ * @param map
+ * @param newx
+ * @param newy
+ * @returns true if the location or map changed
+ */
+ protected boolean jumpTo(TileMap map, int newx, int newy) {
+ if (this.map == map && newx == x && newy == y)
+ return false;
+
+ // TODO: if this is the leader, warp everyone to the new location?
+
+ // FIXME: threads or something
+ if (this.map == (map)) {
+ map.moveEntity(this, newx, newy);
+ } else {
+ this.map.removeEntity(this);
+ this.map = map;
+ this.x = newx;
+ this.y = newy;
+ this.map.addEntity(this);
+ }
+ return true;
+ }
+
+ /**
+ * Move player to new location.
+ *
+ * TODO: it seems to me, that if this is a leader, it should
+ * move all it's followers right away without needing the
+ * recursion.
+ *
+ * @param newx
+ * @param newy
+ * @param newStep TODO: this should just be a direction code, other code
+ * can decide if it means changing the picture or something
+ * @return
+ */
+ protected boolean moveTo(int newx, int newy, int newStep) {
+ //System.out.printf("%s: move to %d,%d moveable=%s sleeping=%s can=%s\n",
+ // name, newx, newy, moveable, sleeping, canMoveTo(newx, newy));
+ /**
+ * l * Allow gods to move outside of map (probably not!)
+ */
+ if (privs < 5 && !map.inside(newx, newy))
+ return false;
+ /**
+ * Check if we can move at all
+ */
+ if (privs <= 1 && (!moveable || isSleeping() || !canMoveTo(newx, newy)))
+ return false;
+
+ int oldx = x;
+ int oldy = y;
+
+ map.moveEntity(this, newx, newy);
+
+ imageStep = newStep;
+
+ game.onLocationAction(this, x, y, map.getTile(x, y));
+
+ Active following = pack != null ? pack.getFollowing(this) : null;
+
+ //move follower
+ if (following != null) {
+ following.followTo(this, oldx, oldy);
+ }
+
+ return true;
+ }
+
+ protected boolean followTo(Active leader, int oldx, int oldy) {
+ //don't move follower if leader has moved onto follower's tile
+ if ((x == leader.x) && (y == leader.y)) {
+ return false;
+ } else if (Math.abs(x - oldx) + Math.abs(y - oldy) > 1) {
+ leader.chatMessage(name + " is no longer following you.");
+ chatMessage("You are no longer following " + leader.name + ".");
+
+ pack.removeFollower(this);
+ return false;
+ } else if (y > oldy) {
+ moveTo(x, y - 1, 0); // n
+ } else if (y < oldy) {
+ moveTo(x, y + 1, 2); // s
+ } else if (x > oldx) {
+ moveTo(x - 1, y, 4); // w
+ } else if (x < oldx) {
+ moveTo(x + 1, y, 6); // e
+ }
+ return true;
+ }
+
+ /**
+ * This is a script helper to find a nearby visible object by name
+ * or number.
+ *
+ * @param name if parseable as a number, used as ID instead.
+ * @return
+ */
+ protected Thing findVisibleObject(String name) {
+ try {
+ long id = Long.valueOf(name);
+ Thing t = game.getThing(id);
+ boolean see = true;
+
+ if (t instanceof Active) {
+ see &= onCanSeeActive((Active) t);
+ }
+
+ if (see && canSee(t.x, t.y)) {
+ return t;
+ }
+ return null;
+ } catch (NumberFormatException x) {
+ }
+
+ // search by name
+ for (TileMap.MapData md : map.range(x, y, game.viewRange)) {
+ for (Thing t : md.entities) {
+ if (t.name.equalsIgnoreCase(name)) {
+ boolean see = true;
+
+ if (t instanceof Active) {
+ see &= onCanSeeActive((Active) t);
+ }
+
+ if (see && canSee(t.x, t.y)) {
+ return t;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public void createBattle(Active enemy) {
+ if (canAttackEnemy(enemy))
+ attackEnemy(enemy);
+ }
+
+ // can attackEnemy a given target?
+ protected boolean canAttackEnemy(Active enemy) {
+ String msg;
+
+ if (isSleeping()) {
+ msg = ("You can't do that while you're sleeping");
+ } else if (isFighting()) {
+ msg = ("You're already busy fighting!");
+ } else if (enemy.ID == this.ID) {
+ msg = ("You can't fight yourself!");
+ } else if (enemy.getType() == TYPE_PET) {
+ msg = ("You can't attack pets.");
+ } else if (distanceL1(enemy) > getRangeWithBonus()) {
+ System.out.println("attack enemy =" + enemy.name + " distance =" + distanceL1(enemy) + " range = " + getRangeWithBonus());
+ msg = ("They're too far away.");
+ } else if (!game.onCanAttack(this, enemy)) {
+ msg = ("You can't attack them.");
+ } else {
+ return true;
+ }
+
+ chatMessage(msg);
+ return false;
+ }
+
+ /**
+ * Attack enemy, must already have been checked using canAttackEnemy()
+ *
+ * @param enemy
+ */
+ protected void attackEnemy(Active enemy) {
+ if (!enemy.isFighting()) {
+ game.addBattle(new Battle(game, this, enemy));
+ } else {
+ int side = 3 - enemy.battle.getSide(enemy);
+
+ for (Active a : getPackMembers()) {
+ enemy.battle.addToBattle(a, side);
+ }
+ }
+ }
+
+ public int getArmourMod() {
+ return wornItems.armourMod();
+ }
+
+ public int getArmourModWithBonus() {
+ return getArmourMod() + ((getDEX()) / 10) + bonus[STAT_ARC];
+ }
+
+ public int getDamageMod() {
+ Wearable item = wornItems.getWorn(Wearing.WIELD);
+ if (item != null)
+ return item.mod;
+ return 100;
+ }
+
+ public int getDamageModWithBonus() {
+ return getDamageMod() + bonus[STAT_DAMAGE];
+ }
+
+ int getTotalSkillLevel() {
+ int total = 0;
+
+ for (Ability skill : skills.values())
+ total += skill.getLevel();
+ for (Ability spell : spells.values())
+ total += spell.getLevel();
+ return total;
+ }
+
+ /**
+ * Get character points
+ *
+ * @return
+ */
+ public int getCP() {
+ int result = stats[STAT_WIS]
+ + stats[STAT_INT]
+ + stats[STAT_STR]
+ + stats[STAT_DEX]
+ + stats[STAT_CON]
+ + stats[STAT_HPMAX] / 10
+ + stats[STAT_MPMAX] / 10
+ + getTotalSkillLevel();
+ return result;
+ }
+
+ /**
+ * get total points
+ *
+ * @return
+ */
+ public int getTP() {
+ int result = getCP()
+ + getArmourMod()
+ + (getDamageMod() - 100);
+ return result;
+ }
+
+ /**
+ * Test skill against a random hit rate
+ *
+ * @param name
+ * @return
+ */
+ public boolean testSkill(String name) {
+ return Math.random() * 100 + 1 < getSkillLevel(name);
+ }
+
+ protected int damageRoll(Active target, int range) {
+ int attackerTotal = getSTR();
+
+ double damage = (attackerTotal / 2.0)
+ * (Math.random() + 0.5)
+ * (getDamageModWithBonus() / 100)
+ - (target.getArmourModWithBonus());
+
+ return (int) damage;
+ }
+
+ protected boolean dodgeRoll(Active target, int attackModifier) {
+ int attackedTotal = Math.min(100, target.getDEX());
+ int attackerTotal = Math.min(100, this.getDEX() + attackModifier);
+
+ return ((Math.random() * 100)
+ < ((target.getSkillLevel("Dodge") * .75)
+ + (.25 * (attackedTotal - attackerTotal))));
+ }
+
+ protected void performAttack(Battle battle, Active target, int range, StringBuilder s) {
+ // The farther away the target, the harder they are to hit.
+ // The more skilled the attecker is in ranged combat, the less range affects ability to hit.
+ // The farther away the target, the easier it is for them to dodge.
+ // The more skilled the attecker is in ranged combat, the harder it is for the target to dodge.
+ int r2 = 0;
+ if (range > 1) {
+// r2= thnAttacking.getSkill("ranged combat") - (int)((range * range - 1) * (double)(100/engGame.viewrange));
+ r2 = this.getSkillLevel("ranged combat");
+ } else {
+ r2 = this.getSkillLevel("close combat");
+ }
+ if (r2 < 0) {
+ s.append(this.name).append(" missed.");
+ battle.hitMessage(this, target, 0, "Missed!");
+ } else if (dodgeRoll(target, r2)) {
+ s.append(target.name).append(" dodged ").append(this.name).append("'s attack");
+ battle.hitMessage(this, target, 0, "Dodged!");
+ } else {
+ // FIXME: audio
+ //if (game.battlesound != -1) {
+ // game.playSound(attackor.map, game.battlesound, attackee.x, attackee.y);
+ //}
+ int i = damageRoll(target, range);
+ if (i < 0) {
+ i = 0;
+ }
+ s.append(this.name + " did " + i + " to " + target.name);
+
+ target.receivedDamage(i);
+ this.causedDamage(i);
+
+ battle.hitMessage(this, target, i, "Hit!");
+ }
+ }
+
+ void splitMoney(double modifier, ArrayList<Active> opponents) {
+ int lost = (int) (modifier * gold);
+ if (lost > 0) {
+ chatBattle("You have lost " + lost + " gp.");
+
+ // FIXME: synchronised
+ addGold(-lost);
+ int share = lost / opponents.size();
+ for (Active a : opponents) {
+ a.addGold(share);
+ // TODO: virtual method for bounty?
+ if (a.isPlayer() && share != 0) {
+ a.chatMessage("You get " + share + " gp.");
+ }
+ }
+ }
+ }
+
+ void splitExp(double modifier, ArrayList<Active> opponents) {
+ int lost = (int) (modifier * this.exp);
+
+ this.chatBattle("You have lost " + lost + " exp.");
+ this.addExp(-lost);
+ this.damageDone = 0;
+
+ double totalPpoints, sidepoints = 0;
+
+ totalPpoints = this.getTP();
+ for (Active a : opponents) {
+ sidepoints += a.getTP();
+ }
+
+ for (Active a : opponents) {
+ // TODO: virtual method for bounty?
+ if (!(a.getType() == TYPE_MOBILE)) {
+ double damageMod = Math.min(getHPMax(), a.damageDone);
+ double gained = (game.expGainMod
+ * (((totalPpoints / sidepoints)
+ + (2.0 * damageMod / getHPMax() * totalPpoints / a.getTP())) / 3.0));
+ int igained = (int) gained;
+ a.chatMessage("You get " + igained + " exp.");
+ a.addExp(igained);
+ }
+ a.damageDone = 0;
+ }
+ }
+
+ public void enterBattle(Battle battle) {
+ assert (this.battle == null);
+
+ this.battle = battle;
+ moveable = false;
+ flagsChanged();
+ }
+
+ /**
+ * If fighting, add the command to the fighting command queue,
+ *
+ * Certain command issued during battle are handled from the
+ * battle even loop.
+ *
+ * TODO: is this just for some sort of thread-save
+ * synchronisation purpose, or does it have a better reason?
+ *
+ * It is useful for flee command, but otherwise?
+ *
+ * @param cmd
+ * @return true if the command was queued
+ */
+ boolean addBattleCommand(String... cmd) {
+ if (isFighting()) {
+ synchronized (battleCommands) {
+ battleCommands.add(cmd);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean addExpiditedBattleCommand(String... cmd) {
+ if (isFighting()) {
+ synchronized (battleCommands) {
+ battleCommands.addFirst(cmd);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the next pending battle command
+ *
+ * @return
+ */
+ String[] pollBattleCommand() {
+ synchronized (battleCommands) {
+ if (battleCommands.isEmpty())
+ return null;
+ return battleCommands.remove();
+ }
+
+ }
+
+ /**
+ * Called during battle to implement a single attack
+ *
+ * @param battle
+ * @param target
+ */
+ public void attack(Battle battle, Active target, int range) {
+ if (range > getRangeWithBonus()) {
+ // out of range
+ battle.chatMessage(name + " is out of range");
+ } else {
+ StringBuilder msg = new StringBuilder();
+
+ performAttack(battle, target, range, msg);
+ if (testSkill("double attack")) {
+ msg.append(",");
+ performAttack(battle, target, range, msg);
+ }
+ if (testSkill("triple attack")) {
+ msg.append(",");
+ performAttack(battle, target, range, msg);
+ }
+ if (testSkill("quadruple attack")) {
+ msg.append(",");
+ performAttack(battle, target, range, msg);
+ }
+ msg.append(".");
+ battle.chatMessage(msg.toString());
+ }
+ }
+
+ /**
+ * Called during battle to flee.
+ *
+ * This will end the battle participation of this player.
+ *
+ * @param battle
+ * @param opponents
+ */
+ public void fleeBattle(Battle battle, ArrayList<Active> opponents) {
+ clearFollow();
+
+ // FIXME: chatBattle
+ chatMessage("You have fled from battle");
+ splitMoney(game.goldFleeMod, opponents);
+ splitExp(game.expFleeMod, opponents);
+
+ endBattle();
+ //updateInfo();
+ //updateStats();
+ //updateActions();
+ //playMusic(0);
+ }
+
+ /**
+ * Called at the end of each battle turn. Includes a list of changes to the battlefield during
+ * that time.
+ *
+ * For updating clients with flags and shit.
+ *
+ * @param battle
+ * @param joined objects joined battle
+ * @param left objects left battle
+ */
+ public void endBattleTurn(Battle battle, ArrayList<Active> joined, ArrayList<Active> left) {
+ }
+
+ /**
+ * Lets the active know that an opponent has left the battle.
+ *
+ * @param battle
+ * @param loser
+ */
+ public void leftBattle(Battle battle, Active loser) {
+ }
+
+ /**
+ * Thing was killed in battle, distribute bounty to opponents
+ *
+ * @param battle
+ * @param winner
+ * @param opponents
+ */
+ abstract public void killedBattle(Battle battle, Active winner, ArrayList<Active> opponents);
+
+ public void endBattle() {
+ this.battle = null;
+
+ moveable = true;
+
+ flagsChanged();
+ // update everything
+ // lt.clearFlags();
+ // lt.battle = null;
+ // lt.battleSide = 0;
+ // lt.isMoveable = true;
+ // lt.updateInfo();
+ // lt.updateStats();
+ // lt.updateActions();
+ // lt.playMusic(0);
+ }
+
+ public boolean isCanLead() {
+ return canLead;
+ }
+
+ public void follow(Active master) {
+ String msg;
+
+ if (isSleeping()) {
+ msg = "You can't do that while you're sleeping";
+ } else if (distanceL1(master) > 1) {
+ msg = ("They're too far away.");
+ } else if (pack != null) {
+ msg = ("You're already following someone. Leave them first.");
+ } else if (this.ID == master.ID) {
+ msg = ("You can't follow yourself.");
+ } else if (!isCanLead()) {
+ msg = ("They won't let you follow them.");
+ } else {
+ if (master.pack == null) {
+ master.pack = new Pack();
+ master.pack.addFollower(master);
+ }
+ pack = master.pack;
+ pack.addFollower(this);
+ // set pack changed?
+ return;
+ }
+
+ chatMessage(msg);
+ }
+
+ void followCommand(String whom) {
+ Thing thing = findVisibleObject(whom);
+ String msg = null;
+
+ if (thing == null) {
+ msg = "You don't see that here.";
+ } else if (thing instanceof Active) {
+ follow((Active) thing);
+ } else {
+ msg = "That's not something you can follow.";
+ }
+
+ chatMessage(msg);
+ }
+
+ /**
+ * Leave the group if you're in one
+ */
+ void leaveCommand() {
+ if (pack != null) {
+ Active leader = pack.getLeader(this);
+ if (leader != this) {
+ pack.removeFollower(this);
+ pack.removeFollower(leader);
+ pack = null;
+ leader.pack = null;
+ }
+ }
+ }
+
+ /**
+ * Force thing to be removed from the pack
+ *
+ * TODO: check it's needed
+ */
+ @Deprecated
+ public void clearFollow() {
+ if (pack != null)
+ pack.members.remove(this);
+ }
+
+ protected void flagsChanged() {
+ flagsChanged = game.getClock();
+ }
+
+ public boolean jumpTo(String mapName, String mapAlias) {
+ System.out.println("script: jumpto(" + mapName + ", " + mapAlias + ")");
+ TileMap newmap = game.getMap(mapName);
+
+ if (newmap != null) {
+ Location l = newmap.locationForAlias(mapAlias);
+
+ if (l != null) {
+ return jumpTo(newmap, l.x, l.y);
+ }
+ }
+ return false;
+ }
+
+ /* *
+ * So all this 'get the player object to implemeent the commands' seemed
+ * like a good idea, but the code will bloat.
+ *
+ * having it reusable is useful for scripts though.
+ *
+ * Perhaps it shoudl be put into antoher class for reusability stake.
+ */
+ public void fleeCommand() {
+ if (isFighting()) {
+ synchronized (battleCommands) {
+ battleCommands.addFirst(new String[]{"flee"});
+ }
+ } else {
+ chatMessage("You're not fighting anyone");
+ }
+ }
+
+ public void sleepCommand() {
+ String msg = null;
+
+ if (isSleeping()) {
+ // otherwise i get spew from tired script: might need to think script i/face
+ //msg = "You are already asleep";
+ } else if (isFighting()) {
+ msg = "Not while you're fighting!";
+ } else if (pack != null && !pack.getLeader(this).isSleeping()) {
+ msg = "You can't sleep if you're following someone who's awake.";
+ } else {
+ msg = "You fall asleep.";
+ setSleeping(true);
+ }
+
+ chatMessage(msg);
+ }
+
+ public void awakeCommand() {
+ String msg;
+
+ if (isSleeping()) {
+ setSleeping(false);
+ msg = "You wake up";
+ } else {
+ msg = "You are already awake";
+ }
+ chatMessage(msg);
+ }
+
+ public void gossipCommand(String text) {
+ // noop, see player
+ chatMessage("Only players can use the gossip/clan/tell channels.");
+ }
+
+ public void sayCommand(String text) {
+ // noop, see player
+ }
+
+ public void attackCommand(String who) {
+ String msg;
+
+ Thing enemy = findVisibleObject(who);
+ if (enemy == null) {
+ msg = ("You don't see that here.");
+ } else if (!(enemy instanceof Active)) {
+ msg = ("You can't fight that.");
+ } else {
+ this.createBattle((Active) enemy);
+ return;
+ }
+
+ chatMessage(msg);
+ }
+
+ public void lookCommand(String what) {
+ // TODO: put all this 'check sleeping' etc into a common function with
+ // a list of things to check.
+ if (isSleeping()) {
+ chatMessage("You can't do that while you're sleeping");
+ } else {
+ Thing thing = findVisibleObject(what);
+
+ if (thing == null) {
+ chatMessage("You don't see that here.");
+ } else {
+ thing.look(this);
+ }
+ }
+ }
+
+ @Override
+ public void look(Active viewer) {
+ chatMessage(viewer.name + " is looking at you.");
+ viewer.chatMessage(name + " has " + getCP() + "cp and " + getHP() + "/" + getHPMax() + "hp.");
+ if (description != null) {
+ viewer.chatMessage("Their description is: " + description);
+ }
+ final String[] formats = {
+ "They are wielding %s.",
+ "They are wearing %s on their arms.",
+ "They are wearing %s on their legs.",
+ "They are wearing %s on their torso.",
+ "They are wearing %s on their waist.",
+ "They are wearing %s on their neck.",
+ "They are wearing %s on their skull.",
+ "They are wearing %s on their eyes.",
+ "They are wearing %s on their hands."
+ };
+ for (int i = 0; i < formats.length; i++) {
+ Wearable item = wornItems.getWorn(i);
+ if (item != null)
+ viewer.chatMessage(String.format(formats[i], item.description));
+ }
+ }
+
+ /**
+ * Wear the item. The item must be in the active's current inventory.
+ *
+ * The item will be worn at the correct location.
+ *
+ * @param w
+ */
+ public void wearItem(Wearable w) {
+ // FIXME: threads.
+ if (inventory.contains(w)) {
+ Wearable old = wornItems.wear(w.getWearing(), w);
+
+ if (old != null) {
+ inventory.add(old);
+ }
+ inventory.remove(w);
+ }
+ }
+
+ public void wearCommand(String what) {
+ Holdable h = inventory.get(what);
+ String msg = null;
+ if (h != null) {
+ if (h instanceof Wearable) {
+ wearItem((Wearable) h);
+ } else {
+ msg = "You'd look pretty stupid trying to wear that!";
+ }
+ } else
+ msg = "You don't have that";
+
+ chatMessage(msg);
+ }
+
+ /**
+ * Unwear item at position index. This is always called for the unwear
+ * command.
+ *
+ * @param index
+ */
+ public void unwearAt(int index) {
+ Wearable w = wornItems.unwear(index);
+
+ // fIXME: onunwear/etc.
+ if (w != null)
+ inventory.add(w);
+ }
+
+ /**
+ * Allows un-wearing by position or name.
+ *
+ * @param what
+ */
+ public void unwearCommand(String what) {
+ if (what.equalsIgnoreCase("all")) {
+ for (int i = 0; i < Wearing.WEARING_COUNT; i++) {
+ unwearAt(i);
+ }
+ } else {
+ int index = Equipment.toIndex(what);
+
+ if (index == -1)
+ index = wornItems.getWornIndex(what);
+
+ if (index != -1) {
+ unwearAt(index);
+ } else {
+ chatMessage("You're not wearing that.");
+ }
+ }
+ }
+
+ public void getCommand(String what) {
+ Thing thing = findVisibleObject(what);
+ String msg = null;
+
+ if (thing == null)
+ msg = "You don't see that here.";
+ else if (!(thing instanceof Holdable))
+ msg = "You can't take that.";
+ else if (distanceL1(thing) >= 2)
+ msg = "That's too far away.";
+ else {
+ Holdable item = (Holdable) thing;
+
+ game.removeThing(thing);
+ inventory.add(item);
+
+ game.onItem(this, item, "get");
+ }
+
+ chatMessage(msg);
+ }
+
+ public void dropCommand(String what) {
+ Holdable h = inventory.get(what);
+ String msg = null;
+
+ if (h == null) {
+ if (wornItems.isWearing(what)) {
+ msg = ("You're wearing that, you cannot drop it.");
+ } else {
+ msg = ("You don't have that");
+ }
+ } else if (game.haveOnItem(h, "drop")) {
+ game.onItem(this, h, "drop");
+ } else {
+ inventory.remove(h);
+ if (h.cost == 0) {
+ msg = h.name + " vanishes into thin air.";
+ } else {
+ game.addThing(h, map, x, y);
+ }
+ }
+
+ chatMessage(msg);
+ }
+
+ public void useCommand(String what) {
+ Holdable item = inventory.get(what);
+ String msg = null;
+
+ // TODO: check type?
+
+ if (item == null)
+ msg = "You don't have that.";
+ else if (!game.haveOnItem(item, "use"))
+ msg = "That cannot be used.";
+ else if (item.uses == 0)
+ msg = "That is used up.";
+ else {
+ if (item.uses != -1)
+ item.uses--;
+ game.onItem(this, item, "use");
+ }
+
+ chatMessage(msg);
+ }
+
+ public void drinkCommand(String what) {
+ Holdable item = inventory.get(what);
+
+ if (item != null && item.getType() != TYPE_DRINK) {
+ chatMessage("You can't drink that.");
+ } else
+ useCommand(what);
+ }
+
+ public void eatCommand(String what) {
+ Holdable item = inventory.get(what);
+
+ if (item != null && item.getType() != TYPE_FOOD) {
+ chatMessage("You can't eat that.");
+ } else
+ useCommand(what);
+ }
+
+ public void castCommand(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Shop visitingShop() {
+ for (Thing thing : map.getEntities(x, y, null)) {
+ if (thing.getType() == TYPE_PLAYER_SHOP
+ || thing.getType() == TYPE_GAME_SHOP) {
+ return (Shop) thing;
+ }
+ }
+ return null;
+ }
+
+ void buyCommand(String what, int quantity) {
+ Shop shop = visitingShop();
+ if (shop != null) {
+ shop.buy(this, what, quantity);
+ } else {
+ chatMessage("Buy from whom?");
+ }
+ }
+
+ void sellCommand(String what, int quantity) {
+ Shop shop = visitingShop();
+ if (shop != null) {
+ shop.sell(this, inventory.getAll(what, quantity));
+ } else {
+ chatMessage("Sell from whom?");
+ }
+ }
+
+ /**
+ * This is the public script API ... probably want to move it to another wrapper class for security reasons
+ */
+ public void emote(String value) {
+ // "thing value"
+ chatMessage(value);
+ }
+
+ public void chat(String value) {
+ // "thing says value" ??
+ chatMessage(value);
+ }
+
+ public void setCondition(String name, int duration) {
+ Condition c = new Condition(name, duration);
+ conditions.setCondition(c);
+ }
+
+ public void addINT(int inc) {
+ addStat(STAT_INT, inc);
+ }
+
+ public void addSTR(int inc) {
+ addStat(STAT_STR, inc);
+ }
+
+ public List<Holdable> getAllItems(String name) {
+ return inventory.getAll(name, Integer.MAX_VALUE);
+ }
+
+ public void addItem(Holdable item) {
+ if (item != null) {
+ inventory.add(item);
+ game.onItem(this, item, "get");
+ }
+ }
+
+ /**
+ * Remove the item from the inventory.
+ *
+ * @param item
+ * @return true if the item was in the inventory and could be removed.
+ */
+ public boolean removeItem(Holdable item) {
+ // FIXME: what if worn?
+ if (item != null) {
+ return inventory.remove(item);
+ }
+ return false;
+ }
+
+ public void sleep() {
+ sleepCommand();
+ }
+
+ public int getInteger(String key) {
+ return variables.getInteger(key, 0);
+ }
+
+ public void setInteger(String key, int value) {
+ variables.put(key, value);
+ }
+
+ /**
+ * check if the active has the item inventory or worn
+ *
+ * @param key
+ * @return
+ */
+ public boolean isOwner(String key) {
+ return inventory.get(key) != null
+ || wornItems.isWearing(key);
+ }
+
+ public boolean isWearing(String key) {
+ return wornItems.isWearing(key);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * Defense
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Armour extends Wearable {
+
+ /**
+ * Position the armour is worn, from Wearable
+ */
+ int worn;
+
+ public Armour(Game game) {
+ super(game);
+ }
+
+ public int getType() {
+ return TYPE_ARMOUR;
+ }
+
+ @Override
+ public int getWearing() {
+ return worn;
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "worn":
+ worn = Wearing.getCode(value);
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ super.writeProperties(out);
+
+ writeProperty(out, "worn", Wearing.wornNames[worn]);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ * Feb-2013 Michael Zucchi - Pretty major cleanup and parameterisation of code.
+ * Mar-2013 Michael Zucchi - changed server protocol
+ * Mar-2013 Michael Zucchi - big refactor, moved most of the logic to Active
+ *
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskProtocol;
+import duskz.protocol.ListMessage;
+import java.util.ArrayList;
+
+/**
+ * Battle represents a fight between two sides comprised of Actives.
+ *
+ * TODO: Remove the general chat stuff and use specific message types where appropriate
+ *
+ * @author Tom Weingarten
+ */
+public class Battle implements DuskProtocol {
+
+ private ArrayList<Active> side1 = new ArrayList<>(),
+ side2 = new ArrayList<>();
+ private Game game;
+ private boolean fighting = true,
+ playerInSide1 = false,
+ playerInSide2 = false;
+ /**
+ * Track players who left during this turn
+ */
+ private ArrayList<Active> left = new ArrayList<>();
+ /**
+ * Players who joined during the last turn
+ */
+ private ArrayList<Active> joined = new ArrayList<>();
+
+ public Battle(Game game, Active inpla1, Active inpla2) {
+ try {
+ this.game = game;
+
+ for (Active a : inpla2.getPackMembers()) {
+ playerInSide2 = addToBattle(2, side2, side1, a, playerInSide2, playerInSide1);
+ }
+ for (Active a : inpla1.getPackMembers()) {
+ playerInSide1 = addToBattle(1, side1, side2, a, playerInSide1, playerInSide2);
+ }
+
+ inpla1.localisedChat("-" + inpla1.name + " has attacked " + inpla2.name);
+ // FIXME: inpla1.startBattle(inpla2);
+ // inpla2.startBattle(inpla1);
+ } catch (Exception e) {
+ fighting = false;
+ System.out.println("erorr in battle new: " + e);
+ // game.log.printError("Battle()", e);
+ }
+ }
+
+ public boolean isFighting() {
+ return fighting;
+ }
+
+ public int getSide(Active thing) {
+ if (side1.contains(thing))
+ return 1;
+ if (side2.contains(thing))
+ return 2;
+ return 0;
+ }
+
+ /*
+ * Used in scripts
+ */
+ public static Active getEnemy(Active lt) {
+ return null;
+ // if (lt.battleSide == 1) {
+ // return (Active) lt.battle.vctSide2.get(0);
+ // } else if (lt.battleSide == 2) {
+ // return (Active) lt.battle.vctSide1.get(0);
+ // } else {
+ // return null;
+ // }
+ }
+
+ public void addToBattle(Active lt, int side) {
+ if (side == 1) {
+ playerInSide1 = addToBattle(1, side1, side2, lt, playerInSide1, playerInSide2);
+ } else {
+ playerInSide2 = addToBattle(2, side2, side1, lt, playerInSide2, playerInSide1);
+ }
+ }
+
+ boolean addToBattle(int sideid, ArrayList<Active> side, ArrayList<Active> opponents, Active added, boolean playerSide, boolean playerOpponent) {
+ if (added.isPlayer()) {
+ // This is already checked: do I need to do it again??
+ // FIXME: yes i do, or some variation of it.
+
+ /*
+ if (playerOpponent) {
+ if (added.clan.equals("none")) {
+ added.chatMessage("Players who are not in clans cannot fight other players.");
+ added.removeFromGroup();
+ return playerSide;
+ }
+ Active thnStore = side.get(0);
+ if (thnStore.clan.equals("none")) {
+ added.chatMessage("Players who are not in clans cannot fight other players.");
+ added.removeFromGroup();
+ return playerSide;
+ }
+ }*/
+ playerSide = true;
+ // FIXME: put in enterBattle?
+ //if (game.blnMusic) {
+ // added.playMusic(1);
+ //}
+ }
+ chatMessage(added.name + " has joined the battle.");
+ side.add(added);
+
+ added.enterBattle(this);
+
+ joined.add(added);
+
+ return playerSide;
+ }
+
+ /**
+ * Attack everything in side1 against side2
+ *
+ * @param list1
+ * @param list2
+ * @return true if the battle continues
+ */
+ boolean attackSide(ArrayList<Active> list1, ArrayList<Active> list2, ArrayList<Active> fled) {
+ int range;
+ Active target;
+ StringBuilder msg = new StringBuilder();
+
+ checkCommands(list1, list2, fled);
+ if (list1.isEmpty()) {
+ endBattle();
+ return false;
+ }
+
+ for (Active attackor : list1) {
+ msg.setLength(0);
+
+ // run attackEnemy script, now in attackor.attack()
+ //attackor.onFight();
+
+ /**
+ * Find the nearest target.
+ *
+ * Allow them to move if they are not in
+ * direct contact with any opponent.
+ *
+ * TODO: should this use the attack range instead?
+ *
+ */
+ range = Integer.MAX_VALUE;
+ target = null;
+ for (Active t : list2) {
+ int distance = attackor.distanceL1(t);
+ if (distance < range) {
+ range = distance;
+ target = t;
+ }
+ }
+ attackor.setMoveable(range > 1);
+
+ /*
+ End the battle if the closest target is off of the screen.
+ */
+ if (range > game.viewRange) {
+ endBattle();
+ return false;
+ }
+
+ attackor.attack(this, target, range);
+ }
+ return true;
+ }
+
+ void endBattle(ArrayList<Active> list) {
+ for (Active lt : list) {
+ lt.endBattle();
+ }
+ }
+
+ void endBattle() {
+ fighting = false;
+ chatMessage("You have won the battle.");
+ endBattle(side1);
+ endBattle(side2);
+ }
+
+ void flee(Active thing, ArrayList<Active> list, ArrayList<Active> opponents, ArrayList<Active> fled) {
+ fled.add(thing);
+ list.remove(thing);
+
+ thing.fleeBattle(this, opponents);
+
+ if (side2.size() == 0 || side1.size() == 0) {
+ endBattle();
+ return;
+ }
+ }
+
+ void chatMessage(ArrayList<Active> side1, ArrayList<Active> side2, String msg) {
+ for (Active a : side1) {
+ a.chatBattle(msg);
+ }
+ }
+
+ void chatMessage(String strStore) {
+ chatMessage(side1, side2, strStore);
+ chatMessage(side2, side1, strStore);
+ }
+
+ void battleMessage(ArrayList<Active> side1, ListMessage lm) {
+ for (Active a : side1) {
+ a.send(lm);
+ //} else if (a.isPet()) {
+ // if (a.getMaster().battle != a.battle) {
+ }
+ }
+
+ /**
+ * New battle interface with more detailed messages
+ *
+ * @param type
+ * @param msg
+ */
+ void battleMessage(ListMessage msg) {
+ battleMessage(side1, msg);
+ battleMessage(side2, msg);
+ }
+
+ void hitMessage(Active attackor, Active attackee, int delta, String what) {
+ ListMessage lm = new ListMessage(MSG_BATTLE_UPDATE);
+
+ lm.add(FIELD_BATTLE_TARGET, attackee.ID);
+ lm.add(FIELD_BATTLE_DAMAGE, delta);
+ lm.add(FIELD_BATTLE_HP, attackee.getHP());
+ lm.add(FIELD_BATTLE_MAXHP, attackee.getHPMax());
+ lm.add(FIELD_BATTLE_SOURCE, attackor.ID);
+ lm.add(FIELD_BATTLE_WHAT, what);
+ battleMessage(lm);
+ }
+
+ void checkCommands(ArrayList<Active> list, ArrayList<Active> opponents, ArrayList<Active> fled) {
+ for (Active lt : list) {
+ String[] argv = lt.pollBattleCommand();
+
+ if (argv != null) {
+ switch (argv[0]) {
+ case "flee":
+ flee(lt, list, opponents, fled);
+ break;
+ case "cast":
+ lt.castCommand(argv[1]);
+ break;
+ case "use":
+ lt.useCommand(argv[1]);
+ break;
+ case "eat":
+ lt.eatCommand(argv[1]);
+ break;
+ case "drink":
+ lt.drinkCommand(argv[1]);
+ break;
+ }
+ }
+ }
+ }
+
+ void checkDeath(ArrayList<Active> list1, ArrayList<Active> list2, ArrayList<Active> killed) {
+ // FIXME: verify this works. this protects the second call
+ if (list1.isEmpty() || list2.isEmpty())
+ return;
+
+ Active front1 = list1.get(0);
+ Active front2 = list2.get(0);
+
+ if (front2.getHP() <= 0 || !front2.isAlive()) {
+ list2.remove(0);
+
+ killed.add(front2);
+
+ front2.killedBattle(this, front1, list1);
+ }
+ }
+
+ void endTurn(ArrayList<Active> list1, ArrayList<Active> joined, ArrayList<Active> left) {
+ for (Active a : list1) {
+ a.endBattleTurn(this, joined, left);
+ }
+ }
+
+ public void run() {
+
+ if (!attackSide(side1, side2, left)) {
+ return;
+ }
+ if (!attackSide(side2, side1, left)) {
+ return;
+ }
+
+ checkDeath(side1, side2, left);
+ checkDeath(side2, side1, left);
+
+ if (side2.isEmpty() || side1.isEmpty()) {
+ endBattle();
+ return;
+ }
+
+ endTurn(side1, joined, left);
+ endTurn(side2, joined, left);
+ }
+
+ /**
+ * Must only be called from a battle callback or internally
+ *
+ * @param thing
+ */
+ void removeParticipant(Active thing) {
+ side1.remove(thing);
+ side2.remove(thing);
+ left.remove(thing);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ * Feb-2013 Michael Zucchi - modernised java
+ * Mar-2013 Michael Zucchi - changed server protocol
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskProtocol;
+import java.util.ArrayList;
+
+/**
+ * Command parser.
+ *
+ * This only implements unprivileged commands, each user only
+ * gets a command parser suitable for their privilege level.
+ *
+ * Each command parser is attached to a single Active.
+ *
+ * FIXME: merge with PlayerCommands
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Commands implements DuskProtocol {
+
+ private Active lt;
+
+ public Commands(Active lt) {
+ this.lt = lt;
+ }
+
+ /**
+ * Simple command line parser, handles quotes (' or "),
+ * literal escapes within or without quotes, and stripping
+ * whitespace.
+ *
+ * @param cmdLine
+ * @return list of unquoted and separated arguments
+ */
+ protected String[] parseArgs(String cmdLine) {
+ StringBuilder arg = new StringBuilder();
+ ArrayList<String> args = new ArrayList<>();
+
+ char q = 0;
+ int e = 0;
+ for (int i = 0; i < cmdLine.length(); i++) {
+ char c = cmdLine.charAt(i);
+
+ if (e > 0) {
+ // escape
+ arg.append(c);
+ e--;
+ } else if (q == 0) {
+ // unquoted text
+ if (c == '"' || c == '\'') {
+ q = c;
+ } else if (c == ' ' || c == '\t') {
+ if (arg.length() != 0) {
+ args.add(arg.toString());
+ arg.setLength(0);
+ }
+ } else if (c == '\\') {
+ e = 1;
+ } else {
+ arg.append(c);
+ }
+ } else {
+ // quoted text
+ if (c == q) {
+ q = 0;
+ if (arg.length() != 0) {
+ args.add(arg.toString());
+ arg.setLength(0);
+ }
+ } else if (c == '\\') {
+ e = 1;
+ } else {
+ arg.append(c);
+ }
+ }
+ }
+ if (arg.length() != 0) {
+ args.add(arg.toString());
+ }
+
+ return args.toArray(new String[args.size()]);
+ }
+ static final String alias_key = ";:'./";
+ static final String alias_values[] = {"gossip", "clan", "say", "emote", "tell"};
+
+ public void execute(String cmdline) throws Exception {
+ String argv[] = parseArgs(cmdline);
+
+ if (argv.length < 1) {
+ lt.chatMessage("Huh?");
+ return;
+ }
+
+ // TODO: scripted commands here
+ String cmd; // command name
+ String text; // all arguments unprocessed
+
+ int alias = alias_key.indexOf(argv[0].charAt(0));
+ if (alias >= 0) {
+ cmd = alias_values[alias];
+ // TODO: strip thing from cmdline properly
+ text = cmdline.substring(1).trim();
+ } else {
+ cmd = argv[0].toLowerCase();
+ text = cmdline.substring(cmd.length()).trim();
+ }
+
+ String msg = null;
+
+ switch (cmd) {
+ case "north":
+ case "n":
+ lt.clearMoveQueue();
+ lt.enqueueMove("n");
+ return;
+ case "south":
+ case "s":
+ lt.clearMoveQueue();
+ lt.enqueueMove("s");
+ return;
+ case "west":
+ case "w":
+ lt.clearMoveQueue();
+ lt.enqueueMove("w");
+ return;
+ case "east":
+ case "e":
+ lt.clearMoveQueue();
+ lt.enqueueMove("e");
+ return;
+ case "goto": {
+ int destX;
+ int destY;
+ try {
+ destX = Integer.parseInt(argv[1]);
+ destY = Integer.parseInt(argv[2]);
+ lt.travelTo(destX, destY, true);
+ } catch (NumberFormatException e) {
+ msg = "goto requires two numbers.";
+ }
+ break;
+ }
+ case "flee":
+ lt.fleeCommand();
+ break;
+ case "sleep":
+ lt.sleepCommand();
+ break;
+ case "wake":
+ lt.awakeCommand();
+ break;
+ case "gossip":
+ if (text.length() == 0) {
+ msg = "Gossip what?";
+ } else if (text.length() > lt.game.messageLimit)
+ msg = "That message was goo long.";
+ else {
+ lt.gossipCommand(text);
+ }
+ break;
+ /// clan
+ case "say":
+ if (text.length() == 0) {
+ msg = "Say what?";
+ } else if (text.length() > lt.game.messageLimit) {
+ msg = "That message was goo long.";
+ } else {
+ lt.sayCommand(text);
+ }
+ break;
+ case "kill":
+ case "attack":
+ case "a":
+ if (argv.length != 2) {
+ msg = "Attack what?";
+ } else {
+ lt.attackCommand(argv[1]);
+ }
+ break;
+ case "look":
+ if (argv.length != 2) {
+ msg = "Look at what?";
+ } else {
+ lt.lookCommand(argv[1]);
+ }
+ break;
+ case "wear": {
+ if (argv.length < 2) {
+ msg = "Wear what?";
+ } else {
+ for (int i = 1; i < argv.length; i++) {
+ lt.wearCommand(argv[i]);
+ }
+ }
+ break;
+ }
+ case "unwear": {
+ if (argv.length < 2) {
+ msg = "Unwear what?";
+ } else {
+ for (int i = 1; i < argv.length; i++) {
+ lt.unwearCommand(argv[i]);
+ }
+ }
+ break;
+ }
+ case "drop": {
+ if (argv.length < 2) {
+ msg = "Drop what?";
+ } else {
+ for (int i = 1; i < argv.length; i++) {
+ lt.dropCommand(argv[i]);
+ }
+ }
+ break;
+ }
+ case "get": {
+ if (argv.length < 2) {
+ msg = "Get what?";
+ } else {
+ for (int i = 1; i < argv.length; i++) {
+ lt.getCommand(argv[i]);
+ }
+ }
+ break;
+ }
+ case "use": {
+ // TODO: battle check inside active?
+ if (argv.length < 2) {
+ msg = "Use what?";
+ } else if (lt.isFighting()) {
+ lt.addBattleCommand(argv);
+ } else {
+ for (int i = 1; i < argv.length; i++) {
+ lt.useCommand(argv[i]);
+ }
+ }
+ break;
+ }
+ case "follow": {
+ if (argv.length < 2) {
+ msg = "Follow whom?";
+ } else {
+ lt.followCommand(argv[1]);
+ }
+ break;
+ }
+ case "leave": {
+ lt.leaveCommand();
+ break;
+ }
+ case "buy": {
+ if (argv.length < 2) {
+ msg = "Buy what?";
+ } else if (argv.length < 3) {
+ msg = "How many of what do you wish to buy?";
+ } else {
+ try {
+ int quantity = Integer.valueOf(argv[1]);
+
+ if (quantity <= 0) {
+ msg = "Quanitity must be at least one.";
+ } else
+ lt.buyCommand(argv[2], quantity);
+ } catch (NumberFormatException ex) {
+ msg = "I don't know how many that is.";
+ }
+ }
+ break;
+ }
+ case "sell": {
+ if (argv.length < 2) {
+ msg = "Sell what?";
+ } else if (argv.length < 3) {
+ msg = "How many of what do you wish to sell?";
+ } else {
+ try {
+ int quantity = Integer.valueOf(argv[1]);
+
+ if (quantity <= 0) {
+ msg = "Quanitity must be at least one.";
+ } else
+ lt.sellCommand(argv[2], quantity);
+ } catch (NumberFormatException ex) {
+ msg = "I don't know how many that is.";
+ }
+ }
+ break;
+ }
+ }
+
+ if (msg != null)
+ lt.chatMessage(msg);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.util.Objects;
+
+/**
+ * Conditions are *user-visible* things that occur for a time,
+ * they can invoke scripts for start/occurance/finish.
+ *
+ * Scripts and code can use other mechanisms for hidden values.
+ *
+ * Script actions are located by convention based on the name.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Condition {
+
+ /**
+ * Name of description.
+ */
+ public String name;
+ /**
+ * Long description
+ */
+ //public String description;
+ /**
+ * Total duration (left, after reload) of condition
+ */
+ int duration;
+ /**
+ * How many ticks between triggering an occurance, <= 0 to disable
+ */
+ int rate = 1;
+ /**
+ * Start of condition, -1 means new condition.
+ */
+ int start = -1;
+ int end = -1;
+
+ public Condition() {
+ }
+
+ public Condition(String name, int duration) {
+ this.name = name;
+ this.duration = duration;
+ }
+
+ public Condition(String name, int duration, int rate) {
+ this.name = name;
+ this.duration = duration;
+ this.rate = rate;
+ }
+
+ void onStart(Active host) {
+ host.game.onCondition(host, this, "start");
+ }
+
+ void onOccurance(Active host) {
+ host.game.onCondition(host, this, "tick");
+ }
+
+ void onEnd(Active host) {
+ host.game.onCondition(host, this, "end");
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Condition) {
+ Condition c = (Condition) obj;
+
+ return c.name.equals(name);
+ } else
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 17 * hash + Objects.hashCode(this.name);
+ return hash;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class ConditionList {
+
+ private final HashMap<String, Condition> conditions = new HashMap<>();
+ private final List<Condition> endedConditions = new ArrayList<>();
+
+ /* ***************
+ * Conditions
+ * ***************/
+ public synchronized void setCondition(Condition c) {
+ Condition old = conditions.put(c.name.toLowerCase(), c);
+ c.start = -1;
+
+ if (old != null)
+ endedConditions.add(old);
+ }
+
+ public List<String> getActiveConditions() {
+ return new ArrayList<>(conditions.keySet());
+ }
+
+ /**
+ * Clear a condition by name.
+ *
+ * @param name
+ * @return true if there are no active conditions
+ */
+ public synchronized boolean clearCondition(String name) {
+ Condition old = conditions.remove(name.toLowerCase());
+ if (old != null) {
+ endedConditions.add(old);
+ }
+ return conditions.isEmpty();
+ }
+
+ /**
+ * Iterate through all conditions running their actions.
+ *
+ * This also handles starting, occurance, and ending conditions
+ *
+ * @return true if the set of active conditions changed.
+ */
+ public synchronized boolean checkConditions(Active host, int tick) {
+ Condition[] list = conditions.values().toArray(new Condition[conditions.size()]);
+ boolean changed = false;
+
+ // End the pending ones
+ for (Condition c : endedConditions) {
+ c.onEnd(host);
+ changed = true;
+ }
+ endedConditions.clear();
+
+ for (Condition c : list) {
+ if (c.start == -1) {
+ c.start = tick;
+ c.end = tick + c.duration;
+ c.onStart(host);
+ changed = true;
+ }
+ if (c.rate > 0 && ((tick - c.start) % c.rate) == 0) {
+ c.onOccurance(host);
+ }
+ if (tick >= c.end) {
+ conditions.remove(c.name);
+ c.onEnd(host);
+ changed = true;
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * i/o
+ */
+ public boolean setProperty(String name, String value) {
+ if (name.equals("condition")) {
+ int c1 = value.indexOf(',');
+ int c2 = value.indexOf(',', c1 + 1);
+
+ Condition c = new Condition();
+ c.name = value.substring(0, c1);
+ c.duration = Integer.valueOf(value.substring(c1 + 1, c2));
+ c.rate = Integer.valueOf(value.substring(c2 + 1));
+
+ conditions.put(c.name.toLowerCase(), c);
+ return true;
+ }
+ return false;
+ }
+
+ public void writeProperties(BufferedWriter out) throws IOException {
+ for (Condition c : conditions.values()) {
+ out.write("condition=");
+
+ // FIXME: i need the duration remaining, which is now - start?
+ out.write(c.name);
+ out.write(',');
+ out.write(String.valueOf(c.duration));
+ out.write(',');
+ out.write(String.valueOf(c.rate));
+ out.write('\n');
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+
+/**
+ * A container that can hold other items - simply increases carrying capacity.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Container extends Holdable {
+
+ public Container(Game game) {
+ super(game);
+ }
+ int volumeLimit;
+ int weightLimit;
+
+ @Override
+ public int getType() {
+ return TYPE_CONTAINER;
+ }
+
+ @Override
+ public int getWearing() {
+ // FIXME: food?
+ return Wearing.INVENTORY;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Convert old things to new things
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Converter {
+
+ static void add(BufferedWriter dst, String name, String value) {
+ }
+
+ public static class PropWriter extends BufferedWriter {
+
+ public PropWriter(Writer out) {
+ super(out);
+ }
+
+ void add(String name, String value) throws IOException {
+ append(name);
+ append("=");
+ append(value);
+ append('\n');
+ }
+ }
+
+ static void addWear(String name, BufferedReader in, PropWriter dst) throws IOException {
+ dst.add(name, in.readLine().toLowerCase() + "," + in.readLine() + "," + in.readLine());
+ }
+
+ static boolean parsePlayer(String type, BufferedReader in, PropWriter dst) throws IOException {
+ switch (type.toLowerCase()) {
+ case "timestamp":
+ case "privs":
+ case "clan":
+ case "race":
+ case "title":
+ case "description":
+ case "map":
+ case "x":
+ case "y":
+ case "hp":
+ case "maxhp":
+ case "mp":
+ case "maxmp":
+ case "cash":
+ case "exp":
+ case "image":
+ dst.add(type, in.readLine());
+ break;
+ case "petname": // but it has pet too? which one?
+ case "pet":
+ dst.add("pet", in.readLine());
+ break;
+ case "master":
+ dst.add(type, in.readLine());
+ break;
+ case "skill":
+ dst.add(type, in.readLine() + "," + in.readLine());
+ //type = in.readLine();
+ //addToSkill(type, Byte.parseByte(in.readLine()));
+ break;
+ case "condition": {
+ // name,passed,duration
+ dst.add(type, in.readLine() + "," + in.readLine() + "," + in.readLine());
+ // FIXME: i/o to Condition
+ // Condition cndStore = game.getCondition(in.readLine());
+ // cndStore.ticksPast = Integer.parseInt(in.readLine());
+ // cndStore.duration = Integer.parseInt(in.readLine());
+ // if (cndStore.duration < -1) { // only necessary to repair
+ // cndStore.duration = -1; // after bug fix, can go away
+ // }
+ // addCondition(cndStore);
+ break;
+ }
+ case "item": {
+ dst.add(type, in.readLine());
+ //Item itmStore = game.getItem(in.readLine());
+ //if (itmStore != null) {
+ // itemList.addElement(itmStore);
+ //}
+ break;
+ }
+ case "item2": {
+ addWear("item", in, dst);
+ //for compatibility. Will be replaced with item later
+ //Item itmStore = game.getItem(in.readLine());
+ //if (itmStore != null) {
+ // itmStore.lngDurability = Long.parseLong(in.readLine());
+ // itmStore.intUses = Integer.parseInt(in.readLine());
+ // itemList.addElement(itmStore);
+ //}
+ break;
+ }
+
+ case "sp":
+ dst.add("mp", in.readLine());
+ break;
+ case "maxsp":
+ dst.add("maxmp", in.readLine());
+ break;
+ case "stre":
+ case "str":
+ dst.add("str", in.readLine());
+ break;
+ case "inte":
+ case "int":
+ dst.add("int", in.readLine());
+ break;
+ case "dext":
+ dst.add("dex", in.readLine());
+ break;
+ case "cons":
+ dst.add("con", in.readLine());
+ break;
+ case "wisd":
+ dst.add("wis", in.readLine());
+ break;
+ // FIXME: do i need these 'old' versions?
+ case "wield":
+ case "arms":
+ case "legs":
+ case "torso":
+ case "waist":
+ case "neck":
+ case "skull":
+ case "eyes":
+ case "hands":
+ dst.add(type.toLowerCase(), in.readLine() + ",,");
+ break;
+ case "wield2":
+ addWear("wield", in, dst);
+ break;
+ case "arms2":
+ addWear("arms", in, dst);
+ break;
+ case "legs2":
+ addWear("legs", in, dst);
+ break;
+ case "torso2":
+ addWear("torso", in, dst);
+ break;
+ case "waist2":
+ addWear("waist", in, dst);
+ break;
+ case "neck2":
+ addWear("neck", in, dst);
+ break;
+ case "skull2":
+ addWear("skull", in, dst);
+ break;
+ case "eyes2":
+ addWear("eyes", in, dst);
+ break;
+ case "hands2":
+ addWear("hands", in, dst);
+ break;
+ case "nofollow":
+ dst.add(type, "true");
+ break;
+ case "nopopup":
+ case "audiooff":
+ case "coloroff":
+ break;
+ case "nochannel":
+ dst.add(type, in.readLine());
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ static boolean parseMob(String type, BufferedReader in, PropWriter dst) throws IOException {
+ // everything in player, plus ...
+
+
+ switch (type.toLowerCase()) {
+ case "bravery":
+ case "grouprelation":
+ case "faction":
+ dst.add(type, in.readLine());
+ break;
+ case "giveitem":
+ dst.add(type, in.readLine().toLowerCase() + "," + in.readLine());
+ break;
+ case "givegp":
+ dst.add("givegold", in.readLine());
+ break;
+ // nice: mob format is different ...
+ case "wield":
+ case "arms":
+ case "legs":
+ case "torso":
+ case "waist":
+ case "neck":
+ case "skull":
+ case "eyes":
+ case "hands":
+ addWear(type.toLowerCase(), in, dst);
+ break;
+ case "onbattle":
+ dst.add("onBattle", in.readLine());
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ static boolean parseRace(String type, BufferedReader in, PropWriter dst) throws IOException {
+ switch (type) {
+ case "hp":
+ case "mp":
+ case "image":
+ dst.add(type, in.readLine());
+ break;
+ case "stre":
+ case "str":
+ dst.add("str", in.readLine());
+ break;
+ case "inte":
+ case "int":
+ dst.add("int", in.readLine());
+ break;
+ case "dext":
+ dst.add("dex", in.readLine());
+ break;
+ case "cons":
+ dst.add("con", in.readLine());
+ break;
+ case "wisd":
+ dst.add("wis", in.readLine());
+ break;
+ // i don't think these are used.
+ case "range":
+ case "hp_limit":
+ case "mp_limit":
+ case "exp_limit":
+ case "stre_limit":
+ case "inte_limit":
+ case "dext_limit":
+ case "cons_limit":
+ case "wisd_limit":
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ static void convertPlayer(File src, File dst) throws IOException {
+ System.out.println("converting: " + src);
+ try (BufferedReader in = new BufferedReader(new FileReader(src));
+ PropWriter out = new PropWriter(new FileWriter(dst))) {
+ String line;
+
+ out.add("name", src.getName());
+ if (!src.getName().equals("default"))
+ out.add("password", in.readLine());
+
+ while ((line = in.readLine()) != null) {
+ line = line.trim();
+ if (line.equals("."))
+ break;
+ if (line.equals("") || line.startsWith("#"))
+ continue;
+ if (!parsePlayer(line, in, out)) {
+ System.out.println(" unknown parameter: " + line);
+ }
+ }
+ }
+ // engGame.log.printError("parseUserFile():Parsing \"" + strStore + "\" from " + entityName + "'s file", e);
+ }
+
+ static void convertMob(File src, File dst) throws IOException {
+ System.out.println("converting: " + src);
+ try (BufferedReader in = new BufferedReader(new FileReader(src));
+ PropWriter out = new PropWriter(new FileWriter(dst))) {
+ String line;
+
+ out.add("name", src.getName());
+
+ while ((line = in.readLine()) != null) {
+ line = line.trim();
+ if (line.equals("."))
+ break;
+ if (line.equals("") || line.startsWith("#"))
+ continue;
+ if (!(parseMob(line, in, out)
+ || parsePlayer(line, in, out))) {
+ System.out.println(" unknown parameter: " + line);
+ }
+ }
+ }
+ // engGame.log.printError("parseUserFile():Parsing \"" + strStore + "\" from " + entityName + "'s file", e);
+ }
+
+ static void convertRace(File src, File dst) throws IOException {
+ System.out.println("converting: " + src);
+ try (BufferedReader in = new BufferedReader(new FileReader(src));
+ PropWriter out = new PropWriter(new FileWriter(dst))) {
+ String line;
+
+ out.add("name", src.getName());
+
+ while ((line = in.readLine()) != null) {
+ line = line.trim();
+ if (line.equals("."))
+ break;
+ if (line.equals("") || line.startsWith("#"))
+ continue;
+ if (!parseRace(line, in, out)) {
+ System.out.println(" unknown parameter: " + line);
+ }
+ }
+ }
+ }
+
+ static boolean parseItem(String type, BufferedReader in, Holdable item) throws IOException {
+ switch (type) {
+ case "type":
+ // discard type
+ in.readLine();
+ break;
+ case "description":
+ item.description = in.readLine();
+ break;
+ case "kind":
+ String w = in.readLine().toLowerCase();
+ switch (w) {
+ case "arms":
+ ((Armour) item).worn = Wearing.ARMS;
+ break;
+ case "legs":
+ ((Armour) item).worn = Wearing.LEGS;
+ break;
+ case "torso":
+ ((Armour) item).worn = Wearing.TORSO;
+ break;
+ case "waist":
+ ((Armour) item).worn = Wearing.WAIST;
+ break;
+ case "neck":
+ ((Armour) item).worn = Wearing.NECK;
+ break;
+ case "skull":
+ ((Armour) item).worn = Wearing.SKULL;
+ break;
+ case "eyes":
+ ((Armour) item).worn = Wearing.EYES;
+ break;
+ case "hands":
+ ((Armour) item).worn = Wearing.HANDS;
+ break;
+ default:
+ System.out.println(" unknown armour location: " + w);
+ break;
+ }
+ break;
+ case "cost":
+ item.cost = Integer.valueOf(in.readLine());
+ break;
+ case "durability":
+ ((Wearable) item).durability = Long.valueOf(in.readLine());
+ break;
+ case "uses":
+ item.uses = Integer.valueOf(in.readLine());
+ break;
+ case "mod": {
+ int mod = Integer.valueOf(in.readLine());
+ if (item instanceof Wearable) {
+ ((Wearable) item).mod = mod;
+ } else if (mod != 0) {
+ System.out.println(" setting mod on non-wearable item = " + mod);
+ }
+ break;
+ }
+ case "range":
+ ((Weapon) item).range = Integer.valueOf(in.readLine());
+ break;
+ case "image":
+ item.image = Integer.valueOf(in.readLine());
+ break;
+ case "onuse":
+ item.onUse = in.readLine();
+ break;
+ case "onwear":
+ ((Wearable) item).onWear = in.readLine();
+ break;
+ case "onunwear":
+ ((Wearable) item).onUnwear = in.readLine();
+ break;
+ case "onget":
+ item.onGet = in.readLine();
+ break;
+ case "ondrop":
+ item.onDrop = in.readLine();
+ break;
+ default:
+ return false;
+ }
+ return true;
+ } // engGame.log.printError("parseUserFile():Parsing \"" + strStore + "\" from " + entityName + "'s file", e);
+
+ static void convertItem(File src, File dst) throws IOException {
+ System.out.println("converting: " + src);
+ String type = null;
+ // First find type
+ try (BufferedReader in = new BufferedReader(new FileReader(src))) {
+ String line;
+
+ while ((line = in.readLine()) != null) {
+ if (line.toLowerCase().equals("type")) {
+ type = in.readLine().trim().toLowerCase();
+ }
+ }
+ }
+ if (type == null)
+ type = "item";
+ //type = armor
+ //type = drink
+ //type = food
+ //type = item
+ //type = weapon
+
+ Holdable item;
+ switch (type) {
+ case "armor":
+ item = new Armour(null);
+ break;
+ case "drink":
+ item = new Drink(null);
+ break;
+ case "food":
+ item = new Food(null);
+ break;
+ case "item":
+ item = new Item(null);
+ break;
+ case "weapon":
+ item = new Weapon(null);
+ break;
+ default:
+ System.out.println("unknown type: " + type);
+ return;
+ }
+
+ //System.out.println(" type = " + type);
+
+ // hang on, items don't have an x/y when saved ...
+ TileMap mainmap = new TileMap("main", 0, 0);
+
+ try (BufferedReader in = new BufferedReader(new FileReader(src));
+ BufferedWriter out = new BufferedWriter(new FileWriter(dst))) {
+ String line;
+
+ item.name = src.getName();
+
+ while ((line = in.readLine()) != null) {
+ line = line.trim();
+ if (line.equals("."))
+ break;
+ if (line.equals("") || line.startsWith("#"))
+ continue;
+ if (!parseItem(line.toLowerCase(), in, item)) {
+ System.out.println(" unknown parameter: " + line);
+ }
+ }
+
+ item.map = mainmap;
+
+ out.append("type.");
+ out.append(item.getClass().getSimpleName());
+ out.append("=");
+ out.append(item.name);
+ out.append('\n');
+
+ item.writeProperties(out);
+
+ out.append("=end");
+ }
+ }
+
+ static public void main(String[] args) {
+ File old = new File("/home/notzed/src/DuskRPG/DuskFiles/DuskX");
+ File game = new File("/home/notzed/dusk/game");
+
+ if (true) {
+ for (File f : new File(old, "users").listFiles()) {
+ try {
+ convertPlayer(f, new File(game, "players/" + f.getName()));
+ } catch (IOException ex) {
+ Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ for (File f : new File(old, "defMobs").listFiles()) {
+ try {
+ convertMob(f, new File(game, "defMobs/" + f.getName()));
+ } catch (IOException ex) {
+ Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ for (File f : new File(old, "defRaces").listFiles()) {
+ try {
+ convertRace(f, new File(game, "defRaces/" + f.getName()));
+ } catch (IOException ex) {
+ Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+ for (File f : new File(old, "defItems").listFiles()) {
+ try {
+ convertItem(f, new File(game, "defItems/" + f.getName()));
+ } catch (Exception ex) {
+ ex.printStackTrace(System.out);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+
+/**
+ * Liquid consumables
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Drink extends Holdable {
+
+ public Drink(Game game) {
+ super(game);
+ }
+
+ public int getType() {
+ return TYPE_DRINK;
+ }
+
+ @Override
+ public int getWearing() {
+ // FIXME: food?
+ return Wearing.INVENTORY;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ * Feb-2013 Michael Zucchi - modernised java, big refactor.
+ * Mar-2013 Michael Zucchi - changed server protocol
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import duskz.protocol.ListMessage;
+import duskz.protocol.Wearing;
+import static duskz.protocol.Wearing.*;
+
+/**
+ * Equipment contains all the Items a LivingThing is wearing.
+ *
+ * @author Tom Weingarten
+ */
+public class Equipment {
+
+ // Must match Wearing
+ public static final String[] USER_NAMES = {
+ "wield2", "arms2", "legs2", "torso2", "waist2", "neck2", "skull2", "eyes2", "hands2"
+ };
+ public static final float[] ARMOUR_MOD = {
+ 0f, 0.05f, 0.05f, 0.40f, 0.15f, 0.05f, 0.20f, 0.05f, 0.05f
+ };
+ private Wearable[] worn = new Wearable[WEARING_COUNT];
+
+ /**
+ * Encode for network.
+ *
+ * @return
+ */
+ public String toEntity() {
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < worn.length; i++) {
+ Wearable item = worn[i];
+ if (item != null)
+ sb.append(item.name).append('\n');
+ else
+ sb.append("none\n");
+ }
+ return sb.toString();
+ }
+
+ public DuskMessage toMessage(int msgid) {
+ ListMessage msg = new ListMessage(msgid);
+
+ for (int i = 0; i < worn.length; i++) {
+ Wearable item = worn[i];
+ if (item != null)
+ msg.add(i, item.name);
+ }
+
+ return msg;
+ }
+
+ public Wearable getWorn(int where) {
+ return worn[where];
+ }
+
+ public int getWornIndex(String what) {
+ for (int i=0;i<worn.length;i++) {
+ Wearable item = worn[i];
+
+ if (item != null && what.equalsIgnoreCase(item.name))
+ return i;
+ }
+ return -1;
+ }
+
+ public boolean isWearing(String what) {
+ for (Wearable item : worn) {
+ if (item != null && what.equalsIgnoreCase(item.name))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Where a sepecific item
+ *
+ * @param where
+ * @param item if null, the item at position 'where' is removed, if any
+ * @return
+ */
+ public Wearable wear(int where, Wearable item) {
+ if (item != null) {
+ if (item.getWearing() != where) {
+ // FIXME: cachable exception?
+ throw new RuntimeException("can't wear an item " + item.name
+ + " type " + item.getClass().getSimpleName()
+ + " at " + Wearing.wornNames[where]
+ + " should be " + Wearing.wornNames[item.getWearing()]);
+ }
+ }
+ Wearable old = worn[where];
+
+ worn[where] = item;
+ return old;
+ }
+
+ /**
+ * Unwear item at specific location
+ *
+ * @param where
+ * @return item if worn
+ */
+ public Wearable unwear(int where) {
+ Wearable old = worn[where];
+
+ worn[where] = null;
+ return old;
+ }
+
+ /**
+ * Unwear an item of a sepcific name
+ *
+ * @param name
+ * @return item if worn
+ */
+ public Wearable unwearByName(String name) {
+ for (int i = 0; i < worn.length; i++) {
+ Wearable item = worn[i];
+
+ if (item != null && name.equalsIgnoreCase(item.name)) {
+ worn[i] = null;
+ return item;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convert to wield type index
+ *
+ * @param where
+ * @return
+ */
+ public static int toIndex(String where) {
+ switch (where.toLowerCase()) {
+ case "wielded":
+ return WIELD;
+ case "arms":
+ return ARMS;
+ case "legs":
+ return LEGS;
+ case "torso":
+ return TORSO;
+ case "waist":
+ return WAIST;
+ case "neck":
+ return NECK;
+ case "skull":
+ return SKULL;
+ case "eyes":
+ return EYES;
+ case "hands":
+ return HANDS;
+ }
+ return -1;
+ }
+
+ public int armourCount() {
+ int count = 0;
+ for (int i = ARMS; i < worn.length; i++) {
+ if (worn[i] != null)
+ count++;
+ }
+ return count;
+ }
+
+ public int armourMod() {
+ int mod = 0;
+
+ for (int i = 0; i < worn.length; i++) {
+ Wearable item = worn[i];
+
+ if (item != null) {
+ mod += ARMOUR_MOD[i] * item.mod;
+ }
+ }
+ return mod;
+ }
+
+ /**
+ * Damage the specified item if it exists
+ *
+ * @param damage
+ * @return the weapon if it was destroyed
+ */
+ public Wearable damageItem(int index, int damage) {
+ Wearable item = worn[index];
+
+ if (item != null && item.durability != -1) {
+ item.durability -= damage;
+ if (item.durability < 0) {
+ worn[index] = null;
+ return item;
+ }
+ }
+ return null;
+ }
+ // TODO: load/save functions here too?
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ * Feb-2013 Michael Zucchi - Moved to new object structure/clean up.
+ */
+/**
+ * a Faction represents a group of mobs.
+ *
+ * @author Tom Weingarten
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * Faction is a group of mobs. TODO: should it override/merge the normal group thing
+ */
+public class Faction {
+
+ final public String name;
+ /**
+ * This is keyed on both player names and clans. Seems it should be two
+ * separate tables
+ */
+ private final HashMap<String, Relation> relations = new HashMap<>();
+ private Game game;
+ private boolean changed = false;
+
+ public Faction(String name, Game game) {
+ this.name = name;
+ this.game = game;
+ }
+
+ public void load() throws IOException {
+ // FIXME: chagne faction format
+ File file = new File(game.getRoot(), "factions/" + name);
+ try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+ String line = br.readLine();
+ while (line != null && !line.equals(".")) {
+ if (line.equalsIgnoreCase("relation")) {
+ Relation r = new Relation(br.readLine(), Double.valueOf(br.readLine()));
+ relations.put(r.name, r);
+ }
+ line = br.readLine();
+ }
+ }
+ }
+
+ synchronized void saveFactionData() {
+ /*
+ ** Only save faction data if it has changed.
+ */
+ if (!changed) {
+ return;
+ }
+ File file = new File(game.getRoot(), "factions/" + name);
+ try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
+ for (Relation r : relations.values()) {
+ bw.write("relation\n" + r.name + "\n" + r.level + "\n");
+ }
+ bw.write(".\n");
+ changed = false;
+ } catch (IOException e) {
+ //game.log.printError("saveFactionData()", e);
+ }
+ }
+
+ double getRelationValue(String name) {
+ Relation r = relations.get(name.toLowerCase());
+
+ return r != null ? r.level : 0;
+ }
+
+ Relation getRelation(String name) {
+ return relations.get(name.toLowerCase());
+ }
+
+ boolean updateRelation(String name, int delta) {
+ Relation rel = getRelation(name);
+
+ if (rel == null) {
+ rel = new Relation(name, 0);
+ relations.put(name, rel);
+ }
+
+ return rel.updateRelation(delta);
+ }
+
+ void killedByEnemy(Mobile died, Active killer) {
+ if (game.isPreference(Game.PREF_AI)) {
+ int delta = killer.getCP() - died.getCP();
+
+ changed |= updateRelation(killer.name, delta);
+
+ if (killer.clan != null && !killer.clan.equals("none")) {
+ changed |= updateRelation(killer.clan, delta);
+ }
+ }
+ }
+
+ void runAI(Mobile mob) {
+ if (!game.isPreference(Game.PREF_AI)) {
+ mob.canSeePlayer = false;
+ return;
+ }
+
+ //Battle AI (inside Battle class)
+ if (mob.isFighting()) {
+ return;
+ }
+
+ // FIXME: can this be moved to DuskEngine somehow?
+
+ //Default AI
+ int intConfidence = 0;
+ Active enemy = null;
+ double enemyrelation = 0;
+ boolean visiblePlayer = false;
+ for (TileMap.MapData md : mob.map.range(mob.x, mob.y, game.viewRange - 1)) {
+ for (Thing o : md.entities) {
+ if (o.getType() == Thing.TYPE_MOBILE || o.getType() == Thing.TYPE_PLAYER) {
+ Active lt = (Active) o;
+
+ if (mob.onCanSeeActive(lt) && mob.canSee(lt.x, lt.y)) {
+ if (o.getType() == Thing.TYPE_PLAYER) {
+ visiblePlayer = true;
+ double relation = getRelationValue(lt.name);
+ if (!(lt.clan == null || lt.clan.equals("none"))) {
+ relation = (relation + getRelationValue(lt.clan)) / 2;
+ }
+ if (relation < 0 && (enemy == null || enemyrelation < relation)) {
+ enemy = lt;
+ enemyrelation = relation;
+ }
+ intConfidence += relation * lt.getTP();
+ System.out.printf("mob '%s' can see player '%s' relation %f confidence=%d\n", mob.name, lt.name, relation, intConfidence);
+ System.out.printf(" player points total = %d armour %d damage %d cmponent %f\n", lt.getTP(), lt.getArmourMod(), lt.getDamageMod(), relation * lt.getTP());
+ } else {
+ double relation;
+
+ Mobile m = (Mobile) lt;
+ if (m.name.equals(mob.name)) {
+ relation = m.groupRelation;
+ } else {
+ relation = getRelationValue(m.name);
+ }
+ intConfidence += relation * m.getTP();
+ if (relation < 0 && (enemy == null || enemyrelation > relation)) {
+ enemy = m;
+ enemyrelation = relation;
+ }
+ intConfidence += relation * lt.getTP();
+ System.out.printf("mob '%s' can see mob '%s' relation %f confidence=%d\n", mob.name, lt.name, relation, intConfidence);
+ System.out.printf(" player points total = %d armour %d damage %d component %f\n", lt.getTP(), lt.getArmourMod(), lt.getDamageMod(), relation * lt.getTP());
+ }
+ }
+ }
+ }
+ }
+ mob.canSeePlayer = visiblePlayer;
+ if (!visiblePlayer) {
+ return;
+ }
+ if (enemy != null) {
+ double delta = (mob.getTP() + intConfidence) * mob.bravery * -1 * enemyrelation;
+ int enemycp = enemy.getTP();
+ //Fight/flee
+ if (enemycp < delta) {
+ if (enemycp > delta - (delta * 0.1 * Math.random())) {
+ return;
+ }
+ //Fight
+ if (enemy.distanceL1(mob) <= mob.getRangeWithBonus()) {
+ System.out.println(mob.name + " close enough, going into battle distance: " + enemy.distanceL1(mob) + " range: " + mob.getRangeWithBonus());
+ // close enough to attack, so stop moving
+ mob.clearMoveQueue();
+ mob.createBattle(enemy);
+ /*
+ try {
+ // TODO: just call duskEngine.newBattle directly?
+ Commands.parseCommand(mob, game, "a " + enemy.name);
+ } catch (Exception e) {
+ game.log.printError("runAI():" + mob.name + " had an error attacking " + enemy.name, e);
+ }*/
+ } else {
+ System.out.println("Mob ai: move to enemy " + enemy.name + " " + enemy.x + "," + enemy.y);
+ mob.travelTo(enemy.x, enemy.y, false);
+ }
+ } else {
+ if (enemycp < delta + (delta * 0.1 * Math.random())) {
+ return;
+ }
+ //Flee
+ int destX = mob.x + Integer.signum(mob.x - enemy.x) * game.viewRange;
+ int destY = mob.y + Integer.signum(mob.y - enemy.y) * game.viewRange;
+ System.out.println("Mob ai: flee from " + enemy.name + " " + destX + "," + destY);
+ mob.travelTo(destX, destY, true);
+ }
+ }
+
+ //If no enemies
+ if ((int) (Math.random() * 25) == 1) {
+ if ((int) (Math.random() * 2) == 1) {
+ if ((int) (Math.random() * 2) == 1) {
+ mob.enqueueMove("e");
+ } else {
+ mob.enqueueMove("w");
+ }
+ } else {
+ if ((int) (Math.random() * 2) == 1) {
+ mob.enqueueMove("s");
+ } else {
+ mob.enqueueMove("n");
+ }
+ }
+ }
+ }
+
+ /**
+ * a Relation represents a feeling held by one faction for another faction, a
+ * player, or a clan. The mob AI bases it's decisions around Relations.
+ *
+ * @author Tom Weingarten
+ */
+ static class Relation {
+
+ String name;
+ double level = 2; //-1 to 1
+
+ Relation(String inName, double inLevel) {
+ name = inName.toLowerCase();
+ level = inLevel;
+ }
+
+ /**
+ *
+ * Uses an optimized form of the function:
+ * ((1.03^delta) + (1.03^-delta)) / (2 + (1.03^delta) + (1.03^-delta))
+ *
+ * But I don't know why ...
+ *
+ * @param delta
+ * @returns true if the level changes
+ */
+ boolean updateRelation(int delta) {
+ double old = level;
+
+ if (delta == 0) {
+ level -= (.5) * (1D + level);
+ } else if (delta > 0) {
+ level -= ((((Math.pow(1.03, delta)) / (Math.pow(1.03, delta) + 2))) / 2) * (1 + level);
+ } else {
+ level -= ((Math.pow(1.03, (-1 * delta)) / (Math.pow(1.03, (-1 * delta)) + 2)) / 2) * (1 + level);
+ }
+
+ return old != level;
+ }
+
+ @Override
+ public String toString() {
+ return "Relation '" + name + "' = " + level;
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+import static duskz.server.entityz.Thing.TYPE_FOOD;
+
+/**
+ * Items that can be eaten
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Food extends Holdable {
+
+ public Food(Game game) {
+ super(game);
+ }
+
+ public int getType() {
+ return TYPE_FOOD;
+ }
+
+ @Override
+ public int getWearing() {
+ // FIXME: food?
+ return Wearing.INVENTORY;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.server.BlockedIPException;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+/**
+ * Main game engine
+ *
+ * Handles game setup, prefs, loading/restoring state, and the main
+ * game clock.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Game {
+
+ public final Log log;
+ // prefs stuff
+ public int port = 7480;
+ public int viewRange = 6;
+ public int mapSize = viewRange * 2 + 1;
+ public String rcName = "somedusk";
+ public double goldFleeMod = 1.0 / 64.0; // fiXME ;lcheck
+ public double goldLoseMod = 1.0 / 16.0;
+ public double expFleeMod = 1.0 / 64.0;
+ public double expLoseMod = 1.0 / 16.0;
+ public double expGainMod = 18;
+ public int messageLimit = 256;
+ // How many ticks between battle steps
+ public int battleRate = 3;//10
+ /**
+ * Time between user messages
+ */
+ public int floodLimit = 500;
+ //
+ private ScriptManager scriptManager = new ScriptManager();
+ //
+ private File root;
+ private File confDir;
+ private File tileActionDir;
+ private File tileAbleDir;
+ private File tileVisibleDir;
+ private File locationActionDir;
+ private File locationAbleDir;
+ private File locationVisibleDir;
+ //
+ private File onScriptDir;
+ private File onConditionDir;
+ // Maps a tile number to a script
+ private HashMap<Integer, String> tileAction = new HashMap<>();
+ private HashMap<Integer, String> tileAble = new HashMap<>();
+ private HashMap<Integer, String> tileVisible = new HashMap<>();
+ // Just indicates which scripts are available, could be a cache or something
+ private HashSet<String> locationAction = new HashSet<>();
+ private HashSet<String> locationAble = new HashSet<>();
+ private HashSet<String> locationVisible = new HashSet<>();
+ /*
+ * Preference keys
+ */
+ public final static String PREF_AI = "ai";
+ // Used for mob info cache
+ HashMap<String, Mobile> allMobs = new HashMap<>();
+ // Used for item info cache, this needs a clone or equivalent ...
+ //HashMap<String, Holdable> allItems = new HashMap<>();
+ // Factions
+ HashMap<String, Faction> factions = new HashMap<>();
+ // do i want any of this now? hmm.
+ ThingTable<Mobile> mobs;
+ ThingTable<GameShop> gameShops;
+ ThingTable<Sign> signs;
+ ThingTable<Prop> props;
+ //
+ HashMap<String, Race> races = new HashMap<>();
+ // All players in game
+ HashMap<String, Player> players = new HashMap<>();
+ // All things in game
+ HashMap<Long, Thing> things = new HashMap<>();
+ // All actives in game
+ HashMap<Long, Active> actives = new HashMap<>();
+ //
+ private final HashMap<String, TileMap> maps = new HashMap<>();
+ //
+ private ArrayList<Battle> battles = new ArrayList<>();
+
+ public Game() {
+ log = new Log(System.out);
+ log.setLogLevel(Log.DEBUG);
+ }
+
+ public File getRoot() {
+ return root;
+ }
+
+ public void init(File root) throws IOException {
+ this.root = root;
+
+ confDir = new File(root, "config");
+ tileActionDir = new File(root, "onTileAction");
+ tileAbleDir = new File(root, "onTileAble");
+ tileVisibleDir = new File(root, "onTileVisible");
+
+ locationActionDir = new File(root, "onLocationAction");
+ locationAbleDir = new File(root, "onLocationAble");
+ locationVisibleDir = new File(root, "onLocationVisible");
+
+ onScriptDir = new File(root, "onItem");
+ onConditionDir = new File(root, "onCondition");
+
+ mobs = new ThingTable<>(new File(root, "mobs"));
+ gameShops = new ThingTable<>(new File(root, "gameShops"));
+ signs = new ThingTable<>(new File(root, "signs"));
+ props = new ThingTable<>(new File(root, "props"));
+
+ // Load preferences
+ // TODO: load preferences
+
+ // Load all races (move to 'racelist' class?)
+ {
+ File path = new File(root, "defRaces");
+ if (!path.isDirectory())
+ throw new FileNotFoundException("Unable to find races");
+
+ for (File file : path.listFiles()) {
+ try {
+ Race race = Race.loadRace(file);
+ races.put(race.name, race);
+ } catch (IOException ex) {
+ log.printf(ex, "Loading map %s failed", file.getName());
+ }
+ }
+ }
+
+ // Load all maps
+ {
+ File path = new File(root, "defMaps");
+ if (!path.isDirectory())
+ throw new FileNotFoundException("Unable to find maps");
+ for (File file : path.listFiles()) {
+ if (file.getName().endsWith(".alias"))
+ continue;
+ try {
+ TileMap map = TileMap.loadLayered(file);
+ maps.put(map.name, map);
+ log.printf(Log.VERBOSE, "Map: %s size=%dx%dx%d", map.name, map.getCols(), map.getRows(), map.getLayerCount());
+ } catch (IOException ex) {
+ log.printf(ex, "Loading map %s failed", file.getName());
+ }
+ }
+ }
+
+ // Load tile scripts
+ // TODO: actually verify scripts exist at least?
+ {
+ File path = new File(root, "tileScriptMap");
+ try (PropertyLoader pl = new PropertyLoader(path)) {
+ for (PropertyLoader.PropertyEntry pe : pl) {
+ String[] line = pe.name.split("\\.");
+ Integer id = Integer.valueOf(line[0]);
+
+ switch (line[1]) {
+ case "visible":
+ tileVisible.put(id, pe.value);
+ break;
+ case "able":
+ tileAble.put(id, pe.value);
+ break;
+ case "action":
+ tileAction.put(id, pe.value);
+ break;
+ }
+ }
+ }
+ }
+
+ // Find out the names of the scripts available
+ {
+ String[] dirs = {"onLocationAble", "onLocationAction", "onLocationVisible"};
+ HashSet[] sets = {locationAble, locationAction, locationVisible};
+
+ for (int i = 0; i < dirs.length; i++) {
+ sets[i].addAll(Arrays.asList(new File(root, dirs[i]).list()));
+ }
+ }
+
+ // load all factions (before mobs)
+ {
+ File path = new File(root, "factions");
+ if (!path.isDirectory())
+ throw new FileNotFoundException("Unable to find factions");
+ for (File file : path.listFiles()) {
+ try {
+ Faction f = new Faction(file.getName(), this);
+ f.load();
+ factions.put(f.name, f);
+ log.printf(Log.VERBOSE, "Faction: %s", f.name);
+ } catch (IOException ex) {
+ log.printf(ex, "Loading faction %s failed", file.getName());
+ }
+ }
+ }
+
+ // load all mob types into cache/check validity
+ // hmm, not using this yet?
+ {
+ File path = new File(root, "defMobs");
+ if (!path.isDirectory())
+ throw new FileNotFoundException("Unable to find mobs");
+ for (File file : path.listFiles()) {
+ try {
+ Mobile mob = new Mobile(this);
+ mob.load(file);
+ allMobs.put(mob.name, mob);
+ //log.printf(Log.VERBOSE, "Mob class: %s", mob.name);
+ } catch (Exception ex) {
+ log.printf(ex, "Loading mob %s failed", file.getName());
+ }
+ }
+ }
+
+
+ // Load all active objects
+ // FIXME: has to copy to various indices
+ mobs.restoreState(this, new File(root, "defMobs"));
+ gameShops.restoreState(this);
+ signs.restoreState(this);
+ props.restoreState(this);
+
+ // HACK: just copy to indices now
+ for (Mobile m : mobs.values()) {
+ System.out.println("add mob " + m.name + " on " + m.map.name);
+ addThing(m);
+ }
+ for (Sign s : signs.values()) {
+ System.out.println("add sign: " + s.name + " at " + s.x + ", " + s.y + " on map " + s.map.name);
+ addThing(s);
+ }
+ for (Shop s : gameShops.values()) {
+ System.out.println("add shop: " + s.name + " at " + s.x + ", " + s.y + " on map " + s.map.name);
+ addThing(s);
+ }
+
+ scriptManager.start();
+
+ // We're ready, time for 'onstart' script
+ runScript(confDir, "onStart", "game", this);
+ }
+
+ // dont think i want this now
+ public void addEveryTick(Active lt) {
+ }
+
+ public void removeEveryTick(Active lt) {
+ }
+
+ public void addTickHandler(Runnable r, int ratio) {
+ // add the tick handler to the queue
+ // if the queue was empty, wake up the tick thread
+ }
+
+ void restoreState() {
+ }
+
+ void saveState() {
+ mobs.saveState(this);
+ gameShops.saveState(this);
+ signs.saveState(this);
+ props.saveState(this);
+ }
+
+ public TileMap getMap(String name) {
+ return maps.get(name);
+ }
+
+ /**
+ * Create a copy of an item prototype
+ *
+ * @param name
+ * @return item, or null if it coulnd't be loaded/found
+ */
+ public Holdable createItem(String name) {
+ Thing.ThingResolver resolver = new Thing.EmptyResolver();
+
+ try (BufferedReader in = new BufferedReader(new FileReader(new File(root, "defItems/" + name.toLowerCase())))) {
+ return (Holdable) Thing.restoreState(this, in, resolver);
+ } catch (IOException ex) {
+ log.printf(ex, "Loading item: %s", name);
+ }
+ return null;
+ }
+
+ /**
+ * Look up a boolean preference.
+ *
+ * @param name
+ * @return
+ */
+ public boolean isPreference(String name) {
+ switch (name) {
+ case PREF_AI:
+ return true;
+ }
+ // FIXME: implement properly
+ return true;
+ }
+
+ /**
+ * Send a global chat from a given player. Calls chatMessage() on
+ * all active players.
+ *
+ * @param from may be null
+ * @param clan may be null
+ * @param msg
+ */
+ public void globalChat(Player from, String clan, String msg) {
+ //log.printMessage(Log.ALWAYS, msg);
+ for (Player player : players.values()) {
+ player.chatMessage(from, clan, msg);
+ }
+ }
+
+ // this shouldn't be needed ...
+ public void cleanup() {
+ }
+
+ public void addBattle(Battle battle) {
+ battles.add(battle);
+ }
+
+ public Thing getThing(long id) {
+ return things.get(id);
+ }
+
+ /**
+ * Remove object from game
+ * Do not call this directly for players.
+ *
+ * @param thing
+ */
+ void removeThing(Thing thing) {
+ assert (things.containsKey(thing.ID));
+
+ switch (thing.getType()) {
+ case Thing.TYPE_PLAYER:
+ players.remove(((Player) thing).name);
+ actives.remove(thing.ID);
+ break;
+ case Thing.TYPE_MOBILE:
+ mobs.remove((Mobile) thing);
+ actives.remove(thing.ID);
+ break;
+ case Thing.TYPE_PET:
+ actives.remove(thing.ID);
+ break;
+ }
+
+ thing.map.removeEntity(thing);
+ things.remove(thing.ID);
+
+ }
+
+ public void addThing(Thing t) {
+ things.put(t.ID, t);
+
+ switch (t.getType()) {
+ case Thing.TYPE_PLAYER: {
+ Player player = (Player) t;
+ players.put(player.name, player);
+ actives.put(player.ID, player);
+ break;
+ }
+ case Thing.TYPE_MOBILE:
+ mobs.add((Mobile) t);
+ actives.put(t.ID, (Active) t);
+ // Mobs are not addded to the map directly, respawn does it
+ return;
+ case Thing.TYPE_PET:
+ actives.put(t.ID, (Active) t);
+ break;
+ }
+ t.map.addEntity(t);
+ }
+
+ /**
+ * Public interfac to remove things from the game
+ *
+ * @param h
+ */
+ public void removeItem(Holdable h) {
+ removeThing(h);
+ }
+
+ /**
+ * Add a thing to game
+ *
+ * @param t
+ * @param map
+ * @param x
+ * @param y
+ */
+ public void addThing(Thing t, TileMap map, int x, int y) {
+ // FIXME: threads
+ t.map = map;
+ t.x = x;
+ t.y = y;
+ addThing(t);
+ }
+
+ /**
+ * runs a script that returns nothing
+ *
+ * @param base
+ * @param name
+ * @param args
+ */
+ public void runScript(File base, String name, Object... args) {
+ if (name == null)
+ return;
+
+ try {
+ Future<Object> res = scriptManager.runScript(new File(base, name), args);
+
+ res.get();
+ } catch (FileNotFoundException ex) {
+ } catch (Exception ex) {
+ Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Runs a script returning a boolean result.
+ *
+ * @param def default value for script with error or no script
+ * @param base
+ * @param name name of script. The special names 'true', and 'false' are handled directly.
+ * @param args
+ * @return
+ */
+ public boolean runScriptBoolean(boolean def, File base, String name, Object... args) {
+ if (name == null)
+ return def;
+
+ if (name.equals("true"))
+ return true;
+ else if (name.equals("false"))
+ return false;
+
+ System.out.println("run bool script: " + base.getName() + "/" + name);
+
+ try {
+ Future<Boolean> res = scriptManager.runScript(new File(base, name), args);
+
+ System.out.println(" = " + res.get());
+
+ return res.get();
+ } catch (FileNotFoundException ex) {
+ System.out.println("Script not found: " + base.getName() + "/" + name);
+ } catch (Exception ex) {
+ Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return def;
+ }
+
+ /**
+ * TODO: do i want to pass the map name to the tile script too?
+ * TODO: can i improve the mapping? e.g. give the tile a name,
+ * and use the name to find the appropriate scripts?
+ *
+ * @param trigger
+ * @param tileid
+ */
+ public void onTileAction(Active trigger, int tileid) {
+ String name = tileAction.get(tileid);
+
+ if (name != null) {
+ runScript(tileActionDir, name,
+ "game", this,
+ "trigger", trigger);
+ }
+ }
+
+ public boolean onTileVisible(Active trigger, int tileid) {
+ String name = tileVisible.get(tileid);
+
+ if (name != null) {
+ return runScriptBoolean(false, tileVisibleDir, name,
+ "game", this,
+ "trigger", trigger);
+ }
+ return false;
+ }
+
+ public boolean onTileAble(Active trigger, int tileid) {
+ String name = tileAble.get(tileid);
+
+ //System.out.printf("onTileTable tid=%d script=%s\n", tileid, name);
+
+ if (name != null) {
+ return runScriptBoolean(false, tileAbleDir, name,
+ "game", this,
+ "trigger", trigger);
+ }
+ return false;
+ }
+
+ private void onMoveAction(Active trigger, int x, int y) {
+ String alias = trigger.map.jumpAlias(x, y);
+ if (alias != null) {
+ TileMap map = trigger.map;
+ Location l = map.locationForAlias(alias);
+ if (l == null) {
+ for (TileMap scan : maps.values()) {
+ l = scan.locationForAlias(alias);
+ if (l != null) {
+ map = scan;
+ break;
+ }
+ }
+ }
+ if (l != null) {
+ System.out.printf("direct jump to '%s' map %s %d,%d\n",
+ alias, map.name, l.x, l.y);
+ trigger.jumpTo(map, l.x, l.y);
+ return;
+ } else {
+ log.printf(Log.INFO, "Location alias not found: %s, map %s %d,%d", alias, trigger.map.name, x, y);
+ }
+ }
+
+ runScript(locationActionDir, trigger.map.locationActionScript(x, y),
+ "game", this,
+ "trigger", trigger);
+ }
+
+ private boolean haveMoveAble(Active trigger, int x, int y) {
+ return trigger.map.locationAbleScript(x, y) != null;
+ }
+
+ private boolean onMoveAble(Active trigger, int x, int y) {
+ return runScriptBoolean(false, locationAbleDir, trigger.map.locationAbleScript(x, y),
+ "game", this,
+ "trigger", trigger);
+ }
+
+ private boolean haveMoveVisible(Active trigger, int x, int y) {
+ return trigger.map.locationVisibleScript(x, y) != null;
+ //return locationVisible.contains(trigger.map.locationVisibleScript(x, y));
+ }
+
+ private boolean onMoveVisible(Active trigger, int x, int y) {
+ return runScriptBoolean(false, locationVisibleDir, trigger.map.locationVisibleScript(x, y),
+ "game", this,
+ "trigger", trigger);
+ }
+
+ /**
+ * Check scripts to see if trigger can move onto location.
+ * If present, the location script overrides the tile script.
+ *
+ * @param trigger
+ * @param x
+ * @param y
+ * @param tileid
+ * @return
+ */
+ public boolean onLocationAble(Active trigger, int x, int y, int tileid) {
+ //System.out.printf("onLocationAble %d,%d %d havemove=%s onmove=%s ontile=%s\n",
+ // x, y, tileid, haveMoveAble(trigger, x, y),
+ // onMoveAble(trigger, x, y),
+ // onTileAble(trigger, tileid));
+ if (haveMoveAble(trigger, x, y))
+ return onMoveAble(trigger, x, y);
+ else
+ return onTileAble(trigger, tileid);
+ }
+
+ public boolean onLocationVisible(Active trigger, int x, int y, int tileid) {
+ //System.out.printf("onLocationVisible %d,%d %d havemove=%s onmove=%s ontile=%s\n",
+ // x, y, tileid, haveMoveVisible(trigger, x, y),
+ // onMoveVisible(trigger, x, y),
+ // onTileVisible(trigger, tileid));
+
+ if (haveMoveVisible(trigger, x, y))
+ return onMoveVisible(trigger, x, y);
+ else
+ return onTileVisible(trigger, tileid);
+ }
+
+ public void onLocationAction(Active trigger, int x, int y, int tileid) {
+ // Do I always do both? Possibly not?
+ onTileAction(trigger, tileid);
+ onMoveAction(trigger, x, y);
+ }
+
+ public boolean onCanAttack(Active attacking, Active attacked) {
+ // TODO: would a race or mob based attack script fit here?
+ return runScriptBoolean(true, confDir, "canAttack", "attacking", attacking, "attacked", attacked);
+ }
+
+ /**
+ * Calls the onPlayerStart script.
+ *
+ * @param player
+ * @return true if the player is allowed to start
+ */
+ public boolean onPlayerStart(Player player) {
+ return runScriptBoolean(true, confDir, "onPlayerStart", "player", player.getCommands());
+ }
+
+ public boolean haveOnItem(Holdable trigger, String what) {
+ return new File(onScriptDir, trigger.name + "." + what).exists();
+ }
+
+ /**
+ * Perform on item callback
+ *
+ * @param owner
+ * @param item
+ * @param what "get" or "drop", etc.
+ */
+ public void onItem(Active owner, Holdable item, String what) {
+ runScript(onScriptDir, item.name + "." + what,
+ "game", this,
+ "owner", owner,
+ "item", item);
+ }
+
+ public boolean haveOnCondition(Holdable trigger, String what) {
+ return new File(onConditionDir, trigger.name + "." + what).exists();
+ }
+
+ /**
+ * Perform on item callback
+ *
+ * @param owner
+ * @param item
+ * @param what "get" or "drop", etc.
+ */
+ public void onCondition(Active owner, Condition cond, String what) {
+ runScript(onConditionDir, cond.name + "." + what,
+ "game", this,
+ "owner", owner,
+ "condition", cond);
+ }
+ private int tick;
+
+ public int getClock() {
+ return tick;
+ }
+
+ /**
+ * Main game loop.
+ *
+ * 1. all actives are moved active.moveTick()
+ * 2. run per-tick activities
+ * 3. run player updates
+ *
+ * @param tick
+ */
+ void gameTick(int tick) {
+ this.tick = tick;
+ // TODO: the order of this might need tweaking
+ for (Active a : actives.values()) {
+ a.moveTick();
+ }
+ for (Active a : actives.values()) {
+ a.tick(tick);
+ }
+ //if (tick % 10 == 0) {
+ if (tick % battleRate == 0) {
+ ArrayList<Battle> done = new ArrayList<>();
+ for (Battle b : battles) {
+ if (b.isFighting()) {
+ b.run();
+ } else {
+ done.add(b);
+ }
+ }
+ battles.removeAll(done);
+ }
+
+ for (Active a : actives.values()) {
+ a.visibilityTick(tick);
+ }
+ }
+
+ void onDeath(Player player, Active winner) {
+ runScript(new File(root, "scripts"), "onDeath", "trigger", player, "killer", winner);
+ }
+
+ boolean playerExists(String name) {
+ return new File(root, "players/" + name.toLowerCase()).exists();
+ }
+
+ boolean petExists(String name) {
+ return new File(root, "pets/" + name.toLowerCase()).exists();
+ }
+
+ // TODO: String insecure for password
+ boolean checkPassword(String name, String pass, String address) throws TooManyTriesException, BlockedIPException {
+
+ // TODO: handle ip blocked checking here
+
+ Player p = new Player(this, null);
+ try {
+ p.load(new File(root, "players/" + name.toLowerCase()));
+ } catch (IOException ex) {
+ return false;
+ }
+
+ if (p.password == null) {
+ // Some error
+ log.printf(Log.ERROR, "User file has no password: %s", name);
+ return false;
+ }
+
+ if (pass.equals(p.password)) {
+ return true;
+ } else if (logPasswordFailure(name, address)) {
+ throw new TooManyTriesException();
+ } else {
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Return true if login limit exceeded. If so, then mark address as banned.
+ *
+ * @param name
+ * @param address
+ * @return
+ */
+ boolean logPasswordFailure(String name, String address) {
+ return false;
+ }
+
+ void logPasswordSuccess(String player, String address) {
+ //throw new UnsupportedOperationException("Not supported yet.");
+ }
+ int namecap = 12;
+
+ boolean isGoodName(String player) {
+ if (player == null)
+ return false;
+
+ String lcplayer = player.toLowerCase();
+
+ // config?
+ Pattern reserved = Pattern.compile("^god|default$");
+ // config?
+ Pattern valid = Pattern.compile("^[A-Za-z]+$");
+
+ if (reserved.matcher(lcplayer).matches()
+ || !valid.matcher(player).matches()) {
+ return false;
+ }
+
+ try (BufferedReader br = new BufferedReader(new FileReader(new File(confDir, "dirtyWordFile")))) {
+ String strDirtyWord = br.readLine();
+ while (strDirtyWord != null) {
+ if (lcplayer.indexOf(strDirtyWord) != -1) {
+ return false;
+ }
+ strDirtyWord = br.readLine();
+ }
+ } catch (IOException x) {
+ x.printStackTrace();
+ }
+
+ return true;
+ }
+
+ /**
+ * Registers the player with the game.
+ * Checks to see if the player is already connected - logs out other player.
+ *
+ * @param player
+ * @throws BlockedIPException
+ */
+ void registerPlayer(Player player) {
+ logPasswordSuccess(player.name, player.getAddress());
+
+ Player old = players.get(player.name);
+ if (old != null) {
+ old.chatMessage("Logged in again from: " + player.getAddress());
+ old.logout();
+ }
+
+ //throw new UnsupportedOperationException("Not supported yet.");
+ players.put(player.name, player);
+ things.put(player.ID, player);
+ actives.put(player.ID, player);
+ player.map.addEntity(player);
+
+ try {
+ System.out.println("added player:");
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
+ player.writeProperties(bw);
+ bw.flush();
+ } catch (IOException ex) {
+ Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ void unregisterPlayer(Player player) {
+ // TODO threading
+ player.map.removeEntity(player);
+ players.remove(player.name);
+ things.remove(player.ID);
+ actives.remove(player.ID);
+ }
+
+ public List<String> getRaceNames() {
+ ArrayList<String> l = new ArrayList<>(races.size());
+
+ for (Race r : races.values()) {
+ l.add(r.name);
+ }
+ return l;
+ }
+
+ Race getRace(String value) {
+ return races.get(value);
+ }
+
+ public Faction getFaction(String name) {
+ return factions.get(name);
+ }
+
+ public static void main(String[] args) throws IOException {
+ Game g = new Game();
+
+ g.init(new File("/home/notzed/dusk/game"));
+
+ String[] names = {"z", "fuckface", "god", "root", "default"};
+ for (String n : names) {
+ System.out.println("good name: " + n + " " + g.isGoodName(n));
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Special thanks to:
+ * Randall Leeds
+ * Vittorio Alberto Floris
+ * Ian Macphail
+ *
+ * Changes
+ * Michael Zucchi Mar-2013 - changed to new backend, implemented
+ * different clock timing.
+ */
+package duskz.server.entityz;
+
+import duskz.server.Log;
+import java.io.File;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This is the main entry point.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class GameServer {
+
+ // ms per tick: TODO: move into game prefs
+ int tickRate = 100; // 250
+ boolean cancelled = false;
+ ServerSocket serverSocket;
+ Socket socket;
+ Game game;
+ MasterClock clockThread;
+ ConnectionManager connectionThread;
+
+ /**
+ * Creates a new DuskServer object;
+ */
+ public static void main(String args[]) {
+ // TODO: parse arguments for game data location
+ File path = new File("/home/notzed/dusk/game");
+
+ GameServer server = new GameServer();
+
+ server.start(path);
+ }
+
+ private void abort() {
+ game.log.printf(Log.ALWAYS, "Aborted shutdown.");
+ System.exit(0);
+ }
+
+ protected void shutdown() {
+ try {
+ cancelled = true;
+ clockThread.interrupt();
+ connectionThread.interrupt();
+ clockThread.join();
+ connectionThread.join();
+ } catch (InterruptedException ex) {
+ Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Creates a DuskEngine object, then a ServerSocket object, then
+ * starts a new thread to accept incoming connections.
+ */
+ public GameServer() {
+ // game location?
+ game = new Game();
+ clockThread = new MasterClock();
+ connectionThread = new ConnectionManager();
+ }
+
+ public void start(File path) {
+ System.out.println("Initialising game at: " + path);
+
+ try {
+ game.init(path);
+ serverSocket = new ServerSocket(game.port, 25);
+ } catch (Exception e) {
+ game.log.printf(e, "Server init failed", e);
+ e.printStackTrace();
+ abort();
+ }
+
+
+ connectionThread.start();
+ clockThread.start();
+ }
+
+ /**
+ * Accepts incoming connections.
+ */
+ /**
+ * Accepts connections.
+ * TODO: handle ip blocking, throttling etc. here
+ * rather than in playerconnection
+ */
+ class ConnectionManager extends Thread {
+
+ public void run() {
+ System.out.println("Accepting connections on port " + game.port + ".");
+
+ while (true) {
+ try {
+ Socket accept = serverSocket.accept();
+ accept.setSoTimeout(30000);
+
+ PlayerConnection pc = new PlayerConnection(game, accept);
+
+ pc.start();
+ } catch (Exception e) {
+ game.log.printf(e, "Error accepting connection", e);
+ abort();
+ return;
+ }
+ }
+ }
+ }
+
+ class MasterClock extends Thread {
+
+ int tick = 0;
+
+ @Override
+ public void run() {
+ long start = System.currentTimeMillis() + 1000;
+
+ System.out.println("Starting game clock, " + tickRate + "ms per tock.");
+
+ while (!cancelled) {
+ try {
+ long target = start + tick * tickRate;
+ long now = System.currentTimeMillis();
+ long delay = (target - now);
+
+ if (delay > 0) {
+ sleep(delay);
+ } else if (delay < -500) {
+ System.out.println("warning: clock failing to keep up, delay: " + (-delay));
+ }
+
+ game.gameTick(tick);
+
+ // battles?
+
+ } catch (InterruptedException ex) {
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ } finally {
+ tick += 1;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.util.List;
+
+/**
+ * A game provided shop.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class GameShop extends Shop {
+
+ public GameShop(Game game) {
+ super(game);
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ // train is just an alias at the moment
+ if (name.equals("train"))
+ name = "item";
+
+ super.setProperty(name, value);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_GAME_SHOP;
+ }
+
+ @Override
+ public void buy(Active buyer, String what, int quantity) {
+ Holdable h = items.get(what);
+
+ if (h == null) {
+ buyer.chatMessage(name + " doesn't sell those.");
+ return;
+ }
+ if (h instanceof Training) {
+ Training t = (Training) h;
+ if (!buyer.addExp(-h.cost * quantity)) {
+ buyer.chatMessage("You can't afford that many.");
+ } else {
+ switch (t.skill) {
+ case "hp":
+ buyer.addStat(Active.STAT_HPMAX, quantity * 10);
+ buyer.chatMessage("You're magically able to withstand more damage.");
+ break;
+ case "mp":
+ buyer.addStat(Active.STAT_MPMAX, quantity * 10);
+ buyer.chatMessage("You're magically able to cast more magic.");
+ break;
+ case "int":
+ buyer.addStat(Active.STAT_INT, quantity);
+ buyer.chatMessage("You begin to think clearer.");
+ break;
+ case "str":
+ buyer.addStat(Active.STAT_STR, quantity);
+ buyer.chatMessage("You feel stronger.");
+ break;
+ case "dex":
+ buyer.addStat(Active.STAT_DEX, quantity);
+ buyer.chatMessage("You gain an extra spring in your step.");
+ break;
+ case "wis":
+ buyer.addStat(Active.STAT_WIS, quantity);
+ buyer.chatMessage("You feel wiser.");
+ break;
+ case "con":
+ buyer.addStat(Active.STAT_CON, quantity);
+ buyer.chatMessage("You feel better equipped to face the world.");
+ break;
+ default:
+ buyer.chatMessage("BUG: I don't know how to train " + t.skill);
+ buyer.addExp(h.cost * quantity);
+ break;
+ }
+ }
+ } else {
+ if (buyer.getGold() < h.cost * quantity) {
+ buyer.chatMessage("You can't afford that many.");
+ } else {
+ while (quantity > 0) {
+ Holdable s = game.createItem(what);
+
+ if (buyer.addGold(-h.cost)) {
+ buyer.addItem(s);
+ }
+ quantity -= 1;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sell(Active customer, List<Holdable> items) {
+ // TODO: I really want to look up the item somewhere to see what
+ // this shop will buy the items for ...?
+
+ for (Holdable h : items) {
+ if (customer.removeItem(h)) {
+ customer.addGold(h.cost / 2);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * Base class for all items.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public abstract class Holdable extends Thing {
+
+ int cost;
+ //int image;
+ int volume;
+ int weight;
+ String onGet, onDrop;
+ int uses = -1;
+ String onUse;
+
+ Holdable(Game game) {
+ super(game);
+ }
+
+ abstract public int getWearing();
+
+ public String getUnits() {
+ return "gp";
+ }
+
+ @Override
+ public void look(Active viewer) {
+ viewer.chatMessage("You see " + description + ".");
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "cost":
+ cost = Integer.parseInt(value);
+ break;
+ case "weight":
+ weight = Integer.parseInt(value);
+ break;
+ case "volume":
+ volume = Integer.parseInt(value);
+ break;
+ case "onGet": // FIXME: use conventions to find script from name
+ onGet = value;
+ break;
+ case "onDrop": // FIXME: use conventions to find script from name
+ onDrop = value;
+ break;
+ case "uses":
+ uses = Integer.parseInt(value);
+ break;
+ case "onUse": // FIXME: use conventions to find script from name
+ onUse = value;
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ writeProperty(out, "name", name);
+ writeProperty(out, "description", description);
+ //writeProperty(out, "map", map.name);
+ //writeProperty(out, "x", x);
+ //writeProperty(out, "y", y);
+ writeProperty(out, "image", image);
+
+ writeProperty(out, "cost", cost);
+ writeProperty(out, "weight", weight);
+ writeProperty(out, "volume", volume);
+ writeProperty(out, "onGet", onGet);
+ writeProperty(out, "onDrop", onDrop);
+
+ if (uses != -1)
+ writeProperty(out, "uses", uses);
+ writeProperty(out, "onUse", onUse);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.TransactionMessage;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list of holdables.
+ *
+ * Original game had table by type, but that seems somewhat pointless overhead.
+ *
+ * This is a bit of a mis-mash right now just trying to get the code working
+ *
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Inventory {
+
+ // do i need a name index? rarely accessed, short list, who cares
+ final private ArrayList<Holdable> items = new ArrayList<>();
+
+ public TransactionMessage createTransactionMessage(int name, float scale) {
+ TransactionMessage msg = new TransactionMessage(name);
+
+ // TODO: count occurances here for player inventory?
+
+ for (Holdable h : items) {
+ // FIXME: how to represent skills
+
+ int cost = (int) (h.cost * scale);
+
+ msg.add(h.getWearing(), h.name, 1, cost, h.getUnits());
+ }
+
+ return msg;
+ }
+
+ public void describeTo(Active viewer) {
+ if (items.isEmpty())
+ viewer.chatMessage("Nothing at the moment.");
+ else {
+ // hack: shopping list
+ for (Holdable h : items) {
+ viewer.chatMessage(String.format("%-20s %s", h.name, h.description));
+ }
+ }
+ }
+
+ /**
+ * Return the first item of the given type
+ * @param key
+ * @return
+ */
+ public Holdable get(String key) {
+ for (Holdable h : items) {
+ if (key.equalsIgnoreCase(h.name))
+ return h;
+ }
+ return null;
+ }
+
+ public void add(Holdable h) {
+ items.add(h);
+ }
+
+ public boolean remove(Holdable h) {
+ return items.remove(h);
+ }
+
+ public List<Holdable> getAll(String key, int maximum) {
+ ArrayList<Holdable> list = new ArrayList<>();
+ for (Holdable h : items) {
+ if (h.name.equalsIgnoreCase(key)) {
+ list.add(h);
+ if (list.size() >= maximum)
+ break;
+ }
+ }
+ return list;
+ }
+
+ public boolean contains(Holdable h) {
+ return items.contains(h);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+
+/**
+ * Normal item whose behaviour is defined by scripts
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Item extends Holdable {
+
+ public Item(Game game) {
+ super(game);
+ }
+
+ public int getType() {
+ return TYPE_ITEM;
+ }
+
+ @Override
+ public int getWearing() {
+ return Wearing.INVENTORY;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+/**
+ * Track position.
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Location {
+
+ public int x;
+ public int y;
+
+ public Location() {
+ }
+
+ public Location(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Location) {
+ Location l = (Location) obj;
+ return l.x == x && l.y == y;
+ } else
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 97 * hash + this.x;
+ hash = 97 * hash + this.y;
+ return hash;
+ }
+
+ public class Global extends Location {
+
+ public TileMap map;
+
+ public Global(TileMap map, int x, int y) {
+ super(x, y);
+
+ this.map = map;
+ }
+
+ public Global(TileMap map, Location l) {
+ super(l.x, l.y);
+
+ this.map = map;
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ * Michael Zucchi 2013-Mar - redone for new i/o api like printf. Fixed the
+ * priority orderings of the messages.
+ */
+package duskz.server.entityz;
+
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple logging interface.
+ */
+public class Log {
+
+ public static final int DEBUG = 0;
+ public static final int ALWAYS = 1;
+ public static final int VERBOSE = 2;
+ public static final int INFO = 3;
+ public static final int ERROR = 4;
+ private static final String[] msg = {
+ "DEBUG",
+ "ALWAYS",
+ "VERBOSE",
+ "INFO",
+ "ERROR"
+ };
+ private static final String LOG_SEP = "::";
+ private PrintStream out;
+ private int logLevel = DEBUG;
+ private SimpleDateFormat formatter;
+
+ public Log(PrintStream ps) {
+ formatter = new SimpleDateFormat("EEE MMM dd hh:mm:ss yyyy", Locale.ROOT);
+ out = ps == null ? System.out : ps;
+ }
+
+ public void setLogLevel(int newlevel) {
+ newlevel = Math.max(newlevel, ALWAYS);
+ logLevel = Math.min(newlevel, ERROR);
+ }
+
+ public int getLogLevel() {
+ return logLevel;
+ }
+
+ private void printTimeStamp(int level) {
+ out.print(msg[level]);
+ out.print(":");
+ out.print(formatter.format(new Date()));
+ out.print(":");
+ if (logLevel <= DEBUG) {
+ out.print("thread=" + Thread.currentThread().getName());
+ out.print(":");
+ }
+ }
+
+ public void printf(int level, String strMessage, Object... args) {
+ if (level >= logLevel) {
+ printTimeStamp(level);
+ out.printf(strMessage, args);
+ out.println();
+ }
+ }
+
+ public void printf(Exception ex, String strMessage, Object... args) {
+ printTimeStamp(ERROR);
+ out.printf(strMessage, args);
+ out.print(": ");
+ if (logLevel <= DEBUG) {
+ ex.printStackTrace(out);
+ } else {
+ out.println(ex.toString());
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Non player character
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Mobile extends Active {
+
+ /**
+ * Home location for mobile
+ */
+ int homeX, homeY;
+ /**
+ * Items given when killed by a player
+ */
+ ArrayList<GiveItem> giveItems = new ArrayList<>();
+ double bravery;
+ double groupRelation;
+ Faction faction;
+ boolean canSeePlayer;
+ // causes respawn on first tick
+ int respawn = 1;
+
+ public Mobile(Game game) {
+ super(game);
+ }
+
+ // onBattle script
+ @Override
+ public int getType() {
+ return TYPE_MOBILE;
+ }
+
+ @Override
+ public boolean isAlive() {
+ if (respawn > 0)
+ return false;
+ return super.isAlive();
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "faction":
+ this.faction = game.getFaction(value);
+ break;
+ case "giveitem": {
+ int c = value.indexOf(',');
+ if (c > 0) {
+ giveItems.add(new GiveItem(value.substring(0, c), Double.valueOf(value.substring(c + 1))));
+ }
+ break;
+ }
+ case "bravery":
+ bravery = Double.valueOf(value);
+ break;
+ case "grouprelation":
+ groupRelation = Double.valueOf(value);
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ super.writeProperties(out);
+ if (faction != null)
+ writeProperty(out, "faction", faction.name);
+ writeProperty(out, "bravery", bravery);
+ writeProperty(out, "grouprelation", groupRelation);
+ for (GiveItem gi : giveItems) {
+ writeProperty(out, "giveitem", gi.name + "," + String.valueOf(gi.probability));
+ }
+ }
+
+ // FIXME: need some way of verifying loading, this isn't quite useful
+ // because loaded isn't called for load() only reconstruct()
+ @Override
+ protected void loaded() throws IOException {
+ homeX = x;
+ homeY = y;
+ if (faction == null)
+ //throw new IOException("Missing faction on " + name);
+ System.out.println("Missing faction on " + name);
+ /*
+ setStat(STAT_HP, getHPMax());
+ setStat(STAT_MP, getMPMax());
+
+ // debug
+ System.out.println("dump of loaded mob:");
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
+ writeProperties(bw);
+ bw.flush();*/
+ }
+
+ // FIXME: this is a hack as mobiles don't have a race.
+ // race is common to pet and player only ...?
+ public int getStat(int key) {
+ return stats[key] + bonus[key];
+ }
+
+ @Override
+ protected boolean canAttackEnemy(Active enemy) {
+ switch (enemy.getType()) {
+ case TYPE_PLAYER: {
+ Player nme = (Player) enemy;
+
+ if (nme.visitingShop() != null) {
+ return false;
+ }
+
+ break;
+ }
+ case TYPE_MOBILE:
+ // free for all?
+ // FIXME: can mobiles attackEnemy mobiles? check ai logic
+ break;
+ case TYPE_PET:
+ return false;
+ }
+ return super.canAttackEnemy(enemy);
+ }
+
+ @Override
+ public void send(DuskMessage msg) {
+ // don't care except for debugging?
+ }
+
+ @Override
+ public void chatMessage(Active from, String clan, String msg) {
+ // don't care except for debugging?
+ System.out.println("mob: " + name + "=" + msg);
+ }
+
+ @Override
+ protected boolean moveTo(int newx, int newy, int newstep) {
+ // So, this I just don't get, shouldn't it already be checked in the 'can move to' stuff somewhere?
+ // This checks that the mob doesn't go out of it's home area
+ // Maybe it's so that path finding works
+ if (Math.abs(newx - homeX) > game.viewRange
+ || Math.abs(newy - homeY) > game.viewRange)
+ return false;
+
+ return super.moveTo(newx, newy, newstep);
+ }
+
+ /**
+ * Noop, mob weapons invincible
+ *
+ * @param amount
+ */
+ @Override
+ public void weaponDamage(int amount) {
+ // noop
+ }
+
+ @Override
+ public void armourDamage(int damage) {
+ // noop
+ }
+
+ /**
+ * Enemy out of range, mob will try to get closer to target,
+ * if they are not already moving.
+ *
+ * @param target
+ */
+ @Override
+ public void attack(Battle battle, Active target, int range) {
+ // FIXME: Invoke "onBatte"
+
+ if (range > getRangeWithBonus()) {
+ battle.chatMessage(name + " is out of range, moving closer. distance=" + range + " range=" + getRangeWithBonus() + " moveable=" + isMoveable());
+ travelTo(target.x, target.y, false);
+ } else {
+ clearMoveQueue();
+ super.attack(battle, target, range);
+ }
+ }
+
+ @Override
+ public void killedBattle(Battle battle, Active winner, ArrayList<Active> opponents) {
+ chatMessage(this.name + " is killed.");
+ splitMoney(1, opponents);
+ splitExp(0, opponents);
+
+ // FIXME: respawnspeed
+ respawn = 100;
+ map.removeEntity(this);
+ endBattle();
+
+ for (Active a : opponents) {
+ switch (a.getType()) {
+ case TYPE_PET:
+ break;
+ case TYPE_PLAYER:
+ case TYPE_MOBILE:
+ if (faction != null) {
+ faction.killedByEnemy(this, a);
+ }
+ break;
+ }
+ }
+ if (winner.isPlayer()) {
+ System.out.println("Player won, checking give items");
+ for (GiveItem gi : giveItems) {
+ System.out.println(" item " + gi.name + " prob " + gi.probability);
+ if (Math.random() < gi.probability) {
+ Holdable h = game.createItem(gi.name);
+
+ winner.chatMessage("You got a " + h.name + ".");
+ winner.addItem(h);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endBattle() {
+ super.endBattle();
+ }
+
+ @Override
+ public void sayCommand(String text) {
+ localisedChat("Mob " + name + " says: " + text);
+ }
+
+ void respawn() {
+ respawn = 0;
+ x = homeX;
+ y = homeY;
+ setStat(STAT_HP, getHPMax());
+ setStat(STAT_MP, getMPMax());
+
+ map.addEntity(this);
+ }
+
+ @Override
+ public void tick(int tick) {
+
+ if (respawn == 1) {
+ respawn();
+ } else if (respawn > 0) {
+ respawn -= 1;
+ return;
+ }
+
+ LinkedList l;
+
+ if (tick % 4 == 0) {
+ if (faction != null && canSeePlayer) {
+ faction.runAI(this);
+ }
+ }
+
+ super.tick(tick);
+ }
+
+ @Override
+ public void visibilityTick(int tick) {
+ if (respawn > 0)
+ return;
+
+ boolean seePlayer = false;
+
+ for (TileMap.MapData md : map.range(x, y, game.viewRange)) {
+ if (!md.entities.isEmpty() && canSee(md.x, md.y)) {
+ for (Thing thing : md.entities) {
+ if (thing instanceof Player)
+ seePlayer = true;
+ }
+ }
+ }
+ canSeePlayer = seePlayer;
+ }
+
+ static class GiveItem {
+
+ String name;
+ double probability;
+
+ public GiveItem(String name, double probability) {
+ this.name = name;
+ this.probability = probability;
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.util.ArrayList;
+
+/**
+ * Tracks a group of things following other things
+ *
+ * I think they all have to be players or pets, maybe they should
+ * sub-class off a common thing.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Pack {
+ /*
+ * Required functionality:
+ * - ordered list of followers
+ * - notifications
+ * - grou tests, e.g. member of, clan inside
+ */
+
+ ArrayList<Active> members = new ArrayList<>();
+
+ public boolean contains(Active member) {
+ return members.contains(member);
+ }
+
+ /**
+ * Returns true if any member is clanless
+ *
+ * @return
+ */
+ public boolean containsClanless() {
+ for (Active a : members) {
+ if (a.clan.equals("none"))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds follower onto the end of the chain of followers
+ *
+ * @param follower
+ */
+ public void addFollower(Active follower) {
+ if (!members.isEmpty()) {
+ for (Active a : members) {
+ a.chatMessage("You are now being followed by " + follower.name + ".");
+ }
+ follower.chatMessage("You are now following " + members.get(members.size() - 1).name + ".");
+ }
+ members.add(follower);
+ }
+
+ public boolean isLeader(Active a) {
+ assert (members.size() > 1);
+
+ return members.get(0).ID == a.ID;
+ }
+
+ public Active getFollowing(Active leader) {
+ int i = members.indexOf(leader);
+
+ assert (members.size() > 1);
+ assert (i != -1);
+
+ if (i + 1 < members.size())
+ return members.get(i + 1);
+ return null;
+ }
+
+ Active getLeader(Active follower) {
+ int i = members.indexOf(follower);
+
+ if (i == 0)
+ return follower;
+
+ return members.get(i - 1);
+ }
+
+ /**
+ * Remove someone who is following.
+ *
+ * FIXME: if this leavs the group emtpy, then what?
+ *
+ * @param follower
+ */
+ void removeFollower(Active follower) {
+ if (isLeader(follower)) {
+ // cant do that
+ } else {
+ Active leader = getLeader(follower);
+
+ members.remove(follower);
+ for (Active a : members) {
+ a.chatMessage(follower.name + " is no longer following you.");
+ }
+ follower.chatMessage("You are no longer following " + leader.name);
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import java.util.ArrayList;
+
+/**
+ * Pet, only Players can have pets
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Pet extends Active {
+
+ /**
+ * The pet's master.
+ */
+ Player master;
+
+ public Pet(Game game) {
+ super(game);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PET;
+ }
+
+ @Override
+ public boolean isCanLead() {
+ return master.isCanLead();
+ }
+
+ @Override
+ public void send(DuskMessage msg) {
+ master.send(msg);
+ }
+
+ @Override
+ public void chatMessage(Active from, String clan, String msg) {
+ master.chatMessage(from, clan, msg);
+ }
+
+ @Override
+ public boolean canAttackEnemy(Active enemy) {
+ master.chatMessage(null, null, "Pets cannot lead battle");
+ return false;
+ }
+
+ @Override
+ public void follow(Active master) {
+ if (this.master.ID != master.ID) {
+ chatMessage("You can only follow your owner.");
+ return;
+ }
+ super.follow(master);
+ }
+
+ @Override
+ void leaveCommand() {
+ chatMessage("You cannot leave your master unless he unfollows you.");
+ }
+
+ @Override
+ protected boolean followTo(Active leader, int oldx, int oldy) {
+
+ assert (leader.ID == master.ID);
+
+ if (leader.ID != master.ID) {
+ throw new RuntimeException("pets can only follow masters");
+ }
+
+ boolean moved = jumpTo(map, oldx, oldy);
+
+ return moved;
+ }
+
+ @Override
+ public void fleeBattle(Battle battle, ArrayList<Active> opponents) {
+ endBattle();
+ }
+
+ @Override
+ public void killedBattle(Battle battle, Active winner, ArrayList<Active> opponents) {
+ battle.chatMessage(name + " is wounded.");
+ chatMessage("You have been wounded.");
+ splitMoney(game.goldLoseMod, opponents);
+ splitExp(game.expLoseMod, opponents);
+ endBattle();
+ }
+
+ @Override
+ public void sayCommand(String text) {
+ localisedChat("Pet " + name + " says: " + text);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import duskz.protocol.DuskProtocol;
+import duskz.protocol.EntityUpdateMessage;
+import duskz.protocol.ListMessage;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Human player
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Player extends Active implements DuskProtocol {
+
+ private HashSet<String> ignore = new HashSet<>();
+ // This shoudln't be stored like this ...
+ protected String password;
+ /**
+ * Privilege level
+ */
+ int privs;
+ /**
+ * Players can have 1 pet
+ */
+ Pet pet;
+ /*
+ * State tracking
+ */
+ private boolean hasMoved = false;
+ /**
+ *
+ */
+ // TODO: check
+ public boolean audioon = true,
+ coloron = true,
+ popup = true,
+ highlight = true;
+ /**
+ *
+ */
+ boolean alive;
+ /**
+ * Abuse control - muting until time
+ */
+ public long muteLimit;
+ /**
+ * Flood control for players
+ */
+ public long lastMessageStamp = 0;
+ /**
+ * Used to optimise network traffic
+ */
+ PlayerState state;
+ /**
+ * Connection to client
+ */
+ private final PlayerConnection connection;
+ /**
+ * Helper for commands
+ */
+ private final PlayerCommands commands;
+ /**
+ * Helper for command line
+ */
+ private final Commands cmdline;
+
+ public Player(Game game, PlayerConnection connection) {
+ super(game);
+ this.connection = connection;
+ this.commands = new PlayerCommands(game, this);
+ this.cmdline = new Commands(this);
+
+ this.state = new PlayerState(this);
+ alive = connection != null;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PLAYER;
+ }
+
+ @Override
+ public int getImage() {
+ // + imagestep?
+ return race.image;
+ }
+
+ void conditionsChanged() {
+ state.conditions = game.getClock();
+ }
+
+ void actionsChanged() {
+ state.actions = game.getClock();
+ }
+
+ void statsChanged() {
+ state.stats = game.getClock();
+ }
+
+ void scoreChanged() {
+ state.score = game.getClock();
+ }
+
+ void wornChanged() {
+ // Wearing items always changes the inventory state
+ state.worn = game.getClock();
+ state.inventory = game.getClock();
+ }
+
+ void inventoryChanged() {
+ state.inventory = game.getClock();
+ }
+
+ @Override
+ public boolean addGold(int amount) {
+ if (super.addGold(amount)) {
+ scoreChanged();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean addExp(int amount) {
+ if (super.addExp(amount)) {
+ scoreChanged();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setStat(int key, int value) {
+ super.setStat(key, value);
+ if (key <= STAT_MPMAX)
+ scoreChanged();
+ else
+ statsChanged();
+ }
+
+ @Override
+ public void setSleeping(boolean sleeping) {
+ super.setSleeping(sleeping);
+ actionsChanged();
+ }
+
+ @Override
+ public void wearItem(Wearable w) {
+ super.wearItem(w);
+ wornChanged();
+
+ }
+
+ @Override
+ public void unwearAt(int index) {
+ super.unwearAt(index);
+ wornChanged();
+ }
+
+ public PlayerCommands getCommands() {
+ return commands;
+ }
+
+ @Override
+ public DuskMessage createUpdateMessage(int name) {
+ EntityUpdateMessage en = (EntityUpdateMessage) super.createUpdateMessage(name);
+ // FIXME: move the base stuff to Active.
+ StringBuilder sb = new StringBuilder();
+ // Hmmm, should sleeping be a condition?
+ // use flags for now ...
+ //if (isSleeping()) {
+ // sb.append("<sleeping>");
+ //}
+ if (!clan.equals("none")) {
+ sb.append('<');
+ sb.append(clan);
+ sb.append('>');
+ }
+ // FIXME: status flags
+ // Hang on, shouldn't that be conditions?
+ //if (isPet() && hp < 0) {
+ // sb.append("<wounded>");
+ //}
+ //for (String s : flags) {
+ // sb.append('<');
+ // sb.append(s);
+ // sb.append('>');
+ //}
+ sb.append(this.name);
+
+ en.entityName = sb.toString();
+ en.imageStep = (short) imageStep;
+
+ return en;
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "password":
+ password = value;
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ // Want this to be first line
+ writeProperty(out, "password", password);
+ super.writeProperties(out);
+
+ writeProperty(out, "privs", privs);
+ if (pet != null)
+ writeProperty(out, "pet", pet.name);
+
+ }
+
+ public String getAddress() {
+ return connection.getAddress();
+ }
+
+ // Groups, clans, etc, can only be done on players!
+ public boolean isSameGroup(Active other) {
+ if (pack != null)
+ return pack.contains(other);
+ return false;
+ }
+
+ public boolean isClanless() {
+ return clan == null || clan.equals("none");
+ }
+
+ /**
+ * Whether the acti
+ * FIXME: Who can have clans? if it's only players, fix Active to account for that.
+ *
+ * @return true if the active or any in it's pack are clanless.
+ */
+ public boolean isClanlessGroup() {
+ if (isClanless())
+ return true;
+ else if (pack != null)
+ return pack.containsClanless();
+ else
+ return false;
+ }
+
+ /**
+ * Tracks if a move occured, and
+ *
+ * @param newLocX
+ * @param newLocY
+ * @param dir
+ * @param newStep
+ * @return
+ */
+ @Override
+ protected boolean moveTo(int newLocX, int newLocY, int newStep) {
+ hasMoved = super.moveTo(newLocX, newLocY, newStep);
+ return hasMoved;
+ }
+
+ @Override
+ protected boolean canAttackEnemy(Active enemy) {
+ switch (enemy.getType()) {
+ case TYPE_PLAYER: {
+ Player nme = (Player) enemy;
+
+ if (isSameGroup(nme)) {
+ chatMessage(null, null, "You can't attack a member of your group.");
+ return false;
+ }
+
+ if (nme.isClanlessGroup()) {
+ chatMessage(null, null, "You can't fight them.");
+ return false;
+ }
+
+ // I don't see why the isFighting test is needed here.
+ if (!enemy.isFighting()) {
+ if (this.isClanless()) {
+ chatMessage(null, null, "Players who are not in clans cannot fight other players.");
+ return false;
+ }
+
+ if (nme.visitingShop() != null) {
+ chatMessage(null, null, "You cannot attack players who are shopping.");
+ return false;
+ }
+ }
+ break;
+ }
+ case TYPE_MOBILE:
+ // free for all?
+ break;
+ case TYPE_PET:
+ chatMessage(null, null, "You can't attack pets.");
+ return false;
+ }
+ return super.canAttackEnemy(enemy);
+ }
+
+ @Override
+ public void enterBattle(Battle battle) {
+ super.enterBattle(battle);
+
+ actionsChanged();
+ }
+
+ @Override
+ public void endBattle() {
+ super.endBattle();
+
+ actionsChanged();
+ }
+
+ @Override
+ public void fleeBattle(Battle battle, ArrayList<Active> opponents) {
+ super.fleeBattle(battle, opponents);
+
+ if (pet != null) {
+ battle.removeParticipant(pet);
+ pet.fleeBattle(battle, opponents);
+ }
+ }
+
+ @Override
+ public void killedBattle(Battle battle, Active winner, ArrayList<Active> opponents) {
+ clearFollow();
+
+ battle.chatMessage(name + " is killed.");
+ chatBattle("You have died.");
+
+ splitMoney(game.goldLoseMod, opponents);
+ splitExp(game.expLoseMod, opponents);
+ endBattle();
+ game.globalChat(null, null, name + " has been killed by " + winner.name);
+
+ // FIXME: on player death script
+ game.onDeath(this, winner);
+
+ if (pet != null) {
+ // how to pass this to battle?
+ // list2.remove(front2.getFollowing());
+ battle.removeParticipant(pet);
+
+ pet.damageDone = 0;
+ pet.endBattle();
+ // FIXME: warp pet to player location
+ // pet.changeLocBypass(map, x, y);
+ }
+ }
+
+ @Override
+ public void send(DuskMessage msg) {
+ if (isPlayer() && alive) {
+ connection.send(msg);
+ }
+ }
+
+ public void chatMessage(Active from, String clan, String msg) {
+ if (from != null && from.getType() == TYPE_PLAYER && ignore.contains(from.name))
+ return;
+
+ if (clan != null && !clan.equals(this.clan))
+ return;
+
+ send(DuskMessage.create(MSG_CHAT, msg));
+
+ // TODO: charmer?
+ }
+
+ @Override
+ public void tick(int tick) {
+ if (tick >= muteLimit)
+ muteLimit = 0;
+
+ super.tick(tick);
+ }
+
+ boolean isVoiceAllowed() {
+ if (muteLimit != 0) {
+ chatMessage("You can't do that when nochanneled.");
+ return false;
+ } else {
+ long last = lastMessageStamp;
+ lastMessageStamp = System.currentTimeMillis();
+ if ((lastMessageStamp - last) < game.floodLimit) {
+ chatMessage("No flooding.");
+ return false;
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public void gossipCommand(String text) {
+ if (isVoiceAllowed()) {
+ String title = name;
+ if (privs > 2) {
+ // && hasCondition("invis")
+ // && hasCondition("invis2")) {
+ title = "A god";
+ }
+ game.globalChat(this, null, title + " gossips: " + text);
+ }
+ }
+
+ @Override
+ public void sayCommand(String text) {
+ if (isVoiceAllowed()) {
+ String title = name;
+ if (privs > 2) {
+ // && hasCondition("invis")
+ // && hasCondition("invis2")) {
+ title = "A god";
+ }
+ localisedChat(title + " says: " + text);
+ }
+ }
+
+ @Override
+ public void visibilityTick(int tick) {
+ super.visibilityTick(tick);
+
+ state.updatePlayer(tick);
+ }
+
+ @Override
+ public void follow(Active master) {
+ if (master.getType() != TYPE_PLAYER
+ && master.getType() != TYPE_PET) {
+ chatMessage("You can only follow players.");
+ return;
+ }
+
+ super.follow(master);
+ }
+
+ @Override
+ void leaveCommand() {
+ boolean linkpet = false;
+ if (pet != null) {
+ // Not really happy with this design here
+ if (pack.contains(pet)) {
+ pack.removeFollower(pet);
+ linkpet = true;
+ }
+ }
+
+ super.leaveCommand();
+
+ // This seems a bit ugly ...
+ if (linkpet) {
+ pack = new Pack();
+ pet.pack = pack;
+ pack.addFollower(this);
+ pack.addFollower(pet);
+ }
+ }
+
+ void initMap() {
+ ListMessage lm = new ListMessage(MSG_INIT_MAP);
+
+ lm.add(FIELD_MAP_WIDTH, game.mapSize);
+ lm.add(FIELD_MAP_HEIGHT, game.mapSize);
+ // FIXME: depends on client info
+ lm.add(FIELD_MAP_ASSETLOCATION, game.rcName);
+
+ send(lm);
+ }
+
+ void startup() {
+ // Sent initial stuff
+ initMap();
+
+ // Add me: no, do it at next game update loop
+ //send(createUpdateMessage(MSG_ADD_ENTITY));
+
+ game.onPlayerStart(this);
+ }
+
+ void logout() {
+ game.unregisterPlayer(this);
+ // FIXME: save player
+ chatMessage("Goodbyte.");
+ connection.shutdown();
+ }
+
+ void parseCommand(String cmd) {
+ System.out.println(name + ": parse command: " + cmd);
+ try {
+ cmdline.execute(cmd);
+ } catch (Exception ex) {
+ // FIXME: log
+ game.log.printf(ex, "executing: " + cmd);
+ Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ public void addItem(Holdable item) {
+ super.addItem(item);
+
+ inventoryChanged();
+ }
+
+ @Override
+ public boolean removeItem(Holdable item) {
+ if (super.removeItem(item)) {
+ inventoryChanged();
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+/**
+ * This is a wrapper for the player object, which is passed to scripts.
+ *
+ * Defines a cleaner external interface, and the intention is that this
+ * is a safer sandboxed set of interfaces.
+ *
+ * TODO: this is probably where i want the commands to live too simply
+ * for a matter of maintainability
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class PlayerCommands {
+ private final Game game;
+
+ final private Player p;
+
+ public PlayerCommands(Game game, Player p) {
+ this.game = game;
+ this.p = p;
+ }
+
+ public void chat(String msg) {
+ p.chatMessage(msg);
+ }
+
+ public String getString(String name, String def) {
+ return p.variables.getString(name, def);
+ }
+
+ public String getString(String name) {
+ return p.variables.getString(name, null);
+ }
+
+ public void setString(String name, String value) {
+ p.variables.put(name, value);
+ }
+
+ public void setInt(String name, int value) {
+ p.variables.put(name, value);
+ }
+
+ public int getInt(String name, int def) {
+ return p.variables.getInteger(name, def);
+ }
+
+ public int getInt(String name) {
+ return p.variables.getInteger(name, 0);
+ }
+
+ /**
+ * Jump to a location
+ * @param mapName
+ * @param mapAlias
+ * @return true if the jump happened.
+ */
+ public boolean jumpTo(String mapName, String mapAlias) {
+ System.out.println("script: jumpto(" + mapName + ", " + mapAlias + ")");
+ TileMap map = game.getMap(mapName);
+
+ if (map != null) {
+ Location l = map.locationForAlias(mapAlias);
+
+ if (l != null) {
+ return p.jumpTo(map, l.x, l.y);
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import duskz.protocol.DuskProtocol;
+import static duskz.protocol.DuskProtocol.MSG_AUTH;
+import static duskz.protocol.DuskProtocol.MSG_COMMAND;
+import static duskz.protocol.DuskProtocol.MSG_QUERY;
+import duskz.protocol.EntityListMessage;
+import duskz.protocol.ListMessage;
+import duskz.server.BlockedIPException;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.HashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Wraps incomming/outgoing communications
+ *
+ * The receiver thread handles login and creates the player object.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class PlayerConnection implements DuskProtocol {
+
+ private final Game game;
+ private final Socket socket;
+ private final ReceiverThread receiver;
+ private final SenderThread sender;
+ boolean isStopped;
+ Player player;
+
+ public PlayerConnection(Game game, Socket socket) throws IOException {
+ this.game = game;
+ this.socket = socket;
+ receiver = new ReceiverThread(socket);
+ sender = new SenderThread(socket);
+ }
+
+ public void start() {
+ receiver.start();
+ sender.start();
+ }
+
+ public String getAddress() {
+ // TODO: the connect code had the following, why?
+ // String strIP = socket.getInetAddress().toString();
+ //int ip = strIP.indexOf("/");
+ //strIP = strIP.substring(ip + 1, strIP.length());
+
+ if (socket != null
+ && socket.isConnected())
+ return socket.getInetAddress().toString();
+ else
+ return null;
+ }
+
+ void send(DuskMessage msg) {
+ sender.send(msg);
+ }
+
+ void shutdown() {
+ // Do I want to save the player here/
+ System.out.println("shutting down player connection");
+
+ try {
+ // shutdown everything
+ isStopped = true;
+ sender.interrupt();
+ receiver.interrupt();
+ try {
+ sender.join();
+ } catch (InterruptedException x) {
+ }
+ socket.close();
+ } catch (IOException ex) {
+ Logger.getLogger(PlayerConnection.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ void abort() {
+ // definitely no-save exit
+ shutdown();
+ }
+
+ public class ReceiverThread extends Thread implements DuskProtocol {
+
+ final private DataInputStream instream;
+
+ public ReceiverThread(Socket socket) throws IOException {
+ instream = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
+ }
+
+ DuskMessage readMessage() throws IOException {
+ DuskMessage msg;
+
+ do {
+ synchronized (instream) {
+ msg = DuskMessage.receiveMessage(instream);
+ }
+
+ // Redirect queries to whomever is waiting for it
+ if (msg.name == MSG_QUERY) {
+ EntityListMessage qmsg = (EntityListMessage) msg;
+ PendingQuery f = sender.getPendingQuery(qmsg.id);
+ f.setResponse(qmsg);
+ }
+ } while (msg.name == MSG_QUERY);
+
+ return msg;
+ }
+ final static int ASK_NEW_RACE = 0;
+
+ private DuskMessage getRaceQuery() {
+ ListMessage racem = new ListMessage(ASK_NEW_RACE);
+
+ racem.add(FIELD_QUERY_PROMPT, "Choose race");
+ racem.add(FIELD_QUERY_OPTIONS, game.getRaceNames());
+
+ return racem;
+ }
+
+ private DuskMessage getNewPlayerInfo() {
+ ListMessage np = new ListMessage(FIELD_AUTH_NEWPLAYER);
+ np.add(getRaceQuery());
+
+ return np;
+ }
+
+ /**
+ * Check if the user can be created, unique name, and all requirements met
+ *
+ * @param name
+ * @param np NEWPLAYER message containing new player infos.
+ * @param res a NEWPLAYER message populated iwth missing bits
+ * @return true if ok
+ */
+ boolean canCreate(String name, ListMessage np, ListMessage res) {
+ boolean ok = true;
+ ListMessage newp = null;
+ String race;
+
+ // Check if we have the info we need to create the user - i.e. race so far
+ if (np == null
+ || (race = np.getString(ASK_NEW_RACE, null)) == null
+ || game.getRace(race) == null) {
+ newp = new ListMessage(FIELD_AUTH_NEWPLAYER);
+ newp.add(getRaceQuery());
+ res.add(newp);
+ ok = false;
+ }
+
+ ok &= !game.playerExists(name);
+ ok &= !game.petExists(name);
+
+ return ok;
+ }
+
+ Player loadPlayer(String name, String password) throws IOException, BlockedIPException {
+ Player player = new Player(game, PlayerConnection.this);
+
+ try {
+ player.load(new File(game.getRoot(), "players/default"));
+ } catch (IOException x) {
+ }
+
+ player.load(new File(game.getRoot(), "players/" + name));
+
+ return player;
+ }
+
+ Player createPlayer(String name, String password, ListMessage np) throws IOException, BlockedIPException {
+ String race = np.getString(ASK_NEW_RACE, null);
+
+ Player player = new Player(game, PlayerConnection.this);
+
+ try {
+ player.load(new File(game.getRoot(), "players/default"));
+ } catch (IOException x) {
+ }
+
+ player.setProperty("name", name);
+ // this will load the race
+ player.setProperty("race", race);
+ player.setProperty("password", password);
+
+ if (player.race == null)
+ throw new IOException("Unknown race");
+
+ player.save(new File(game.getRoot(), "players/" + name));
+
+ return player;
+ }
+
+ Player handleLogin() {
+ String address = getAddress();
+
+ // Handle auth state.
+ try {
+ while (true) {
+ DuskMessage dm = readMessage();
+ ListMessage res = new ListMessage(MSG_AUTH);
+
+ // TODO: FIELD_AUTH_CLIENT stuff
+
+ switch (dm.name) {
+ case MSG_AUTH: {
+ ListMessage lm = (ListMessage) dm;
+ String player = lm.getString(FIELD_AUTH_PLAYER, null);
+ String pass = lm.getString(FIELD_AUTH_PASS, null);
+ ListMessage newp = (ListMessage) lm.getMessage(FIELD_AUTH_NEWPLAYER);
+
+ // Don't give detailed reasons for failure (e.g. 'player not found') for security
+
+ if (player == null || pass == null) {
+ // Just send new player info
+ res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_INCOMPLETE);
+ res.add(FIELD_AUTH_REASON, "Login failed.");
+ res.add(getNewPlayerInfo());
+ } else if (newp == null && game.playerExists(player)) {
+ // Try normal login
+ if (game.checkPassword(player, pass, address)) {
+ return loadPlayer(player, pass);
+ } else {
+ res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_FAILED);
+ res.add(FIELD_AUTH_REASON, "Login failed.");
+ }
+ } else if (!game.isGoodName(player)) {
+ // disallowed name
+ res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_FAILED);
+ res.add(FIELD_AUTH_REASON, "Name is not allowed, try again.");
+ res.add(getNewPlayerInfo());
+ } else {
+ System.out.println("? new player " + player);
+ // Trying to create a player
+ if (canCreate(player, newp, res)) {
+ System.out.println(" creating player\n");
+ return createPlayer(player, pass, newp);
+ } else {
+ if (res.get(FIELD_AUTH_NEWPLAYER) != null) {
+ res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_INCOMPLETE);
+ res.add(FIELD_AUTH_REASON, "Insufficient information to create player.");
+ } else {
+ res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_EXISTS);
+ res.add(FIELD_AUTH_REASON, "A player with that name already exists.");
+ }
+ }
+ }
+ break;
+ }
+ default: // Anything else is a protocol error
+ res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_FAILED);
+ res.add(FIELD_AUTH_REASON, "Unexpected message.");
+ break;
+ }
+
+ sender.send(res);
+ }
+ } catch (BlockedIPException ex) {
+ sender.send("There's already a player connected from your IP address.");
+ } catch (IOException ex) {
+ sender.send("IO Error: " + ex);
+ } catch (ClassCastException ex) {
+ sender.send("Loading error: " + ex);
+ } catch (TooManyTriesException ex) {
+ sender.send("Too many login failures, try again in an hour.");
+ } catch (Exception ex) {
+ sender.send("Unexpected error: " + ex);
+ ex.printStackTrace();
+ }
+ abort();
+ return null;
+ }
+
+ @Override
+ public void run() {
+ player = handleLogin();
+
+ if (player != null) {
+ ListMessage res = new ListMessage(MSG_AUTH);
+ res.add(player.ID, FIELD_AUTH_RESULT, AUTH_LOGIN_OK);
+ res.add(FIELD_AUTH_REASON, "Login ok.");
+ player.send(res);
+
+ setName("Player thread: " + player.name);
+ sender.setName("Ouptut thread: " + player.name);
+
+ // log successful connection, etc.
+ //
+ player.startup();
+
+ // Do this afterwards, as it then goes into the game update loop
+ game.registerPlayer(player);
+
+ while (!isStopped) {
+ try {
+ DuskMessage dm = readMessage();
+
+ switch (dm.name) {
+ case MSG_COMMAND: {
+ DuskMessage.StringMessage sm = (DuskMessage.StringMessage) dm;
+
+ player.parseCommand(sm.value);
+ break;
+ }
+ default:
+ // anything else is bogus
+ System.out.println("Unexpected server command (ignored):");
+ dm.format(System.out);
+ }
+ } catch (SocketTimeoutException e) {
+ sender.send(DuskMessage.create(MSG_PING));
+ } catch (Exception e) {
+ //game.log.printError("LivingThing.run():" + name + " disconnected", e);
+ e.printStackTrace();
+ player.logout();
+ return;
+ }
+ }
+ shutdown();
+ }
+ }
+ }
+
+ public class SenderThread extends Thread implements DuskProtocol {
+
+ private DataOutputStream outstream;
+ long qid;
+ final HashMap<Long, PendingQuery> pendingQuestions = new HashMap<>();
+ final public LinkedBlockingDeque<DuskMessage> messageQueue = new LinkedBlockingDeque<>();
+
+ public SenderThread(Socket socket) throws IOException {
+ outstream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
+ }
+
+ synchronized long getQuestionID() {
+ return qid++;
+ }
+
+ PendingQuery getPendingQuery(long id) {
+ synchronized (pendingQuestions) {
+ return pendingQuestions.get(id);
+ }
+ }
+
+ /**
+ * This asks a query asynchronously
+ * FIXME: abnormal shutdown requires the queries to be flushed
+ *
+ * @param query
+ * @return a future used to retrieve the query
+ */
+ public Future<EntityListMessage> askQuestion(EntityListMessage query) throws IOException {
+ PendingQuery pq = new PendingQuery(query);
+
+ if (query.name != MSG_QUERY)
+ throw new RuntimeException("Trying to ask non-query message");
+
+ synchronized (pendingQuestions) {
+ query.id = qid++;
+ pendingQuestions.put(query.id, pq);
+ }
+
+ messageQueue.add(query);
+
+ // Just discard messages until we get a response
+ // And what if we don't?
+ if (Thread.currentThread() == this) {
+ do {
+ receiver.readMessage();
+ } while (pq.response == null);
+ }
+
+ return pq;
+ }
+
+ public void send(DuskMessage dm) {
+ messageQueue.add(dm);
+ }
+
+ public void send(String msg) {
+ messageQueue.add(new DuskMessage.StringMessage(MSG_CHAT, msg));
+ }
+
+ public void run() {
+ DuskMessage msg;
+
+ while (!isStopped) {
+ try {
+ msg = messageQueue.take();
+
+ // low level protocol dump
+ msg.format(System.out);
+
+ try {
+ msg.sendMessage(outstream);
+ outstream.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ player.logout();
+ }
+ } catch (InterruptedException ex) {
+ //Logger.getLogger(SendThread.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+ }
+
+ static class PendingQuery implements Future<EntityListMessage> {
+
+ EntityListMessage query;
+ EntityListMessage response;
+
+ public PendingQuery(EntityListMessage query) {
+ this.query = query;
+ }
+
+ synchronized void setResponse(EntityListMessage response) {
+ this.response = response;
+ notify();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public boolean isCancelled() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public boolean isDone() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public synchronized EntityListMessage get() throws InterruptedException, ExecutionException {
+ while (response == null) {
+ wait();
+ }
+ return response;
+ }
+
+ @Override
+ public EntityListMessage get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ if (response == null) {
+ wait(unit.convert(timeout, unit));
+ if (response == null)
+ throw new TimeoutException();
+ }
+ return response;
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.util.List;
+
+/**
+ * A player provided shop, the list of items can be exhausted.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class PlayerShop extends Shop {
+
+ /**
+ * Gold in till (it appears unused in previous game?)
+ */
+ private long gold;
+ /**
+ * Owner of shop
+ */
+ private Player owner;
+
+ public PlayerShop(Game game) {
+ super(game);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PLAYER_SHOP;
+ }
+
+ private void buy(Active buyer, List<Holdable> all) {
+ for (Holdable h : all) {
+ items.remove(h);
+ buyer.addItem(h);
+ }
+ inventoryChanged = game.getClock();
+ }
+
+ @Override
+ public void buy(Active buyer, String what, int quantity) {
+ List<Holdable> all = items.getAll(what, quantity);
+
+ if (all.isEmpty()) {
+ buyer.chatMessage(name + " doesn't have that for sale.");
+ } else if (all.size() < quantity) {
+ buyer.chatMessage(name + " doesn't have that many to sell.");
+ } else if (buyer == owner) {
+ buy(buyer, all);
+ } else if (buyer.addGold(-all.get(0).cost * quantity * 3 / 4)) {
+ gold += all.get(0).cost * quantity * 3 / 4;
+ buy(buyer, all);
+ } else {
+ buyer.chatMessage("You can't afford that.");
+ }
+ }
+
+ @Override
+ public void sell(Active customer, List<Holdable> all) {
+ if (owner != customer)
+ customer.chatMessage("You cannot sell to this shop.");
+ else {
+ for (Holdable h : all) {
+ items.add(h);
+ customer.removeItem(h);
+ }
+ inventoryChanged = game.getClock();
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import duskz.protocol.DuskProtocol;
+import static duskz.protocol.DuskProtocol.FIELD_ENTITY_FLAGS;
+import static duskz.protocol.DuskProtocol.MSG_UPDATE_ENTITY;
+import duskz.protocol.EntityListMessage;
+import duskz.protocol.ListMessage;
+import duskz.protocol.MapMessage;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This tracks the state of the player client, to optimise network transfers.
+ *
+ * Each integer field contains the last tick the item was changed, this is
+ * to allow for polled clients.
+ *
+ * The map update reqiures more information, so the location of the last update
+ * is recorded.
+ *
+ * TODO: pets need most of this monitored too
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class PlayerState implements DuskProtocol {
+
+ Player player;
+ /**
+ * Slowly changing stats, str, dex, etc.
+ */
+ protected int stats;
+ /**
+ * hp/mp, gold or exp changed
+ */
+ protected int score;
+ /**
+ * Condition list changed
+ */
+ protected int conditions;
+ /**
+ * Skill list/level changed
+ */
+ protected int skills;
+ /**
+ * Spell list/level changed
+ */
+ protected int spells;
+ /**
+ * Item list changed
+ */
+ protected int inventory;
+ /**
+ * worn items changed
+ */
+ protected int worn;
+ /**
+ * something affecting actions changed
+ */
+ protected int actions;
+ /**
+ * Last updated location
+ */
+ TileMap map;
+ int x;
+ int y;
+ /**
+ * If we were last over a shop
+ */
+ Shop overShop;
+ /**
+ * All visible things
+ */
+ final HashMap<Long, VisibleThing> visibleThings = new HashMap<>();
+ /*
+ * Last update tick
+ */
+ int lastUpdate = -1;
+
+ public PlayerState(Player player) {
+ this.player = player;
+ }
+
+ /**
+ * Send the player all updates since last tick
+ *
+ * @param player
+ */
+ public void updatePlayer(int tick) {
+ boolean moved = player.x != x || player.y != y || player.map != map;
+
+ x = player.x;
+ y = player.y;
+ map = player.map;
+
+ updateVisibleThings(tick);
+
+ if (moved) {
+ updateMap();
+ updateCheckShop();
+ } else if (overShop != null && overShop.inventoryChanged >= lastUpdate) {
+ player.send(overShop.createTransactionMessage(MSG_UPDATE_MERCHANT));
+ }
+
+ if (actions >= lastUpdate)
+ updateActions();
+
+ if (worn >= lastUpdate)
+ updateWorn();
+
+ if (inventory >= lastUpdate)
+ updateInventory();
+
+ if (conditions >= lastUpdate)
+ updateConditions();
+
+ lastUpdate = tick;
+ }
+
+ void updateMap() {
+ int r = player.game.viewRange;
+
+ System.out.printf("update map %d,%d map %s\n", x, y, map.name);
+
+ int width = r * 2 + 1;
+ int height = r * 2 + 1;
+ short[] tiles = null;
+ int nlayers = map.getLayerCount();
+ int nused = 0;
+ short[][] layers = new short[nlayers][];
+ int groundLayer = 0;
+ for (int l = 0; l < nlayers; l++) {
+ if (tiles == null)
+ tiles = new short[width * height];
+
+ short[] visible = map.getRegion(l, x - r, y - r, width, height, tiles);
+
+ if (visible != null) {
+ tiles = null;
+
+ if (l == map.getGroundLayer())
+ groundLayer = nused;
+
+ layers[nused++] = visible;
+ }
+ }
+
+ player.send(new MapMessage(MSG_UPDATE_MAP, width, height, x, y, groundLayer, nused, layers));
+ }
+
+ /**
+ * Check if the player is on a shop square, and if so update the client
+ * state and send off the shop details. If the player has left a shop
+ * square, then send a leaving update.
+ */
+ void updateCheckShop() {
+ Shop thing = player.visitingShop();
+
+ if (thing != null) {
+ overShop = thing;
+ player.send(overShop.createTransactionMessage(MSG_UPDATE_MERCHANT));
+ } else if (overShop != null) {
+ player.send(DuskMessage.create(MSG_EXIT_MERCHANT));
+ overShop = null;
+ }
+ }
+
+ void updateActions() {
+ DuskMessage.StringListMessage list = new DuskMessage.StringListMessage(MSG_UPDATE_ACTIONS);
+
+ if (player.isFighting()) {
+ list.add("flee");
+ } else {
+ if (player.isSleeping()) {
+ list.add("wake");
+ } else {
+ list.add("sleep");
+ }
+ }
+ player.send(list);
+ }
+
+ void updateWorn() {
+ player.send(player.wornItems.toMessage(MSG_EQUIPMENT));
+ }
+
+ void updateInventory() {
+ // FIXME: make player item sale price ratio adjustable
+ player.send(player.inventory.createTransactionMessage(MSG_INVENTORY, 0.5f));
+ }
+
+ void updateConditions() {
+ }
+
+ /**
+ * Update the visibleThings list, and queue updates to the client.
+ *
+ * Note that this is the ONLY way that visibleThings should ever be changed.
+ */
+ void updateVisibleThings(int tick) {
+ HashMap<Long, VisibleThing> left = (HashMap<Long, VisibleThing>) visibleThings.clone();
+ ArrayList<VisibleThing> newlyVisible = new ArrayList<>();
+
+ //System.out.println("checking visible range");
+
+ // FIXME: I think this is missing something here, i.e. some sort of object visibility check
+
+ for (TileMap.MapData md : map.range(x, y, player.game.viewRange)) {
+ if (!md.entities.isEmpty() && player.canSee(md.x, md.y)) {
+ for (Thing thing : md.entities) {
+ VisibleThing here = left.remove(thing.ID);
+ if (here == null) {
+ newlyVisible.add(new VisibleThing(thing));
+ } else {
+ here.updateIfChanged(tick);
+ }
+ }
+ }
+ }
+
+ for (VisibleThing thing : newlyVisible) {
+ // Add thing to client
+ //send(thing.thing.createUpdateMessage(MSG_ADD_ENTITY));
+ visibleThings.put(thing.thing.ID, thing);
+ thing.updateIfChanged(tick);
+ }
+ // anything left over must be removed
+ for (long id : left.keySet()) {
+ player.send(DuskMessage.EntityMessage.create(id, MSG_REMOVE_ENTITY));
+ visibleThings.remove(id);
+ }
+ }
+
+ private void addStat(ListMessage lm, int field, int index) {
+ int total = player.getStat(index);
+ int bonus = player.getBonus(index);
+ addStat(lm, field, total - bonus, bonus);
+ }
+
+ private void addStat(ListMessage lm, int field, int base, int bonus) {
+ lm.add(field, base);
+ lm.add(field, bonus);
+ }
+
+ protected ListMessage buildStats(int msg) {
+ ListMessage lm = new ListMessage(msg);
+
+ lm.add(FIELD_INFO_CASH, player.getGold());
+ lm.add(FIELD_INFO_EXP, player.getExp());
+ addStat(lm, FIELD_INFO_STR, Active.STAT_STR);
+ addStat(lm, FIELD_INFO_INT, Active.STAT_INT);
+ addStat(lm, FIELD_INFO_DEX, Active.STAT_DEX);
+ addStat(lm, FIELD_INFO_CON, Active.STAT_CON);
+ addStat(lm, FIELD_INFO_WIS, Active.STAT_WIS);
+ addStat(lm, FIELD_INFO_DAM, player.getDamageMod(), player.getBonus(Active.STAT_DAMAGE));
+ addStat(lm, FIELD_INFO_ARC, player.getArmourMod(), player.getBonus(Active.STAT_ARC));
+
+ // What else?
+
+ return lm;
+ }
+
+ class VisibleThing {
+
+ Thing thing;
+ int clientX = -10;
+ int clientY = -10;
+
+ public VisibleThing(Thing thing) {
+ this.thing = thing;
+ }
+
+ public void updateIfChanged(int tick) {
+ int dx = thing.x - clientX;
+ int dy = thing.y - clientY;
+ DuskMessage msg = null;
+
+ clientX = thing.x;
+ clientY = thing.y;
+
+ /*
+ if (thing instanceof Active
+ && ((Active) thing).isStateChanged(tick)) {
+ System.out.println("sending remove/add for changed entity");
+ // FIXME: this should just update the flags or whatever
+ player.send(DuskMessage.EntityMessage.create(thing.ID, MSG_REMOVE_ENTITY));
+ player.send(thing.createUpdateMessage(MSG_ADD_ENTITY));
+ return;
+ }*/
+
+ if (dx != 0 || dy != 0) {
+ System.out.println("Thing " + thing.name + " moved: " + dx + ", " + dy);
+
+ // TODO: rather than relative move, send the specific location/faceing direction?
+
+ if (dx == 0) {
+ if (dy == 1) {
+ msg = DuskMessage.create(thing.ID, MSG_MOVE, (byte) 's');
+ } else if (dy == -1) {
+ msg = DuskMessage.create(thing.ID, MSG_MOVE, (byte) 'n');
+ }
+ }
+ if (dy == 0) {
+ if (dx == 1) {
+ msg = DuskMessage.create(thing.ID, MSG_MOVE, (byte) 'e');
+ } else if (dx == -1) {
+ msg = DuskMessage.create(thing.ID, MSG_MOVE, (byte) 'w');
+ }
+ }
+ if (msg == null) {
+ // Need to remove it first to move it further
+ player.send(DuskMessage.EntityMessage.create(thing.ID, MSG_REMOVE_ENTITY));
+ msg = thing.createUpdateMessage(MSG_ADD_ENTITY);
+ }
+
+ player.send(msg);
+ }
+
+ // FIXME: this could include location?
+ if (thing instanceof Active) {
+ Active a = (Active) thing;
+
+ if (a.flagsChanged >= lastUpdate
+ || a.conditionsChanged >= lastUpdate) {
+ EntityListMessage elm = new EntityListMessage(thing.ID, MSG_UPDATE_ENTITY);
+
+ if (a.flagsChanged >= lastUpdate) {
+ int flags = 0;
+ if (a.isFighting()) {
+ flags = a.getBattleSide();
+ }
+ if (a.isSleeping())
+ flags |= ENTITY_FLAG_SLEEPING;
+
+ elm.add(FIELD_ENTITY_FLAGS, flags);
+ }
+
+ if (a.conditionsChanged >= lastUpdate) {
+ elm.add(FIELD_ENTITY_CONDITIONS, a.getActiveConditions());
+ }
+
+ player.send(elm);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+/**
+ * Immovable object/graphic?
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Prop extends Thing {
+
+ public Prop(Game game) {
+ super(game);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PROP;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Slight abuse of iterators and autoclosable and iterable to be able to iterate
+ * through properties easily.
+ *
+ * Used as:
+ * try (PropertyLoader pl = new PropertyLoader(file)) {
+ * for (PropertyEntry pe: pl) {
+ * }
+ * }
+ *
+ * FIXME: maye just change to PropertyReader and use a readEntry() interface
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class PropertyLoader implements Iterable<PropertyLoader.PropertyEntry>, AutoCloseable {
+
+ BufferedReader reader;
+
+ public PropertyLoader(File src) throws FileNotFoundException {
+ reader = new BufferedReader(new FileReader(src));
+ }
+
+ /**
+ * Should only be called once for a given property loader.
+ *
+ * @return
+ */
+ @Override
+ public Iterator<PropertyEntry> iterator() {
+ return new PropertyIterator(reader);
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (reader != null)
+ reader.close();
+ }
+
+ static class PropertyIterator implements Iterator<PropertyEntry> {
+
+ BufferedReader reader;
+ String name;
+ PropertyEntry entry = new PropertyEntry();
+
+ public PropertyIterator(BufferedReader reader) {
+ this.reader = reader;
+ }
+
+ @Override
+ public boolean hasNext() {
+ try {
+ if (reader == null)
+ return false;
+
+ if (name != null)
+ return true;
+
+ do {
+ String line = reader.readLine();
+
+ if (line == null)
+ return false;
+ else if (!line.startsWith("#")) {
+ int c = line.indexOf('=');
+
+ if (c != -1) {
+ entry.value = line.substring(c + 1).trim();
+ entry.name = name = line.substring(0, c).trim();
+ } else {
+ System.out.println("unparsable property line: " + line);
+ }
+ }
+ } while (name == null);
+
+ return true;
+ } catch (IOException ex) {
+ Logger.getLogger(PropertyLoader.class.getName()).log(Level.SEVERE, null, ex);
+ return false;
+ }
+ }
+
+ @Override
+ public PropertyEntry next() {
+ if (name == null)
+ if (!hasNext()) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ name = null;
+ return entry;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ public static class PropertyEntry {
+
+ public String name;
+ public String value;
+
+ public int asInteger(int def) {
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException x) {
+ return def;
+ }
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ File file = new File("mobs");
+ try (PropertyLoader pl = new PropertyLoader(file)) {
+ for (PropertyEntry pe : pl) {
+ System.out.println("entry: '" + pe.name + "' " + pe.value);
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import static duskz.server.entityz.Active.STAT_CON;
+import static duskz.server.entityz.Active.STAT_DEX;
+import static duskz.server.entityz.Active.STAT_HP;
+import static duskz.server.entityz.Active.STAT_HPMAX;
+import static duskz.server.entityz.Active.STAT_INT;
+import static duskz.server.entityz.Active.STAT_MP;
+import static duskz.server.entityz.Active.STAT_MPMAX;
+import static duskz.server.entityz.Active.STAT_STR;
+import static duskz.server.entityz.Active.STAT_WIS;
+import duskz.server.entityz.PropertyLoader.PropertyEntry;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Meta-data about race
+ *
+ * Races adjust the base values of attributes.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Race {
+
+ public String name;
+ public int image;
+ // Uses STAT_* constants from Active
+ public int stats[] = new int[12];
+
+ Race() {
+ }
+
+ boolean setProperty(String name, String value) {
+ switch (name) {
+ case "name":
+ this.name = value;
+ break;
+ case "image":
+ image = Integer.valueOf(value);
+ break;
+ case "hp":
+ stats[STAT_HP] = Integer.valueOf(value);
+ break;
+ case "maxhp":
+ stats[STAT_HPMAX] = Integer.valueOf(value);
+ break;
+ case "mp":
+ stats[STAT_MP] = Integer.valueOf(value);
+ break;
+ case "maxmp":
+ stats[STAT_MPMAX] = Integer.valueOf(value);
+ break;
+ case "str":
+ stats[STAT_STR] = Integer.valueOf(value);
+ break;
+ case "int":
+ stats[STAT_INT] = Integer.valueOf(value);
+ break;
+ case "dex":
+ stats[STAT_DEX] = Integer.valueOf(value);
+ break;
+ case "con":
+ stats[STAT_CON] = Integer.valueOf(value);
+ break;
+ case "wis":
+ stats[STAT_WIS] = Integer.valueOf(value);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ public static Race loadRace(File file) throws IOException {
+ Race r = new Race();
+
+ r.name = file.getName();
+ try (PropertyLoader pl = new PropertyLoader(file)) {
+ for (PropertyEntry pe: pl) {
+ r.setProperty(pe.name, pe.value);
+ }
+ }
+
+ return r;
+ }
+}
/**
* Changes
*/
-package duskz.server;
+package duskz.server.entityz;
import java.io.File;
import java.io.FileNotFoundException;
public ScriptManager() {
Permissions perms = new Permissions();
+
+ // FIXME: fix security manager for the actual paths used
+
//perms.add(new AllPermission());
perms.add(new FilePermission("scripts/*", "read,write"));
// perms.add(new NetPermission("*"));
new WatchThread().start();
}
- public Future<?> runScript(String script, Object... args) {
+ public <T> Future<T> runScript(String script, Object... args) throws ClassCastException {
return pool.submit(new ScriptData(script, args));
}
- public Future<?> runScript(File script, Object... args) throws FileNotFoundException {
+ public <T> Future<T> runScript(File script, Object... args) throws FileNotFoundException, ClassCastException {
return pool.submit(new ScriptData(script, args));
}
this.script = script;
}
- public ScriptData(File scriptFile, Object... args) throws FileNotFoundException {
+ public ScriptData(File scriptFile, Object... args) throws FileNotFoundException, ClassCastException {
for (int i = 0; i < args.length; i += 2) {
this.args.put((String) args[i], args[i + 1]);
}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.TransactionMessage;
+import java.util.List;
+
+/**
+ * Shops contain sellable items
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public abstract class Shop extends Thing {
+
+ Inventory items = new Inventory();
+ /**
+ * Tick of last inventory change.
+ */
+ int inventoryChanged;
+
+ Shop(Game game) {
+ super(game);
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "item":
+ Holdable h = game.createItem(value);
+ if (h != null) {
+ System.out.println(this.name + " sells " + h.name);
+ items.add(h);
+ } else
+ game.log.printf(Log.ERROR, "Shop: %s, unkown item: %s", this.name, value);
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ /**
+ * Create a transactionmessage representing the shop stores.
+ *
+ * @param name
+ * @return
+ */
+ public TransactionMessage createTransactionMessage(int name) {
+ return items.createTransactionMessage(name, 1);
+ }
+
+ @Override
+ public void look(Active viewer) {
+ if (description != null)
+ viewer.chatMessage("You see " + name + ", " + description);
+ else
+ viewer.chatMessage("You see " + name);
+ viewer.chatMessage("Currently for sale:");
+
+ items.describeTo(viewer);
+
+ /*
+ * } else if (objStore.isPlayerMerchant()) {
+ lt.chatMessage("You see a merchant that sells");
+ PlayerMerchant pmrStore = (PlayerMerchant) objStore;
+ boolean blnEmptyMerchant = true;
+ for (LinkedList<Item> list : pmrStore.vctItems.values()) {
+ Item item = (Item) list.element();
+ cmdline = item.name;
+ String strSpacer = "\t";
+ if (cmdline.length() < 11) {
+ strSpacer = "\t\t";
+ }
+ lt.chatMessage("\t" + cmdline + strSpacer + item.description);
+ blnEmptyMerchant = false;
+ }
+ if (blnEmptyMerchant) {
+ lt.chatMessage("\tNothing at the moment.");
+ }
+ } else if (objStore.isMerchant()) {
+ lt.chatMessage("You see a merchant that sells");
+ Merchant mrcStore = (Merchant) objStore;
+ boolean blnEmptyMerchant = true;
+ for (String item : mrcStore.items) {
+ Item itmStore = game.getItem(item);
+ if (itmStore != null) {
+ String strSpacer = "\t";
+ if (item.length() < 11) {
+ strSpacer = "\t\t";
+ }
+ lt.chatMessage("\t" + item + strSpacer + itmStore.description);
+ blnEmptyMerchant = false;
+ } else {
+ if (item.equals("pet")) {
+ lt.chatMessage("\tpets.");
+ blnEmptyMerchant = false;
+ } else {
+ item = item.substring(6, item.length());
+ lt.chatMessage("\ttraining in " + item);
+ blnEmptyMerchant = false;
+ }
+ }
+ }
+ if (blnEmptyMerchant) {
+ lt.chatMessage("\tNothing.");
+ }
+ return null;
+ }
+
+ */
+ }
+
+ abstract public void buy(Active buyer, String what, int quantity);
+
+ abstract public void sell(Active customer, List<Holdable> items);
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * Sign
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+class Sign extends Thing {
+
+ public Sign(Game game) {
+ super(game);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_SIGN;
+ }
+
+ @Override
+ void writeState(BufferedWriter out) throws IOException {
+ super.writeState(out);
+ writeProperty(out, "description", description);
+ }
+
+ @Override
+ public void look(Active viewer) {
+ viewer.chatMessage("The sign says " + description + ".");
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.DuskMessage;
+import duskz.protocol.EntityUpdateMessage;
+import duskz.server.entityz.PropertyLoader.PropertyEntry;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Comparator;
+
+/**
+ * Base object for all dusk things
+ *
+ * Handles common fields, creation and typeing, and i/o.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public abstract class Thing implements Cloneable {
+
+ /**
+ * Every non-abstract sub-class of thing needs a unique
+ * number. Used to simplify some code decisions.
+ */
+ public static final int TYPE_PLAYER = 0;
+ public static final int TYPE_PET = 1;
+ public static final int TYPE_MOBILE = 2;
+ public static final int TYPE_SIGN = 3;
+ public static final int TYPE_PROP = 4;
+ public static final int TYPE_GAME_SHOP = 5;
+ public static final int TYPE_PLAYER_SHOP = 6;
+ public static final int TYPE_ITEM = 7;
+ public static final int TYPE_FOOD = 8;
+ public static final int TYPE_DRINK = 9;
+ public static final int TYPE_WEAPON = 10;
+ public static final int TYPE_ARMOUR = 11;
+ public static final int TYPE_CONTAINER = 12;
+ public static final int TYPE_TRAINING = 13;
+ public final static Comparator<Thing> cmpName = new Comparator<Thing>() {
+ @Override
+ public int compare(Thing o1, Thing o2) {
+ return o1.name.compareTo(o2.name);
+ }
+ };
+ protected final Game game;
+ /**
+ * Game unique id
+ */
+ public final long ID;
+ /**
+ * Name
+ */
+ public String name;
+ /**
+ * Description
+ */
+ public String description = null;
+ /**
+ * Hide the name from players.
+ *
+ * boolean isHideName;
+ * /**
+ * Map object belongs to
+ */
+ public TileMap map;
+ /**
+ * Location on map
+ */
+ public int x, y;
+ /**
+ * Current image id. TODO: players get images from race instead.
+ */
+ protected int image;
+ //
+ static long nextid = 1;
+ static Integer idlock = new Integer(0);
+
+ /**
+ * Create a new globally unique id
+ *
+ * @return
+ */
+ public static long createID() {
+ synchronized (idlock) {
+ return nextid++;
+ }
+ }
+
+ public Thing(Game game) {
+ this.ID = createID();
+ this.game = game;
+ }
+
+ /*
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Thing) {
+ return ((Thing) obj).ID == ID;
+ } else
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 61 * hash + (int) (this.ID ^ (this.ID >>> 32));
+ return hash;
+ }
+
+ public int getImage() {
+ return image;
+ }
+
+ /**
+ * For efficient decision making, return TYPE_* types
+ *
+ * @return
+ */
+ public abstract int getType();
+
+ /**
+ * Calculate the l1 or manhatten distance to another thing
+ *
+ * @param other
+ * @return
+ */
+ public int distanceL1(Thing other) {
+ return Math.abs(other.x - x) + Math.abs(other.y - y);
+ }
+
+ /**
+ * Create an EntityUpdate message with the given name
+ *
+ * @param msg
+ * @return
+ */
+ public DuskMessage createUpdateMessage(int name) {
+ EntityUpdateMessage en = new EntityUpdateMessage();
+
+ en.name = name;
+ en.id = ID;
+ en.entityName = this.name;
+ en.entityType = (byte) getType();
+ en.x = (short) x;
+ en.y = (short) y;
+ en.image = (short) this.getImage();
+ en.imageStep = -1;
+
+ return en;
+ }
+
+ /**
+ * Used by loader to read descriptor files
+ *
+ * @param name
+ * @param value
+ */
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "name":
+ this.name = value;
+ break;
+ case "description":
+ this.description = value;
+ break;
+ case "map":
+ this.map = game.getMap(value);
+ break;
+ case "x":
+ this.x = Integer.valueOf(value);
+ break;
+ case "y":
+ this.y = Integer.valueOf(value);
+ break;
+ case "image":
+ this.image = Integer.valueOf(value);
+ break;
+ default:
+ //System.out.println("unknown property: " + name + " on class " + getClass().getSimpleName());
+ break;
+ }
+ }
+ // FIXME: move to a PropertyWriter class
+
+ protected void writeProperty(BufferedWriter out, String name, String value) throws IOException {
+ if (value != null) {
+ out.write(name);
+ out.write('=');
+ out.write(value);
+ out.write('\n');
+ }
+ }
+
+ protected void writeProperty(BufferedWriter out, String name, int value) throws IOException {
+ writeProperty(out, name, String.valueOf(value));
+ }
+
+ protected void writeProperty(BufferedWriter out, String name, long value) throws IOException {
+ writeProperty(out, name, String.valueOf(value));
+ }
+
+ protected void writeProperty(BufferedWriter out, String name, double value) throws IOException {
+ writeProperty(out, name, String.valueOf(value));
+ }
+
+ /**
+ * Write out the object properties, i.e. for saving the entire object.
+ *
+ * @param out
+ * @throws IOException
+ */
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ writeProperty(out, "name", name);
+ writeProperty(out, "description", description);
+ writeProperty(out, "map", map.name);
+ writeProperty(out, "x", x);
+ writeProperty(out, "y", y);
+ writeProperty(out, "image", image);
+ }
+
+ /**
+ * Write out live game state - parameters which allow an instance of the object
+ * to be restored later.
+ *
+ * For mobs it will be their location, for players it is nothing as they are
+ * always stored in full separately.
+ *
+ * The default implementation saves out the name and location.
+ *
+ * @param out
+ * @throws IOException
+ */
+ void writeState(BufferedWriter out) throws IOException {
+ writeProperty(out, "name", name);
+ writeProperty(out, "x", x);
+ writeProperty(out, "y", y);
+ }
+
+ void writeHeader(BufferedWriter out) throws IOException {
+ out.append("type.");
+ out.append(getClass().getSimpleName());
+ out.append('=');
+ out.append(name);
+ out.append('\n');
+ }
+
+ void writeFooter(BufferedWriter out) throws IOException {
+ out.append("=end\n");
+ }
+
+ /**
+ * Save the state to a stream in a way which can also restore the same object.
+ * this will call writeState which should write out volatile fields, it doesn't
+ * need to write out fields that are loaded from a prototype.
+ *
+ * @param out
+ * @throws IOException
+ */
+ public static void saveState(Thing thing, BufferedWriter out) throws IOException {
+ thing.writeHeader(out);
+ thing.writeState(out);
+ thing.writeFooter(out);
+ }
+
+ static Thing createThing(Game game, String type) throws ClassNotFoundException {
+ try {
+ Class<Thing> c = (Class<Thing>) Class.forName("duskz.server.entityz." + type);
+ return c.getConstructor(Game.class).newInstance(game);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ throw new ClassNotFoundException("Failed", ex);
+ }
+ }
+
+ public interface ThingResolver {
+
+ Thing resolve(Game game, String klass, String name);
+ }
+
+ public static class PrototypeResolver implements ThingResolver {
+
+ File base;
+
+ /**
+ * Create a new thing resolver which loads a prototype
+ * from a file.
+ *
+ * @param base
+ */
+ public PrototypeResolver(File base) {
+ this.base = base;
+ }
+
+ @Override
+ public Thing resolve(Game game, String klass, String name) {
+ Thing thing = null;
+ try {
+ thing = createThing(game, klass);
+ thing.name = name;
+ thing.load(new File(base, name));
+ } catch (ClassNotFoundException | IOException ex) {
+ System.out.println("Cannot load prototype: " + ex);
+ }
+ return thing;
+ }
+ }
+
+ public static class EmptyResolver implements ThingResolver {
+
+ /**
+ * Create a new resolver which just instantiates an empty
+ * object.
+ */
+ public EmptyResolver() {
+ }
+
+ @Override
+ public Thing resolve(Game game, String klass, String name) {
+ Thing thing = null;
+ try {
+ thing = createThing(game, klass);
+ thing.name = name;
+ } catch (ClassNotFoundException ex) {
+ System.out.println("Cannot load prototype: " + ex);
+ }
+ return thing;
+ }
+ }
+ // todo this might need to load some object prototype first
+
+ public static Thing restoreState(Game game, BufferedReader in, ThingResolver resolve) throws IOException {
+ String line;
+ Thing thing = null;
+
+ while ((line = in.readLine()) != null) {
+ line = line.trim();
+ int col = line.indexOf('=');
+
+ if (thing == null) {
+ if (line.startsWith("type.") && col > 0) {
+ String type = line.substring(5, col);
+ String name = line.substring(col + 1);
+
+ thing = resolve.resolve(game, type, name);
+ } else {
+ continue;
+ }
+ } else if (line.equals("=end")) {
+ break;
+ } else if (col > 0) {
+ String name = line.substring(0, col);
+ String value = line.substring(col + 1);
+ try {
+ thing.setProperty(name, value);
+ } catch (NumberFormatException ex) {
+ }
+ }
+ }
+ if (thing != null)
+ thing.loaded();
+ return thing;
+ }
+
+ /**
+ * Save whole state to file
+ *
+ * @param path
+ * @throws IOException
+ */
+ public void save(File path) throws IOException {
+ // Write to temp file first, and if ok, overwrite old one
+ File tmp = new File(path.getParentFile(), path.getName() + "~");
+
+ try (BufferedWriter out = new BufferedWriter(new FileWriter(tmp))) {
+ writeHeader(out);
+ // writeProperty(getFileVersion()?))
+ writeProperties(out);
+ writeFooter(out);
+ }
+
+ Files.move(tmp.toPath(), path.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ /**
+ * Load whole state from a file
+ *
+ * This works with the typed fields as well as not.
+ *
+ * @param path
+ * @throws IOException
+ */
+ public void load(File path) throws IOException {
+ try (PropertyLoader pl = new PropertyLoader(path)) {
+ for (PropertyEntry pe : pl) {
+ if (pe.name.startsWith("type.")) {
+ if (pe.name.startsWith("type." + getClass().getSimpleName())) {
+ continue;
+ } else {
+ throw new IOException("Trying to load " + pe.name.substring(5) + " into " + getClass().getSimpleName());
+ }
+ } else if (pe.name.equals("=end")) {
+ break;
+ }
+ try {
+ setProperty(pe.name, pe.value);
+ } catch (NumberFormatException ex) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Called after an object has been fully loaded or restored.
+ * Can validate/setup stuff.
+ */
+ protected void loaded() throws IOException {
+ }
+
+ public void look(Active viewer) {
+ if (description != null)
+ viewer.chatMessage("You see " + description + ".");
+ else
+ viewer.chatMessage("You see nothing special.");
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Manages things, indices thereof, etc.
+ *
+ * Ideally it should be possible to implement different ways including
+ * live disk backing.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class ThingTable<T extends Thing> {
+
+ // By name index
+ final private HashMap<String, HashSet<T>> byName = new HashMap<>();
+ // By id index
+ final private HashMap<Long, T> byID = new HashMap<>();
+ // backing store
+ private final File path;
+
+ public ThingTable(File path) {
+ this.path = path;
+ }
+
+ void restoreState(Game game, File prototypeBase) {
+ Thing.ThingResolver resolver = new Thing.PrototypeResolver(prototypeBase);
+
+ try (BufferedReader in = new BufferedReader(new FileReader(path))) {
+ T thing;
+
+ while ((thing = (T) Thing.restoreState(game, in, resolver)) != null) {
+ add(thing);
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ void restoreState(Game game) {
+ Thing.ThingResolver resolver = new Thing.EmptyResolver();
+
+ try (BufferedReader in = new BufferedReader(new FileReader(path))) {
+ T thing;
+
+ while ((thing = (T) Thing.restoreState(game, in, resolver)) != null) {
+ add(thing);
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ void saveState(Game game) {
+ try (BufferedWriter out = new BufferedWriter(new FileWriter(path))) {
+
+ for (T o : byID.values()) {
+ Thing.saveState(o, out);
+ }
+ } catch (IOException ex) {
+ }
+ }
+
+ // Shoudln't be allowed for concurrency reasons
+ @Deprecated
+ public Collection<T> values() {
+ return byID.values();
+ }
+
+ public void add(T thing) {
+ HashSet<T> set = byName.get(thing.name);
+ if (set == null) {
+ set = new HashSet<>();
+ byName.put(thing.name, set);
+ }
+ set.add(thing);
+ byID.put(thing.ID, thing);
+ }
+
+ public void remove(T thing) {
+ HashSet<T> set = byName.get(thing.name);
+
+ set.remove(thing);
+ if (set.isEmpty())
+ byName.remove(thing.name);
+
+ byID.remove(thing.ID);
+ }
+
+ /**
+ * Get the thing with the given id.
+ *
+ * @param id
+ * @return
+ */
+ public T get(long id) {
+ return byID.get(id);
+ }
+
+ /**
+ * Get the first item with the given name
+ *
+ * @param name
+ * @return
+ */
+ public T get(String name) {
+ HashSet<T> set = byName.get(name);
+
+ if (set != null)
+ return set.iterator().next();
+ else
+ return null;
+ }
+
+ /**
+ * Get all items with the same name
+ *
+ * @param name
+ * @return
+ */
+ public Set<T> getAll(String name) {
+ return byName.get(name);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/**
+ * Changes
+ */
+package duskz.server.entityz;
+
+import duskz.server.entityz.PropertyLoader.PropertyEntry;
+import duskz.util.Maths;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.PriorityQueue;
+
+/**
+ * Low level map management and helpers.
+ *
+ * Some of the helpers provide higher level functionality like path finding.
+ *
+ * @author notzed
+ */
+public class TileMap implements Iterable<TileMap.MapData> {
+
+ /**
+ * Name of map for referencing
+ */
+ public final String name;
+ /**
+ * Size
+ */
+ private int rows, cols;
+ /**
+ * Tile map
+ */
+ private short tiles[];
+ /**
+ * Index of Entities by location.
+ */
+ private HashMap<Location, LinkedList<Thing>> entities = new HashMap<>();
+ //private Thing entities[];
+ /**
+ * Aliases for locations, by location and name
+ */
+ private HashMap<Location, String> aliases = new HashMap<>();
+ private HashMap<String, Location> aliasesByName = new HashMap<>();
+ /**
+ * Scripts for locations
+ */
+ private HashMap<Location, String> ableScript = new HashMap<>();
+ private HashMap<Location, String> visibleScript = new HashMap<>();
+ private HashMap<Location, String> actionScript = new HashMap<>();
+ /**
+ * Aliases for jumps. These are implicit actions on a location
+ */
+ private HashMap<Location, String> jumpTable = new HashMap<>();
+ /**
+ * Layers
+ */
+ private int groundLayer;
+ private TileLayer layers[];
+ /**
+ * privileges for each cell. This appears to be unimplemented in Dusk so
+ * isn't here either.
+ */
+ protected short privs[];
+ /**
+ * Ownership for each cell. This appears to be unimplemented in Dusk so
+ * isn't here either.
+ */
+ protected int owner[];
+ /**
+ * Flags for iterators
+ */
+ public final static int SKIP_START = 1;
+ public final static int SKIP_END = 2;
+
+ /**
+ * Create a new empty map of the given size
+ *
+ * @param cols
+ * @param rows
+ */
+ public TileMap(String name, int cols, int rows) {
+ this.name = name;
+ this.rows = rows;
+ this.cols = cols;
+
+ tiles = new short[rows * cols];
+ //entities = new Thing[rows * cols];
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ public int getCols() {
+ return cols;
+ }
+
+ public int getLayerCount() {
+ return layers.length;
+ }
+
+ public int getGroundLayer() {
+ return groundLayer;
+ }
+
+ public void saveMap(File path) throws IOException {
+ path.delete();
+ try (RandomAccessFile rafFile = new RandomAccessFile(path, "rw")) {
+ rafFile.writeInt(cols);
+ rafFile.writeInt(rows);
+ for (int x = 0; x < cols; x++) {
+ for (int y = 0; y < rows; y++) {
+ rafFile.writeShort(getTile(x, y));
+ }
+ }
+ }
+ }
+
+ static class TileLayer {
+
+ int x, y;
+ int width, height;
+ private short[] tiles;
+
+ public TileLayer(int x, int y, int width, int height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.tiles = new short[width * height];
+ }
+
+ public TileLayer(int x, int y, int width, int height, short[] tiles) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.tiles = tiles;
+ }
+
+ public boolean inside(int tx, int ty) {
+ tx -= this.x;
+ ty -= this.y;
+ return tx >= 0 && tx < width
+ && ty >= 0 && ty < height;
+ }
+
+ public short getTile(int tx, int ty) {
+ tx -= this.x;
+ ty -= this.y;
+ if (tx >= 0 && tx < width
+ && ty >= 0 && ty < height) {
+ return tiles[tx + ty * width];
+ } else
+ return 0;
+ }
+ }
+ public static final int FORMAT_BYTE = 0;
+ public static final int FORMAT_SHORT = 1;
+ // 'mapz'
+ // TODO: gzip?
+ public static final int MAGIC_LAYERED = 0x6d61707a;
+
+ /**
+ * Load a layered map. Format is:
+ * magic: int
+ * version: int 0
+ * flags: int (0 - short)
+ * width: int must contain all layers
+ * height: int must contain all layers
+ * layer ground: int index of ground layer, ground layer must be same size as map
+ * layer count: int
+ * then layer count {
+ * layer x: int
+ * layer y: int
+ * layer width: int
+ * layer height: int
+ * layer data: short width*height in row major format
+ * }
+ *
+ * @param path
+ * @return
+ * @throws IOException
+ */
+ public static TileMap loadLayered(File path) throws IOException {
+ TileMap map;
+
+ try (DataInputStream mapFile = new DataInputStream(new FileInputStream(path))) {
+ int magic = mapFile.readInt();
+ int version = mapFile.readInt();
+ int flags = mapFile.readInt();
+ int cols = mapFile.readInt();
+ int rows = mapFile.readInt();
+ int groundLayer = mapFile.readInt();
+ int layerCount = mapFile.readInt();
+
+ if (magic != MAGIC_LAYERED
+ || version != 0) {
+ throw new IOException("Invalid format/magic/unknown version");
+ }
+
+ System.out.println("Load map: " + path);
+ System.out.printf(" size: %dx%d\n", cols, rows);
+ System.out.printf(" groundLayer: %d\n", groundLayer);
+ System.out.printf(" layerCount: %d\n", layerCount);
+
+ map = new TileMap(path.getName(), cols, rows);
+
+ map.groundLayer = groundLayer;
+ map.layers = new TileLayer[layerCount];
+ for (int l = 0; l < layerCount; l++) {
+ int tx = mapFile.readInt();
+ int ty = mapFile.readInt();
+ int twidth = mapFile.readInt();
+ int theight = mapFile.readInt();
+ TileLayer tl;
+
+ System.out.printf(" layer %2d: at %3d,%3d size %3dx%3d\n", l, tx, ty, twidth, theight);
+ if (l == groundLayer)
+ tl = new TileLayer(tx, ty, twidth, theight, map.tiles);
+ else
+ tl = new TileLayer(tx, ty, twidth, theight);
+
+ map.layers[l] = tl;
+
+ for (int i = 0; i < twidth * theight; i++) {
+ tl.tiles[i] = mapFile.readShort();
+ }
+ }
+ }
+
+ // Format is alias.x.y=name
+ File aliasPath = new File(path.getParentFile(), path.getName() + ".alias");
+ try (PropertyLoader pl = new PropertyLoader(aliasPath)) {
+ for (PropertyEntry pe : pl) {
+ String[] line = pe.name.split("\\.");
+ int x = Integer.valueOf(line[0]);
+ int y = Integer.valueOf(line[1]);
+ Location l = new Location(x, y);
+
+ switch (line[2]) {
+ case "alias":
+ map.aliases.put(l, pe.value);
+ map.aliasesByName.put(pe.value, l);
+ break;
+ //case "script":
+ // map.seeScript.put(l, pe.value);
+ // map.moveScript.put(l, pe.value);
+ // map.locationActionScript.put(l, pe.value);
+ // break;
+ case "visible":
+ map.visibleScript.put(l, pe.value);
+ break;
+ case "able":
+ map.ableScript.put(l, pe.value);
+ break;
+ case "action":
+ map.actionScript.put(l, pe.value);
+ break;
+ case "goto":
+ map.jumpTable.put(l, pe.value);
+ break;
+ }
+ }
+ } catch (NullPointerException | IndexOutOfBoundsException x) {
+ throw new IOException("Error in map alias file " + aliasPath);
+ } catch (FileNotFoundException x) {
+ // don't care
+ }
+
+ return map;
+ }
+
+ public void saveAlias(File path) throws IOException {
+ File tmp = new File(path.getParentFile(), path.getName() + ".alias~");
+ File file = new File(path.getParentFile(), path.getName() + ".alias");
+
+ // how do i preserve comments?
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ // Map in row major format (i.e. more efficient)
+ public static TileMap loadMapX(File path) throws IOException {
+ TileMap map;
+
+ try (DataInputStream mapFile = new DataInputStream(new FileInputStream(path))) {
+ int cols = mapFile.readInt();
+ int rows = mapFile.readInt();
+ map = new TileMap(path.getName(), cols, rows);
+ for (int y = 0; y < rows; y++) {
+ for (int x = 0; x < cols; x++) {
+ map.setTile(x, y, mapFile.readShort());
+ }
+ }
+
+ map.layers = new TileLayer[1];
+ map.layers[0] = new TileLayer(0, 0, cols, rows, map.tiles);
+ }
+ return map;
+ }
+
+ public static TileMap loadMap(File path, int format) throws IOException {
+ TileMap map;
+
+ try (RandomAccessFile mapFile = new RandomAccessFile(path, "r")) {
+ int cols = mapFile.readInt();
+ int rows = mapFile.readInt();
+ map = new TileMap(path.getName(), cols, rows);
+ for (int x = 0; x < cols; x++) {
+ for (int y = 0; y < rows; y++) {
+ if (format == FORMAT_BYTE)
+ map.setTile(x, y, mapFile.readByte());
+ else
+ map.setTile(x, y, mapFile.readShort());
+ }
+ }
+ map.layers = new TileLayer[1];
+ map.layers[0] = new TileLayer(0, 0, cols, rows, map.tiles);
+ }
+ return map;
+ }
+
+ /**
+ * Create a new map
+ *
+ * @param newcols
+ * @param newrows
+ * @return
+ */
+ public synchronized void resize(int newcols, int newrows) {
+ short[] ntiles = new short[newcols * newrows];
+ //Thing[] nentities = new Thing[newcols * newrows];
+
+ int rx = Math.min(newcols, cols);
+ int ry = Math.min(newrows, rows);
+
+ for (int y = 0; y < ry; y++) {
+ for (int x = 0; x < rx; x++) {
+ int indexa = x + y * cols;
+ int indexb = x + y * newcols;
+
+ ntiles[indexb] = tiles[indexa];
+ //nentities[indexb] = entities[indexa];
+ }
+ }
+
+ tiles = ntiles;
+ //entities = nentities;
+ cols = newcols;
+ rows = newrows;
+ }
+
+ public boolean inside(int x, int y) {
+ return x >= 0 && x < cols
+ && y >= 0 && y < rows;
+ }
+
+ public boolean inside(int layer, int x, int y) {
+ return x >= 0 && x < cols
+ && y >= 0 && y < rows;
+ }
+
+ /**
+ * Calculate if the layer is empty over the given region
+ *
+ * @param layer
+ * @param x
+ * @param y
+ * @param width
+ * @param height
+ * @return
+ */
+ public boolean empty(int layer, int x, int y, int width, int height) {
+ TileLayer tl = layers[layer];
+
+ for (int ty = y; ty < y + height; ty++) {
+ for (int tx = x; tx < x + width; tx++) {
+ if (tl.getTile(tx, ty) != 0)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get the region bounded by the supplied coordinates.
+ * If the region is all empty, then null is returned.
+ *
+ * @param layer
+ * @param x
+ * @param y
+ * @param width
+ * @param height
+ * @param region if supplied use this array otherwise allocate one
+ * @return
+ */
+ public short[] getRegion(int layer, int tx, int ty, int width, int height, short[] region) {
+ TileLayer tl = layers[layer];
+ if (region == null)
+ region = new short[width * height];
+ boolean empty = true;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ short t = tl.getTile(tx + x, ty + y);
+
+ region[x + width * y] = t;
+
+ empty &= t == 0;
+ }
+ }
+ if (empty)
+ return null;
+ else
+ return region;
+ }
+
+ public void setTile(int x, int y, int t) {
+ tiles[x + y * cols] = (short) t;
+ }
+
+ public short getTile(int x, int y) {
+ return tiles[x + y * cols];
+ }
+
+ public short getTile(int layer, int x, int y) {
+ return layers[layer].getTile(x, y);
+ }
+
+ public synchronized List<Thing> getEntities(int x, int y, List<Thing> list) {
+ if (list == null)
+ list = new ArrayList<>();
+ //Thing o = entities[x + y * cols];
+ //while (o != null) {
+ // list.add(o);
+ // o = o.getNext();
+ //}
+ LinkedList<Thing> ll = entities.get(new Location(x, y));
+ if (ll != null)
+ list.addAll(ll);
+
+ return list;
+ }
+
+ public synchronized List<Thing> getEntities(Location l, List<Thing> list) {
+ if (list == null)
+ list = new ArrayList<>();
+ //Thing o = entities[x + y * cols];
+ //while (o != null) {
+ // list.add(o);
+ // o = o.getNext();
+ //}
+ LinkedList<Thing> ll = entities.get(l);
+ if (ll != null)
+ list.addAll(ll);
+
+ return list;
+ }
+
+ public synchronized void addEntity(Thing o) {
+ if (inside(o.x, o.y)) {
+ //int index = o.x + o.y * cols;
+ //entities[index] = Thing.append(entities[index], o);
+ Location l = new Location(o.x, o.y);
+ LinkedList<Thing> ll = entities.get(l);
+ if (ll == null) {
+ ll = new LinkedList<>();
+ entities.put(l, ll);
+ }
+ if (ll.contains(o)) {
+ System.out.println("adding the same thing twice: " + o);
+ } else {
+ ll.add(o);
+ }
+ }
+ }
+
+ public synchronized void removeEntity(Thing o) {
+ if (inside(o.x, o.y)) {
+ //int index = o.x + o.y * cols;
+ //entities[index] = Thing.remove(entities[index], o);
+ Location l = new Location(o.x, o.y);
+ LinkedList<Thing> ll = entities.get(l);
+ ll.remove(o);
+ if (ll.isEmpty()) {
+ entities.remove(l);
+ }
+ }
+ }
+
+ public synchronized void moveEntity(Thing o, int x, int y) {
+ if (inside(x, y)) {
+ removeEntity(o);
+ o.x = x;
+ o.y = y;
+ addEntity(o);
+ }
+ }
+
+ /**
+ * Get an iterable over a range - allows foreach support
+ *
+ * @param x0
+ * @param y0
+ * @param x1
+ * @param y1
+ * @return
+ */
+ public Iterable<MapData> range(int x0, int y0, int x1, int y1) {
+ return new MapIterable(x0, y0, x1, y1);
+ }
+
+ /**
+ * Get an iterable over a range with a given centre and radius
+ *
+ * @param x
+ * @param y
+ * @param radius
+ * @return
+ */
+ public Iterable<MapData> range(int x, int y, int radius) {
+ return new MapIterable(x - radius, y - radius, x + radius + 1, y + radius + 1);
+ }
+
+ /**
+ * Get an iterable which will iterate over the looking path
+ *
+ * @param sx
+ * @param sy
+ * @param ex
+ * @param ey
+ * @param flags SKIP_END, SKIP_START to skip end/start locations
+ * (UNIMPLEMENTED)
+ * @return
+ */
+ public Iterable<MapData> look(int sx, int sy, int ex, int ey, int flags) {
+ return new LookIterable(sx, sy, ex, ey, flags);
+ }
+
+ public Iterable<MapData> look(int sx, int sy, int ex, int ey) {
+ return new LookIterable(sx, sy, ex, ey, 0);
+ }
+
+ public Iterable<MoveData> move(int sx, int sy, int ex, int ey, int flags, MoveListener l) {
+ return new MoveIterable(sx, sy, ex, ey, flags, l);
+ }
+
+ public Location locationForAlias(String name) {
+ // FIXME: aliases need to be global!
+ return aliasesByName.get(name);
+ }
+
+ public String aliasForLocation(int x, int y) {
+ return aliases.get(new Location(x, y));
+ }
+
+ public String locationAbleScript(int x, int y) {
+ return ableScript.get(new Location(x, y));
+ }
+
+ public String locationVisibleScript(int x, int y) {
+ return visibleScript.get(new Location(x, y));
+ }
+
+ public String locationActionScript(int x, int y) {
+ return actionScript.get(new Location(x, y));
+ }
+
+ public String jumpAlias(int x, int y) {
+ return jumpTable.get(new Location(x, y));
+ }
+
+ public void setJumpAlias(int tx, int ty, String alias) {
+ jumpTable.put(new Location(tx, ty), alias);
+ }
+
+ public void setAlias(int x, int y, String alias) {
+ Location l = new Location(x, y);
+
+ if (alias == null || alias.equals("")) {
+ alias = aliases.remove(l);
+ if (alias != null)
+ aliasesByName.remove(alias);
+ } else {
+ aliases.put(l, alias);
+ aliasesByName.put(alias, l);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof TileMap) {
+ return name.equals(((TileMap) obj).name);
+ } else
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 97 * hash + Objects.hashCode(this.name);
+ return hash;
+ }
+
+ private class MapIterable implements Iterable<MapData> {
+
+ int x0, y0, x1, y1;
+
+ public MapIterable(int x0, int y0, int x1, int y1) {
+ this.x0 = x0;
+ this.y0 = y0;
+ this.x1 = x1;
+ this.y1 = y1;
+ }
+
+ @Override
+ public Iterator<MapData> iterator() {
+ return new MapIterator(x0, y0, x1, y1);
+ }
+ }
+
+ private class LookIterable implements Iterable<MapData> {
+
+ int sx, sy, ex, ey;
+ int flags;
+
+ public LookIterable(int sx, int sy, int ex, int ey, int flags) {
+ this.sx = sx;
+ this.sy = sy;
+ this.ex = ex;
+ this.ey = ey;
+ this.flags = flags;
+ }
+
+ @Override
+ public Iterator<MapData> iterator() {
+ return new LookIterator(sx, sy, ex, ey, flags);
+ }
+ }
+
+ private class MoveIterable implements Iterable<MoveData> {
+
+ int sx, sy, ex, ey;
+ int flags;
+ MoveListener l;
+
+ public MoveIterable(int sx, int sy, int ex, int ey, int flags, MoveListener l) {
+ this.sx = sx;
+ this.sy = sy;
+ this.ex = ex;
+ this.ey = ey;
+ this.flags = flags;
+ this.l = l;
+ }
+
+ @Override
+ public Iterator<MoveData> iterator() {
+ return new MoveIterator(sx, sy, ex, ey, flags, l);
+ }
+ }
+
+ public Iterator<MapData> getIterator(int x0, int y0, int x1, int y1) {
+ return new MapIterator(x0, y0, x1, y1);
+ }
+
+ @Override
+ public Iterator<MapData> iterator() {
+ return getIterator(0, 0, cols, rows);
+ }
+
+ public class MapData extends Location {
+
+ public final List<Thing> entities = new ArrayList<>();
+ // Tileid of ground layer
+ public short tile;
+ // Tileid of visible layers in correct order
+ public short[] layers = new short[TileMap.this.layers.length];
+ // lessen gc load by re-using
+ //private Location l = new Location(0, 0);
+
+ protected void setData(int x, int y) {
+ this.x = x;
+ this.y = y;
+ //l.x = x;
+ //l.y = y;
+ this.entities.clear();
+ if (inside(x, y)) {
+ this.tile = getTile(x, y);
+ getEntities(this, this.entities);
+ } else {
+ this.tile = 0;
+ }
+ for (int l = 0; l < layers.length; l++) {
+ layers[l] = TileMap.this.layers[l].getTile(x, y);
+ }
+ }
+ }
+
+ public class MoveData extends MapData {
+
+ public String direction;
+ }
+
+ private class MapIterator implements Iterator<MapData> {
+
+ int x0, y0, x1, y1;
+ int x, y;
+ MapData data = new MapData();
+
+ public MapIterator(int x0, int y0, int x1, int y1) {
+ this.x0 = Maths.clamp(x0, 0, cols);
+ this.y0 = Maths.clamp(y0, 0, rows);
+ this.x1 = Maths.clamp(x1, 0, cols);
+ this.y1 = Maths.clamp(y1, 0, rows);
+ this.x = -1;
+ this.y = -1;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return (x + 1) < x1
+ || (y + 1) < y1;
+ }
+
+ @Override
+ public MapData next() {
+ // FIXME: this needs to iterate in x first then y
+ if (x == -1) {
+ x = x0;
+ y = y0;
+ } else if (y + 1 < y1) {
+ y++;
+ } else {
+ y = y0;
+ x++;
+ }
+
+ data.setData(x, y);
+
+ return data;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ /**
+ * Implements an iterator which follows a 'looking' path
+ *
+ * TODO: it should probably use Bresenhams line algorithm
+ */
+ private class LookIterator implements Iterator<MapData> {
+
+ int x, y;
+ final int sx, sy;
+ final int ex, ey;
+ int flags;
+ boolean there = false;
+ MapData data = new MapData();
+
+ public LookIterator(int sx, int sy, int ex, int ey, int flags) {
+ this.x = sx;
+ this.y = sy;
+ this.sx = sx;
+ this.sy = sy;
+ this.ex = ex;
+ this.ey = ey;
+ this.flags = flags;
+
+ diffx = Math.abs(ex - sx);
+ diffy = Math.abs(ey - sy);
+ stepx = Integer.signum(ex - sx);
+ stepy = Integer.signum(ey - sy);
+ err = diffx - diffy;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !there;
+ }
+ // Bresenham algorithm data, from wikipedia
+ int diffx, diffy;
+ int stepx, stepy;
+ int err;
+
+ void lineStep() {
+ int e2 = 2 * err;
+ if (e2 > -diffy) {
+ err -= diffy;
+ x += stepx;
+ }
+ if (e2 < diffx) {
+ err += diffx;
+ y += stepy;
+ }
+ }
+
+ @Override
+ public MapData next() {
+ there = x == ex && y == ey;
+
+ // FIXME: impelement?
+ //if ((flags & SKIP_START) != 0 && sx == x && sy == y) {
+ // lineStep();
+ //}
+
+ data.setData(x, y);
+
+ if (!there) {
+ lineStep();
+ }
+ return data;
+ }
+
+ public MapData nextOld() {
+ there = x == ex && y == ey;
+
+ data.setData(x, y);
+
+ if (!there) {
+ int stepx = Integer.signum(ex - y);
+ int stepy = Integer.signum(ey - y);
+ int dx = Math.abs(ex - x);
+ int dy = Math.abs(ey - y);
+
+ if (dx > dy) {
+ x += stepx;
+ } else if (dx < dy) {
+ y += stepy;
+ } else {
+ x += stepx;
+ y += stepy;
+ }
+ }
+
+ return data;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ public interface MoveListener {
+
+ public boolean canMoveto(MapData md);
+ }
+
+ /**
+ * Iterator for movement.
+ */
+ private static class MoveInfo implements Comparable<MoveInfo> {
+
+ int x, y;
+ float cost;
+ float estimate;
+ String direction;
+ MoveInfo parent;
+
+ public MoveInfo(int x, int y, float cost, String direction) {
+ this.x = x;
+ this.y = y;
+ this.cost = cost;
+ this.direction = direction;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ MoveInfo o = (MoveInfo) obj;
+
+ return x == o.x && y == o.y;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 59 * hash + this.x;
+ hash = 59 * hash + this.y;
+ return hash;
+ }
+
+ @Override
+ public int compareTo(MoveInfo o) {
+ return Float.compare(cost, o.cost);
+ }
+ }
+
+ /**
+ * An iterator which steps through the individual moves to get to a
+ * destination.
+ *
+ * Implemented using A* algorithm, so can handle obstructions.
+ *
+ * TODO: Limit the search space.
+ */
+ private class MoveIterator implements Iterator<MoveData> {
+
+ int x, y;
+ final int sx, sy;
+ final int ex, ey;
+ int flags;
+ boolean there = false;
+ private final MoveListener l;
+ // quick hack version
+ Iterator<MoveInfo> iterator;
+ MoveData data = new MoveData();
+
+ public MoveIterator(int sx, int sy, int ex, int ey, int flags, MoveListener l) {
+ this.x = sx;
+ this.y = sy;
+ this.sx = sx;
+ this.sy = sy;
+ this.ex = ex;
+ this.ey = ey;
+ this.flags = flags;
+ this.l = l;
+
+ // A* requires the whole path to be calculated ahead of time.
+
+ List<MoveInfo> path = findPath();
+
+ System.out.printf("Finding path from %d,%d to %d,%d: ", sx, sy, ex, ey);
+
+ if (path != null) {
+ for (MoveInfo mi : path) {
+ System.out.print(" ");
+ System.out.print(mi.direction);
+ }
+ System.out.println();
+
+ if (!path.isEmpty() && (flags & SKIP_END) != 0) {
+ path.remove(path.size() - 1);
+ }
+
+ iterator = path.iterator();
+ } else {
+ System.out.println("No path found!");
+ }
+ }
+
+ float estimateCost(int sx, int sy, int ex, int ey) {
+ return (float) Math.sqrt((ex - sx) * (ex - sx) + (ey - sy) * (ey - sy));
+ }
+
+ List<MoveInfo> constructPath(List<MoveInfo> list, MoveInfo n) {
+ if (list == null)
+ list = new ArrayList<>();
+
+ if (n.parent != null) {
+ constructPath(list, n.parent);
+ list.add(n);
+ }
+
+ return list;
+ }
+
+ void moveIf(List<MoveInfo> list, int x, int y, float cost, String dir) {
+ data.setData(x, y);
+
+ if (l.canMoveto(data)) {
+ list.add(new MoveInfo(x, y, cost + 1, dir));
+ }
+ }
+
+ List<MoveInfo> getNeighbours(MoveInfo n) {
+ List<MoveInfo> list = new ArrayList<>(4);
+
+ moveIf(list, n.x + 1, n.y, n.cost, "e");
+ moveIf(list, n.x - 1, n.y, n.cost, "w");
+ moveIf(list, n.x, n.y + 1, n.cost, "s");
+ moveIf(list, n.x, n.y - 1, n.cost, "n");
+
+ return list;
+ }
+
+ // A* algorithm from here:
+ //http://www.peachpit.com/articles/article.aspx?p=101142&seqNum=2
+ public List<MoveInfo> findPath() {
+
+ PriorityQueue<MoveInfo> openList = new PriorityQueue();
+ HashSet<MoveInfo> closedList = new HashSet<>();
+
+ MoveInfo startNode = new MoveInfo(sx, sy, 0, null);
+ startNode.estimate = estimateCost(sx, sy, ex, ey);
+ startNode.parent = null;
+ openList.add(startNode);
+
+ while (!openList.isEmpty()) {
+ MoveInfo node = openList.poll();
+ if (node.x == ex && node.y == ey) {
+ // construct the path from start to goal
+ return constructPath(null, node);
+ }
+
+ List<MoveInfo> neighbors = getNeighbours(node);
+ for (int i = 0; i < neighbors.size(); i++) {
+ MoveInfo nnode = neighbors.get(i);
+ boolean isOpen = openList.contains(nnode);
+ boolean isClosed = closedList.contains(nnode);
+ float costFromStart = node.cost + 1;
+
+ // check if the neighbor node has not been
+ // traversed or if a shorter path to this
+ // neighbor node is found.
+ if ((!isOpen && !isClosed)
+ || costFromStart < nnode.cost) {
+ nnode.parent = node;
+ nnode.cost = costFromStart;
+ nnode.estimate = estimateCost(nnode.x, nnode.y, ex, ey);
+ if (isClosed) {
+ closedList.remove(nnode);
+ }
+ if (!isOpen) {
+ openList.add(nnode);
+ }
+ }
+ }
+ closedList.add(node);
+ }
+
+ // no path found
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (iterator != null)
+ return iterator.hasNext();
+ else
+ return false;
+ }
+
+ @Override
+ public MoveData next() {
+ MoveInfo mi = iterator.next();
+
+ data.setData(mi.x, mi.y);
+ data.direction = mi.direction;
+
+ return data;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+ }
+
+ public static void main(String[] args) {
+ TileMap map = new TileMap("test", 16, 16);
+
+ /*
+ for (MapData md : map.look(5, 5, 10, 3)) {
+ System.out.printf(" %d,%d\n", md.x, md.y);
+ }
+ for (MapData md : map.look(15, 15, 0, 0)) {
+ System.out.printf(" %d,%d\n", md.x, md.y);
+ }
+ for (MapData md : map.look(15, 0, 0, 15)) {
+ System.out.printf(" %d,%d\n", md.x, md.y);
+ }*/
+
+
+ MoveListener l = new MoveListener() {
+ @Override
+ public boolean canMoveto(MapData md) {
+ //System.out.printf("can move %d,%d\n", md.x, md.y);
+ return md.tile == 0;
+ }
+ };
+ System.out.println("no obstacles");
+ for (MoveData md : map.move(0, 0, 10, 10, 0, l)) {
+ System.out.printf(" %d,%d %s\n", md.x, md.y, md.direction);
+ }
+
+ // put an obstacle in the way
+ for (int x = 0; x < 11; x++) {
+ map.setTile(x, 5, 1);
+ }
+ System.out.println("line in the way");
+ for (MoveData md : map.move(0, 0, 10, 10, SKIP_END, l)) {
+ System.out.printf(" %d,%d %s\n", md.x, md.y, md.direction);
+ }
+ System.out.println("line in the way to last");
+ for (MoveData md : map.move(0, 0, 1, 1, 0, l)) {
+ System.out.printf(" %d,%d %s\n", md.x, md.y, md.direction);
+ }
+
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+/**
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class TooManyTriesException extends SecurityException {
+
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+
+/**
+ * This is a pseudo-object that can only be held by a game-shop
+ * and represents training.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Training extends Holdable {
+
+ String skill;
+
+ public Training(Game game) {
+ super(game);
+ }
+
+ @Override
+ public String getUnits() {
+ return "exp";
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "skill":
+ skill = value;
+ break;
+ default:
+ super.setProperty(name, value);
+ break;
+ }
+ }
+
+ @Override
+ public int getWearing() {
+ // TODO: maybe add a training class
+ return Wearing.INVENTORY;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_TRAINING;
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Maps tracks per-user variables. Available for arbitrary script use.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class VariableList {
+
+ private HashMap<String, Object> map = new HashMap<>();
+
+ public void put(String name, int v) {
+ map.put(name, v);
+ System.out.println("put integer " + name + " = " + v + " ? " + map.get(name));
+ }
+
+ public void put(String name, long v) {
+ map.put(name, v);
+ }
+
+ public void put(String name, double v) {
+ map.put(name, v);
+ }
+
+ public void put(String name, String v) {
+ map.put(name, v);
+ }
+
+ public int getInteger(String name, int def) {
+ Number x = (Number) map.get(name);
+ System.out.println("getInteger " + name + " = " + x);
+ if (x != null)
+ return x.intValue();
+ else
+ return def;
+ }
+
+ public long getLong(String name, long def) {
+ Number x = (Number) map.get(name);
+ if (x != null)
+ return x.longValue();
+ else
+ return def;
+ }
+
+ public double getDouble(String name, double def) {
+ Number x = (Number) map.get(name);
+ if (x != null)
+ return x.doubleValue();
+ else
+ return def;
+ }
+
+ public String getString(String name, String def) {
+ String x = (String) map.get(name);
+ if (x != null)
+ return x;
+ else
+ return def;
+ }
+
+ /**
+ * Set a property value as created by writeProperties
+ *
+ * @param name will be"varDouble", "varInteger", etc.
+ * @param value will be name:value
+ */
+ public boolean setProperty(String name, String value) {
+ int col = value.indexOf(':');
+
+ if (col > 0) {
+ String vname = value.substring(0, col);
+
+ value = value.substring(col + 1);
+
+ switch (name) {
+ case "var.Integer":
+ map.put(vname, Integer.valueOf(value));
+ break;
+ case "var.Long":
+ map.put(vname, Long.valueOf(value));
+ break;
+ case "var.Double":
+ map.put(vname, Double.valueOf(value));
+ break;
+ case "var.String":
+ put(vname, value);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void writeProperties(BufferedWriter out) throws IOException {
+ for (Entry<String, Object> entry : map.entrySet()) {
+ out.write("var.");
+ out.write(entry.getValue().getClass().getSimpleName());
+ out.write('=');
+ out.write(entry.getKey());
+ out.write(':');
+ out.write(String.valueOf(entry.getValue()));
+ out.write('\n');
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ VariableList v = new VariableList();
+
+ v.put("int", 1);
+ v.put("long", 1L);
+ v.put("double", 1.0);
+ v.put("string", "some string");
+
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
+ v.writeProperties(bw);
+ bw.flush();
+
+ VariableList v2 = new VariableList();
+ v2.setProperty("var.Double", "shit:99.0");
+ v2.writeProperties(bw);
+ bw.flush();
+
+ System.out.println("shit = " + v2.getDouble("shit", -1));
+
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import duskz.protocol.Wearing;
+import static duskz.server.entityz.Thing.TYPE_WEAPON;
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * Attack weapon
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class Weapon extends Wearable {
+
+ int range = 1;
+
+ public Weapon(Game game) {
+ super(game);
+ }
+
+ public int getType() {
+ return TYPE_WEAPON;
+ }
+
+ @Override
+ public int getWearing() {
+ return Wearing.WIELD;
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "range":
+ range = Integer.parseInt(value);
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ super.writeProperties(out);
+
+ if (range != 1)
+ writeProperty(out, "range", range);
+ }
+}
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2000 Tom Weingarten <captaint@home.com>
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package duskz.server.entityz;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * Base class for items that can be worn, and wear out over time.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public abstract class Wearable extends Holdable {
+
+ int mod;
+ long durability;
+ String onWear, onUnwear;
+
+ Wearable(Game game) {
+ super(game);
+ }
+
+ @Override
+ void setProperty(String name, String value) {
+ switch (name) {
+ case "mod":
+ mod = Integer.parseInt(value);
+ break;
+ case "durability":
+ durability = Long.parseLong(value);
+ break;
+ case "onWear":
+ onWear = value;
+ break;
+ case "onUnwear":
+ onUnwear = value;
+ break;
+ default:
+ super.setProperty(name, value);
+ }
+ }
+
+ @Override
+ protected void writeProperties(BufferedWriter out) throws IOException {
+ super.writeProperties(out);
+
+ writeProperty(out, "mod", mod);
+ writeProperty(out, "durability", durability);
+ writeProperty(out, "onWear", onWear);
+ writeProperty(out, "onUnwear", onUnwear);
+ }
+}