From: notzed@gmail.com Date: Sat, 2 Mar 2013 09:22:13 +0000 (+0000) Subject: Complete revamp of the client-server protocol. It's now all binary in both directions, X-Git-Tag: dusk-0.1~13 X-Git-Url: https://code.zedzone.au/cvs?a=commitdiff_plain;h=8c122353aa6ae183a9e65283082c15f70dc838c0;p=duskz Complete revamp of the client-server protocol. It's now all binary in both directions, and encoding and syntax is independent of semantics. The protocol is now documented. git-svn-id: file:///home/notzed/svn/duskz/trunk@8 b8b59bfb-1aa4-4687-8f88-a62eeb14c21e --- diff --git a/DuskCommon/src/duskz/protocol/DuskMessage.java b/DuskCommon/src/duskz/protocol/DuskMessage.java new file mode 100644 index 0000000..dbf35be --- /dev/null +++ b/DuskCommon/src/duskz/protocol/DuskMessage.java @@ -0,0 +1,776 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * This is the base class and most of the implementation of a re-usable + * message encoding and decoding class. + * + * It encodes the data to a tagged binary format so that it may be decoded + * with no knowledge of the semantics of the content, and yet still provide + * a relatively efficient access. + * + * The semantics of the dusk protocol are stored in DuskProtocol. + * + * @author Michael Zucchi + */ +public class DuskMessage { + + // A list of any type of message including another list. + protected static final byte TC_LIST = 0; + // no args + protected static final byte TC_NOTIFY = 1; + // byte args + protected static final byte TC_BYTE = 2; + protected static final byte TC_INTEGER = 3; + protected static final byte TC_LONG = 4; + protected static final byte TC_FLOAT = 5; + protected static final byte TC_STRING = 6; + protected static final byte TC_STRING_LIST = 7; + // Generic messages targetted to a specific entity + protected static final byte TC_ENTITY_LIST = 8; + protected static final byte TC_ENTITY_NOTIFY = 9; + protected static final byte TC_ENTITY_BYTE = 10; + protected static final byte TC_ENTITY_INTEGER = 11; + protected static final byte TC_ENTITY_LONG = 12; + protected static final byte TC_ENTITY_FLOAT = 13; + protected static final byte TC_ENTITY_STRING = 14; + protected static final byte TC_ENTITY_STRING_LIST = 15; + // More specific messages for easier use and/or performance + // Should be used sparingly as changes are not hideable + // TODO: have a think about whether i really some of these as + // the overhead is only 2 bytes per field. + protected static final byte TC_MAP = 16; + protected static final byte TC_ENTITY_UPDATE = 17; + protected static final byte TC_TRANSACTION = 18; + /** + * The name is sent as a byte, allowing 256 different + * attributes per message type. + */ + public int name; + + public DuskMessage() { + } + + public DuskMessage(int name) { + this.name = name; + } + + public void send(DataOutputStream ostream) throws IOException { + ostream.writeByte(name); + } + + public void receive(DataInputStream istream) throws IOException { + name = istream.readByte(); + } + + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d\n", s, getClass().getSimpleName(), name); + } + + final public void format(PrintStream out) { + try { + Class c = Class.forName("duskz.protocol.DuskProtocol"); + for (Field f : c.getDeclaredFields()) { + if (f.getName().startsWith("MSG_")) { + if (f.getInt(c) == name) { + System.out.print(f.getName()); + System.out.print(" "); + } + } + } + } catch (Exception x) { + } + format(out, ""); + } + + /** + * Must return a unique id from the field constants of DuskMessage which + * identifies the source object class. + * + * @return + */ + public byte getType() { + return TC_NOTIFY; + } + + static public DuskMessage createForType(int type) { + switch (type) { + case TC_LIST: + return new ListMessage(); + case TC_NOTIFY: + return new DuskMessage(); + case TC_BYTE: + return new DuskMessage.ByteMessage(); + case TC_INTEGER: + return new DuskMessage.IntegerMessage(); + case TC_LONG: + return new DuskMessage.LongMessage(); + case TC_FLOAT: + return new DuskMessage.FloatMessage(); + case TC_STRING: + return new DuskMessage.StringMessage(); + case TC_STRING_LIST: + return new DuskMessage.StringListMessage(); + case TC_ENTITY_LIST: + return new EntityListMessage(); + case TC_ENTITY_NOTIFY: + return new EntityMessage(); + case TC_ENTITY_BYTE: + return new EntityByteMessage(); + case TC_ENTITY_INTEGER: + return new EntityIntegerMessage(); + case TC_ENTITY_LONG: + return new EntityLongMessage(); + case TC_ENTITY_FLOAT: + return new EntityFloatMessage(); + case TC_ENTITY_STRING: + return new EntityStringMessage(); + case TC_ENTITY_STRING_LIST: + return new EntityStringListMessage(); + case TC_MAP: + return new MapMessage(); + case TC_ENTITY_UPDATE: + return new EntityUpdateMessage(); + case TC_TRANSACTION: + return new TransactionMessage(); + default: + return null; + } + } + + public void sendMessage(DataOutputStream ostream) throws IOException { + ostream.writeByte(getType()); + send(ostream); + } + + public static DuskMessage receiveMessage(DataInputStream istream) throws IOException { + int tc = istream.read(); + DuskMessage a = createForType(tc); + if (a == null) + throw new IOException("Unknown message tc: " + tc); + + a.receive(istream); + return a; + } + + static public DuskMessage create(int name) { + return new DuskMessage(name); + } + + static public ByteMessage create(int name, byte value) { + return new ByteMessage(name, value); + } + + static public IntegerMessage create(int name, int value) { + return new IntegerMessage(name, value); + } + + static public LongMessage create(int name, long value) { + return new LongMessage(name, value); + } + + static public FloatMessage create(int name, float value) { + return new FloatMessage(name, value); + } + + static public StringMessage create(int name, String value) { + return new StringMessage(name, value); + } + + static public StringListMessage create(int name, List value) { + return new StringListMessage(name, value); + } + + static public EntityMessage create(long id, int name) { + return new EntityMessage(id, name); + } + + static public EntityByteMessage create(long id, int name, byte value) { + return new EntityByteMessage(id, name, value); + } + + static public EntityIntegerMessage create(long id, int name, int value) { + return new EntityIntegerMessage(id, name, value); + } + + static public EntityLongMessage create(long id, int name, long value) { + return new EntityLongMessage(id, name, value); + } + + static public EntityFloatMessage create(long id, int name, float value) { + return new EntityFloatMessage(id, name, value); + } + + static public EntityStringMessage create(long id, int name, String value) { + return new EntityStringMessage(id, name, value); + } + + static public EntityStringListMessage create(long id, int name, List value) { + return new EntityStringListMessage(id, name, value); + } + + public static class ByteMessage extends DuskMessage { + + public byte value; + + public ByteMessage() { + } + + public ByteMessage(int name) { + super(name); + } + + public ByteMessage(int name, byte value) { + super(name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeByte(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readByte(); + } + + @Override + public byte getType() { + return TC_BYTE; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value=%d\n", s, getClass().getSimpleName(), name, value); + } + } + + public static class IntegerMessage extends DuskMessage { + + public int value; + + public IntegerMessage() { + } + + public IntegerMessage(int name) { + super(name); + } + + public IntegerMessage(int name, int value) { + super(name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeInt(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readInt(); + } + + @Override + public byte getType() { + return TC_INTEGER; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value=%d\n", s, getClass().getSimpleName(), name, value); + } + } + + public static class LongMessage extends DuskMessage { + + public long value; + + public LongMessage() { + } + + public LongMessage(int name) { + super(name); + } + + public LongMessage(int name, long value) { + super(name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeLong(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readLong(); + } + + @Override + public byte getType() { + return TC_LONG; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value=%d\n", s, getClass().getSimpleName(), name, value); + } + } + + public static class FloatMessage extends DuskMessage { + + public float value; + + public FloatMessage() { + } + + public FloatMessage(int name) { + super(name); + } + + public FloatMessage(int name, float value) { + super(name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeFloat(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readFloat(); + } + + @Override + public byte getType() { + return TC_FLOAT; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value=%f\n", s, getClass().getSimpleName(), name, value); + } + } + + public static class StringMessage extends DuskMessage { + + public String value; + + public StringMessage() { + } + + public StringMessage(int name) { + super(name); + } + + public StringMessage(int name, String value) { + super(name); + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeUTF(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readUTF(); + } + + @Override + public byte getType() { + return TC_STRING; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value=%s\n", s, getClass().getSimpleName(), name, value); + } + } + + public static class StringListMessage extends DuskMessage { + + public final List value; + + public StringListMessage() { + value = new ArrayList<>(); + } + + public StringListMessage(int name) { + super(name); + value = new ArrayList<>(); + } + + public StringListMessage(int name, List value) { + super(name); + this.value = value; + } + + public void add(String v) { + value.add(v); + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeShort(value.size()); + for (String s : value) { + ostream.writeUTF(s); + } + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + int len = istream.readShort() & 0xffff; + + for (int i = 0; i < len; i++) + value.add(istream.readUTF()); + } + + @Override + public byte getType() { + return TC_STRING_LIST; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value= {\n", s, getClass().getSimpleName(), name); + for (String v : value) { + out.printf("%s '%s'\n", s, v); + } + out.printf("%s}\n", s); + } + } + + public static class EntityMessage extends DuskMessage { + + /** + * Id of entity this attribute applies to + */ + public long id; + + public EntityMessage() { + } + + public EntityMessage(long id, int name) { + this.id = id; + this.name = name; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeLong(id); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + id = istream.readLong(); + } + + @Override + public byte getType() { + return TC_ENTITY_NOTIFY; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d\n", s, getClass().getSimpleName(), id, name); + } + } + + public static class EntityByteMessage extends EntityMessage { + + public byte value; + + public EntityByteMessage() { + } + + public EntityByteMessage(long id, int name) { + super(id, name); + } + + public EntityByteMessage(long id, int name, byte value) { + super(id, name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeByte(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readByte(); + } + + @Override + public byte getType() { + return TC_ENTITY_BYTE; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d value=%d\n", s, getClass().getSimpleName(), id, name, value); + } + } + + public static class EntityIntegerMessage extends EntityMessage { + + public int value; + + public EntityIntegerMessage() { + } + + public EntityIntegerMessage(long id, int name) { + super(id, name); + } + + public EntityIntegerMessage(long id, int name, int value) { + super(id, name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeInt(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readInt(); + } + + @Override + public byte getType() { + return TC_ENTITY_INTEGER; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d value=%d\n", s, getClass().getSimpleName(), id, name, value); + } + } + + public static class EntityLongMessage extends EntityMessage { + + public long value; + + public EntityLongMessage() { + } + + public EntityLongMessage(long id, int name) { + super(id, name); + } + + public EntityLongMessage(long id, int name, long value) { + super(id, name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeLong(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readLong(); + } + + @Override + public byte getType() { + return TC_ENTITY_LONG; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d value=%d\n", s, getClass().getSimpleName(), id, name, value); + } + } + + public static class EntityFloatMessage extends EntityMessage { + + public float value; + + public EntityFloatMessage() { + } + + public EntityFloatMessage(long id, int name) { + super(id, name); + } + + public EntityFloatMessage(long id, int name, float value) { + super(id, name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeFloat(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readFloat(); + } + + @Override + public byte getType() { + return TC_ENTITY_FLOAT; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d value=%f\n", s, getClass().getSimpleName(), id, name, value); + } + } + + public static class EntityStringMessage extends EntityMessage { + + public String value; + + public EntityStringMessage() { + } + + public EntityStringMessage(long id, int name) { + super(id, name); + } + + public EntityStringMessage(long id, int name, String value) { + super(id, name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeUTF(value); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + value = istream.readUTF(); + } + + @Override + public byte getType() { + return TC_ENTITY_STRING; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d value=%s\n", s, getClass().getSimpleName(), id, name, value); + } + } + + public static class EntityStringListMessage extends EntityMessage { + + public final List value; + + public EntityStringListMessage() { + value = new ArrayList<>(); + } + + public EntityStringListMessage(long id, int name) { + super(id, name); + value = new ArrayList<>(); + } + + public EntityStringListMessage(long id, int name, List value) { + super(id, name); + this.value = value; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeShort(value.size()); + for (String s : value) { + ostream.writeUTF(s); + } + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + int len = istream.readShort() & 0xffff; + + for (int i = 0; i < len; i++) + value.add(istream.readUTF()); + } + + @Override + public byte getType() { + return TC_ENTITY_STRING_LIST; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s id=%d name=%d value= {\n", s, getClass().getSimpleName(), id, name); + for (String v : value) { + out.printf("%s '%s'\n", s, v); + } + out.printf("%s}\n", s); + } + } +} diff --git a/DuskCommon/src/duskz/protocol/DuskProtocol.java b/DuskCommon/src/duskz/protocol/DuskProtocol.java new file mode 100644 index 0000000..1dfb068 --- /dev/null +++ b/DuskCommon/src/duskz/protocol/DuskProtocol.java @@ -0,0 +1,489 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +/** + * Although the message type could be used as a secondary index, instead + * i've decided to just use the message name alone. The first way + * would allow 256 messages per type, but 256 is enough total anyway. + * + * FIXME: the messages which just update various client state could be + * merged into a general "update" message which has variable content. + * FIXME: do this before freezing the protocol + * + * @author Michael Zucchi + */ +public interface DuskProtocol { + // Notify messages, hmm, maybe some of these should contain a string + + /** + * MessageList + * I've tried to make this as simple as possible whilst retaining flexibility. + * Everything goes through a single message type, with optional fields. + * + * From Server: + * FIELD_AUTH_RESULT 0=ok,1=failed,2=exists + * FIELD_AUTH_NEWPLAYER (list of missing things, like race) + * To Server: + * FIELD_AUTH_USER + * FIELD_AUTH_PASS (hashed?) + * FIELD_AITH_CLIENT (applet/jar, etc) + * FIELD_AUTH_NEWPLAYER (list of missing things, like race) + */ + public final static int MSG_AUTH = 0; + /** + * Notify only + * One ping and one ping only. + */ + public final static int MSG_PING = 1; + /** + * Notify only + * Server dumped you + */ + public final static int MSG_QUIT = 2; + /** + * TODO: I need some sort of question-asking mechanism. + * + * Probably work similar to the NEWPLAYER thing above. + * EntityListMessage with id set to unique value + * Each entry in the list is a ListMessage whose + * id is server defined. + * Each list message contains: + * StringMessage FIELD_QUERY_PROMPT + * optional + * StringListMessage FIELD_QUERY_OPTIONS + */ + public final static int MSG_QUERY = 3; + /** + * Notify only + * Clear 'battle' flags. + */ + public final static int MSG_CLEAR_FLAGS = 4; + /** + * ListMessage + * int FIELD_MAP_WIDTH + * int FIELD_MAP_HEIGHT + * TODO: merge this with other init stuff like images + */ + public final static int MSG_INIT_MAP = 5; + /** + * MapMessage + * Update the map tiles + */ + public final static int MSG_UPDATE_MAP = 6; + /** + * StringAttribute + * General chat + */ + public final static int MSG_CHAT = 7; + /** + * EntityMessage + * Add a new entity + */ + public final static int MSG_ADD_ENTITY = 8; + /** + * ListMessage + * see entity fields + */ + public final static int MSG_UPDATE_ENTITY = 9; + /** + * EntityMessage + * id is of message to remove + */ + public final static int MSG_REMOVE_ENTITY = 10; + /** + * ByteMessage + * Value is cardinal direction as byte (nsew) + */ + public final static int MSG_MOVE = 11; + /** + * StringListAttribute + * list of actions + */ + public final static int MSG_UPDATE_ACTIONS = 12; + /** + * TransactionMessage + * List of items the user has. + */ + public final static int MSG_INVENTORY = 13; + /** + * ListMessage + * A list of AttributeString values. + * Name of fields is the index of the position worn + * (See Wearing) + */ + public final static int MSG_EQUIPMENT = 14; + /** + * ListMessage + * Any of the fields in the INFO section below + */ + public final static int MSG_INFO_PLAYER = 15; + /** + * ListMessage + * Any of the fields in the INFO section below + * except for FOLLOWING/FOLLOWED and the STATS (?) + */ + public final static int MSG_INFO_PET = 16; + /** + * TransactionMessage + * List of items for sale + */ + public final static int MSG_UPDATE_MERCHANT = 17; + /** + * Notify only + * The player left a shop + */ + public final static int MSG_EXIT_MERCHANT = 18; + /** + * Start of battle. + * long FIELD_BATTLE_SOURCE (TODO) + * string FIELD_BATTLE_OPPONENT + */ + public final static int MSG_BATTLE_START = 19; + /** + * ListMessage + * long FIELD_BATTLE_TARGET who got it + * int FIELD_BATTLE_HP target hp + * int FIELD_BATTLE_MAXHP target max hp + * int FIELD_BATTLE_DAMAGE +- damage + * long FIELD_BATTLE_SOURCE who did it + * string FIELD_BATTLE_WHAT how it was done + */ + public final static int MSG_BATTLE_UPDATE = 20; + /** + * StringAttribute + * Battle related messages + */ + public final static int MSG_BATTLE_CHAT = 21; + /** + * ListMessage + * string FIELD_TEXT_NAME + * byte FIELD_TEXT_EDITABLE + * string FIELD_TEXT_TEXT + */ + public final static int MSG_VIEW_TEXT = 22; + /** + * ********************************************* + * Client to server messages. + * + * MSG_AUTH and MSG_QUERY are also included + */ + /** + * StringMessage + * A simple string message containing the command to run + */ + public final static int MSG_COMMAND = 23; + // + // + // + //TODO: + //LoadMusic, + //PlayMusic, + //PlaySound, + //ColourChat, + /** + * MSG_AUTH fields + */ + public final static int AUTH_LOGIN_OK = 0; + public final static int AUTH_LOGIN_FAILED = 1; + public final static int AUTH_LOGIN_EXISTS = 2; + public final static int AUTH_LOGIN_INCOMPLETE = 3; + /** + * EntityIntegerMessage + * Result of authentication,as above + * if ok, id = id of player + */ + public final static int FIELD_AUTH_RESULT = 0; + /** + * StringMessage + * reason/prompt for failure + */ + public final static int FIELD_AUTH_REASON = 1; + /** + * ListMessage of ListMessage + * server: create failed because of missing information, list lists it. + * The name of each message is for the server, and each list describes + * a query using the FIELD_QUERY constants + * client: new player extra information. i.e. race, email, whatever. + * The name of each response should match those in the server list. + * + */ + public final static int FIELD_AUTH_NEWPLAYER = 2; + /** + * StringMessage + * client: player name + */ + public final static int FIELD_AUTH_PLAYER = 3; + /** + * StringMessage + * client: player password + */ + public final static int FIELD_AUTH_PASS = 4; + /** + * ListMessage + * client: Details about client + */ + public final static int FIELD_AUTH_CLIENT = 5; + /** + * Query fields + * These are fields within each ListMessage in + * NEWPLAYER or QUERY message + * TODO: could add more types + */ + /** + * StringMessage + * server: prompt for query + */ + public final static int FIELD_QUERY_PROMPT = 0; + /** + * StringListMessage + * server: a list of options to choose from. The client + * response will be a String with a name which matches the + * ListMessage containing this item. + */ + public final static int FIELD_QUERY_OPTIONS = 1; + /** + * StringMessage + * Present a password box + * server: string is prompt + * client: string is response + */ + public final static int FIELD_QUERY_HIDDEN = 2; + // TOOD: could add more options here. + /** + * Entity update fields + */ + public final static int FIELD_ENTITY_FLAGS = 0; + /** + * Player update fields + * MSG_INFO_PLAYER + * MSG_INFO_PET + */ + /** + * IntegerMessage + * Player hp + */ + public final static int FIELD_INFO_HP = 0; + /** + * IntegerMessage + * max hp + */ + public final static int FIELD_INFO_MAXHP = 1; + /** + * IntegerMessage + * player mp + */ + public final static int FIELD_INFO_MP = 2; + /** + * IntegerMessage + * max mp + */ + public final static int FIELD_INFO_MAXMP = 3; + /** + * LongMessage + * Money in hand + */ + public final static int FIELD_INFO_CASH = 4; + /** + * IntegerMessage + * Experience + */ + public final static int FIELD_INFO_EXP = 5; + /** + * IntegerMessage + * Strength + */ + public final static int FIELD_INFO_STR = 6; + /** + * IntegerMessage + * Strength bonus - if not zero + */ + public final static int FIELD_INFO_STRBON = 7; + /** + * IntegerMessage + * Intelligence + */ + public final static int FIELD_INFO_INT = 8; + /** + * IntegerMessage + * Intelligence bonus if not zero + */ + public final static int FIELD_INFO_INTBON = 9; + /** + * IntegerMessage + * Dexterity + */ + public final static int FIELD_INFO_DEX = 10; + /** + * IntegerMessage + * Dexterity bonus if not zero + */ + public final static int FIELD_INFO_DEXBON = 11; + /** + * IntegerMessage + * You get the picture + */ + public final static int FIELD_INFO_CON = 12; + /** + * IntegerMessage + * + */ + public final static int FIELD_INFO_CONBON = 13; + /** + * IntegerMessage + * + */ + public final static int FIELD_INFO_WIS = 14; + /** + * IntegerMessage + * + */ + public final static int FIELD_INFO_WISBON = 15; + /** + * IntegerMessage + * + */ + public final static int FIELD_INFO_DAM = 16; + /** + * IntegerMessage + * + */ + public final static int FIELD_INFO_DAMBON = 17; + /** + * IntegerMessage + * Armour class + */ + public final static int FIELD_INFO_ARC = 18; + /** + * IntegerMessage + * + */ + public final static int FIELD_INFO_ARCBON = 19; + /** + * StringArrayMessage + * A list of conditions active on the player/pet + */ + public final static int FIELD_INFO_CONDITIONS = 20; + /** + * StringArrayMessage + * List of skills the player has + */ + public final static int FIELD_INFO_SKILLS = 21; + /** + * StringArrayMessage + * List of spells the player has + * FIXME: do this in a way that i know the castable spells in the frontend, + * even if it's just string parsing + */ + public final static int FIELD_INFO_SPELLS = 22; + /** + * StringMessage + * Who player is following + */ + public final static int FIELD_INFO_FOLLOWING = 23; + /** + * StringMessage + * Who player is being followed by (pet) + */ + public final static int FIELD_INFO_FOLLOWED = 24; + /** + * IntegerMessage + * Player attack range + */ + public final static int FIELD_INFO_RANGE = 25; + /** + * MSG_INIT_MAP fields + */ + /** + * IntegerMessage + * width of displayed map + */ + public final static int FIELD_MAP_WIDTH = 0; + /** + * IntegerMessage + * height of displayed map + */ + public final static int FIELD_MAP_HEIGHT = 1; + /** + * StringMessage + * location of asset jar for this map + */ + public final static int FIELD_MAP_ASSETLOCATION = 2; + /** + * MSG_VIEW_TEXT + */ + /** + * StringMessage + * Name of file/title of content + */ + public final static int FIELD_TEXT_NAME = 0; + /** + * ByteMessage + * Present only if true (==1), message is editable + */ + public final static int FIELD_TEXT_EDITABLE = 1; + /** + * StringMessage + * Content of file + */ + public final static int FIELD_TEXT_TEXT = 2; + /** + * MSG_BATTLE_UPDATE + */ + /** + * LongMessage + * id of target of message/hit + */ + public final static int FIELD_BATTLE_TARGET = 0; + /** + * IntegerMessage + * current hp + */ + public final static int FIELD_BATTLE_HP = 1; + /** + * IntegerMessage + * maximum hp + */ + public final static int FIELD_BATTLE_MAXHP = 2; + /** + * IntegerMessage + * +/- healing/damage from some action + */ + public final static int FIELD_BATTLE_DAMAGE = 3; + /** + * LongMessage + * Who caused the damage/healing + */ + public final static int FIELD_BATTLE_SOURCE = 4; + /** + * What it was they did + */ + public final static int FIELD_BATTLE_WHAT = 5; + /** + * MSG_BATTLE_START + * FIXME: Will also include FIELD_BATTLE_SOURCE for + * the opponent id + */ + /** + * StringMessage + * Name of opponent + */ + public final static int FIELD_BATTLE_OPPONENT = 6; +} diff --git a/DuskCommon/src/duskz/protocol/EntityListMessage.java b/DuskCommon/src/duskz/protocol/EntityListMessage.java new file mode 100644 index 0000000..dd7f75f --- /dev/null +++ b/DuskCommon/src/duskz/protocol/EntityListMessage.java @@ -0,0 +1,59 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * A list message with a target. + * + * @author Michael Zucchi + */ +public class EntityListMessage extends ListMessage { + + public long id; + + EntityListMessage() { + } + + public EntityListMessage(long id, int name) { + super(name); + this.id = id; + } + + @Override + public byte getType() { + return TC_ENTITY_LIST; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeLong(id); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + id = istream.readLong(); + } +} diff --git a/DuskCommon/src/duskz/protocol/EntityUpdateMessage.java b/DuskCommon/src/duskz/protocol/EntityUpdateMessage.java new file mode 100644 index 0000000..d754b03 --- /dev/null +++ b/DuskCommon/src/duskz/protocol/EntityUpdateMessage.java @@ -0,0 +1,68 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * An entity update. + * + * @author Michael Zucchi + */ +public class EntityUpdateMessage extends DuskMessage.EntityMessage { + + public String entityName; + public byte entityType; + public short x; + public short y; + public short image; + public short imageStep; + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeLong(id); + ostream.writeUTF(entityName); + ostream.writeByte(entityType); + ostream.writeShort(x); + ostream.writeShort(y); + ostream.writeShort(image); + ostream.writeShort(imageStep); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + id = istream.readLong(); + entityName = istream.readUTF(); + entityType = istream.readByte(); + x = istream.readShort(); + y = istream.readShort(); + image = istream.readShort(); + imageStep = istream.readShort(); + } + + @Override + public byte getType() { + return TC_ENTITY_UPDATE; + } +} diff --git a/DuskCommon/src/duskz/protocol/ListMessage.java b/DuskCommon/src/duskz/protocol/ListMessage.java new file mode 100644 index 0000000..38fec03 --- /dev/null +++ b/DuskCommon/src/duskz/protocol/ListMessage.java @@ -0,0 +1,195 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +/** + * DuskMessage for a list of named value + * + * @author Michael Zucchi + */ +public class ListMessage extends DuskMessage { + + final public List value; + + ListMessage() { + value = new ArrayList<>(); + } + + public ListMessage(int name) { + super(name); + value = new ArrayList<>(); + } + + public void add(DuskMessage a) { + value.add(a); + } + + public void add(int name, byte value) { + this.value.add(DuskMessage.create(name, value)); + } + + public void add(int name, int value) { + this.value.add(DuskMessage.create(name, value)); + } + + public void add(int name, long value) { + this.value.add(DuskMessage.create(name, value)); + } + + public void add(int name, String value) { + this.value.add(DuskMessage.create(name, value)); + } + + public void add(long id, int name, int value) { + this.value.add(DuskMessage.create(id, name, value)); + } + public void add(long id, int name, String value) { + this.value.add(DuskMessage.create(id, name, value)); + } + + public DuskMessage.StringListMessage add(int name, List value) { + StringListMessage a = DuskMessage.create(name, value); + this.value.add(a); + return a; + } + + public DuskMessage get(int name) { + int len = value.size(); + for (int i = 0; i < len; i++) { + DuskMessage a = value.get(i); + if (a.name == name) + return a; + } + return null; + } + + public byte getByte(int name) { + return ((ByteMessage) get(name)).value; + } + + public int getInteger(int name) { + DuskMessage m = get(name); + + if (m instanceof IntegerMessage) + return ((IntegerMessage)m).value; + else if (m instanceof EntityIntegerMessage) { + return ((EntityIntegerMessage)m).value; + } + throw new ClassCastException(m.getClass().getName() + " not IntegerMessage"); + } + + public long getLong(int name) { + return ((LongMessage) get(name)).value; + } + + public String getString(int name) { + return ((StringMessage) get(name)).value; + } + + public List getStringList(int name) { + return ((StringListMessage) get(name)).value; + } + + public byte getByte(int name, byte def) { + DuskMessage a = get(name); + + if (a == null || a.getType() != TC_BYTE) + return def; + + return ((ByteMessage) a).value; + } + + public int getInteger(int name, int def) { + DuskMessage a = get(name); + + if (a == null || a.getType() != TC_INTEGER) + return def; + return ((IntegerMessage) a).value; + } + + public long getLong(int name, long def) { + DuskMessage a = get(name); + + if (a == null || a.getType() != TC_INTEGER) + return def; + return ((LongMessage) a).value; + } + + public String getString(int name, String def) { + DuskMessage a = get(name); + + if (a == null || a.getType() != TC_STRING) + return def; + return ((StringMessage) a).value; + } + + public List getStringList(int name, List def) { + DuskMessage a = get(name); + + if (a == null || a.getType() != TC_STRING_LIST) + return def; + return ((StringListMessage) a).value; + } + + public DuskMessage getMessage(int name) { + return get(name); + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeShort(value.size()); + for (DuskMessage a : value) { + ostream.writeByte(a.getType()); + a.send(ostream); + } + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + + int len = istream.readShort(); + for (int i = 0; i < len; i++) { + value.add(receiveMessage(istream)); + } + } + + @Override + public byte getType() { + return TC_LIST; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value = {\n", s, getClass().getSimpleName(), name); + for (DuskMessage dm : value) { + dm.format(out, s + "\t"); + } + out.printf("%s}\n", s); + } +} diff --git a/DuskCommon/src/duskz/protocol/MapMessage.java b/DuskCommon/src/duskz/protocol/MapMessage.java new file mode 100644 index 0000000..5917aaa --- /dev/null +++ b/DuskCommon/src/duskz/protocol/MapMessage.java @@ -0,0 +1,83 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * DuskMessage for a client view map update. + * + * @author Michael Zucchi + */ +public class MapMessage extends DuskMessage { + + public short x; + public short y; + public short width; + public short height; + public short[] map; + + public MapMessage() { + } + + public MapMessage(int name, int width, int height, int locx, int locy, short[] map) { + super(name); + + this.x = (short) locx; + this.y = (short) locy; + this.width = (short) width; + this.height = (short) height; + this.map = map; + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeShort(x); + ostream.writeShort(y); + ostream.writeShort(width); + ostream.writeShort(height); + for (int i = 0; i < width * height; i++) + ostream.writeShort(map[i]); + } + + @Override + public void receive(DataInputStream istream) throws IOException { + int len; + + super.receive(istream); + x = istream.readShort(); + y = istream.readShort(); + width = istream.readShort(); + height = istream.readShort(); + len = width * height; + map = new short[len]; + for (int i = 0; i < len; i++) { + map[i] = istream.readShort(); + } + } + + @Override + public byte getType() { + return TC_MAP; + } +} diff --git a/DuskCommon/src/duskz/protocol/MessageType.java b/DuskCommon/src/duskz/protocol/MessageType.java deleted file mode 100644 index fee4a92..0000000 --- a/DuskCommon/src/duskz/protocol/MessageType.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of DuskZ, a graphical mud engine. - * - * Copyright (C) 2013 Michael Zucchi - * - * 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.protocol; - -import java.io.DataInputStream; -import java.io.IOException; - -/** - * Type of message sent from server. - * - * @author notzed - */ -public enum MessageType { - - // 0 - Quit, - UpdateImages, - UpdateLocMap { - - @Override - public ServerMessage decode(DataInputStream instream) throws IOException { - return ServerMessage.MapMessage.decode(instream); - } - }, - Chat, - AddEntity, - // 5 - UpdateStats, - UpdateItems, - UpdateEquipment, - UpdateInfo, - Halt, - // 10 - UpdateActions, - LoadMusic, - PlayMusic, - Ping, - Proceed, - // 15 - PlaySound, - RemoveEntity, - UpdateMerchant, - EditText, - ResizeMap, - // 20 - ViewText, - ExitMerchant, - UpdateSell, - ColourChat, - MoveNorth, - // 25 - MoveSouth, - MoveWest, - MoveEast, - UpdateRange, - SetFlag, - // 30 - ClearFlags, - StartBattle, - UpdateBattle, - LogBattle, - // Above list is compatible with original dusk, new ones follow - // A bunch of login/setup related stuff - /** - * Choose race, response is - * code header - * race 0 - * race 1 - * ... - * . - */ - ChooseRace, - /** - * Damage (or healing) to an entity. - * Encoded as: - * targetID - * +-damage - * newhp - * totalhp - * fromID - * how - * . - */ - HitEntity,; - - public static MessageType fromServer(int v) { - return values()[v]; - } - - public char code() { - return (char) ordinal(); - } - - /** - * Not implemented yet - * @param instream - * @return - * @throws IOException - * @deprecated - */ - @Deprecated - public ServerMessage decode(DataInputStream instream) throws IOException { - return null; - } -} diff --git a/DuskCommon/src/duskz/protocol/ServerMessage.java b/DuskCommon/src/duskz/protocol/ServerMessage.java deleted file mode 100644 index a133fd2..0000000 --- a/DuskCommon/src/duskz/protocol/ServerMessage.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * This file is part of DuskZ, a graphical mud engine. - * - * Copyright (C) 2013 Michael Zucchi - * - * 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.protocol; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Message from server to client - * - * @author notzed - */ -public abstract class ServerMessage { - - final MessageType type; - - public ServerMessage(MessageType type) { - this.type = type; - } - - public abstract void send(DataOutputStream ostream) throws IOException; - - public static ServerMessage stringMessage(String s) { - return new StringMessage(s); - } - - public static ServerMessage stringMessage(MessageType type, String data) { - return new StringMessage(type, data); - } - - public static ServerMessage mapMessage(int locx, int locy, short[] map) { - return new MapMessage(locx, locy, map); - } - - public static ServerMessage fromServer(DataInputStream instream) throws IOException { - int typec = instream.readByte(); - - MessageType type = MessageType.values()[typec]; - // TODO: more protocol, length? how? - - return type.decode(instream); - } - - public static class StringMessage extends ServerMessage { - - final String data; - - public StringMessage(String data) { - super(null); - this.data = data; - } - - private StringMessage(MessageType type, String data) { - super(type); - this.data = data; - } - - @Override - public void send(DataOutputStream ostream) throws IOException { - if (type != null) - ostream.writeByte(type.code()); - ostream.writeBytes(data); - } - - @Override - public String toString() { - return data; - } - } - - public static class MapMessage extends ServerMessage { - - public int x; - public int y; - public short[] map; - - public MapMessage(int locx, int locy, short[] map) { - super(MessageType.UpdateLocMap); - this.x = locx; - this.y = locy; - this.map = map; - } - - public static MapMessage decode(DataInputStream instream) throws IOException { - int x = instream.readShort(); - int y = instream.readShort(); - int length = instream.readInt(); - short[] map = new short[length]; - for (int i = 0; i < length; i++) { - map[i] = instream.readShort(); - } - return new MapMessage(x, y, map); - } - - @Override - public void send(DataOutputStream ostream) throws IOException { - ostream.writeByte(type.code()); - ostream.writeShort(x); - ostream.writeShort(y); - ostream.writeInt(map.length); - for (int i = 0; i < map.length; i++) - ostream.writeShort(map[i]); - } - } - - public static class EntityMessage extends ServerMessage { - - public long id; - public String name; - public byte entityType; - public short x; - public short y; - public short image; - public short imageStep; - - public EntityMessage(MessageType type, long id, String name, byte entityType, short x, short y, short image, short imageStep) { - super(type); - this.id = id; - this.name = name; - this.entityType = entityType; - this.x = x; - this.y = y; - this.image = image; - this.imageStep = imageStep; - - System.out.printf("create entity message: %d '%s' %d %dx%d image %d %d\n", - id, name, entityType, x, y, image, imageStep); - } - - public static EntityMessage decode(MessageType type, DataInputStream instream) throws IOException { - long id = instream.readLong(); - String name = instream.readUTF(); - byte entityType = instream.readByte(); - short x = instream.readShort(); - short y = instream.readShort(); - short image = instream.readShort(); - short imageStep = instream.readShort(); - - return new EntityMessage(type, id, name, entityType, x, y, image, imageStep); - } - - @Override - public void send(DataOutputStream ostream) throws IOException { - ostream.writeByte(type.code()); - ostream.writeLong(id); - ostream.writeUTF(name); - ostream.writeByte(entityType); - ostream.writeShort(x); - ostream.writeShort(y); - ostream.writeShort(image); - ostream.writeShort(imageStep); - } - } - - /** - * Main player stats - */ - public static class StatsMessage extends ServerMessage { - - public long cash; - public int exp; - public int stre, strebonus; - public int inte, intebonus; - public int dext, dextbonus; - public int cons, consbonus; - public int wisd, wisdbonus; - public int damm, dammbonus; - public int ac, acbonus; - - public StatsMessage(MessageType type, long cash, int exp, int stre, int strebonus, - int inte, int intebonus, int dext, int dextbonus, int cons, int consbonus, - int wisd, int wisdbonus, int damm, int dammbonus, int ac, int acbonus) { - super(type); - this.cash = cash; - this.exp = exp; - this.stre = stre; - this.strebonus = strebonus; - this.inte = inte; - this.intebonus = intebonus; - this.dext = dext; - this.dextbonus = dextbonus; - this.cons = cons; - this.consbonus = consbonus; - this.wisd = wisd; - this.wisdbonus = wisdbonus; - this.damm = damm; - this.dammbonus = dammbonus; - this.ac = ac; - this.acbonus = acbonus; - } - - public static StatsMessage decode(MessageType type, DataInputStream instream) throws IOException { - long cash = instream.readLong(); - int exp = instream.readInt(); - int stre = instream.readInt(); - int strebonus = instream.readInt(); - int inte = instream.readInt(); - int intebonus = instream.readInt(); - int dext = instream.readInt(); - int dextbonus = instream.readInt(); - int cons = instream.readInt(); - int consbonus = instream.readInt(); - int wisd = instream.readInt(); - int wisdbonus = instream.readInt(); - int damm = instream.readInt(); - int dammbonus = instream.readInt(); - int ac = instream.readInt(); - int acbonus = instream.readInt(); - - return new StatsMessage(type, cash, exp, - stre, strebonus, - inte, intebonus, - dext, dextbonus, - cons, consbonus, - wisd, wisdbonus, - damm, dammbonus, - ac, acbonus); - } - - @Override - public void send(DataOutputStream ostream) throws IOException { - ostream.writeByte(type.code()); - ostream.writeLong(cash); - ostream.writeInt(exp); - ostream.writeInt(stre); - ostream.writeInt(strebonus); - ostream.writeInt(inte); - ostream.writeInt(intebonus); - ostream.writeInt(dext); - ostream.writeInt(dextbonus); - ostream.writeInt(cons); - ostream.writeInt(consbonus); - ostream.writeInt(wisd); - ostream.writeInt(wisdbonus); - ostream.writeInt(damm); - ostream.writeInt(dammbonus); - ostream.writeInt(ac); - ostream.writeInt(acbonus); - } - } - - /** - * Other player stats? - * All together or separate? - * UpdateSkills - * UpdateConditions? - */ - /* - strResult += "-Affected by-\n"; - for (Condition cond : conditions) { - if (cond.display) { - strResult += cond.name + "\n"; - } - } - strResult += "-Skills-\n"; - for (Ability skill : skillMap.values()) { - strResult += skill.name + ": " + skill.getAbility() + "\n"; - } - strResult += "-Spells-\n"; - for (Ability spell : spellMap.values()) { - grpStore = game.getSpellGroup(spell.name); - if (grpStore != null) { - strResult += spell.name + ": " + spell.getAbility() + "\n"; - strResult += grpStore.spellList(spell.getAbility()); - } - } - if (master != null) { - strResult += "\nFollowing: " + master.name + "\n"; - }*/ - public static class ItemsMessage extends ServerMessage { - - List items; - - public ItemsMessage(MessageType type, List items) { - super(type); - this.items = items; - } - - public static ItemsMessage decode(MessageType type, DataInputStream instream) throws IOException { - List items = new ArrayList<>(); - - int count = instream.readShort(); - for (int i = 0; i < count; i++) { - TransactionItem item = new TransactionItem(); - - item.name = instream.readUTF(); - item.count = instream.readInt(); - item.cost = instream.readInt(); - item.units = instream.readUTF(); - } - - return new ItemsMessage(type, items); - } - - @Override - public void send(DataOutputStream ostream) throws IOException { - ostream.writeByte(type.code()); - ostream.writeShort(items.size()); - for (TransactionItem item : items) { - ostream.writeUTF(item.getName()); - ostream.writeInt(item.getCount()); - ostream.writeInt(item.getCost()); - ostream.writeUTF(item.getUnits()); - } - } - } -} diff --git a/DuskCommon/src/duskz/protocol/TransactionItem.java b/DuskCommon/src/duskz/protocol/TransactionItem.java index cead913..3a42cd8 100644 --- a/DuskCommon/src/duskz/protocol/TransactionItem.java +++ b/DuskCommon/src/duskz/protocol/TransactionItem.java @@ -22,12 +22,19 @@ */ package duskz.protocol; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + /** * Represents a transaction item (buy/sell) for server protocol. + * * @author notzed */ -public class TransactionItem implements Comparable{ - +public class TransactionItem implements Comparable { + + // From wearing + public int type; public String name; public int count; public int cost; @@ -36,14 +43,14 @@ public class TransactionItem implements Comparable{ public TransactionItem() { } - - public TransactionItem(String name, int count, int cost, String units) { + public TransactionItem(int type, String name, int count, int cost, String units) { + this.type = type; this.name = name; this.count = count; this.cost = cost; this.units = units; } - + public String getName() { return name; } @@ -56,17 +63,35 @@ public class TransactionItem implements Comparable{ return cost; } + public String getCostText() { + return cost + " " + units; + } + public String getUnits() { return units; } - + @Override public String toString() { return cost + ") " + name + "[" + count + "]"; } - + @Override public int compareTo(TransactionItem t) { return name.compareTo(t.name); } + + public void send(DataOutputStream ostream) throws IOException { + ostream.writeUTF(name); + ostream.writeInt(count); + ostream.writeInt(cost); + ostream.writeUTF(units); + } + + public void receive(DataInputStream istream) throws IOException { + name = istream.readUTF(); + count = istream.readInt(); + cost = istream.readInt(); + units = istream.readUTF(); + } } diff --git a/DuskCommon/src/duskz/protocol/TransactionMessage.java b/DuskCommon/src/duskz/protocol/TransactionMessage.java new file mode 100644 index 0000000..9484d4f --- /dev/null +++ b/DuskCommon/src/duskz/protocol/TransactionMessage.java @@ -0,0 +1,93 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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.protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +/** + * A list of for buy or sell items. + * + * @author Michael Zucchi + */ +public class TransactionMessage extends DuskMessage { + + public final List items; + + TransactionMessage() { + items = new ArrayList<>(); + } + + public TransactionMessage(int name) { + super(name); + items = new ArrayList<>(); + } + + public TransactionMessage(int name, List items) { + super(name); + this.items = items; + } + + public void add(TransactionItem item) { + items.add(item); + } + + public void add(int type, String name, int count, int cost, String units) { + items.add(new TransactionItem(type, name, count, cost, units)); + } + + @Override + public void send(DataOutputStream ostream) throws IOException { + super.send(ostream); + ostream.writeShort(items.size()); + for (TransactionItem item : items) { + item.send(ostream); + } + } + + @Override + public void receive(DataInputStream istream) throws IOException { + super.receive(istream); + int count = istream.readShort(); + for (int i = 0; i < count; i++) { + TransactionItem item = new TransactionItem(); + item.receive(istream); + items.add(item); + } + } + + @Override + public byte getType() { + return TC_TRANSACTION; + } + + @Override + protected void format(PrintStream out, String s) { + out.printf("%s%s name=%d value= {\n", s, getClass().getSimpleName(), name); + for (TransactionItem v : items) { + out.printf("%s %d '%s' %d %s\n", s, v.count, v.name, v.cost, v.units); + } + out.printf("%s}\n", s); + } +} diff --git a/DuskCommon/src/duskz/protocol/Wearing.java b/DuskCommon/src/duskz/protocol/Wearing.java index ccd254e..a86bb2b 100644 --- a/DuskCommon/src/duskz/protocol/Wearing.java +++ b/DuskCommon/src/duskz/protocol/Wearing.java @@ -24,18 +24,50 @@ package duskz.protocol; /** * Constants for wearing locations. + * * @author notzed */ public interface Wearing { + /** + * Non-wearable item + */ + public static final int INVENTORY = -1; + /** + * Wieldable + */ public static final int WIELD = 0; + /** + * Armour on arms + */ public static final int ARMS = 1; + /** + * Armour on legs + */ public static final int LEGS = 2; + /** + * Armour on body + */ public static final int TORSO = 3; + /** + * Armour belt + */ public static final int WAIST = 4; + /** + * Armour on neck + */ public static final int NECK = 5; + /** + * Armour on head + */ public static final int SKULL = 6; + /** + * Saftey goggles + */ public static final int EYES = 7; + /** + * Gloves + */ public static final int HANDS = 8; public static final int WEARING_COUNT = 9; public static final String[] titles = { diff --git a/DuskServer/src/duskz/server/BannedIPException.java b/DuskServer/src/duskz/server/BannedIPException.java new file mode 100644 index 0000000..af20cd2 --- /dev/null +++ b/DuskServer/src/duskz/server/BannedIPException.java @@ -0,0 +1,27 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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; + +/** + * IP was banned automatically + * @author Michael Zucchi + */ +public class BannedIPException extends Exception { +} diff --git a/DuskServer/src/duskz/server/Battle.java b/DuskServer/src/duskz/server/Battle.java index 87418ed..38cd620 100644 --- a/DuskServer/src/duskz/server/Battle.java +++ b/DuskServer/src/duskz/server/Battle.java @@ -21,10 +21,12 @@ /** * Changes * Feb-2013 Michael Zucchi - Pretty major cleanup and parameterisation of code. + * Mar-2013 Michael Zucchi - changed server protocol */ package duskz.server; -import duskz.protocol.MessageType; +import duskz.protocol.DuskProtocol; +import duskz.protocol.ListMessage; import duskz.server.entity.Mob; import duskz.server.entity.LivingThing; import duskz.server.entity.Item; @@ -39,7 +41,7 @@ import java.util.LinkedList; * * @author Tom Weingarten */ -public class Battle { +public class Battle implements DuskProtocol { private ArrayList vctSide1 = new ArrayList<>(), vctSide2 = new ArrayList<>(); @@ -81,12 +83,8 @@ public class Battle { } thnFront1 = inpla1; engGame.chatMessage("-" + inpla1.name + " has attacked " + inpla2.name, inpla1.x, inpla1.y, "default"); - if (inpla1.popup) { - inpla1.send(MessageType.StartBattle, inpla2.name + "\n"); - } - if (inpla2.popup) { - inpla2.send(MessageType.StartBattle, inpla1.name + "\n"); - } + inpla1.startBattle(inpla2); + inpla2.startBattle(inpla1); } catch (Exception e) { blnRunning = false; engGame.log.printError("Battle()", e); @@ -131,7 +129,7 @@ public class Battle { thnAdded.playMusic(1); } } - chatMessage("\t" + thnAdded.name + " has joined the battle."); + chatMessage(thnAdded.name + " has joined the battle."); side.add(thnAdded); thnAdded.enterBattle(this, sideid); @@ -266,7 +264,7 @@ public class Battle { } msg += "."; } - chatMessage("\t" + msg); + chatMessage(msg); } return true; } @@ -303,7 +301,7 @@ public class Battle { void endBattle() { blnRunning = false; - chatMessage("\tYou have won the battle."); + chatMessage("You have won the battle."); endBattle(vctSide1); endBattle(vctSide2); } @@ -330,11 +328,7 @@ public class Battle { } } list.remove(thnStore); - if (thnStore.popup) { - thnStore.send("" + (char) 33 + "You have fled from battle\n"); - } else { - thnStore.chatMessage("You have fled from battle"); - } + thnStore.chatBattle("You have fled from battle"); splitMoney(thnStore, (int) (thnStore.cash * engGame.gpfleemod), opponents); splitExp(thnStore, (int) (thnStore.exp * engGame.expfleemod), opponents); thnStore.leaveBattle(); @@ -358,11 +352,8 @@ public class Battle { if (money == 0) { return; } - if (thnStore.popup) { - thnStore.send("" + (char) 33 + "You have lost " + money + " gp.\n"); - } else { - thnStore.chatMessage("You have lost " + money + " gp."); - } + thnStore.chatBattle("You have lost " + money + " gp."); + // FIXME: atm's need transactions! thnStore.cash -= money; int i, i2 = 0; @@ -394,11 +385,7 @@ public class Battle { } void splitExp(LivingThing thnFront, int exp, ArrayList opponents) { - if (thnFront.popup) { - thnFront.send("" + (char) 33 + "You have lost " + exp + " exp.\n"); - } else { - thnFront.chatMessage("You have lost " + exp + " exp."); - } + thnFront.chatBattle("You have lost " + exp + " exp."); thnFront.exp -= exp; double tp, sidepoints = 0; @@ -446,32 +433,22 @@ public class Battle { void chatMessage(ArrayList side1, ArrayList side2, String strStore) { LivingThing thnStore = null; - String strStore2 = null; - if (!side2.isEmpty()) { - thnStore = (LivingThing) side2.get(0); - strStore2 = thnStore.name + " has " + thnStore.getCharacterPoints() + "cp and " + thnStore.hp + "/" + thnStore.maxhp + "hp."; - } + //String strStore2 = null; + //if (!side2.isEmpty()) { + // thnStore = (LivingThing) side2.get(0); + // strStore2 = thnStore.name + " has " + thnStore.getCharacterPoints() + "cp and " + thnStore.hp + "/" + thnStore.maxhp + "hp."; + //} for (int i = 0; i < side1.size(); i++) { thnStore = (LivingThing) side1.get(i); if (thnStore.isPlayer()) { - if (thnStore.popup) { - if (strStore2 != null) { - thnStore.send(MessageType.UpdateBattle, strStore2 + "\n"); - thnStore.send(MessageType.LogBattle, strStore + "\n"); - } - } else { - thnStore.chatMessage(strStore); - } + //thnStore.send(MessageType.UpdateBattle, strStore2 + "\n"); + //thnStore.send(MessageType.LogBattle, strStore + "\n"); + thnStore.chatBattle(strStore); } else if (thnStore.isPet()) { if (thnStore.getMaster().battle != thnStore.battle) { - if (thnStore.getMaster().popup) { - if (strStore2 != null) { - thnStore.getMaster().send(MessageType.UpdateBattle, "From " + thnStore.name + ": " + strStore2 + "\n"); - thnStore.getMaster().send(MessageType.LogBattle, "From " + thnStore.name + ": " + strStore + "\n"); - } - } else { - thnStore.chatMessage(strStore); - } + //thnStore.getMaster().send(MessageType.UpdateBattle, "From " + thnStore.name + ": " + strStore2 + "\n"); + //thnStore.getMaster().send(MessageType.LogBattle, "From " + thnStore.name + ": " + strStore + "\n"); + thnStore.chatBattle(strStore); } } } @@ -482,13 +459,13 @@ public class Battle { chatMessage(vctSide2, vctSide1, strStore); } - void battleMessage(ArrayList side1, MessageType type, String msg) { + void battleMessage(ArrayList side1, ListMessage lm) { for (LivingThing thnStore : side1) { if (thnStore.isPlayer()) { - thnStore.send(type, msg); + thnStore.send(lm); } else if (thnStore.isPet()) { if (thnStore.getMaster().battle != thnStore.battle) { - thnStore.send(type, msg); + thnStore.send(lm); } } } @@ -500,14 +477,21 @@ public class Battle { * @param type * @param msg */ - void battleMessage(MessageType type, String msg) { - battleMessage(vctSide1, type, msg); - battleMessage(vctSide2, type, msg); + void battleMessage(ListMessage msg) { + battleMessage(vctSide1, msg); + battleMessage(vctSide2, msg); } void hitMessage(long targetid, int delta, int targethp, int targettotalhp, long fromid, String what) { - battleMessage(MessageType.HitEntity, - String.format("%d\n%d\n%d\n%d\n%d\n%s\n.\n", targetid, delta, targethp, targettotalhp, fromid, what)); + ListMessage lm = new ListMessage(MSG_BATTLE_UPDATE); + + lm.add(FIELD_BATTLE_TARGET, targetid); + lm.add(FIELD_BATTLE_DAMAGE, delta); + lm.add(FIELD_BATTLE_HP, targethp); + lm.add(FIELD_BATTLE_MAXHP, targettotalhp); + lm.add(FIELD_BATTLE_SOURCE, fromid); + lm.add(FIELD_BATTLE_WHAT, what); + battleMessage(lm); } /** @@ -620,12 +604,8 @@ public class Battle { updateFlags(vctSide2, thnFront2.ID, 0); if (thnFront2.isPlayer()) { thnFront2.removeFromGroup(); - chatMessage("\t" + thnFront2.name + " is killed."); - if (thnFront2.popup) { - thnFront2.send("" + (char) 33 + "\tYou have died.\n"); - } else { - thnFront2.chatMessage("\tYou have died."); - } + chatMessage(thnFront2.name + " is killed."); + thnFront2.chatBattle("You have died."); splitMoney(thnFront2, (int) (thnFront2.cash * engGame.gplosemod), vctSide1); splitExp(thnFront2, (int) (thnFront2.exp * engGame.explosemod), vctSide1); thnFront2.leaveBattle(); @@ -650,7 +630,7 @@ public class Battle { } } else if (thnFront2.isMob()) { Mob mob = (Mob) thnFront2; - chatMessage("\t" + thnFront2.name + " is killed."); + chatMessage(thnFront2.name + " is killed."); splitMoney(thnFront2, (int) (thnFront2.cash), vctSide1); splitExp(thnFront2, 0, vctSide1); /** @@ -691,8 +671,8 @@ public class Battle { } } } else if (thnFront2.isPet()) { - chatMessage("\t" + thnFront2.name + " is wounded."); - thnFront2.chatMessage("\tYou have been wounded."); + chatMessage(thnFront2.name + " is wounded."); + thnFront2.chatMessage("You have been wounded."); splitMoney(thnFront2, (int) (thnFront2.cash * engGame.gplosemod), vctSide1); splitExp(thnFront2, (int) (thnFront2.exp * engGame.explosemod), vctSide1); thnFront2.leaveBattle(); diff --git a/DuskServer/src/duskz/server/BlockedIPException.java b/DuskServer/src/duskz/server/BlockedIPException.java new file mode 100644 index 0000000..24aeffd --- /dev/null +++ b/DuskServer/src/duskz/server/BlockedIPException.java @@ -0,0 +1,33 @@ +/* + * This file is part of DuskZ, a graphical mud engine. + * + * Copyright (C) 2013 Michael Zucchi + * + * 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; + +/** + * IP is blocked + * + * @author Michael Zucchi + */ +public class BlockedIPException extends Exception { + + public BlockedIPException(String message) { + super(message); + } + +} diff --git a/DuskServer/src/duskz/server/Commands.java b/DuskServer/src/duskz/server/Commands.java index 01a790c..c28b57f 100644 --- a/DuskServer/src/duskz/server/Commands.java +++ b/DuskServer/src/duskz/server/Commands.java @@ -21,9 +21,11 @@ /** * Changes * Feb-2013 Michael Zucchi - modernised java + * Mar-2013 Michael Zucchi - changed server protocol */ package duskz.server; +import duskz.protocol.DuskProtocol; import duskz.server.entity.Mob; import duskz.server.entity.Merchant; import duskz.server.entity.Sign; @@ -37,7 +39,7 @@ import java.io.*; import java.util.LinkedList; import java.util.StringTokenizer; -public class Commands { +public class Commands implements DuskProtocol { public static String parseCommand(LivingThing lt, DuskEngine game, String cmdline) throws Exception { if (cmdline == null) { @@ -265,36 +267,38 @@ public class Commands { // 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(); + /* + // 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": { @@ -508,78 +512,79 @@ public class Commands { game.log.printMessage(Log.INFO, "godcommand:" + lt.name + ":" + cmdline + ":" + lt.x + "," + lt.y); cmdline = cmdline.substring(5); String filename = null; - String strTitle = null; + String title = null; if (args.equals("items")) { filename = "defItems"; - strTitle = "Items:\n"; + title = "Items:\n"; } else if (args.equals("conf")) { filename = "conf"; - strTitle = "Conf files:\n"; + title = "Conf files:\n"; } else if (args.equals("mobs")) { filename = "defMobs"; - strTitle = "Mobiles:\n"; + title = "Mobiles:\n"; } else if (args.equals("commands")) { filename = "commands"; - strTitle = "Custom commands:\n"; + title = "Custom commands:\n"; } else if (args.equals("races")) { filename = "defRaces"; - strTitle = "Races:\n"; + title = "Races:\n"; } else if (args.equals("pets")) { filename = "defPets"; - strTitle = "Pets:\n"; + title = "Pets:\n"; } else if (args.equals("factions")) { filename = "factions"; - strTitle = "Factions:\n"; + title = "Factions:\n"; } else if (args.equals("conditions")) { filename = "defConditions"; - strTitle = "Conditions:\n"; + title = "Conditions:\n"; } else if (args.equals("help")) { filename = "helpFiles"; - strTitle = "Help Files:\n"; + title = "Help Files:\n"; } else if (args.equals("scripts")) { filename = "scripts"; - strTitle = "Scripts:\n"; + title = "Scripts:\n"; } else if (args.equals("spell groups")) { filename = "defSpellGroups"; - strTitle = "Spell Groups:\n"; + title = "Spell Groups:\n"; } else if (args.equals("spells")) { filename = "defSpells"; - strTitle = "Spells:\n"; + title = "Spells:\n"; } else if (args.equals("props")) { filename = "defProps"; - strTitle = "Props:\n"; + title = "Props:\n"; } else if (args.equals("move actions")) { filename = "defMoveActions"; - strTitle = "Move Action Scripts:\n"; + title = "Move Action Scripts:\n"; } else if (args.equals("can move")) { filename = "defCanMoveScripts"; - strTitle = "Can Move Scripts:\n"; + title = "Can Move Scripts:\n"; } else if (args.equals("can see")) { filename = "defCanSeeScripts"; - strTitle = "Can See Scripts:\n"; + title = "Can See Scripts:\n"; } else if (args.equals("tile actions")) { filename = "defTileActions"; - strTitle = "Tile Action Scripts:\n"; + title = "Tile Action Scripts:\n"; } else if (args.equals("tile move")) { filename = "defTileScripts"; - strTitle = "Can Move Tile Scripts:\n"; + title = "Can Move Tile Scripts:\n"; } else if (args.equals("tile see")) { filename = "defTileSeeScripts"; - strTitle = "Tile See Scripts:\n"; + title = "Tile See Scripts:\n"; } if (filename != null) { File filList = new File(filename); String strResult[] = filList.list(); - StringBuilder strBuff = new StringBuilder(); - strBuff.append("").append((char) 20).append(strTitle).append("\n"); + 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) { - strBuff.append(strResult[i]).append("\n"); + sb.append(strResult[i]).append("\n"); } } - strBuff.append("--EOF--\n"); - lt.send(strBuff.toString()); + //strBuff.append("--EOF--\n"); + //lt.send(strBuff.toString()); + lt.viewText(title, false, sb.toString()); return null; } return "You can't list that."; @@ -658,24 +663,25 @@ public class Commands { if (blnPet) { return "The player named \"" + filView.getName() + "\" does not have a pet."; } - lt.send((char) 18 + args + "\n--EOF--\n"); + //lt.send((char) 18 + args + "\n--EOF--\n"); + lt.viewText(args, true, null); return null; } RandomAccessFile rafView = null; - StringBuffer strBuff = new StringBuffer(); + StringBuilder sb = new StringBuilder(); try { rafView = new RandomAccessFile(filView, "rw"); if (blnUser) { rafView.readLine(); //Skip over users' password } String strStore2 = rafView.readLine(); - strBuff.append((char) 18 + args + "\n"); + //sb.append((char) 18 + args + "\n"); while (strStore2 != null) { - strBuff.append(strStore2 + "\n"); + sb.append(strStore2 + "\n"); strStore2 = rafView.readLine(); } - strBuff.append("--EOF--\n"); - lt.send(strBuff.toString()); + //sb.append("--EOF--\n"); + lt.viewText(args, true, sb.toString()); } catch (Exception e) { game.log.printError("parseCommand():Reading file for " + filView.getName(), e); } @@ -699,6 +705,12 @@ public class Commands { 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; @@ -798,11 +810,15 @@ public class Commands { */ rafView.writeBytes(cmdline + "\n"); } - cmdline = lt.instream.readLine(); - while (!cmdline.equals("--EOF--")) { - rafView.writeBytes(cmdline + "\n"); - cmdline = lt.instream.readLine(); - } + /** + * 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); @@ -1307,6 +1323,10 @@ public class Commands { 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."; @@ -1314,40 +1334,41 @@ public class Commands { lt.unloadRace(); // FIXME: I'm not sure why this needs to clear messages here. +/* + if (lt.isPet()) { - 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(); - } + 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); lt.updateStats(); @@ -2248,27 +2269,35 @@ public class Commands { 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(); + /* + 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(); @@ -2309,7 +2338,11 @@ public class Commands { 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")) { @@ -2321,11 +2354,11 @@ public class Commands { 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(); + //lt.proceed(); return null; } @@ -2357,6 +2390,8 @@ public class Commands { 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."); @@ -2384,6 +2419,7 @@ public class Commands { game.log.printError("parseCommand():While " + lt.name + " was changing their password", e); } lt.proceed(); + */ } case "wear": { @@ -2554,12 +2590,12 @@ public class Commands { return "You are no longer ignoring " + strIgnoreName; } case "appletimages": { - lt.updateAppletImages(); - return null; + //lt.updateAppletImages(); + return "obsolete command"; } case "applicationimages": { - lt.updateApplicationImages(); - return null; + //lt.updateApplicationImages(); + return "obsolete command"; } case "notdead": { return null; diff --git a/DuskServer/src/duskz/server/DuskEngine.java b/DuskServer/src/duskz/server/DuskEngine.java index cd438ef..61eec18 100644 --- a/DuskServer/src/duskz/server/DuskEngine.java +++ b/DuskServer/src/duskz/server/DuskEngine.java @@ -22,10 +22,12 @@ * Changes * Feb-2013 Michael Zucchi - general cleanup, modernisation, refactoring, * abstracting, fixing synchronisation issues. + * Mar-2013 Michael Zucchi - changed server protocol */ package duskz.server; -import duskz.protocol.MessageType; +import duskz.protocol.DuskMessage; +import duskz.protocol.DuskProtocol; import duskz.server.entity.TileMap; import duskz.server.entity.Mob; import duskz.server.entity.Merchant; @@ -35,11 +37,13 @@ import duskz.server.entity.Prop; import duskz.server.entity.DuskObject; import duskz.server.entity.PlayerMerchant; import duskz.server.entity.LivingThing; -import duskz.protocol.ServerMessage; import duskz.protocol.TransactionItem; +import duskz.protocol.TransactionMessage; +import duskz.protocol.Wearing; import java.io.*; import java.io.PrintStream; import java.io.FileOutputStream; +import java.net.Socket; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.Date; @@ -55,7 +59,7 @@ import java.util.Properties; * * @author Tom Weingarten */ -public class DuskEngine implements Runnable { +public class DuskEngine implements Runnable, DuskProtocol { //Logger public Log log; @@ -107,7 +111,7 @@ public class DuskEngine implements Runnable { scrOnDeath = null, scrOnLogOut = null; //End Prefs - public final static String version = "2.6.2.W42"; + public final static String version = "3.0 dev"; public final Date datStart = new Date(System.currentTimeMillis()); //public int MapRows, // MapColumns; @@ -141,9 +145,14 @@ public class DuskEngine implements Runnable { */ final private HashMap bannedIPMap = new HashMap<>(); final public HashSet checkConditionList = new HashSet<>(); + // FIXME: This shouldn't be public + // FIXME: Requires synchronised access! public final HashMap playersByName = new HashMap<>(); VariableSet varVariables; - public VariableSet varIP; + /** + * Tracks login failures for a given address + */ + final private HashMap failureAddress = new HashMap<>(); private long nextid = 0; boolean blnVariableListChanged = false, blnMapHasChanged = false, @@ -155,7 +164,6 @@ public class DuskEngine implements Runnable { public DuskEngine() { RandomAccessFile rafFile = null; varVariables = new VariableSet(); - varIP = new VariableSet(); log = new Log(System.out); try { @@ -524,7 +532,7 @@ public class DuskEngine implements Runnable { oldviewrange = viewrange; mapsize = 1 + (2 * viewrange); for (LivingThing thnStore : playersByName.values()) { - thnStore.resizeMap(); + thnStore.initMap(); } } //Load Triggered Scripts @@ -734,8 +742,8 @@ public class DuskEngine implements Runnable { if (o.isPlayerMerchant()) { PlayerMerchant shop = (PlayerMerchant) o; if (refresh.x == shop.x && refresh.y == shop.y) { - //strResult = (char) 17 + ""; - StringBuilder sb = new StringBuilder(); + TransactionMessage tm = new TransactionMessage(MSG_UPDATE_MERCHANT); + for (String name : shop.vctItems.keySet()) { LinkedList vctStore = shop.vctItems.get(name); Item item = (Item) vctStore.element(); @@ -744,83 +752,54 @@ public class DuskEngine implements Runnable { if (refresh.name.equalsIgnoreCase(shop.strOwner)) { intCost = 0; } - sb.append(intCost + "gp)" + name + "\n"); + tm.add(item.getWearLocation(), name, vctStore.size(), intCost, "gp"); } - sb.append(".\n"); - refresh.send(MessageType.UpdateMerchant, sb.toString()); - refresh.updateSell(); + refresh.send(tm); + refresh.updateInventory(); } } if (o.isMerchant()) { Merchant merchant = (Merchant) o; if (refresh.x == merchant.x && refresh.y == merchant.y) { - if (true) { - StringBuilder sb = new StringBuilder(); - //strResult = (char) 17 + ""; - if (refresh.getFollowing() != null && refresh.getFollowing().isPet()) { - for (int i5 = 0; i5 < merchant.items.size(); i5++) { - String itemname = (String) merchant.items.get(i5); - Item item = getItem(itemname); - if (item != null) { - sb.append(item.intCost).append("gp)").append(itemname).append("\n"); - } else if (itemname.equals("pet")) { - sb.append(petcost).append("gp)").append(itemname).append("\n"); - } else { - sb.append(traincost).append("exp)").append(itemname).append("\n"); - sb.append(traincost).append("exp)").append(refresh.getFollowing().name).append(":").append(itemname).append("\n"); + HashMap items = new HashMap<>(); + for (String itemname : merchant.items) { + TransactionItem titem = items.get(itemname); + if (titem == null) { + Item item = getItem(itemname); + if (item != null) { + titem = new TransactionItem(item.getWearLocation(), itemname, 1, item.intCost, "gp"); + } else if (itemname.equals("pet")) { + // FIXME: pet? + titem = new TransactionItem(Wearing.INVENTORY, itemname, 1, petcost, "gp"); + } else { + if (refresh.getFollowing() != null && refresh.getFollowing().isPet()) { + // FIXME: pet skill? + titem = new TransactionItem(Wearing.INVENTORY, refresh.getFollowing().name + ":" + itemname, 1, traincost, "exp"); + items.put(titem.name, titem); } + // FIXME: Wearing.skill? + titem = new TransactionItem(Wearing.INVENTORY, itemname, 1, traincost, "exp"); + //sb.append(traincost).append("exp)").append(itemname).append("\n"); + //sb.append(traincost).append("exp)").append(refresh.getFollowing().name).append(":").append(itemname).append("\n"); } + items.put(titem.name, titem); } else { - for (int i5 = 0; i5 < merchant.items.size(); i5++) { - String itemname = (String) merchant.items.get(i5); - Item item = getItem(itemname); - if (item != null) { - sb.append(item.intCost).append("gp)").append(itemname).append("\n"); - } else if (itemname.equals("pet")) { - sb.append(petcost).append("gp)").append(itemname).append("\n"); - } else { - sb.append(traincost).append("exp)").append(itemname).append("\n"); - } - } - } - sb.append(".\n"); - refresh.send(MessageType.UpdateMerchant, sb.toString()); - refresh.updateSell(); - } else { - HashMap items = new HashMap<>(); - for (String itemname : merchant.items) { - TransactionItem titem = items.get(itemname); - if (titem == null) { - Item item = getItem(itemname); - if (item != null) { - titem = new TransactionItem(itemname, 1, item.intCost, "gp"); - } else if (itemname.equals("pet")) { - titem = new TransactionItem(itemname, 1, petcost, "gp"); - } else { - if (refresh.getFollowing() != null && refresh.getFollowing().isPet()) { - titem = new TransactionItem(refresh.getFollowing().name + ":" + itemname, 1, traincost, "exp"); - items.put(titem.name, titem); - } - titem = new TransactionItem(itemname, 1, traincost, "exp"); - //sb.append(traincost).append("exp)").append(itemname).append("\n"); - //sb.append(traincost).append("exp)").append(refresh.getFollowing().name).append(":").append(itemname).append("\n"); - } - items.put(titem.name, titem); - } else { - titem.count++; - } + titem.count++; } - refresh.send(new ServerMessage.ItemsMessage(MessageType.UpdateMerchant, new ArrayList<>(items.values()))); - refresh.updateSell(); } + + TransactionMessage tm = new TransactionMessage(MSG_UPDATE_MERCHANT); + tm.items.addAll(items.values()); + refresh.send(tm); + refresh.updateInventory(); } - //if (old == null) { //i4 != -1 - // if (!objStore.isLivingThing() - // || canSeeLivingThing(thnRefresh, (LivingThing) objStore)) { - // thnRefresh.send(MessageType.AddEntity, objStore.toEntity()); - // } - //} } + //if (old == null) { //i4 != -1 + // if (!objStore.isLivingThing() + // || canSeeLivingThing(thnRefresh, (LivingThing) objStore)) { + // thnRefresh.send(MessageType.AddEntity, objStore.toEntity()); + // } + //} } } } @@ -1430,7 +1409,7 @@ public class DuskEngine implements Runnable { synchronized void resizeMap(int x, int y) { map.resize(x, y); for (LivingThing lt : playersByName.values()) { - lt.resizeMap(); + lt.initMap(); } blnMapHasChanged = true; } @@ -1728,7 +1707,7 @@ public class DuskEngine implements Runnable { * @param dir */ // FIXME: now i think this probably needs to be moved back to livingthing ... - public void moveDuskObject(LivingThing thing, int inlocx, int inlocy, MessageType dir) { + public void moveDuskObject(LivingThing thing, int inlocx, int inlocy, byte dir) { for (TileMap.MapData md : map.range(thing.x, thing.y, viewrange)) { for (DuskObject o : md.entities) { if (o.isLivingThing()) { @@ -1741,7 +1720,7 @@ public class DuskEngine implements Runnable { if (canSee) { // Add/update it if now visible lt.addEntity(thing); - lt.send(dir, thing.ID + "\n"); + lt.send(DuskMessage.create(thing.ID, MSG_MOVE, dir)); } else { // Remove it if it isn't lt.removeEntity(thing.ID); @@ -2042,4 +2021,68 @@ public class DuskEngine implements Runnable { bannedIPMap.clear();; } } + + /** + * Log/track password failures + * + * @param name name of user who failed + * @param address address of failure + * @return true if the host is now banned + */ + public boolean passwordFailure(String name, String address) { + log.printMessage(Log.INFO, address + ":" + name + " entered the wrong password"); + Integer failCount = failureAddress.get(address); + if (failCount != null) { + int fc = failCount.intValue(); + if (fc >= 4) { + banAddress(address); + return true; + } else { + failureAddress.put(address, fc + 1); + } + } else { + failureAddress.put(address, 1); + } + + return false; + } + + public void passwordSuccess(String name, String address) { + failureAddress.remove(address); + } + + /** + * This registers a new player atomically. + * + * Any existing player with the same name is booted + */ + public void registerPlayer(LivingThing lt, String name) throws BlockedIPException { + synchronized (playersByName) { + // Check ip filter, FIXME: test + if (blnIPF) { + String address = lt.getAddress(); + + for (LivingThing thing : playersByName.values()) { + if (thing.getAddress().equalsIgnoreCase(address)) { + throw new BlockedIPException("Already a player connected from your address"); + } + } + } + + LivingThing thing; + name = name.toLowerCase(); + thing = playersByName.get(name); + + if (thing != null) { + log.printMessage(Log.INFO, lt.getAddress() + ":" + name + " tried to log in twice"); + // FIXME: nowhere to send this + //chatMessage("That user is already logged in. They are being logged out."); + lt.chatMessage("There has been another logon under this name, you are being logged out."); + lt.close(); + + } + + playersByName.put(name, lt); + } + } } diff --git a/DuskServer/src/duskz/server/Script.java b/DuskServer/src/duskz/server/Script.java index 7e9dc6e..fe0e5fb 100644 --- a/DuskServer/src/duskz/server/Script.java +++ b/DuskServer/src/duskz/server/Script.java @@ -1710,10 +1710,11 @@ public class Script { } if (true) // FIXME: protocol implementation - throw new RuntimeException("cannot ask questions yet"); + throw new RuntimeException("unimplementeD: cannot ask questions yet"); // FIXME: this looks dodgy // FIXME: move to livingthing - thnStore.halt(); + // FIXME: needs command query thing + // thnStore.halt(); thnStore.stillThere(); // This puts something in the buffer thnStore.stillThere(); // Need to do this twice to ensure thnStore out of read loop thnStore.connectionThread.sleep(750); // wait for it... @@ -1725,9 +1726,9 @@ public class Script { } catch (Exception e) { engGame.log.printError("parseScript():While trying to empty read buffer to get player input", e); } - strInput = thnStore.instream.readLine(); + // strInput = thnStore.instream.readLine(); varVariables.addVariable(getString(), strInput); - thnStore.proceed(); + // thnStore.proceed(); return true; } case 49: { @@ -1759,20 +1760,23 @@ public class Script { return true; } case 52: { - LivingThing thnStore = getLivingThing(getString()); - String strTitle = getString(); - String strStore2 = getString(); - String strLine = null; - int intIndex = strStore2.indexOf("\n"); - thnStore.send((char) 20 + strTitle + "\n"); - while (intIndex != -1) { - strLine = strStore2.substring(0, intIndex); - thnStore.send(strLine + "\n"); - strStore2 = strStore2.substring(intIndex + 1); - intIndex = strStore2.indexOf("\n"); - } - thnStore.send(strStore2 + "\n"); - thnStore.send("--EOF--\n"); + // FIXME: This is the VIEW_TEXT command + /* + LivingThing thnStore = getLivingThing(getString()); + String strTitle = getString(); + String strStore2 = getString(); + String strLine = null; + int intIndex = strStore2.indexOf("\n"); + thnStore.send((char) 20 + strTitle + "\n"); + while (intIndex != -1) { + strLine = strStore2.substring(0, intIndex); + thnStore.send(strLine + "\n"); + strStore2 = strStore2.substring(intIndex + 1); + intIndex = strStore2.indexOf("\n"); + } + thnStore.send(strStore2 + "\n"); + thnStore.send("--EOF--\n"); + * */ return true; } case 53: { @@ -1842,23 +1846,25 @@ public class Script { return true; } case 58: { - LivingThing thnStore = getLivingThing(getString()); - String strStore2 = getString(); - int intIndex = strStore2.indexOf("\n"); - while (intIndex != -1) { - if (thnStore.battle != null && thnStore.popup) { - thnStore.send("" + (char) 33 + strStore2.substring(0, intIndex + 1)); - } else { - thnStore.chatMessage(strStore2.substring(0, intIndex)); - } - strStore2 = strStore2.substring(intIndex + 1); - intIndex = strStore2.indexOf("\n"); - } - if (thnStore.battle != null && thnStore.popup) { - thnStore.send("" + (char) 33 + strStore2 + "\n"); - } else { - thnStore.chatMessage(strStore2); - } + // FIXME: battle chat + /* + LivingThing thnStore = getLivingThing(getString()); + String strStore2 = getString(); + int intIndex = strStore2.indexOf("\n"); + while (intIndex != -1) { + if (thnStore.battle != null && thnStore.popup) { + thnStore.send("" + (char) 33 + strStore2.substring(0, intIndex + 1)); + } else { + thnStore.chatMessage(strStore2.substring(0, intIndex)); + } + strStore2 = strStore2.substring(intIndex + 1); + intIndex = strStore2.indexOf("\n"); + } + if (thnStore.battle != null && thnStore.popup) { + thnStore.send("" + (char) 33 + strStore2 + "\n"); + } else { + thnStore.chatMessage(strStore2); + }*/ return true; } case 32: { diff --git a/DuskServer/src/duskz/server/SpellGroup.java b/DuskServer/src/duskz/server/SpellGroup.java index e853cc3..1aa021c 100644 --- a/DuskServer/src/duskz/server/SpellGroup.java +++ b/DuskServer/src/duskz/server/SpellGroup.java @@ -101,4 +101,30 @@ public class SpellGroup { } return sb.toString(); } + + public List spellList(List list, int percent) { + String name; + + if (list == null) + list = new ArrayList<>(); + + if (vctSpells.isEmpty()) { + return list; + } + + name = (String) vctSpells.get(0); + if (name == null) { + return list; + } + list.add(((110 - percent) / 2) + " mp) " + name); + for (int i = 1; i < vctSpells.size(); i++) { + if (percent < (100 * i) / (vctSpells.size() - 1)) { + break; + } + name = (String) vctSpells.get(i); + + list.add(((110 - (percent - (100 * i) / (vctSpells.size() - 1))) / 2) + " mp) " + name); + } + return list; + } } \ No newline at end of file diff --git a/DuskServer/src/duskz/server/entity/DuskObject.java b/DuskServer/src/duskz/server/entity/DuskObject.java index fb48dc3..92e413f 100644 --- a/DuskServer/src/duskz/server/entity/DuskObject.java +++ b/DuskServer/src/duskz/server/entity/DuskObject.java @@ -22,12 +22,11 @@ * Changes * Feb-2013 Michael Zucchi - modernised java, added list functions and * serialisation.. + * Mar-2013 Michael Zucchi - changed server protocol */ package duskz.server.entity; -import duskz.protocol.MessageType; -import duskz.protocol.ServerMessage; -import duskz.protocol.ServerMessage.EntityMessage; +import duskz.protocol.EntityUpdateMessage; /* All code copyright Tom Weingarten (captaint@home.com) 2000 @@ -122,9 +121,19 @@ public abstract class DuskObject { return sb.toString(); } - public EntityMessage toMessage(MessageType type) { - return new ServerMessage.EntityMessage(type, ID, name, - (byte) getEntityType(), (short) x, (short) y, (short) getImage(), (short) -1); + public EntityUpdateMessage toMessage(int msg) { + EntityUpdateMessage en = new EntityUpdateMessage(); + + en.name = msg; + en.id = ID; + en.entityName = name; + en.entityType = (byte)getEntityType(); + en.x = (short)x; + en.y = (short)y; + en.image = (short) getImage(); + en.imageStep = -1; + + return en; } // Linked list stuff - should it just use a container? diff --git a/DuskServer/src/duskz/server/entity/Equipment.java b/DuskServer/src/duskz/server/entity/Equipment.java index 5eb7d78..f26fb1c 100644 --- a/DuskServer/src/duskz/server/entity/Equipment.java +++ b/DuskServer/src/duskz/server/entity/Equipment.java @@ -21,9 +21,12 @@ /** * Changes * Feb-2013 Michael Zucchi - modernised java, big refactor. + * Mar-2013 Michael Zucchi - changed server protocol */ package duskz.server.entity; +import duskz.protocol.DuskMessage; +import duskz.protocol.ListMessage; import duskz.protocol.Wearing; /** @@ -60,6 +63,18 @@ public class Equipment implements Wearing { return sb.toString(); } + public DuskMessage toMessage(int msgid) { + ListMessage msg = new ListMessage(msgid); + + for (int i = 0; i < worn.length; i++) { + Item item = worn[i]; + if (item != null) + msg.add(i, item.name); + } + + return msg; + } + public Item getWorn(int where) { return worn[where]; } diff --git a/DuskServer/src/duskz/server/entity/Item.java b/DuskServer/src/duskz/server/entity/Item.java index b48901e..66fa8a7 100644 --- a/DuskServer/src/duskz/server/entity/Item.java +++ b/DuskServer/src/duskz/server/entity/Item.java @@ -25,6 +25,7 @@ */ package duskz.server.entity; +import duskz.protocol.Wearing; import duskz.server.DuskEngine; import duskz.server.Script; import java.io.File; @@ -147,6 +148,24 @@ public class Item extends DuskObject { return 1; } + /** + * type of kind as from Wearing + * + * @return + */ + public int getWearLocation() { + int kind; + + if (isArmor()) { + kind = 1 + intKind; + } else if (isWeapon()) { + kind = Wearing.WIELD; + } else { + kind = Wearing.INVENTORY; + } + return kind; + } + /* ** This method formats the Item for saving. ** It generates a String that can later be passed diff --git a/DuskServer/src/duskz/server/entity/LivingThing.java b/DuskServer/src/duskz/server/entity/LivingThing.java index 4c5e9f6..90d3fb5 100644 --- a/DuskServer/src/duskz/server/entity/LivingThing.java +++ b/DuskServer/src/duskz/server/entity/LivingThing.java @@ -1,4 +1,4 @@ -/* + /* * This file is part of DuskZ, a graphical mud engine. * * Copyright (C) 2000 Tom Weingarten @@ -22,32 +22,48 @@ * Changes * Feb-2013 Michael Zucchi - modernised java, lots of clean up and encapsulation * and synchronisation fixes. Moved loader here. + * Mar-2013 Michael Zucchi - changed server protocol */ package duskz.server.entity; +import duskz.protocol.DuskMessage; +import duskz.protocol.DuskMessage.EntityMessage; +import duskz.protocol.DuskMessage.StringMessage; +import duskz.protocol.DuskProtocol; +import static duskz.protocol.DuskProtocol.AUTH_LOGIN_FAILED; +import static duskz.protocol.DuskProtocol.FIELD_AUTH_REASON; +import static duskz.protocol.DuskProtocol.FIELD_AUTH_RESULT; +import duskz.protocol.EntityListMessage; +import duskz.protocol.EntityUpdateMessage; +import duskz.protocol.ListMessage; +import duskz.protocol.MapMessage; +import duskz.protocol.TransactionMessage; import duskz.server.Battle; +import duskz.server.BlockedIPException; import duskz.server.Commands; import duskz.server.Condition; import duskz.server.DuskEngine; import duskz.server.ItemList; import duskz.server.Log; -import duskz.protocol.MessageType; import duskz.server.Script; import duskz.server.SpellGroup; -import duskz.server.Variable; import duskz.server.entity.TileMap.MapData; import duskz.server.entity.TileMap.MoveData; -import duskz.protocol.ServerMessage; import java.io.*; import java.net.Socket; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.concurrent.LinkedBlockingQueue; +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; @@ -57,10 +73,10 @@ import java.util.logging.Logger; * * @author Tom Weingarten */ -public class LivingThing extends DuskObject implements Runnable { +public class LivingThing extends DuskObject implements Runnable, DuskProtocol { DuskEngine game; - final public LinkedBlockingQueue messageQueue = new LinkedBlockingQueue<>(); + final public LinkedBlockingDeque messageQueue = new LinkedBlockingDeque<>(); //StillWorking? public boolean isWorking = true; //Has the player finished loading yet? @@ -167,9 +183,8 @@ public class LivingThing extends DuskObject implements Runnable { //Socket connection private Socket socket; // FIXME: not public - public DataInputStream instream; - // FIXME: not public - public DataOutputStream outstream; + private DataInputStream instream; + private DataOutputStream outstream; public Thread connectionThread; private SendThread sendThread; //Prefs @@ -231,11 +246,16 @@ public class LivingThing extends DuskObject implements Runnable { } /** - * Get IP address of user + * Get IP address of player * * @return */ 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(); @@ -275,7 +295,9 @@ public class LivingThing extends DuskObject implements Runnable { } @Override - public ServerMessage.EntityMessage toMessage(MessageType type) { + public EntityUpdateMessage toMessage(int msg) { + EntityUpdateMessage en = super.toMessage(msg); + StringBuilder sb = new StringBuilder(); if (isSleeping) { sb.append(""); @@ -294,8 +316,12 @@ public class LivingThing extends DuskObject implements Runnable { sb.append('>'); } sb.append(name); - return new ServerMessage.EntityMessage(type, ID, sb.toString(), - (byte) getEntityType(), (short) x, (short) y, (short) getImage(), (short) (isPlayer() ? imagestep : -1)); + + en.entityName = sb.toString(); + if (isPlayer()) + en.imageStep = (short) imagestep; + + return en; } /** @@ -329,7 +355,7 @@ public class LivingThing extends DuskObject implements Runnable { if (game.maxconnections != 0 && game.playersByName.size() >= game.maxconnections) { chatMessage("Sorry, the server has reached it's connection limit. Try again later."); - send("Goodbye.\n" + (char) 0); + quit(); Thread.sleep(1000); closeNosavePlayer(); return; @@ -337,32 +363,18 @@ public class LivingThing extends DuskObject implements Runnable { // Game is shutting down if (game.blnShuttingDown) { chatMessage("Sorry, the server is not accepting new connections. It is either being shutdown or worked on. Try again later."); - send("Goodbye.\n" + (char) 0); + quit(); Thread.sleep(1000); closeNosavePlayer(); return; } if (!game.isGoodIP(socket.getInetAddress().toString())) { chatMessage("Connections from your machine are no longer being accepted."); - send("Goodbye.\n" + (char) 0); + quit(); Thread.sleep(1000); closeNosavePlayer(); return; } - // IP Filter FIXME: put on engine - if (game.blnIPF) { - for (LivingThing lt : game.playersByName.values()) { - String IP = lt.socket.getInetAddress().toString(); - if (IP.equalsIgnoreCase(socket.getInetAddress().toString())) { - chatMessage("There's already a player connected from your IP address."); - send("Goodbye.\n" + (char) 0); - Thread.sleep(1000); - closeNosavePlayer(); - return; - } - } - } - // End IPF } catch (Exception e) { game.log.printError("LivingThing():Creating player", e); } @@ -404,13 +416,13 @@ public class LivingThing extends DuskObject implements Runnable { return; } } - loadUserFile(new File("pets/default")); + loadUserFile(new File("pets/default"), false); strStore = this.master.name.toLowerCase(); File filPlayer = new File("pets/" + strStore); File filBackup = new File("pets/" + strStore + ".backup"); /* - ** Check for old style pet name - ** (Only need to check if we already have a valid pet name) + ** Check for old style pet entityName + ** (Only need to check if we already have a valid pet entityName) */ if (!strName.equalsIgnoreCase("default")) { strStore = strName.toLowerCase(); @@ -449,7 +461,7 @@ public class LivingThing extends DuskObject implements Runnable { file = new File("pets", strStore); backup = new File("pets", strStore + ".backup"); - loadUserFile(file); + loadUserFile(file, false); if (race != null) { loadRaceFile(new File("defPets/" + race.toLowerCase()), true); } @@ -520,7 +532,7 @@ public class LivingThing extends DuskObject implements Runnable { /** * Train a skill or spell. * - * @param name + * @param entityName * @param add increment * @return true if the skill/spell exists and was created/incremented */ @@ -591,14 +603,20 @@ public class LivingThing extends DuskObject implements Runnable { return total; } - public void loadUserFile(File path) throws FileNotFoundException, IOException { - String line; + public void loadUserFile(File path, boolean haspass) throws FileNotFoundException, IOException { try (RandomAccessFile file = new RandomAccessFile(path, "r")) { + String line; + int lineNo = 1; + if (haspass) { + line = file.readLine(); + lineNo++; + } while (!((line = file.readLine()) == null || line.equals("."))) { try { parseUserFile(file, line); + lineNo += 1; } catch (NumberFormatException x) { - throw new IOException("Problem parsing user " + path + " on field " + line, x); + throw new IOException("Problem parsing user " + path + " line: " + lineNo + " `" + line + "'", x); } } } @@ -791,7 +809,7 @@ public class LivingThing extends DuskObject implements Runnable { coloron = false; break; } - // engGame.log.printError("parseUserFile():Parsing \"" + strStore + "\" from " + name + "'s file", e); + // engGame.log.printError("parseUserFile():Parsing \"" + strStore + "\" from " + entityName + "'s file", e); } public void loadRaceFile(File path, boolean add) throws FileNotFoundException, IOException { @@ -1013,7 +1031,7 @@ public class LivingThing extends DuskObject implements Runnable { } if (!blnSaveSuccessful || (lngFileLength < 100)) { - engGame.log.printMessage(Log.ERROR, "savePlayer():Saving primary file for " + name + ", second try failed, aborting savePlayer"); + engGame.log.printMessage(Log.ERROR, "savePlayer():Saving primary file for " + entityName + ", second try failed, aborting savePlayer"); blnCanSave = true; return; } @@ -1047,7 +1065,7 @@ public class LivingThing extends DuskObject implements Runnable { } if (!blnSaveSuccessful || (lngFileLength < 100)) { - engGame.log.printMessage(Log.ERROR, "savePlayer():Saving backup file for " + name + ", second try failed, aborting savePlayer"); + engGame.log.printMessage(Log.ERROR, "savePlayer():Saving backup file for " + entityName + ", second try failed, aborting savePlayer"); blnCanSave = true; return; } @@ -1056,6 +1074,7 @@ public class LivingThing extends DuskObject implements Runnable { isSaveable = true; } + // TODO: the close commands need merging somehow public void close() { if (name == null || name.equalsIgnoreCase("default")) { return; @@ -1234,6 +1253,7 @@ public class LivingThing extends DuskObject implements Runnable { isWorking = false; isSaveable = false; isSaveNeeded = false; + if (!conditions.isEmpty() && game.checkConditionList.contains(this)) { game.checkConditionList.remove(this); } @@ -1287,12 +1307,17 @@ public class LivingThing extends DuskObject implements Runnable { } } + // TODO: shouldn' this work on 'this.ID'? public void updateFlag(long ID, int Value) { - send((char) 29 + "" + ID + "\n" + Value + "\n"); + ListMessage lm = new EntityListMessage(ID, MSG_UPDATE_ENTITY); + + lm.add(FIELD_ENTITY_FLAGS, Value); + + send(lm); } public void clearFlags() { - send((char) 30 + ""); + send(DuskMessage.create(MSG_CLEAR_FLAGS)); } public boolean hasPendingMoves() { @@ -1396,7 +1421,7 @@ public class LivingThing extends DuskObject implements Runnable { } // FIXME: should probably be in DuskEngine - protected synchronized void moveTo(int newLocX, int newLocY, MessageType dir, int intNewStep) { + protected synchronized void moveTo(int newLocX, int newLocY, byte dir, int intNewStep) { if (privs < 5 && (newLocX >= (game.map.getCols() - 1) || newLocY >= (game.map.getRows() - 1) || newLocX < 0 @@ -1487,19 +1512,19 @@ public class LivingThing extends DuskObject implements Runnable { } public void moveN() { - moveTo(x, y - 1, MessageType.MoveNorth, 0); + moveTo(x, y - 1, (byte) 'n', 0); } public void moveS() { - moveTo(x, y + 1, MessageType.MoveSouth, 2); + moveTo(x, y + 1, (byte) 's', 2); } public void moveW() { - moveTo(x - 1, y, MessageType.MoveWest, 4); + moveTo(x - 1, y, (byte) 'w', 4); } public void moveE() { - moveTo(x + 1, y, MessageType.MoveEast, 6); + moveTo(x + 1, y, (byte) 'e', 6); } public DuskObject removeEntity(long id) { @@ -1508,7 +1533,7 @@ public class LivingThing extends DuskObject implements Runnable { if (o != null) { System.out.println("Removing client entity " + id + " from " + name); - send(MessageType.RemoveEntity, id + "\n"); + send(EntityMessage.create(id, MSG_REMOVE_ENTITY)); } return o; } @@ -1535,7 +1560,7 @@ public class LivingThing extends DuskObject implements Runnable { || game.canSeeLivingThing(this, (LivingThing) ent)) { System.out.println("Adding entity " + ent.name + " to " + name); //send(MessageType.AddEntity, ent.toEntity()); - send(ent.toMessage(MessageType.AddEntity)); + send(ent.toMessage(MSG_ADD_ENTITY)); } else { System.out.println("Not adding entity " + ent.name + " to " + name); } @@ -1651,7 +1676,9 @@ public class LivingThing extends DuskObject implements Runnable { int r = game.viewrange; - short[] tiles = new short[(r * 2 + 1) * (r * 2 + 1)]; + int width = r * 2 + 1; + int height = r * 2 + 1; + short[] tiles = new short[width * height]; int i = 0; //System.out.printf("map at %d,%d\n", x, y); for (int my = y - r; my <= y + r; my++) { @@ -1662,41 +1689,43 @@ public class LivingThing extends DuskObject implements Runnable { //System.out.println("\n"); } - send(ServerMessage.mapMessage(x, y, tiles)); + send(new MapMessage(MSG_UPDATE_MAP, width, height, x, y, tiles)); } - public void chatMessage(String inMessage) { - if (inMessage == null) { + public void startBattle(LivingThing opponent) { + ListMessage lm = new ListMessage(MSG_BATTLE_UPDATE); + lm.add(FIELD_BATTLE_OPPONENT, opponent.name); + send(lm); + } + + public void chatBattle(String msg) { + if (msg == null) { return; } if (isPlayer()) { - send(MessageType.Chat, inMessage + "\n"); + send(DuskMessage.create(MSG_BATTLE_CHAT, msg)); } if (charmer != null) { - charmer.chatMessage("From " + name + ": " + inMessage); - return; + charmer.chatBattle("From " + name + ": " + msg); } } - public void chatMessage(int red, int green, int blue, String inMessage) { + public void chatMessage(String inMessage) { if (inMessage == null) { return; } if (isPlayer()) { - if (!coloron) { - chatMessage(inMessage); - return; - } - String strResult = "" + (char) 23; - strResult += red + "\n" + green + "\n" + blue + "\n" + inMessage + "\n"; - send(strResult); + send(DuskMessage.create(MSG_CHAT, inMessage)); } if (charmer != null) { - charmer.chatMessage(red, green, blue, "From " + name + ": " + inMessage); - return; + charmer.chatMessage("From " + name + ": " + inMessage); } } + public void chatMessage(int red, int green, int blue, String inMessage) { + chatMessage(inMessage); + } + public int getCharacterPoints() { int result = wisd + inte @@ -2173,27 +2202,142 @@ public class LivingThing extends DuskObject implements Runnable { following = null; } } + final static int ASK_NEW_RACE = 0; + + private DuskMessage getRaceQuery() { + ListMessage racem = new ListMessage(ASK_NEW_RACE); + // FIXME: confirm behaviour here + String dir = isPet() ? "defPets" : "defRaces"; + + racem.add(FIELD_QUERY_PROMPT, "Choose race"); + racem.add(FIELD_QUERY_OPTIONS, Arrays.asList(new File(dir).list())); + + return racem; + } + + private DuskMessage getNewPlayerInfo() { + ListMessage np = new ListMessage(FIELD_AUTH_NEWPLAYER); + np.add(getRaceQuery()); + + return np; + } + + static class TooManyFailures extends Exception { + } public void run() { connectionThread = Thread.currentThread(); + + String address = getAddress(); + + // Handle auth state. try { + boolean haveuser = false; + boolean create = false; do { - name = instream.readLine(); - if (name != null) { - name = name.trim(); + 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 sent new player info + res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_INCOMPLETE); + res.add(FIELD_AUTH_REASON, "Login failed."); + res.add(getNewPlayerInfo()); + } 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 if (newp == null && playerExists(player)) { + // Try normal login + if (passwordCorrect(player, pass)) { + haveuser = true; + } else { + res.add(-1L, FIELD_AUTH_RESULT, AUTH_LOGIN_FAILED); + res.add(FIELD_AUTH_REASON, "Login failed."); + } + } else { + System.out.println("? new player " + player); + // Trying to create a player + if (canCreate(player, newp, res)) { + System.out.println(" creating player\n"); + haveuser = true; + create = true; + } 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."); + } + } + } + + if (haveuser) { + this.name = player; + // security: don't store this? + this.password = pass; + + game.passwordSuccess(player, address); + game.registerPlayer(this, player); + res.add(ID, FIELD_AUTH_RESULT, AUTH_LOGIN_OK); + res.add(FIELD_AUTH_REASON, "Login ok."); + } + 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; } - } while (!getPlayer()); - if (!isWorking) { - return; - } - } catch (Exception e) { - game.log.printError("LivingThing.run():start", e); + + send(res); + } while (!haveuser); + + greetUser(); + loadPlayer(create); + } catch (BlockedIPException ex) { + chatMessage("There's already a player connected from your IP address."); + //Thread.sleep(1000); + quit(); + closeNosavePlayer(); + return; + } catch (IOException ex) { + game.log.printMessage(Log.INFO, getAddress() + ": login/create " + name + " " + ex); + closeNosavePlayer(); + return; + } catch (ClassCastException ex) { + game.log.printMessage(Log.INFO, getAddress() + ": code error " + name + " " + ex); + // some server error + quit(); closeNosavePlayer(); return; + } catch (TooManyFailures ex) { + chatMessage("Too many login failures, try again in an hour."); + quit(); + closeNosavePlayer(); + return; + } catch (Exception ex) { + game.log.printMessage(Log.INFO, getAddress() + ": unexpected error " + name + " " + ex); } + + // Player loaded, ready to go, send off all the shit + connectionThread.setName("LivingThing(" + name + ")"); sendThread.setName("LivingThing(" + name + ").send"); - resizeMap(); + initMap(); changeLocBypass(x, y); updateInfo(); updateStats(); @@ -2211,24 +2355,25 @@ public class LivingThing extends DuskObject implements Runnable { chatMessage("You have entered the world hidden from players."); game.log.printMessage(Log.INFO, socket.getInetAddress().toString() + ":" + name + " has entered the world hidden from players"); } - String strInput, - strStore; isSaveable = true; isReady = true; - while (true) { - if (isStopped) { - return; - } + while (!isStopped) { try { - if (!isHalted) { - strInput = instream.readLine(); - if (strInput != null) { - strInput = strInput.trim(); - } - strStore = Commands.parseCommand(this, game, strInput); - if (strStore != null) { - chatMessage(strStore); + DuskMessage dm = readMessage(); + + switch (dm.name) { + case MSG_COMMAND: { + StringMessage sm = (StringMessage) dm; + String res = Commands.parseCommand(this, game, sm.value); + + if (res != null) + chatMessage(res); + break; } + default: + // anything else is bogus + System.out.println("Unexpected server command (ignored):"); + dm.format(System.out); } } catch (Exception e) { game.log.printError("LivingThing.run():" + name + " disconnected", e); @@ -2238,272 +2383,146 @@ public class LivingThing extends DuskObject implements Runnable { } } - boolean getPlayer() { - if (!game.isGoodName(name)) { - chatMessage("Not a valid name. This may because you left it blank, used invalid symbols, or made it too long. Please try again."); - return false; - } - String strStore; - int i; - LivingThing thnStore; - try { - File filStore = new File("users/" + name.toLowerCase()); - if (!filStore.exists() || filStore.length() < 100) { - File filBackup = new File("users/" + name.toLowerCase() + ".backup"); - if (filBackup.exists()) { - filBackup.renameTo(filStore); - } - } - - String strIP = socket.getInetAddress().toString(); - int ip = strIP.indexOf("/"); - strIP = strIP.substring(ip + 1, strIP.length()); - - if (filStore.exists()) { - try (RandomAccessFile rafFile = new RandomAccessFile("users/" + name.toLowerCase(), "r")) { - chatMessage("enter your password:"); - password = instream.readLine(); - if (!password.equals(rafFile.readLine())) { - rafFile.close(); - game.log.printMessage(Log.INFO, socket.getInetAddress().toString() + ":" + name + " entered the wrong password"); - Variable failCount = game.varIP.getVariable(strIP); - if (failCount != null) { - int fc = failCount.intValue(); - if (fc >= 4) { - game.banAddress(strIP); - chatMessage("Too many login failures, try again in an hour."); - send("Goodbye.\n" + (char) 0); - Thread.sleep(1000); - closeNosavePlayer(); - rafFile.close(); - return true; - } else { - game.varIP.addVariable(strIP, fc + 1); - } - } else { - game.varIP.addVariable(strIP, (double) 1); - } - Thread.sleep(3000); - chatMessage("Incorrect Password."); - chatMessage("Enter your login name: "); - return false; - } - } catch (Exception e) { - game.log.printError("getPlayer():" + socket.getInetAddress().toString() + ":" + name, e); - closeNosavePlayer(); - return true; - } + boolean playerExists(String name) { + File playerFile = new File("users/" + name.toLowerCase()); + + return playerFile.exists(); + } + + boolean passwordCorrect(String name, String pass) throws TooManyFailures, IOException, InterruptedException { + + try (RandomAccessFile pf = new RandomAccessFile("users/" + name.toLowerCase(), "r")) { + String userPass = pf.readLine(); + + if (pass.equals(userPass)) { + return true; + } else if (game.passwordFailure(name, getAddress())) { + throw new TooManyFailures(); } else { - File fileStore = new File("pets/" + name.toLowerCase()); - if (fileStore.exists()) { - chatMessage(name + " is already in use. Please choose another."); - return false; - } - chatMessage(name + ", Is that correct? (yes/no)"); - if (!instream.readLine().equalsIgnoreCase("yes")) { - chatMessage("Then what IS your name?"); - return false; - } - chatMessage("Enter a new password:"); - password = instream.readLine(); - chatMessage("Confirm that password:"); - while (!password.equals(instream.readLine())) { - chatMessage("Passwords did not match, enter a new password:"); - password = instream.readLine(); - chatMessage("Confirm that password:"); - } - } - game.varIP.removeVariable(strIP); - try { - wornItems = new Equipment(); - itemList = new ItemList(); - conditions.clear(); - nearEntities.clear(); - flags.clear(); - ignoreList.clear(); - chatMessage("Login Accepted."); - proceed(); - chatMessage("This game is running DuskServer v" + game.version + ". http://dusk.wesowin.org/"); - chatMessage("Started at " + game.datStart.toString() + "."); - for (LivingThing lt : game.playersByName.values()) { - if (name.equalsIgnoreCase(lt.name)) { - game.log.printMessage(Log.INFO, socket.getInetAddress().toString() + ":" + name + " tried to log in twice"); - chatMessage("That user is already logged in. They are being logged out."); - lt.chatMessage("There has been another logon under this name, you are being logged out."); - lt.close(); - break; - } - // Second IP Filter check to catch delayed sign-ons - if (game.blnIPF) { - String IP = lt.socket.getInetAddress().toString(); - if (IP.equalsIgnoreCase(socket.getInetAddress().toString())) { - chatMessage("There's already a player connected from your IP address."); - Thread.sleep(1000); - closeNosavePlayer(); - return false; - } - } - } - game.playersByName.put(this.name.toLowerCase(), this); - game.onStart(this); - try (RandomAccessFile rafFile = new RandomAccessFile("users/default", "r")) { - strStore = rafFile.readLine(); - while (!(strStore == null || strStore.equals("."))) { - parseUserFile(rafFile, strStore); - strStore = rafFile.readLine(); - } - } - } catch (Exception e) { - game.log.printError("getPlayer():While loading default user file for " + name, e); - } - strStore = name.toLowerCase(); - File filPlayer = new File("users/" + strStore); - File filBackup = new File("users/" + strStore + ".backup"); - File filCheck; - int i2 = 0; - if (filBackup.exists()) { - if (filPlayer.length() > filBackup.length()) { - filCheck = new File("backup/" + strStore + ".possiblyDamaged"); - while (filCheck.exists()) { - i2++; - filCheck = new File("backup/" + strStore + ".possiblyDamaged." + i2); - } - filBackup.renameTo(filCheck); - } else if (filPlayer.length() < filBackup.length()) { - filCheck = new File("backup/" + strStore + ".possiblyDamaged"); - while (filCheck.exists()) { - i2++; - filCheck = new File("backup/" + strStore + ".possiblyDamaged." + i2); - } - filPlayer.renameTo(filCheck); - filBackup.renameTo(new File("users/" + strStore)); - } - } - /* - ** Load the user - */ - file = new File("users/", strStore); - backup = new File("users/", strStore + ".backup"); - try (RandomAccessFile rafFile = new RandomAccessFile(file, "rw")) { - strStore = rafFile.readLine(); - while (!(strStore == null || strStore.equals("."))) { - parseUserFile(rafFile, strStore); - strStore = rafFile.readLine(); - } + Thread.sleep(3000); + return false; } - /* - ** Try to load a pet, if they don't already have one - */ - if (following == null) { - following = new LivingThing("default", null, this, game); - if (following.name.equalsIgnoreCase("default")) { - following.closeNosavePlayer(); - following = null; - } + } + } + + /** + * 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; + + // 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, race)) == null + || !checkRace(race)) { + newp = new ListMessage(FIELD_AUTH_NEWPLAYER); + newp.add(getRaceQuery()); + res.add(newp); + ok = false; + } + + ok &= !new File("users/" + name.toLowerCase()).exists(); + ok &= !new File("pets/" + name.toLowerCase()).exists(); + + return ok; + } + + void greetUser() { + chatMessage("DuskZ Server " + game.version + " -- http://code.google.com/p/duskz/"); + chatMessage("Started at " + game.datStart.toString() + "."); + chatMessage("You're playing DuskZ, a graphical mud."); + } + + void loadPlayer(boolean create) throws IOException { + wornItems = new Equipment(); + itemList = new ItemList(); + conditions.clear(); + nearEntities.clear(); + flags.clear(); + ignoreList.clear(); + + game.onStart(this); + + /** + * Load default user - ignore errors + */ + try { + loadUserFile(new File("users/default"), false); + } catch (IOException e) { + game.log.printError("getPlayer():While loading default user file for " + name, e); + } + + file = new File("users", name.toLowerCase()); + backup = new File("users", name.toLowerCase() + ".backup"); + + /** + * Load actual user + */ + if (!create) + loadUserFile(file, true); + + /* + ** Try to load a pet, if they don't already have one + */ + if (following == null) { + following = new LivingThing("default", null, this, game); + if (following.name.equalsIgnoreCase("default")) { + following.closeNosavePlayer(); + following = null; } - } catch (Exception e) { - game.log.printError("getPlayer():While loading file for " + name, e); - closeNosavePlayer(); - return true; } + // } catch (Exception e) { + // game.log.printError("getPlayer():While loading file for " + name, e); + // closeNosavePlayer(); + // return true; + // } loadRace(); isLoaded = true; if (following != null) { following.isLoaded = true; following.changeLocBypass(x, y); } - return true; - } - String askRace(String strRaceDir, String prompt) throws IOException { - File filRaces = new File(strRaceDir); - String strList[] = filRaces.list(); - StringBuilder sb = new StringBuilder(prompt); - sb.append("\n"); - for (String s : strList) { - sb.append(s).append("\n"); + if (create) { + isSaveNeeded = true; + savePlayer(); } - sb.append(".\n"); - String s = sb.toString(); + } - // ?? - if (isPlayer()) { - send(MessageType.ChooseRace, s); - } - if (charmer != null) { - charmer.send(MessageType.ChooseRace, s); - } - if (isPet()) { - return master.instream.readLine().toLowerCase(); - } else { - return instream.readLine().toLowerCase(); - } + boolean checkRace(String race) { + // FIXME: confirm behaviour here + String dir = isPet() ? "defPets" : "defRaces"; + + return new File(dir, race).exists(); } public void loadRace() { - String s; - String dir; - if (isPet()) { - dir = "defPets"; - } else { - dir = "defRaces"; - } + String dir = isPet() ? "defPets" : "defRaces"; + try { - if (race == null || !(new File(dir + "/" + race).exists())) { - s = askRace(dir, "Choose one of the following races:"); - // FIXME: check this isn't broken - File filCheck = new File(dir + "/" + s); - while (s.equals("") || !filCheck.exists()) { - s = askRace(dir, "That is not a valid race, please choose again."); - filCheck = new File(dir + "/" + s); - } - race = s; - } - loadRaceFile(new File(dir + "/" + race), true); + loadRaceFile(new File(dir, race), true); } catch (Exception e) { game.log.printError("loadRace():Loading " + name + "'s race file \"" + dir + "/" + race + "\"", e); } } public void unloadRace() { - String dir; - if (isPet()) { - dir = "defPets"; - } else { - dir = "defRaces"; - } + String dir = isPet() ? "defPets" : "defRaces"; + try { - if (race == null || !(new File(dir + "/" + race).exists())) { - race = null; - return; - } - loadRaceFile(new File(dir + "/" + race), false); + loadRaceFile(new File(dir, race), false); } catch (Exception e) { game.log.printError("unloadRace():Un-loading " + name + "'s race file \"" + dir + "/" + race + "\"", e); } race = null; } - public void updateAppletImages() { - String strResult = "" + (char) 1; - strResult += game.strRCAddress + "\n"; - try { - send(strResult); - } catch (Exception e) { - game.log.printError("updateAppletImages()", e); - } - } - - public void updateApplicationImages() { - String strResult = "" + (char) 1; - strResult += game.strRCName + "\n"; - try { - send(strResult); - } catch (Exception e) { - game.log.printError("updateApplicationImages()", e); - } - } - public void updateMusic() { /*try { @@ -2565,39 +2584,34 @@ public class LivingThing extends DuskObject implements Runnable { public void playSFX(int intSFX) { if (audioon) { - try { - send((char) 15 + "" + intSFX + "\n"); - } catch (Exception e) { - game.log.printError("playSFX()", e); - } + // try { + // send((char) 15 + "" + intSFX + "\n"); + // } catch (Exception e) { + // game.log.printError("playSFX()", e); + // } } } public void updateActions() { - try { - if (isPlayer()) { - String strResult = "" + (char) 10; - if (battle != null) { - strResult += "flee\n"; + DuskMessage.StringListMessage list = new DuskMessage.StringListMessage(MSG_UPDATE_ACTIONS); + + if (isPlayer()) { + if (battle != null) { + list.add("flee"); + } else { + if (isSleeping) { + list.add("wake"); } else { - if (isSleeping) { - strResult += "wake\n"; - } else { - strResult += "sleep\n"; - } + list.add("sleep"); } - strResult += ".\n"; - send(strResult); } - } catch (Exception e) { - game.log.printError("updateActions()", e); } + send(list); } - // FIXME: I think this should be encapsulated in a more desriptive message public void updateEquipment() { try { - send(MessageType.UpdateEquipment, wornItems.toEntity()); + send(wornItems.toMessage(MSG_EQUIPMENT)); } catch (Exception e) { game.log.printError("updateEquipment():" + name + " disconnected", e); isStopped = true; @@ -2605,24 +2619,165 @@ public class LivingThing extends DuskObject implements Runnable { } } - public void send(MessageType type, String data) { + public void quit() { + send(DuskMessage.create(MSG_QUIT)); + } + + public void viewText(String name, boolean editable, String text) { + // FIXME: some error + if (privs <= 2) + return; + + ListMessage lm = new ListMessage(MSG_VIEW_TEXT); + lm.add(FIELD_TEXT_NAME, name); + lm.add(FIELD_TEXT_EDITABLE, (byte) (editable ? 1 : 0)); + if (text != null) + lm.add(FIELD_TEXT_TEXT, text); + + send(lm); + } + + /** + * This should only be called from login stuff, + * and then the main input loop. + * + * TODO: put this and the instream stuff into a separate object + * + * @return + * @throws IOException + */ + private 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; + + synchronized (pendingQuestions) { + f = pendingQuestions.get(qmsg.id); + } + + f.setResponse(qmsg); + } + } while (msg.name == MSG_QUERY); + + return msg; + } + + public void send(int msg, String data) { if (isPlayer() && isWorking && !isClosing) { - // FIXME: put code in senddata - messageQueue.offer(ServerMessage.stringMessage(type, data)); + messageQueue.offer(DuskMessage.create(msg, data)); } } - public void send(ServerMessage msg) { + public void sendUrgent(DuskMessage msg) { if (isPlayer() && isWorking && !isClosing) { - messageQueue.offer(msg); + messageQueue.addFirst(msg); } } - public void send(String data) { + public void send(DuskMessage msg) { if (isPlayer() && isWorking && !isClosing) { - messageQueue.offer(ServerMessage.stringMessage(data)); + messageQueue.offer(msg); } } + long qid; + final HashMap pendingQuestions = new HashMap<>(); + + synchronized long getQuestionID() { + return qid++; + } + + static class PendingQuery implements Future { + + 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; + } + } + + /** + * 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 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() == connectionThread) { + do { + readMessage(); + } while (pq.response == null); + } + + return pq; + } + + //public void send(String data) { + // if (isPlayer() && isWorking && !isClosing) { + // messageQueue.offer(DuskMessage.create(MSG_CHAT, data)); + // } + //} /* public void send(byte data) { @@ -2642,248 +2797,122 @@ public class LivingThing extends DuskObject implements Runnable { } }*/ public void updateInfo() { - try { - String strResult; - strResult = hp + "\n"; - strResult += (maxhp + hpbon) + "\n"; - strResult += mp + "\n"; - strResult += (maxmp + mpbon) + "\n"; - send(MessageType.UpdateStats, strResult); - } catch (Exception e) { - game.log.printError("updateInfo():" + name + " disconnected", e); - isStopped = true; - } + ListMessage lm = new ListMessage(MSG_INFO_PLAYER); + + lm.add(FIELD_INFO_HP, hp); + lm.add(FIELD_INFO_MAXHP, maxhp + hpbon); + lm.add(FIELD_INFO_MP, hp); + lm.add(FIELD_INFO_MAXMP, maxmp + mpbon); + + send(lm); } public void updateRange() { - try { - String strResult = "" + (char) 28; - strResult += getRangeWithBonus() + "\n"; - send(strResult); - } catch (Exception e) { - game.log.printError("updateRange():" + name + " disconnected", e); - } + ListMessage lm = new ListMessage(MSG_INFO_PLAYER); + + lm.add(FIELD_INFO_RANGE, getRangeWithBonus()); + + send(lm); } + // Use updateInventory instead + @Deprecated public void updateItems() { - try { - StringBuilder sb = new StringBuilder(); - - for (LinkedList qStore : itemList.values()) { - if (qStore.size() > 0) { - Item itmStore = (Item) qStore.element(); - if (itmStore.isArmor()) { - sb.append((2 + itmStore.intKind) + "\n"); - } else if (itmStore.isWeapon()) { - sb.append("1\n"); - } else { - sb.append("0\n"); - } - sb.append(itmStore.name + "\n"); - } - } - sb.append(".\n"); - send(MessageType.UpdateItems, sb.toString()); - if (game.overMerchant(x, y) != null) { - updateSell(); - } - if (game.overPlayerMerchant(x, y) != null) { - updateSell(); - } - } catch (Exception e) { - game.log.printError("updateItems():" + name + " disconnected", e); - isStopped = true; - } + updateInventory(); + + // FIXME: change on observe? This aint no quantum effect! isSaveNeeded = true; } - public void updateStats() { - SpellGroup grpStore; - int i, - i2; + private void addStat(ListMessage lm, int field, int val, int valbon) { + lm.add(field, val); + if (valbon != 0) + lm.add(field + 1, valbon); + } - // FIXME: wtf, duplicated shit code again - // FIXME: convert to stringbuilder - try { - String strResult = ""; - strResult += cash + " gp\n"; - strResult += exp + " exp\n"; - if (strebon == 0) { - strResult += "str: " + stre + "\n"; - } else { - strResult += "str: " + stre + " + " + strebon + "\n"; - } - if (intebon == 0) { - strResult += "int: " + inte + "\n"; - } else { - strResult += "int: " + inte + " + " + intebon + "\n"; - } - if (dextbon == 0) { - strResult += "dex: " + dext + "\n"; - } else { - strResult += "dex: " + dext + " + " + dextbon + "\n"; - } - if (consbon == 0) { - strResult += "con: " + cons + "\n"; - } else { - strResult += "con: " + cons + " + " + consbon + "\n"; - } - if (wisdbon == 0) { - strResult += "wis: " + wisd + "\n"; - } else { - strResult += "wis: " + wisd + " + " + wisdbon + "\n"; - } - if (dammodbon == 0) { - strResult += "DamMod: " + getDamMod() + "\n"; - } else { - strResult += "DamMod: " + getDamMod() + " + " + dammodbon + "\n"; - } - if (acbon == 0) { - strResult += "AC: " + getArmorMod() + "\n\n"; - } else { - strResult += "AC: " + getArmorMod() + " + " + acbon + "\n"; - } - strResult += "-Affected by-\n"; - for (Condition cond : conditions) { - if (cond.display) { - strResult += cond.name + "\n"; - } - } - strResult += "-Skills-\n"; - for (Ability skill : skillMap.values()) { - strResult += skill.name + ": " + skill.getAbility() + "\n"; - } - strResult += "-Spells-\n"; - for (Ability spell : spellMap.values()) { - grpStore = game.getSpellGroup(spell.name); - if (grpStore != null) { - strResult += spell.name + ": " + spell.getAbility() + "\n"; - strResult += grpStore.spellList(spell.getAbility()); - } - } - if (master != null) { - strResult += "\nFollowing: " + master.name + "\n"; - } - if (following != null) { - strResult += "\nFollowed By: " + following.name + "\n"; - if (following.isPet()) { - strResult += following.hp + "/" + following.maxhp + " hp\n"; - strResult += following.mp + "/" + following.maxmp + " mp\n"; - strResult += following.cash + " gp\n"; - strResult += following.exp + " exp\n"; - if (following.strebon == 0) { - strResult += "str: " + following.stre + "\n"; - } else { - strResult += "str: " + following.stre + " + " + following.strebon + "\n"; - } - if (following.intebon == 0) { - strResult += "int: " + following.inte + "\n"; - } else { - strResult += "int: " + following.inte + " + " + following.intebon + "\n"; - } - if (following.dextbon == 0) { - strResult += "dex: " + following.dext + "\n"; - } else { - strResult += "dex: " + following.dext + " + " + following.dextbon + "\n"; - } - if (following.consbon == 0) { - strResult += "con: " + following.cons + "\n"; - } else { - strResult += "con: " + following.cons + " + " + following.consbon + "\n"; - } - if (following.wisdbon == 0) { - strResult += "wis: " + following.wisd + "\n"; - } else { - strResult += "wis: " + following.wisd + " + " + following.wisdbon + "\n"; - } - if (following.dammodbon == 0) { - strResult += "DamMod: " + following.getDamMod() + "\n"; - } else { - strResult += "DamMod: " + following.getDamMod() + " + " + following.dammodbon + "\n"; - } - if (following.acbon == 0) { - strResult += "AC: " + following.getArmorMod() + "\n\n"; - } else { - strResult += "AC: " + following.getArmorMod() + " + " + following.acbon + "\n"; - } - strResult += "-Affected by-\n"; - for (Condition cndStore : following.conditions) { - if (cndStore.display) { - strResult += cndStore.name + "\n"; - } - } - strResult += "-Skills-\n"; - for (Ability skill : following.skillMap.values()) { - strResult += skill.name + ": " + skill.getAbility() + "\n"; - } - strResult += "-Spells-\n"; - for (Ability spell : following.skillMap.values()) { - grpStore = game.getSpellGroup(spell.name); - if (grpStore != null) { - strResult += spell.name + ": " + spell.getAbility() + "\n"; - strResult += grpStore.spellList(spell.getAbility()); - } - } - } + private ListMessage buildStats(LivingThing lt, int msg) { + ListMessage lm = new ListMessage(msg); + + lm.add(FIELD_INFO_CASH, cash); + lm.add(FIELD_INFO_EXP, exp); + addStat(lm, FIELD_INFO_STR, stre, strebon); + addStat(lm, FIELD_INFO_INT, inte, intebon); + addStat(lm, FIELD_INFO_DEX, dext, dextbon); + addStat(lm, FIELD_INFO_CON, cons, consbon); + addStat(lm, FIELD_INFO_WIS, wisd, wisdbon); + addStat(lm, FIELD_INFO_DAM, getDamMod(), dammodbon); + addStat(lm, FIELD_INFO_ARC, getArmorMod(), acbon); + + DuskMessage.StringListMessage list; + + lm.add(list = new DuskMessage.StringListMessage(FIELD_INFO_CONDITIONS)); + for (Condition cond : conditions) { + if (cond.display) { + list.add(cond.name); } - strResult += ".\n"; - send(MessageType.UpdateInfo, strResult); - } catch (Exception e) { - game.log.printError("updateStats():" + name + " disconnected", e); - isStopped = true; } - //updateRange(); - isSaveNeeded = true; - } - public void halt() { - isHalted = true; - try { - send(MessageType.Halt, ""); - } catch (Exception e) { - isHalted = false; - game.log.printError("halt()", e); + lm.add(list = new DuskMessage.StringListMessage(FIELD_INFO_SKILLS)); + for (Ability skill : skillMap.values()) { + list.add(skill.name + ": " + skill.getAbility()); + } + + lm.add(list = new DuskMessage.StringListMessage(FIELD_INFO_SPELLS)); + for (Ability spell : spellMap.values()) { + SpellGroup sg = game.getSpellGroup(spell.name); + if (sg != null) { + list.add(spell.name + ": " + spell.getAbility()); + sg.spellList(list.value, spell.getAbility()); + } + } + if (!lt.isPet()) { + if (master != null) + lm.add(FIELD_INFO_FOLLOWING, master.name); + if (following != null) + lm.add(FIELD_INFO_FOLLOWED, following.name); } + return lm; } - public void proceed() { - isHalted = false; - try { - send(MessageType.Proceed, ID + "\n"); - } catch (Exception e) { - game.log.printError("proceed()", e); + public void updateStats() { + send(buildStats(this, MSG_INFO_PLAYER)); + if (following != null && following.isPet()) { + + send(buildStats(this, MSG_INFO_PET)); } + + isSaveNeeded = true; } public void stillThere() { - try { - send("" + (char) 13); - } catch (Exception e) { - game.log.printError("stillThere()", e); - } + send(DuskMessage.create(MSG_PING)); } - public void resizeMap() { - int i, i2; - String strResult = (char) 19 + ""; - strResult += game.mapsize + "\n"; - send(strResult); + public 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.strRCName); + + send(lm); } - public void updateSell() { - StringBuilder sb = new StringBuilder(); + public void updateInventory() { + TransactionMessage tm = new TransactionMessage(MSG_INVENTORY); for (LinkedList list : itemList.values()) { - for (Item item : list) { - sb.append(item.intCost / 2).append("gp)").append(item.name).append("\n"); + if (!list.isEmpty()) { + Item item = list.get(0); + tm.add(item.getWearLocation(), item.name, list.size(), item.intCost / 2, "gp"); } } - sb.append(".\n"); - send(MessageType.UpdateSell, sb.toString()); + send(tm); } void offMerchant() { - send(MessageType.ExitMerchant, ""); + send(DuskMessage.create(MSG_EXIT_MERCHANT)); } private class SendThread extends Thread { @@ -2892,12 +2921,17 @@ public class LivingThing extends DuskObject implements Runnable { } public void run() { - ServerMessage msg; + DuskMessage msg; + while (!isStopped) { try { msg = messageQueue.take(); + + // low level protocol dump + msg.format(System.out); + try { - msg.send(outstream); + msg.sendMessage(outstream); outstream.flush(); } catch (IOException e) { game.log.printError("SendThread.run():" + msg + " to " + name, e); diff --git a/DuskServer/src/duskz/server/entity/Merchant.java b/DuskServer/src/duskz/server/entity/Merchant.java index ca9764c..58aec25 100644 --- a/DuskServer/src/duskz/server/entity/Merchant.java +++ b/DuskServer/src/duskz/server/entity/Merchant.java @@ -83,7 +83,14 @@ public class Merchant extends DuskObject { if (thnMaster.isPet()) { thnMaster.chatMessage("You ARE a pet!"); return; + } + + thnMaster.chatMessage("The developer hasn't implemented pets yet!, see Merchangt.java line 90"); + if (true)return; + + // FIXME: requires query system. Use callbacks? + /* thnMaster.halt(); thnMaster.chatMessage("Enter a name for your pet:"); String strName = thnMaster.instream.readLine().trim(); @@ -110,10 +117,11 @@ public class Merchant extends DuskObject { filCheck = new File("defPets/" + strStore); } thnMaster.following = new LivingThing(strName, strStore, thnMaster, engGame); + */ } catch (Exception e) { engGame.log.printError("Merchant.pet()", e); } finally { - thnMaster.proceed(); + //thnMaster.proceed(); } } diff --git a/DuskZ/src/duskz/client/ClientMap.java b/DuskZ/src/duskz/client/ClientMap.java index b39a324..5055ac3 100644 --- a/DuskZ/src/duskz/client/ClientMap.java +++ b/DuskZ/src/duskz/client/ClientMap.java @@ -21,7 +21,6 @@ */ package duskz.client; -import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -197,49 +196,6 @@ public class ClientMap { && ly >= 0 && ly < rows; } - public synchronized void updateTiles(int offx, int offy, DataInputStream instream) throws IOException { - //for (int x = 0; x < cols; x++) { - // for (int y = 0; y < rows; y++) { - // setTile(x, y, Short.parseShort(instream.readLine())); - // } - //} - int length = instream.readInt(); - if (length != cols * rows) { - throw new IOException("Protocol error incorrect map size"); - } - for (int y = 0; y < rows; y++) { - for (int x = 0; x < cols; x++) { - setTile(x, y, instream.readShort()); - } - } - - this.offx = offx; - this.offy = offy; - - // Prune out of range thingies - List removing = new ArrayList<>(); - for (Entry> es : entityByLocation.entrySet()) { - int ex = es.getKey() & 0xffff; - int ey = (es.getKey() >> 16) & 0xffff; - - System.out.printf("check inside %d,%d = %s\n", ex, ey, inside(ex, ey)); - - if (!inside(ex, ey)) { - removing.addAll(es.getValue()); - } - } - - // TODO: this could be optimised - // TODO: does the server need to know this - if (!removing.isEmpty()) { - System.out.println("offset: " + this.offx + "," + this.offy); - for (Entity e : removing) { - System.out.println("removing out of range :" + e); - removeEntity(e); - } - } - } - public synchronized void updateTiles(int offx, int offy, short[] tiles) throws IOException { //for (int x = 0; x < cols; x++) { // for (int y = 0; y < rows; y++) { diff --git a/DuskZ/src/duskz/client/Dusk.java b/DuskZ/src/duskz/client/Dusk.java index aedb57f..6008d55 100644 --- a/DuskZ/src/duskz/client/Dusk.java +++ b/DuskZ/src/duskz/client/Dusk.java @@ -29,12 +29,14 @@ * Feb-2013 Michael Zucchi - modernised java, cleanup, abstracted the GUI behind * an interface. Abstracted most commands. Added some binary protocol changes. * Track the player id for better battles. + * Mar-2013 Michael Zucchi - new protocol implementation. */ package duskz.client; -import duskz.protocol.MessageType; -import duskz.protocol.ServerMessage; -import duskz.protocol.ServerMessage.EntityMessage; +import duskz.protocol.*; +import duskz.protocol.DuskMessage.*; +import static duskz.protocol.DuskProtocol.FIELD_AUTH_NEWPLAYER; +import static duskz.protocol.DuskProtocol.FIELD_AUTH_REASON; import java.io.*; import java.net.*; import java.util.ArrayList; @@ -48,10 +50,10 @@ import java.util.logging.Logger; * * @author notzed */ -public class Dusk implements Runnable { +public class Dusk implements Runnable, DuskProtocol { public final static boolean dumpRaw = false; - static String strVersion = "2.7.1.W3"; + static String strVersion = "3.0 dev"; Status status = new Status(); long loncash; boolean loaded, @@ -67,19 +69,28 @@ public class Dusk implements Runnable { List buyList = new ArrayList<>(); List sellList = new ArrayList<>(); Equipment worn = new Equipment(); - Socket socket; - DataOutputStream outstream; - DataInputStream instream; + private Socket socket; + private DataOutputStream outstream; + private DataInputStream instream; int mapSize = 11; ClientMap map; boolean intStep, blnMusic = true; - Thread thrRun; + Thread gameThread; int tileSize = 32; int spriteSize = 64; GUI frame; String address = "dusk.wesowin.org"; int port = 7423; + // TODO: management at both ends could be put into the protocol namespace + // outstanding queries, do one at a time + ListMessage pendingQueries; + int pendingQueryIndex; + int queryID; + // message to send when all queries complete + ListMessage queryMessage; + // Where to put the query fields + ListMessage queryFields; public Dusk(GUI gui) { frame = gui; @@ -91,9 +102,15 @@ public class Dusk implements Runnable { //frame.initComponents(); //frame.setVisible(true); frame.logClear(); - frame.log("Dusk Client v" + strVersion + " -- http://FIXME/\n"); + frame.log("DuskZ Client " + strVersion + " -- http://code.google.com/p/duskz/\n"); frame.log("You are using Java version " + System.getProperty("java.version") + "\n"); + frame.log("Copyright (C) 2000 Tom Weingarten\n"); + frame.log("Copyright (C) 2013 Michael Zucchi\n"); + frame.log("This program comes with ABSOLUTELY NO WARRANTY.\n"); + frame.log("This is free software, and you are welcome to redistribute it\n"); + frame.log("under certain conditions.\n"); + if (blnApplet) { //address = appShell.getParameter("address"); //port = Integer.parseInt(appShell.getParameter("port")); @@ -137,6 +154,7 @@ public class Dusk implements Runnable { } State state = State.Unconnected; String pass; + String user; public void connect(String address, int port, String user, String pass) { // Connect to Server @@ -149,8 +167,8 @@ public class Dusk implements Runnable { state = State.Connected; // Load Images and start checking for incoming commands - thrRun = new Thread(this); - thrRun.start(); + gameThread = new Thread(this); + gameThread.start(); connected = true; //Initialize objects map = new ClientMap(11, 11); @@ -158,8 +176,15 @@ public class Dusk implements Runnable { sellList.clear(); this.pass = pass; + this.user = user; state = State.Username; - command(user); + + // try a login command + ListMessage lm = new ListMessage(MSG_AUTH); + lm.add(FIELD_AUTH_PLAYER, user); + lm.add(FIELD_AUTH_PASS, pass); + send(lm); + } catch (Exception e) { log("Error connecting to server: " + e.toString() + "\n"); return; @@ -191,460 +216,238 @@ public class Dusk implements Runnable { map.removeEntity(entStore); } - /** - * Read a . terminated list from server - * - * @return - */ - List readList() throws IOException { - List l = new ArrayList<>(); - - String s = instream.readLine(); - while (!s.equals(".")) { - l.add(s); - s = instream.readLine(); - } - return l; - } - - String readContent(String end) throws IOException { - StringBuilder sb = new StringBuilder(); - String s = instream.readLine(); - - while (!s.equals(end)) { - sb.append(s).append('\n'); - s = instream.readLine(); - } - return sb.toString(); - } - //Thread to process incoming commands public void run() { int incoming = 0; while (incoming != -1) { try { - // Handle incoming messages from Server -// incoming = stmIn.readByte(); - incoming = instream.read(); + DuskMessage dm = DuskMessage.receiveMessage(instream); - if (incoming == -1) { - continue; - } + // debug + System.out.print(state + ": "); + dm.format(System.out); - MessageType type = MessageType.fromServer(incoming); - - System.out.println("state " + state + " incoming = " + type); - - // Hack to auto-login to server - switch (state) { - case Username: - if (type == MessageType.Chat) { - String s = instream.readLine(); - System.out.println("username: text: " + s); - if (s.equals("enter your password:")) { - state = State.Password; - command(pass); - } else if (s.endsWith("Is that correct? (yes/no)")) { - // "new user" shit - state = state.Create; - command("yes"); - } - } - continue; - case Password: - if (type == MessageType.Chat) { - String s = instream.readLine(); - System.out.println("password: text: " + s); - if (s.equals("Login Accepted.")) { - state = State.Ready; + switch (dm.name) { + case MSG_AUTH: { + ListMessage am = (ListMessage) dm; + EntityIntegerMessage res = (EntityIntegerMessage) am.get(FIELD_AUTH_RESULT); + + switch (res.value) { + case AUTH_LOGIN_OK: { + log(am.getString(FIELD_AUTH_REASON) + "\n"); frame.loginOk(); - } else if (s.equals("Incorrect Password.")) { - // blah blah what now? - state = state.Connected; - } - } - continue; - case Create: - if (type == MessageType.Chat) { - String s = instream.readLine(); - System.out.println("create: text: " + s); - if (s.equals("Enter a new password:")) { - command(pass); - } else if (s.equals("Confirm that password:")) { - //state = State.Password; - command(pass); - } else if (s.equals("Login Accepted.")) { - // this should now ask for a race + status.id = res.id; state = State.Ready; - frame.loginOk(); + break; + } + case AUTH_LOGIN_FAILED: { + state = State.Username; + log(am.getString(FIELD_AUTH_REASON) + "\n"); + //frame.loginError(); + break; + } + case AUTH_LOGIN_EXISTS: { + state = State.Username; + log(am.getString(FIELD_AUTH_REASON) + "\n"); + //frame.loginError(); + break; + } + case AUTH_LOGIN_INCOMPLETE: { + state = State.Username; + log(am.getString(FIELD_AUTH_REASON) + "\n"); + + pendingQueries = (ListMessage) am.get(FIELD_AUTH_NEWPLAYER); + pendingQueryIndex = 0; + + queryMessage = new ListMessage(MSG_AUTH); + queryMessage.add(FIELD_AUTH_PLAYER, user); + queryMessage.add(FIELD_AUTH_PASS, pass); + queryFields = new ListMessage(FIELD_AUTH_NEWPLAYER); + queryMessage.add(queryFields); + + nextQuery(); + break; } - // TODO: find out what happens if that doesn't work - } - continue; - case SelectRace: - switch (type) { - case ChooseRace: - // Bad choice, try again - handleChooseRace(instream); - continue; - default: - state = State.Ready; - // any other message drop through to process } - break; - } - switch (type) { - case Quit: //Quit - { + break; + } + /** + * Notify only + * One ping and one ping only. + */ + case MSG_PING: + ping(); + break; + /** + * Notify only + * Server dumped you + */ + case MSG_QUIT: loaded = false; connected = false; socket.close(); + return; + case MSG_QUERY: { + EntityListMessage query = (EntityListMessage) dm; + EntityListMessage response = new EntityListMessage(query.id, MSG_QUERY); + + // Not imjplemented yet, just return a dummy message and let the server deal with it + // it will work like the login NEWPLAYER shit + + send(response); + return; } - case UpdateImages: //update Images - { - rcLocation = instream.readLine(); - // FIXME: not if applet or something? + case MSG_CLEAR_FLAGS: + synchronized (map) { + for (Entity e : map.getEntities()) { + e.intFlag = 0; + } + } + update(); + break; + + case MSG_INIT_MAP: { + ListMessage lm = (ListMessage) dm; + + // FIXME: use jar stuff + rcLocation = lm.getString(FIELD_MAP_ASSETLOCATION); + // FIXME: hack rcLocation = "rc/" + rcLocation; frame.setImages(new File(rcLocation + "/images/map.gif").toURI().toString(), 32, new File(rcLocation + "/images/players.gif").toURI().toString(), 64, new File(rcLocation + "/images/sprites.gif").toURI().toString(), 64); - update(); + + mapSize = lm.getInteger(FIELD_MAP_WIDTH); + map = new ClientMap(mapSize, mapSize); break; } - case UpdateLocMap: { - /* - status.updateLocation(instream); - System.out.printf("Updating map to %d %d map size %d %d\n", - status.locx, status.locy, mapSize, mapSize); - map.updateTiles(status.locx - (map.cols - 1) / 2, status.locy - (map.rows - 1) / 2, instream); - */ - ServerMessage.MapMessage msg = ServerMessage.MapMessage.decode(instream); - status.updateLocation(msg.x, msg.y); - map.updateTiles(msg.x - (map.cols - 1) / 2, msg.y - (map.rows - 1) / 2, msg.map); + case MSG_UPDATE_MAP: { + MapMessage mm = (MapMessage) dm; + + status.updateLocation(mm.x, mm.y); + map.updateTiles(mm.x - (map.cols - 1) / 2, mm.y - (map.rows - 1) / 2, mm.map); update(); frame.setStatus(status); //frame.info.setText("HP: " + inthp + "/" + intmaxhp + " MP: " + intsp + "/" + intmaxsp + " Loc: " + LocX + "/" + LocY); buyList.clear(); reloadChoiceLookGetAttack(); - //paint(); + + // Perhaps i need a status message to go from 'starting' to 'ready' + loaded = true; break; } - case Chat: //incoming chat - { - chat(instream.readLine() + "\n"); + + case MSG_CHAT: + chat(((StringMessage) dm).value + "\n"); break; - } - case AddEntity: //add Entity - { - /* - String s = instream.readLine(); - int etype = Byte.parseByte(instream.readLine()); - Entity ent = null; - if (etype == 0) { - try { - ent = new Entity(s, - Long.parseLong(instream.readLine()), - Integer.parseInt(instream.readLine()), - Integer.parseInt(instream.readLine()), - Integer.parseInt(instream.readLine()), - Integer.parseInt(instream.readLine()), - etype); - } catch (NullPointerException e) { - ent = null; - } - } else { - try { - ent = new Entity(s, - Long.parseLong(instream.readLine()), - Integer.parseInt(instream.readLine()), - Integer.parseInt(instream.readLine()), - Integer.parseInt(instream.readLine()), - -1, - etype); - } catch (NullPointerException e) { - ent = null; - } - }*/ - EntityMessage msg = ServerMessage.EntityMessage.decode(type, instream); - if (msg != null) { - addEntity(new Entity(msg)); - } + case MSG_ADD_ENTITY: + addEntity(new Entity((EntityUpdateMessage) dm)); reloadChoiceLookGetAttack(); update(); break; - } - case UpdateStats: //update Stats - { - status.updateStatus(instream); - frame.setStatus(status); - break; - } - case UpdateItems: //update Items - { - worn.updateAvailable(instream); - frame.setDropList(worn.all); - frame.setEquipment(worn); - break; - } - case UpdateEquipment: //update Equipment - { - worn.updateWorn(instream); - frame.setEquipment(worn); - break; - } - case UpdateInfo: //update Stats - { - try { - StringBuilder sb = new StringBuilder(); - String s = instream.readLine(); - while (!s.equals(".")) { - sb.append(s); - sb.append("\n"); - s = instream.readLine(); + case MSG_UPDATE_ENTITY: { + // fixme: put on entity + EntityListMessage el = (EntityListMessage) dm; + Entity e = map.getEntity(el.id); + if (e != null) { + for (DuskMessage m : el.value) { + switch (m.name) { + case FIELD_ENTITY_FLAGS: + e.intFlag = ((IntegerMessage) m).value; + break; + } } - frame.setStats(sb.toString()); - } catch (Exception e) { - System.err.println("Error loading stats" + e.toString()); + update(); + } else { + log("Error: set flag on unknown entity: " + el.id); } break; } - case Halt: //halt - { - loaded = false; - break; - } - case UpdateActions: //update Actions - { - frame.setActionList(readList()); + case MSG_REMOVE_ENTITY: + removeEntity(((EntityMessage) dm).id); + reloadChoiceLookGetAttack(); + update(); break; - } - case LoadMusic: //load music - { - try { - // FIXME; applet - /* - if (blnApplet) { - log("Loading music.\n"); - intMusicTypes = Integer.parseInt(instream.readLine()); - audMusic = new AudioClip[intMusicTypes][]; - intNumSongs = new int[intMusicTypes]; - for (intStore = 0; intStore < intMusicTypes; intStore++) { - intNumSongs[intStore] = Integer.parseInt(instream.readLine()); - audMusic[intStore] = new AudioClip[intNumSongs[intStore]]; - for (intStore2 = 0; intStore2 < intNumSongs[intStore]; intStore2++) { - String s = instream.readLine(); - try { - //audMusic[intStore][intStore2] = appShell.getAudioClip(new URL(strStore)); - while (audMusic[intStore][intStore2] == null) { - } - } catch (Exception e) { - System.err.println("Error while trying to load music file " + s + ":" + e.toString()); - } - } - } - log("Music loaded.\n"); - }*/ - } catch (Exception e) { - blnMusic = false; - log("Your java virtual machine does not support midi music\n"); - System.err.println("Error while trying to load music files:" + e.toString()); + case MSG_MOVE: { + EntityByteMessage mm = (EntityByteMessage) dm; + + switch (mm.value) { + case 'n': + handleMove(mm.id, 1, 0, -1, 0); + break; + case 's': + handleMove(mm.id, 3, 0, 1, 1); + break; + case 'w': + handleMove(mm.id, 5, -1, 0, 2); + break; + case 'e': + handleMove(mm.id, 7, 1, 0, 3); + break; } break; } - case PlayMusic: //play music - { - if (blnMusic) { - /* FIXME: sound - try { - intStore = Integer.parseInt(instream.readLine()); - if (audMusicPlaying != null) { - audMusicPlaying.stop(); - } - audMusicPlaying = audMusic[intStore][(int) (Math.random() * intNumSongs[intStore])]; - audMusicPlaying.loop(); - } catch (Exception e) { - System.err.println("Error while trying to play music file:" + e.toString()); - }*/ - } + case MSG_UPDATE_ACTIONS: + frame.setActionList(((StringListMessage) dm).value); break; - } - case Ping: //stillThere? - { - outstream.writeBytes("notdead\n"); + case MSG_INVENTORY: + sellList = ((TransactionMessage) dm).items; + + // fixme: merge inventory + frame.setSellList(sellList); + worn.updateAvailable(sellList); + frame.setDropList(worn.all); + frame.setEquipment(worn); break; - } - case Proceed: //proceed - { - status.id = Long.parseLong(instream.readLine()); - loaded = true; + case MSG_EQUIPMENT: + worn.updateWorn((ListMessage) dm); + frame.setEquipment(worn); break; - } - case PlaySound: //play sfx - { - try { - //audSFX[Integer.parseInt(instream.readLine())].play(); - } catch (Exception e) { + case MSG_INFO_PLAYER: { + // FIXME: status will include all the other shit about the player below too + status.updateStatus((ListMessage) dm); + frame.setStatus(status); + + StringBuilder sb = new StringBuilder(); + // HACK: don't bother showing this shit yet + ListMessage lm = (ListMessage) dm; + for (DuskMessage d : lm.value) { + sb.append(d).append('\n'); } + frame.setStats(sb.toString()); break; } - case RemoveEntity: //remove entity - { - long id = Long.valueOf(instream.readLine()).longValue(); - - removeEntity(id); - reloadChoiceLookGetAttack(); - update(); + case MSG_INFO_PET: { + // FIXME: Same data as above but for any pet attached + System.out.println("Update pet ignored"); break; } - case UpdateMerchant: //update Merchant - { - buyList = TransactionItem.createItems(readList()); + case MSG_UPDATE_MERCHANT: + buyList = ((TransactionMessage) dm).items; frame.setBuyList(buyList); break; - } - case EditText: //view/edit - { - String name = instream.readLine(); - frame.visitFile(name, readContent("--EOF--"), true); - break; - } - case ResizeMap: //resize Map - { - mapSize = Integer.parseInt(instream.readLine()); - map = new ClientMap(mapSize, mapSize); - if (blnApplet) { - outstream.writeBytes("appletimages\n"); - } else { - outstream.writeBytes("applicationimages\n"); - } - //scaleWindow(); - break; - } - case ViewText: //view/no-edit - { - String name = instream.readLine(); - frame.visitFile(name, readContent("--EOF--"), false); - break; - } - case ExitMerchant: //offMerchant - { + case MSG_EXIT_MERCHANT: frame.exitShop(); buyList.clear(); sellList.clear(); break; - } - case UpdateSell: //update Sell - { - sellList = TransactionItem.createItems(readList()); - frame.setSellList(sellList); - break; - } - case ColourChat: //colour chat - { - // FIXME: re-implement colour, or probably fix it (hex codes) - instream.readLine(); - instream.readLine(); - instream.readLine(); - chat(instream.readLine() + "\n"); - break; - } - case MoveNorth: //move north - { - long ID = Long.parseLong(instream.readLine()); - - handleMove(ID, 1, 0, -1, 0); - break; - } - case MoveSouth: //move south - { - long ID = Long.parseLong(instream.readLine()); - - handleMove(ID, 3, 0, 1, 1); - break; - } - case MoveWest: //move west - { - long ID = Long.parseLong(instream.readLine()); - - handleMove(ID, 5, -1, 0, 2); - break; - } - case MoveEast: //move east - { - long ID = Long.parseLong(instream.readLine()); + case MSG_BATTLE_START: { + ListMessage lm = (ListMessage) dm; - handleMove(ID, 7, 1, 0, 3); + battle("Start battling: " + lm.getString(FIELD_BATTLE_OPPONENT)); break; } - case UpdateRange: //update range - { - status.range = Integer.parseInt(instream.readLine()); - break; - } - case SetFlag: //set flag - { - long id = Long.valueOf(instream.readLine()).longValue(); - int flags = Integer.parseInt(instream.readLine()); - - Entity e = map.getEntity(id); - if (e != null) { - e.intFlag = flags; - update(); - } else { - log("Error: set flag on unknown entity: " + id); - } - break; - } - case ClearFlags: //clear all flags - { - // FIXME: hideinfo? - synchronized (map) { - for (Entity e : map.getEntities()) { - e.intFlag = 0; - } - } - update(); - break; - } - case StartBattle: //show battle window and clear text - { - // FIXME: no battle popup window at the moment - String s = instream.readLine(); - battle("Battle started: " + s + "\n"); - break; - } - case UpdateBattle: //show battle window and update title - { - // FIXME: no battle popup window at the moment - String s = instream.readLine(); - battle("Battle status: " + s + "\n"); - break; - } - case LogBattle: //add text to battle window - { - // FIXME: no battle popup window at the moment - String s = instream.readLine(); - battle(s + "\n"); - break; - } - case ChooseRace: - handleChooseRace(instream); - break; - case HitEntity: { - long toID = Long.valueOf(instream.readLine()); - int delta = -Integer.valueOf(instream.readLine()); - int newhp = Integer.valueOf(instream.readLine()); - int tothp = Integer.valueOf(instream.readLine()); - long fromID = Long.valueOf(instream.readLine()); - String how = instream.readLine(); - - String s; - do { - s = instream.readLine(); - } while (!s.equals(".")); + case MSG_BATTLE_UPDATE: { + ListMessage lm = (ListMessage) dm; + long toID = lm.getLong(FIELD_BATTLE_TARGET); + int delta = -lm.getInteger(FIELD_BATTLE_DAMAGE); + int newhp = lm.getInteger(FIELD_BATTLE_HP); + int tothp = lm.getInteger(FIELD_BATTLE_MAXHP); + long fromID = lm.getLong(FIELD_BATTLE_SOURCE); + String how = lm.getString(FIELD_BATTLE_WHAT); System.out.printf("entity %d %s delta %d hp %d/%d\n", toID, how, delta, newhp, tothp); @@ -669,14 +472,30 @@ public class Dusk implements Runnable { break; } + case MSG_BATTLE_CHAT: + battle(((StringMessage) dm).value + "\n"); + break; + case MSG_VIEW_TEXT: { + ListMessage lm = (ListMessage) dm; + System.out.println("View: " + lm.getString(FIELD_TEXT_NAME, "")); + System.out.println("Edit: " + lm.getByte(FIELD_TEXT_EDITABLE, (byte) 0)); + System.out.println("Text: " + lm.getString(FIELD_TEXT_TEXT, "")); + break; + } } - } catch (Exception e) { + } catch (ClassCastException e) { + e.printStackTrace(System.out); + // ignore this, protocol broken it should recover + } catch (IOException e) { System.err.println("Error at run() with value " + incoming + " : " + e.toString()); e.printStackTrace(System.out); log("Error at run() with value " + incoming + " : " + e.toString() + "\n"); connected = false; return; + } catch (Exception e) { + e.printStackTrace(System.out); + // ignore this, client broken, it might recover } } System.err.println("Error at run() with value " + incoming); @@ -713,22 +532,29 @@ public class Dusk implements Runnable { } } - private void handleChooseRace(DataInputStream in) throws IOException { - List races = new ArrayList<>(); - String prompt = in.readLine(); - String l = in.readLine(); - while (!l.equals(".")) { - races.add(l); - l = in.readLine(); + synchronized void nextQuery() { + if (pendingQueries == null) + return; + + if ((pendingQueryIndex < pendingQueries.value.size())) { + ListMessage lm = (ListMessage) pendingQueries.value.get(pendingQueryIndex++); + + queryID = lm.name; + frame.chooseRace(lm.getString(FIELD_QUERY_PROMPT), lm.getStringList(FIELD_QUERY_OPTIONS)); + } else { + try { + send(queryMessage); + } catch (IOException ex) { + Logger.getLogger(Dusk.class.getName()).log(Level.SEVERE, null, ex); + } } - state = State.SelectRace; - frame.chooseRace(prompt, races); } + // This currently a generic async response to a list query + // FIXME; rename public void chooseRace(String race) { - if (state == State.SelectRace) { - command(race); - } + queryFields.add(queryID, race); + nextQuery(); } // TBD - use the individual functions @@ -854,7 +680,7 @@ public class Dusk implements Runnable { public void logout() { try { if (connected) { - outstream.writeBytes("quit\n"); + command("quit"); } } catch (Exception exc) { } @@ -869,7 +695,7 @@ public class Dusk implements Runnable { public void quit() { try { if (connected) { - outstream.writeBytes("quit\n"); + command("quit"); } } catch (Exception exc) { } @@ -894,10 +720,15 @@ public class Dusk implements Runnable { } } + private void send(DuskMessage lm) throws IOException { + System.out.print("sending: "); + lm.format(System.out); + lm.sendMessage(outstream); + } + private void docmd(String cmd) { - System.out.println("send command: " + cmd); try { - outstream.writeBytes(cmd); + send(new StringMessage(MSG_COMMAND, cmd)); } catch (IOException ex) { // TODO: close connection here Logger.getLogger(Dusk.class.getName()).log(Level.SEVERE, null, ex); @@ -905,11 +736,11 @@ public class Dusk implements Runnable { } public void command(String what) { - docmd(what + "\n"); + docmd(what); } public void command(String what, String params) { - docmd(what + " " + params + "\n"); + docmd(what + " " + params); } public void move(Direction dir) { @@ -919,8 +750,7 @@ public class Dusk implements Runnable { public void moveTo(int dx, int dy) { //Move to location try { - System.out.println("goto " + dx + "," + dy); - outstream.writeBytes("goto " + dx + " " + dy + "\n"); + command("goto " + dx + " " + dy); } catch (Exception e) { frame.log("Error in goto %d,%d: %s", dx, dy, e); } @@ -934,7 +764,10 @@ public class Dusk implements Runnable { command("sell " + quantity + " " + item.name); } - // TODO: find out why these uses the base name and ignores the numbers. + private void ping() { + command("notdead"); + } + public void attack(Entity e) { command("a " + e.ID); } diff --git a/DuskZ/src/duskz/client/Entity.java b/DuskZ/src/duskz/client/Entity.java index f141c10..2e3bdbd 100644 --- a/DuskZ/src/duskz/client/Entity.java +++ b/DuskZ/src/duskz/client/Entity.java @@ -24,7 +24,7 @@ */ package duskz.client; -import duskz.protocol.ServerMessage; +import duskz.protocol.EntityUpdateMessage; public class Entity { @@ -62,8 +62,8 @@ public class Entity { intType = inintType; } - public Entity(ServerMessage.EntityMessage msg) { - strName = msg.name; + public Entity(EntityUpdateMessage msg) { + strName = msg.entityName; locx = msg.x; locy = msg.y; ID = msg.id; diff --git a/DuskZ/src/duskz/client/Equipment.java b/DuskZ/src/duskz/client/Equipment.java index b988c77..7472473 100644 --- a/DuskZ/src/duskz/client/Equipment.java +++ b/DuskZ/src/duskz/client/Equipment.java @@ -21,16 +21,18 @@ */ package duskz.client; +import duskz.protocol.DuskMessage; +import duskz.protocol.DuskMessage.StringMessage; +import duskz.protocol.ListMessage; +import duskz.protocol.TransactionItem; import duskz.protocol.Wearing; -import java.io.DataInputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * This is a bit junk. - * Server code also has a similar mess + * FIXME: merge with inventory "sell list" * * @author notzed */ @@ -80,38 +82,48 @@ public class Equipment implements Wearing { return -1; } - /** - * Decode worn from network - * - * @return - */ - public void updateWorn(DataInputStream instream) throws IOException { - for (int i = 0; i < worn.length; i++) { - worn[i] = instream.readLine(); - System.out.println(" worn: " + worn[i]); + public void updateWorn(ListMessage lm) { + for (int i=0;i= 0 && i < available.length) { - available[i].add(s); - wearableAt.put(s, i); + available[i].add(sm.value); + wearableAt.put(sm.value, i); + } + all.add(sm.value); + } + } + + public void updateAvailable(List list) { + all.clear(); + for (int i = 0; i < available.length; i++) + available[i].clear(); + wearableAt.clear(); + + for (TransactionItem item: list) { + if (item.type >= 0 && item.type < available.length) { + available[item.type].add(item.name); + wearableAt.put(item.name, item.type); } - all.add(s); + all.add(item.name); } + } } diff --git a/DuskZ/src/duskz/client/GUI.java b/DuskZ/src/duskz/client/GUI.java index a6dace1..9bd3490 100644 --- a/DuskZ/src/duskz/client/GUI.java +++ b/DuskZ/src/duskz/client/GUI.java @@ -21,6 +21,7 @@ */ package duskz.client; +import duskz.protocol.TransactionItem; import java.util.List; /** diff --git a/DuskZ/src/duskz/client/Status.java b/DuskZ/src/duskz/client/Status.java index a6efd0e..35d4ce9 100644 --- a/DuskZ/src/duskz/client/Status.java +++ b/DuskZ/src/duskz/client/Status.java @@ -25,7 +25,10 @@ */ package duskz.client; -import java.io.DataInputStream; +import duskz.protocol.DuskMessage; +import duskz.protocol.DuskMessage.IntegerMessage; +import duskz.protocol.DuskProtocol; +import duskz.protocol.ListMessage; import java.io.IOException; /** @@ -33,7 +36,7 @@ import java.io.IOException; * * @author notzed */ -public class Status { +public class Status implements DuskProtocol { long id; // Attack range @@ -42,11 +45,27 @@ public class Status { int mp, maxmp; int locx, locy; - public void updateStatus(DataInputStream instream) throws IOException { - hp = Integer.parseInt(instream.readLine()); - maxhp = Integer.parseInt(instream.readLine()); - mp = Integer.parseInt(instream.readLine()); - maxmp = Integer.parseInt(instream.readLine()); + public void updateStatus(ListMessage lm) throws IOException { + // FIXME: int/dex and all that junk + for (DuskMessage a : lm.value) { + switch (a.name) { + case FIELD_INFO_HP: + hp = ((IntegerMessage) a).value; + break; + case FIELD_INFO_MAXHP: + maxhp = ((IntegerMessage) a).value; + break; + case FIELD_INFO_MP: + mp = ((IntegerMessage) a).value; + break; + case FIELD_INFO_MAXMP: + maxmp = ((IntegerMessage) a).value; + break; + case FIELD_INFO_RANGE: + range = ((IntegerMessage) a).value; + break; + } + } } public void updateLocation(int x, int y) { @@ -54,13 +73,6 @@ public class Status { locy = y; } - public void updateLocation(DataInputStream instream) throws IOException { - //locx = Integer.parseInt(instream.readLine()); - //locy = Integer.parseInt(instream.readLine()); - locx = instream.readShort(); - locy = instream.readShort(); - } - @Override public String toString() { return "HP: " + hp + "/" + maxhp + " MP: " + mp + "/" + maxmp + " Loc: " + locx + "/" + locy; diff --git a/DuskZ/src/duskz/client/TransactionItem.java b/DuskZ/src/duskz/client/TransactionItem.java deleted file mode 100644 index 910b873..0000000 --- a/DuskZ/src/duskz/client/TransactionItem.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is part of DuskZ, a graphical mud engine. - * - * Copyright (C) 2013 Michael Zucchi - * - * 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 3 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, see . - */ -package duskz.client; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Takes a server response and parses it into a name/cost/count - * list suitable for a toolkit. - * - * @deprecated should be merged with DuskCommon's TransactionItem - * @author notzed - */ -@Deprecated -public class TransactionItem implements Comparable { - - public String cost; - public int count; - public String name; - - /** - * Convert a list of items as from the server into a list of - * transaction items. - * i.e. count them up, separate cost. - * - * @param source - * @return - */ - public static List createItems(List source) { - List list = new ArrayList<>(); - - Collections.sort(source); - TransactionItem last = null; - String lastName = null; - for (String s : source) { - if (lastName != null && lastName.equals(s)) { - last.count++; - } else { - int i; - - last = new TransactionItem(); - last.count = 1; - - i = s.indexOf(')'); - if (i != -1) { - last.cost = s.substring(0, i); - last.name = s.substring(i + 1); - } else { - last.name = s; - last.cost = ""; - } - list.add(last); - } - lastName = s; - } - Collections.sort(list); - return list; - } - - public String getName() { - return name; - } - - public int getCount() { - return count; - } - - public String getCost() { - return cost; - } - - @Override - public String toString() { - return cost + ") " + name + "[" + count + "]"; - } - - @Override - public int compareTo(TransactionItem t) { - return name.compareTo(t.name); - } -} diff --git a/DuskZ/src/duskz/client/fx/MainFrameFX.java b/DuskZ/src/duskz/client/fx/MainFrameFX.java index be60d02..60823f7 100644 --- a/DuskZ/src/duskz/client/fx/MainFrameFX.java +++ b/DuskZ/src/duskz/client/fx/MainFrameFX.java @@ -34,7 +34,7 @@ import duskz.client.Entity; import duskz.client.Equipment; import duskz.client.GUI; import duskz.client.Status; -import duskz.client.TransactionItem; +import duskz.protocol.TransactionItem; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; diff --git a/DuskZ/src/duskz/client/fx/TransactionPane.java b/DuskZ/src/duskz/client/fx/TransactionPane.java index 733bdf9..637b6f6 100644 --- a/DuskZ/src/duskz/client/fx/TransactionPane.java +++ b/DuskZ/src/duskz/client/fx/TransactionPane.java @@ -21,7 +21,7 @@ */ package duskz.client.fx; -import duskz.client.TransactionItem; +import duskz.protocol.TransactionItem; import java.util.List; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -95,7 +95,7 @@ public class TransactionPane extends HBox { nameCol.setCellValueFactory(new PropertyValueFactory("name")); TableColumn costCol = new TableColumn("Cost"); - costCol.setCellValueFactory(new PropertyValueFactory("cost")); + costCol.setCellValueFactory(new PropertyValueFactory("costText")); table.getColumns().setAll(countCol, nameCol, costCol);