From 854f4d49407e45d67dd5754afd21a7e59970ca5b Mon Sep 17 00:00:00 2001 From: Joseph Burton Date: Sun, 3 May 2020 21:06:38 +0100 Subject: Multiplayer support (#221) * First pass on multiplayer * Apply review suggestions * Dedicated Enigma server * Don't jump to references when other users do stuff * Better UI + translations * french translation * Apply review suggestions * Document the protocol * Fix most issues with scrolling. * Apply review suggestions * Fix zip hash issues + add a bit more logging * Optimize zip hash * Fix a couple of login bugs * Add message log and user list * Make Message an abstract class * Make status bar work, add chat box * Hide message log/users list when not connected * Fix status bar not resetting entirely * Run stop server task on server thread to prevent multithreading race conditions * Add c2s message to packet id list * Fix message scroll bar not scrolling to the end * Formatting * User list size -> ushort * Combine contains and remove check * Check removal before sending packet * Add password to login packet * Fix the GUI closing the rename text field when someone else renames something * Update fr_fr.json * oups * Make connection/server create dialogs not useless if it fails once * Refactor UI state updating * Fix imports * Fix Collab menu * Fix NPE when rename not allowed * Make the log file a configurable option * Don't use modified UTF * Update fr_fr.json * Bump version to 0.15.4 * Apparently I can't spell neither words nor semantic versions Co-authored-by: Yanis48 Co-authored-by: 2xsaiko --- src/main/java/cuchaz/enigma/utils/Message.java | 392 +++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 src/main/java/cuchaz/enigma/utils/Message.java (limited to 'src/main/java/cuchaz/enigma/utils/Message.java') diff --git a/src/main/java/cuchaz/enigma/utils/Message.java b/src/main/java/cuchaz/enigma/utils/Message.java new file mode 100644 index 0000000..d7c5f23 --- /dev/null +++ b/src/main/java/cuchaz/enigma/utils/Message.java @@ -0,0 +1,392 @@ +package cuchaz.enigma.utils; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Objects; + +import cuchaz.enigma.network.packet.PacketHelper; +import cuchaz.enigma.translation.representation.entry.Entry; + +public abstract class Message { + + public final String user; + + public static Chat chat(String user, String message) { + return new Chat(user, message); + } + + public static Connect connect(String user) { + return new Connect(user); + } + + public static Disconnect disconnect(String user) { + return new Disconnect(user); + } + + public static EditDocs editDocs(String user, Entry entry) { + return new EditDocs(user, entry); + } + + public static MarkDeobf markDeobf(String user, Entry entry) { + return new MarkDeobf(user, entry); + } + + public static RemoveMapping removeMapping(String user, Entry entry) { + return new RemoveMapping(user, entry); + } + + public static Rename rename(String user, Entry entry, String newName) { + return new Rename(user, entry, newName); + } + + public abstract String translate(); + + public abstract Type getType(); + + public static Message read(DataInput input) throws IOException { + byte typeId = input.readByte(); + if (typeId < 0 || typeId >= Type.values().length) { + throw new IOException(String.format("Invalid message type ID %d", typeId)); + } + Type type = Type.values()[typeId]; + String user = input.readUTF(); + switch (type) { + case CHAT: + String message = input.readUTF(); + return chat(user, message); + case CONNECT: + return connect(user); + case DISCONNECT: + return disconnect(user); + case EDIT_DOCS: + Entry entry = PacketHelper.readEntry(input); + return editDocs(user, entry); + case MARK_DEOBF: + entry = PacketHelper.readEntry(input); + return markDeobf(user, entry); + case REMOVE_MAPPING: + entry = PacketHelper.readEntry(input); + return removeMapping(user, entry); + case RENAME: + entry = PacketHelper.readEntry(input); + String newName = input.readUTF(); + return rename(user, entry, newName); + default: + throw new IllegalStateException("unreachable"); + } + } + + public void write(DataOutput output) throws IOException { + output.writeByte(getType().ordinal()); + PacketHelper.writeString(output, user); + } + + private Message(String user) { + this.user = user; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Message message = (Message) o; + return Objects.equals(user, message.user); + } + + @Override + public int hashCode() { + return Objects.hash(user); + } + + public enum Type { + CHAT, + CONNECT, + DISCONNECT, + EDIT_DOCS, + MARK_DEOBF, + REMOVE_MAPPING, + RENAME, + } + + public static final class Chat extends Message { + + public final String message; + + private Chat(String user, String message) { + super(user); + this.message = message; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeString(output, message); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.chat.text"), user, message); + } + + @Override + public Type getType() { + return Type.CHAT; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Chat chat = (Chat) o; + return Objects.equals(message, chat.message); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), message); + } + + @Override + public String toString() { + return String.format("Message.Chat { user: '%s', message: '%s' }", user, message); + } + + } + + public static final class Connect extends Message { + + private Connect(String user) { + super(user); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.connect.text"), user); + } + + @Override + public Type getType() { + return Type.CONNECT; + } + + @Override + public String toString() { + return String.format("Message.Connect { user: '%s' }", user); + } + + } + + public static final class Disconnect extends Message { + + private Disconnect(String user) { + super(user); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.disconnect.text"), user); + } + + @Override + public Type getType() { + return Type.DISCONNECT; + } + + @Override + public String toString() { + return String.format("Message.Disconnect { user: '%s' }", user); + } + + } + + public static final class EditDocs extends Message { + + public final Entry entry; + + private EditDocs(String user, Entry entry) { + super(user); + this.entry = entry; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.edit_docs.text"), user, entry); + } + + @Override + public Type getType() { + return Type.EDIT_DOCS; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + EditDocs editDocs = (EditDocs) o; + return Objects.equals(entry, editDocs.entry); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry); + } + + @Override + public String toString() { + return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry); + } + + } + + public static final class MarkDeobf extends Message { + + public final Entry entry; + + private MarkDeobf(String user, Entry entry) { + super(user); + this.entry = entry; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.mark_deobf.text"), user, entry); + } + + @Override + public Type getType() { + return Type.MARK_DEOBF; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + MarkDeobf markDeobf = (MarkDeobf) o; + return Objects.equals(entry, markDeobf.entry); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry); + } + + @Override + public String toString() { + return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry); + } + + } + + public static final class RemoveMapping extends Message { + + public final Entry entry; + + private RemoveMapping(String user, Entry entry) { + super(user); + this.entry = entry; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.remove_mapping.text"), user, entry); + } + + @Override + public Type getType() { + return Type.REMOVE_MAPPING; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + RemoveMapping that = (RemoveMapping) o; + return Objects.equals(entry, that.entry); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry); + } + + @Override + public String toString() { + return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry); + } + + } + + public static final class Rename extends Message { + + public final Entry entry; + public final String newName; + + private Rename(String user, Entry entry, String newName) { + super(user); + this.entry = entry; + this.newName = newName; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + PacketHelper.writeString(output, newName); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.rename.text"), user, entry, newName); + } + + @Override + public Type getType() { + return Type.RENAME; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Rename rename = (Rename) o; + return Objects.equals(entry, rename.entry) && + Objects.equals(newName, rename.newName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry, newName); + } + + @Override + public String toString() { + return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName); + } + + } + +} -- cgit v1.2.3