diff --git a/scripts/commands/generic/addFriend.py b/scripts/commands/generic/addFriend.py index 485e1ee0a..140dec685 100644 --- a/scripts/commands/generic/addFriend.py +++ b/scripts/commands/generic/addFriend.py @@ -1,29 +1,15 @@ from intents.chat import ChatAvatarRequestIntent -from intents.chat import ChatBroadcastIntent -from resources.encodables import ProsePackage -from intents.chat.ChatAvatarRequestIntent.RequestType import TARGET_STATUS -import sys +from intents.chat.ChatAvatarRequestIntent.RequestType import FRIEND_ADD_TARGET def execute(galacticManager, player, target, args): ghost = player.getPlayerObject() - if ghost is None: + if ghost is None or args is None: return - name = str(args.split(" ")[0]) - name.lower() + name = str(args.split(" ")[0]).lower() - # TODO: Check ignore list for name -- return message @cmnty:friend_fail_is_ignored TT name if ignored - - if ghost.getFriendsList().contains(name): - ChatBroadcastIntent(player, ProsePackage("@cmnty:friend_duplicate", "TT", name)).broadcast() + if not name: return - if galacticManager.getPlayerManager().playerExists(name) is False: - ChatBroadcastIntent(player, ProsePackage("@cmnty:friend_duplicate", "TT", name)).broadcast() - return - - ghost.addFriend(name) - ChatBroadcastIntent(player, ProsePackage("@cmnty:friend_added", "TT", name)).broadcast() - - ChatAvatarRequestIntent(player, name, TARGET_STATUS).broadcast() + ChatAvatarRequestIntent(player, name, FRIEND_ADD_TARGET).broadcast() return diff --git a/scripts/commands/generic/getFriendList.py b/scripts/commands/generic/getFriendList.py new file mode 100644 index 000000000..c75dd1c4b --- /dev/null +++ b/scripts/commands/generic/getFriendList.py @@ -0,0 +1,7 @@ +from intents.chat import ChatAvatarRequestIntent +from intents.chat.ChatAvatarRequestIntent.RequestType import FRIEND_LIST + + +def execute(galacticManager, player, target, args): + ChatAvatarRequestIntent(player, None, FRIEND_LIST).broadcast() + return diff --git a/scripts/commands/generic/removeFriend.py b/scripts/commands/generic/removeFriend.py index 4ff98acf1..6871aacd4 100644 --- a/scripts/commands/generic/removeFriend.py +++ b/scripts/commands/generic/removeFriend.py @@ -1,20 +1,22 @@ from intents.chat import ChatBroadcastIntent from resources.encodables import ProsePackage -import sys +from intents.chat import ChatAvatarRequestIntent +from intents.chat.ChatAvatarRequestIntent.RequestType import FRIEND_REMOVE_TARGET def execute(galacticManager, player, target, args): ghost = player.getPlayerObject() - if ghost is None: + if ghost is None or not args: return - name = str(args.split(" ")[0]) - name.lower() + name = str(args.split(" ")[0]).lower() + + if not name: + return if ghost.getFriendsList().contains(name) is False: ChatBroadcastIntent(player, ProsePackage("@cmnty:friend_not_found", "TT", name)).broadcast() return - ghost.removeFriend(name) - ChatBroadcastIntent(player, ProsePackage("@cmnty:friend_removed", "TT", name)).broadcast() + ChatAvatarRequestIntent(player, name, FRIEND_REMOVE_TARGET).broadcast() return diff --git a/src/intents/chat/ChatAvatarRequestIntent.java b/src/intents/chat/ChatAvatarRequestIntent.java index 8243cbb2a..2ed8d4538 100644 --- a/src/intents/chat/ChatAvatarRequestIntent.java +++ b/src/intents/chat/ChatAvatarRequestIntent.java @@ -60,7 +60,10 @@ public class ChatAvatarRequestIntent extends Intent{ } public enum RequestType { + FRIEND_LIST, TARGET_STATUS, + FRIEND_ADD_TARGET, + FRIEND_REMOVE_TARGET, IGNORE_ADD_TARGET, IGNORE_REMOVE_TARGET } diff --git a/src/intents/chat/ChatBroadcastIntent.java b/src/intents/chat/ChatBroadcastIntent.java index 16dc9043a..86afae761 100644 --- a/src/intents/chat/ChatBroadcastIntent.java +++ b/src/intents/chat/ChatBroadcastIntent.java @@ -53,11 +53,12 @@ public class ChatBroadcastIntent extends Intent { this(null, receiver, null, BroadcastType.PERSONAL); this.prose = prose; } - public ChatBroadcastIntent(String message, BroadcastType type) { this(message, null, null, type); } - + public ChatBroadcastIntent(Player receiver, String message) { + this(message, receiver, null, BroadcastType.PERSONAL); + } public ChatBroadcastIntent(String message) { this(message, null, null, BroadcastType.GALAXY); } diff --git a/src/network/packets/Packet.java b/src/network/packets/Packet.java index 3f37d3571..af0742e69 100644 --- a/src/network/packets/Packet.java +++ b/src/network/packets/Packet.java @@ -34,8 +34,8 @@ import java.nio.charset.Charset; public class Packet { - public static Charset ascii = Charset.forName("UTF-8"); - public static Charset unicode = Charset.forName("UTF-16LE"); + public static final Charset ascii = Charset.forName("UTF-8"); + public static final Charset unicode = Charset.forName("UTF-16LE"); private InetAddress address; private ByteBuffer data; private int port = 0; @@ -125,9 +125,9 @@ public class Packet { addShort(bb, b.length); bb.put(b); } - + public static boolean getBoolean(ByteBuffer bb) { - return getByte(bb) == 1 ? true : false; + return getByte(bb) == 1; } public static String getAscii(ByteBuffer bb) { @@ -149,7 +149,7 @@ public class Packet { bb.get(str); return new String(str, unicode); } - + public static byte getByte(ByteBuffer bb) { return bb.get(); } diff --git a/src/network/packets/swg/zone/ServerWeatherMessage.java b/src/network/packets/swg/zone/ServerWeatherMessage.java index 812eb4f3d..238a57f22 100644 --- a/src/network/packets/swg/zone/ServerWeatherMessage.java +++ b/src/network/packets/swg/zone/ServerWeatherMessage.java @@ -24,15 +24,12 @@ public class ServerWeatherMessage extends SWGPacket { type = WeatherType.CLEAR; break; case 1: - type = WeatherType.CLOUDY; - break; - case 2: type = WeatherType.LIGHT; break; - case 3: + case 2: type = WeatherType.MEDIUM; break; - case 4: + case 3: type = WeatherType.HEAVY; break; } diff --git a/src/network/packets/swg/zone/chat/ChatOnAddFriend.java b/src/network/packets/swg/zone/chat/ChatOnAddFriend.java new file mode 100644 index 000000000..f3dbf6020 --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatOnAddFriend.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Holocore 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Holocore. If not, see + ******************************************************************************/ + +package network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; + +import java.nio.ByteBuffer; + +/** + * @author Waverunner + */ +public class ChatOnAddFriend extends SWGPacket { + public static final int CRC = 0x2B2A0D94; + + public ChatOnAddFriend() {} + + @Override + public ByteBuffer encode() { + ByteBuffer bb = ByteBuffer.allocate(14); + addShort(bb, 3); + addInt(bb, CRC); + addLong(bb, 0); + return bb; + } +} diff --git a/src/network/packets/swg/zone/chat/ChatOnChangeFriendStatus.java b/src/network/packets/swg/zone/chat/ChatOnChangeFriendStatus.java new file mode 100644 index 000000000..148618bca --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatOnChangeFriendStatus.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Holocore 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Holocore. If not, see + ******************************************************************************/ + +package network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; + +import java.nio.ByteBuffer; + +/** + * @author Waverunner + */ +public class ChatOnChangeFriendStatus extends SWGPacket { + public static final int CRC = 0x54336726; + + private String galaxy; + private String friendName; + private boolean remove; + private long playerID; + + public ChatOnChangeFriendStatus(long playerID, String galaxy, String friendName, boolean remove) { + this.playerID = playerID; + this.galaxy = galaxy; + this.friendName = friendName; + this.remove = remove; + } + + @Override + public ByteBuffer encode() { + ByteBuffer bb = ByteBuffer.allocate(32 + galaxy.length() + friendName.length()); + addShort(bb, ((short) 0x06)); + addInt(bb, CRC); + addLong(bb, playerID); + addAscii(bb, "SWG"); + addAscii(bb, galaxy); + addAscii(bb, friendName); + addInt(bb, 0); + addInt(bb, (remove ? 1 : 0)); + addByte(bb, 0); + return bb; + } +} diff --git a/src/network/packets/swg/zone/chat/ChatOnGetFriendsList.java b/src/network/packets/swg/zone/chat/ChatOnGetFriendsList.java new file mode 100644 index 000000000..7460eb40f --- /dev/null +++ b/src/network/packets/swg/zone/chat/ChatOnGetFriendsList.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Holocore 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Holocore. If not, see + ******************************************************************************/ + +package network.packets.swg.zone.chat; + +import network.packets.swg.SWGPacket; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * @author Waverunner + */ +public class ChatOnGetFriendsList extends SWGPacket { + public static final int CRC = 0xE97AB594; + + private List friends; + private String galaxy; + private long objectId; + + public ChatOnGetFriendsList(long objectId, String galaxy, List friends) { + this.objectId = objectId; + this.galaxy = galaxy; + this.friends = friends; + } + + @Override + public ByteBuffer encode() { + int additional = galaxy.length() + 9; + int length = 0; + for (String friend : friends) { + length += friend.length() + additional; + } + + ByteBuffer bb = ByteBuffer.allocate(18 + length); + addShort(bb, 3); + addInt(bb, CRC); + addLong(bb, objectId); + + addInt(bb, friends.size()); + for (String friend : friends) { + addAscii(bb, "SWG"); + addAscii(bb, galaxy); + addAscii(bb, friend); + } + + return bb; + } +} diff --git a/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java b/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java index 198c14733..55863ba5c 100644 --- a/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java +++ b/src/network/packets/swg/zone/chat/ChatOnSendPersistentMessage.java @@ -28,27 +28,29 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import network.packets.swg.SWGPacket; +import services.chat.ChatResult; public class ChatOnSendPersistentMessage extends SWGPacket { public static final int CRC = 0x94E7A7AE; - private int errorCode; + private ChatResult result; private int count; - public ChatOnSendPersistentMessage(int errorCode, int count) { - this.errorCode = errorCode; + public ChatOnSendPersistentMessage(ChatResult result, int count) { + this.result = result; this.count = count; } @Override public ByteBuffer encode() { - ByteBuffer data = ByteBuffer.allocate(20); + ByteBuffer data = ByteBuffer.allocate(14); addShort(data, 3); - addInt(data, errorCode); + addInt(data, CRC); + addInt(data, result.getCode()); addInt(data, count); return data; } - } diff --git a/src/network/packets/swg/zone/chat/ChatPersistentMessageToClient.java b/src/network/packets/swg/zone/chat/ChatPersistentMessageToClient.java index 841757038..2b69d1d50 100644 --- a/src/network/packets/swg/zone/chat/ChatPersistentMessageToClient.java +++ b/src/network/packets/swg/zone/chat/ChatPersistentMessageToClient.java @@ -28,334 +28,44 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.nio.ByteOrder; -import resources.Terrain; -import resources.network.BaselineBuilder.Encodable; +import resources.encodables.player.Mail; import network.packets.swg.SWGPacket; public class ChatPersistentMessageToClient extends SWGPacket { public static final int CRC = 0x08485E17; - private String senderName; + private Mail mail; private String galaxy; - private int mailId; private boolean header; - private String subject; - private String message; - private List waypointAttachments; - private List proseAttachments; - private byte status; - private int timestamp; - - public ChatPersistentMessageToClient(boolean header, String sender, String galaxy, int mailId, String subject, String message, int timestamp, byte status) { - waypointAttachments = new ArrayList(); - proseAttachments = new ArrayList(); - this.senderName = sender; + + public ChatPersistentMessageToClient(Mail mail, String galaxy, boolean header) { + this.mail = mail; this.galaxy = galaxy; - this.mailId = mailId; this.header = header; - this.subject = subject; - this.message = message; - this.status = status; - this.timestamp = timestamp; } - - public ChatPersistentMessageToClient(Encodable mailEncodable) { - - } - public ChatPersistentMessageToClient(ByteBuffer data) { - decode(data); - } - - public void decode(ByteBuffer data) { - if (!super.decode(data, CRC)) - return; - senderName = getAscii(data); - getAscii(data); // SWG - galaxy = getAscii(data); - mailId = getInt(data); - header = getBoolean(data); - - if (header) { - getInt(data); // 0? - subject = getUnicode(data); - getInt(data); - } else { - message = getUnicode(data); - subject = getUnicode(data); - int size = getInt(data) * 2; - int start = data.position(); - do { - getShort(data); // 0? - byte type = getByte(data); - getInt(data); // Integer version of the type, it seems - if (type == 4) - waypointAttachments.add(WaypointAttachment.deserialize(data)); - else if (type == 1) - proseAttachments.add(ProseAttachment.deserialize(data)); - else { - System.err.println("Invalid type supplied in ChatPersistentMessageToClient.decode"); - break; - } - } while (data.position() - start < size); - } - status = getByte(data); - timestamp = getInt(data); - } - + + @Override public ByteBuffer encode() { - int extraSize = 0; - if (header) { - extraSize += 12 + subject.length()*2; - } else { - extraSize += 12 + message.length()*2 + subject.length()*2; - for (WaypointAttachment waypoint : waypointAttachments) - extraSize += 7 + waypoint.getSerializableSize(); - for (ProseAttachment prose : proseAttachments) - extraSize += 7 + prose.getSerializableSize(); - } - ByteBuffer data = ByteBuffer.allocate(25 + extraSize + galaxy.length() + senderName.length()); - addShort(data, 2); - addInt( data, CRC); - addAscii(data, senderName); - addAscii(data, "SWG"); - addAscii(data, galaxy); - addInt(data, mailId); - addBoolean(data, header); - - if (header) { - addInt(data, 0); // message (empty as 1 is sending only the header to the client) - addUnicode(data, subject); - addInt(data, 0); - } else { - addUnicode(data, message); - addUnicode(data, subject); - addInt(data, 0); // TODO: Mail Attachments - /*for (WaypointAttachment waypoint : waypointAttachments) { - addShort(data, 0); - addByte(data, 4); - addInt(data, 0xFFFFFFFD); - waypoint.serialize(data); - } - for (ProseAttachment prose : proseAttachments) { - addShort(data, 0); - addByte(data, 1); - addInt(data, 0xFFFFFFFF); - prose.serialize(data); - }*/ - } - addByte(data, status); - addInt(data, timestamp); - return data; + byte[] data = (header ? mail.encodeHeader() : mail.encode()); + + ByteBuffer bb = ByteBuffer.allocate(25 + galaxy.length() + data.length + (mail.getSender().length() * 2)).order(ByteOrder.LITTLE_ENDIAN); + addShort(bb, 2); + addInt(bb, CRC); + + addAscii(bb, mail.getSender()); + addAscii(bb, "SWG"); + addAscii(bb, galaxy); + addInt(bb, mail.getId()); + addBoolean(bb, header); + + bb.put(data); + + addByte(bb, mail.getStatus()); + addInt(bb, mail.getTimestamp()); + return bb; } - - public void setSenderName(String senderName) { - this.senderName = senderName; - } - - public void setGalaxy(String galaxy) { - this.galaxy = galaxy; - } - - public void setMailId(int mailId) { - this.mailId = mailId; - } - - public void setHeader(boolean header) { - this.header = header; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public void setMessage(String message) { - this.message = message; - } - - public void addWaypoint(WaypointAttachment waypoint) { - waypointAttachments.add(waypoint); - } - - public void addAllWaypoints(Collection waypoints) { - waypointAttachments.addAll(waypoints); - } - - public void addProse(ProseAttachment prose) { - proseAttachments.add(prose); - } - - public void addAllProses(Collection proses) { - proseAttachments.addAll(proses); - } - - public void setStatus(byte status) { - this.status = status; - } - - public void setTimestamp(int timestamp) { - this.timestamp = timestamp; - } - - public static class WaypointAttachment { - private float x; - private float y; - private float z; - private Terrain planet; - private String attachmentName; - private long cellId; - private byte color; - private boolean active; - - public float getX() { return x; } - public float getY() { return y; } - public float getZ() { return z; } - public Terrain getPlanet() { return planet; } - public String getAttachmentName() { return attachmentName; } - public long getCellId() { return cellId; } - public byte getColor() { return color; } - public boolean isActive() { return active; } - - public void setX(float x) { this.x = x; } - public void setY(float y) { this.y = y; } - public void setZ(float z) { this.z = z; } - public void setPlanet(Terrain p) { this.planet = p; } - public void setAttachmentName(String name) { this.attachmentName = name; } - public void setCellId(long id) { this.cellId = id; } - public void setColor(byte c) { this.color = c; } - public void setActive(boolean active) { this.active = active; } - - private final int getSerializableSize() { - return 43 + attachmentName.length()*2; - } - - private static final WaypointAttachment deserialize(ByteBuffer data) { - WaypointAttachment waypoint = new WaypointAttachment(); - getInt(data); // 0? - waypoint.setX(getFloat(data)); - waypoint.setY(getFloat(data)); - waypoint.setZ(getFloat(data)); - getLong(data); // 0? - waypoint.setPlanet(Terrain.getTerrainFromCrc(getInt(data))); - waypoint.setAttachmentName(getUnicode(data)); - waypoint.setCellId(getLong(data)); - waypoint.setColor(getByte(data)); - waypoint.setActive(getBoolean(data)); - getByte(data); // 0? - return waypoint; - } - - public final void serialize(ByteBuffer data) { - addInt (data, 0); - addFloat (data, getX()); - addFloat (data, getY()); - addFloat (data, getZ()); - addLong (data, 0); - addInt (data, planet.getCrc()); - addUnicode(data, attachmentName); - addLong (data, cellId); - addByte (data, color); - addBoolean(data, active); - addByte (data, 0); - } - } - - public static class ProseAttachment { - private String stf; - private ProseSegment tu; - private ProseSegment tt; - private ProseSegment to; - private int diInt; - private float dfFloat; - - public String getSTF() { return stf; } - public ProseSegment getTu() { return tu; } - public ProseSegment getTt() { return tt; } - public ProseSegment getTo() { return to; } - public int getDiInteger() { return diInt; } - public float getDfFloat() { return dfFloat; } - - public void setSTF(String stf) { this.stf = stf; } - public void setTu(ProseSegment tu) { this.tu = tu; } - public void setTt(ProseSegment tt) { this.tt = tt; } - public void setTo(ProseSegment to) { this.to = to; } - public void setDiInteger(int di) { this.diInt = di; } - public void setDfFloat(float df) { this.dfFloat = df; } - - private final int getSerializableSize() { - return 10 + stf.length() + tu.getSerializableSize() + tt.getSerializableSize() + to.getSerializableSize(); - } - - private static final ProseAttachment deserialize(ByteBuffer data) { - ProseAttachment prose = new ProseAttachment(); - prose.setSTF(getAscii(data)); - prose.setTu(ProseSegment.deserialize(data)); - prose.setTt(ProseSegment.deserialize(data)); - prose.setTo(ProseSegment.deserialize(data)); - prose.setDiInteger(getInt(data)); - prose.setDfFloat(getFloat(data)); - return prose; - } - - public final void serialize(ByteBuffer data) { - addAscii(data, stf); - tu.serialize(data); - tt.serialize(data); - to.serialize(data); - addInt(data, diInt); - addFloat(data, dfFloat); - } - - public static class ProseSegment { - private long objectId; - private String stf; - private String customString; - - public long getObjectId() { return objectId; } - public String getSTF() { return stf; } - public String getCustomString() { return customString; } - - public void setObjectId(long id) { this.objectId = id; } - public void setSTF(String stf) { this.stf = stf; } - public void setCustomString(String custom) { this.customString = custom; } - - private final int getSerializableSize() { - return 14 + stf.length() + customString.length()*2; - } - - private static final ProseSegment deserialize(ByteBuffer data) { - ProseSegment segment = new ProseSegment(); - segment.setObjectId(getLong(data)); - segment.setSTF(getAscii(data)); - segment.setCustomString(getUnicode(data)); - return segment; - } - - private final void serialize(ByteBuffer data) { - addLong(data, objectId); - addAscii(data, stf); - addUnicode(data, customString); - } - } - } - /* - * ASCII Stf - * LONG TuObjectId - * ASCII TuStf - * UNICODE TuCustomString - * LONG TtObjectId - * ASCII TtStf - * UNICODE TtCustomString - * LONG ToObjectId - * ASCII ToStf - * UNICODE ToCustomString - * INT DiInteger - * FLOAT DfFloat - * BYTE 0 - */ - + } diff --git a/src/network/packets/swg/zone/chat/ChatPersistentMessageToServer.java b/src/network/packets/swg/zone/chat/ChatPersistentMessageToServer.java index 93e41524d..c5287e046 100644 --- a/src/network/packets/swg/zone/chat/ChatPersistentMessageToServer.java +++ b/src/network/packets/swg/zone/chat/ChatPersistentMessageToServer.java @@ -28,14 +28,16 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import network.packets.swg.SWGPacket; +import resources.encodables.OutOfBandPackage; public class ChatPersistentMessageToServer extends SWGPacket { public static final int CRC = 0x25A29FA6; private String message; - private String outOfBand; + private OutOfBandPackage outOfBandPackage; private int counter; private String subject; private String galaxy; @@ -43,9 +45,8 @@ public class ChatPersistentMessageToServer extends SWGPacket { public ChatPersistentMessageToServer() { message = ""; - outOfBand = ""; + outOfBandPackage = new OutOfBandPackage(); subject = ""; - galaxy = ""; recipient = ""; } @@ -55,10 +56,9 @@ public class ChatPersistentMessageToServer extends SWGPacket { if (!super.decode(data, CRC)) return; message = getUnicode(data); - outOfBand = getUnicode(data); + outOfBandPackage.decode(data); counter = getInt(data); subject = getUnicode(data); - getAscii(data); // "SWG" galaxy = getAscii(data); recipient = getAscii(data); @@ -66,10 +66,11 @@ public class ChatPersistentMessageToServer extends SWGPacket { @Override public ByteBuffer encode() { - int dataLength = 31 + message.length()*2+outOfBand.length()*2+subject.length()*2+galaxy.length()+recipient.length(); + byte[] oob = outOfBandPackage.encode(); + int dataLength = 31 + message.length()*2+oob.length+subject.length()*2+galaxy.length()+recipient.length(); ByteBuffer data = ByteBuffer.allocate(dataLength); addUnicode(data, message); - addUnicode(data, outOfBand); + data.put(oob); addInt(data, counter); addUnicode(data, subject); addAscii(data, "SWG"); @@ -82,8 +83,8 @@ public class ChatPersistentMessageToServer extends SWGPacket { return message; } - public String getOutOfBand() { - return outOfBand; + public OutOfBandPackage getOutOfBandPackage() { + return outOfBandPackage; } public int getCounter() { @@ -106,8 +107,8 @@ public class ChatPersistentMessageToServer extends SWGPacket { this.message = message; } - public void setOutOfBand(String outOfBand) { - this.outOfBand = outOfBand; + public void setOutOfBandPackage(OutOfBandPackage outOfBandPackage) { + this.outOfBandPackage = outOfBandPackage; } public void setCounter(int counter) { diff --git a/src/network/packets/swg/zone/chat/ChatSystemMessage.java b/src/network/packets/swg/zone/chat/ChatSystemMessage.java index 3d6107d8f..c5b096d7d 100644 --- a/src/network/packets/swg/zone/chat/ChatSystemMessage.java +++ b/src/network/packets/swg/zone/chat/ChatSystemMessage.java @@ -29,7 +29,7 @@ package network.packets.swg.zone.chat; import java.nio.ByteBuffer; -import resources.encodables.OutOfBand; +import resources.encodables.OutOfBandPackage; import network.packets.swg.SWGPacket; public class ChatSystemMessage extends SWGPacket { @@ -37,7 +37,7 @@ public class ChatSystemMessage extends SWGPacket { public static final int CRC = 0x6D2A6413; private int type = 0; private String message = ""; - private OutOfBand oob; + private OutOfBandPackage oob; public ChatSystemMessage() { @@ -48,7 +48,7 @@ public class ChatSystemMessage extends SWGPacket { this.message = message; } - public ChatSystemMessage(int type, OutOfBand oob) { + public ChatSystemMessage(int type, OutOfBandPackage oob) { this.type = type; this.oob = oob; } @@ -57,7 +57,7 @@ public class ChatSystemMessage extends SWGPacket { this(type.ordinal(), message); } - public ChatSystemMessage(SystemChatType type, OutOfBand oob) { + public ChatSystemMessage(SystemChatType type, OutOfBandPackage oob) { this(type.ordinal(), oob); } diff --git a/src/network/packets/swg/zone/object_controller/ObjectMenuResponse.java b/src/network/packets/swg/zone/object_controller/ObjectMenuResponse.java index a3c342d37..7e1d67849 100644 --- a/src/network/packets/swg/zone/object_controller/ObjectMenuResponse.java +++ b/src/network/packets/swg/zone/object_controller/ObjectMenuResponse.java @@ -56,7 +56,7 @@ public class ObjectMenuResponse extends ObjectController { public ObjectMenuResponse(ByteBuffer data) { super(CRC); - options = new ArrayList(); + options = new ArrayList<>(); decode(data); } @@ -72,6 +72,7 @@ public class ObjectMenuResponse extends ObjectController { option.setId(getShort(data)); option.setOptionType(getByte(data)); option.setText(getUnicode(data)); + options.add(option); } counter = getByte(data); } diff --git a/src/resources/Terrain.java b/src/resources/Terrain.java index b0ab3bdc0..38469cb39 100644 --- a/src/resources/Terrain.java +++ b/src/resources/Terrain.java @@ -123,8 +123,8 @@ public enum Terrain { Terrain(String file) { this.file = file; - this.crc = CRC.getCrc(file); this.name = file.substring(8, file.length() - 4); + this.crc = CRC.getCrc(name); } public String getFile() { return file; } diff --git a/src/resources/WeatherType.java b/src/resources/WeatherType.java index e68f1405a..63b65e187 100644 --- a/src/resources/WeatherType.java +++ b/src/resources/WeatherType.java @@ -1,19 +1,30 @@ package resources; +/** + * Make sure that the chances for each weather type add up to 1.0 (100%). + * @author Ziggy + * + */ public enum WeatherType { - CLEAR(0), - CLOUDY(1), - LIGHT(2), - MEDIUM(3), - HEAVY(4); + CLEAR(0, .60f), // 60% chance + LIGHT(1, .20f), // 20% chance + MEDIUM(2, .15f), // 15% chance + HEAVY(3, .05f); // 5% chance private int value; + private float chance; - WeatherType(int value) { + WeatherType(int value, float chance) { this.value = value; + this.chance = chance; } public int getValue() { return value; } + + public float getChance() { + return chance; + } + } diff --git a/src/resources/collections/SWGList.java b/src/resources/collections/SWGList.java index bfcd0124c..e66fee37f 100644 --- a/src/resources/collections/SWGList.java +++ b/src/resources/collections/SWGList.java @@ -231,7 +231,24 @@ public class SWGList extends AbstractList implements Encodable { // Clear the queue since the delta has been sent to observers through the builder clearDeltaQueue(); } - + + public void sendRefreshedListData(SWGObject target) { + clearDeltaQueue(); + updateCount = 0; + + ByteBuffer bb = ByteBuffer.allocate(11 + dataSize).order(ByteOrder.LITTLE_ENDIAN); + bb.putInt(data.size() + 1); + bb.putInt(updateCount += data.size() + 1); + bb.put((byte) 3); + bb.putShort((short) data.size()); + for (byte[] bytes : data) { + bb.put(bytes); + } + + DeltaBuilder builder = new DeltaBuilder(target, baseline, view, updateType, bb.array()); + builder.send(); + } + public void clearDeltaQueue() { deltas.clear(); deltaSize = 0; @@ -271,6 +288,10 @@ public class SWGList extends AbstractList implements Encodable { data.add(encodedData); } + createIndexedDelta(encodedData, index, update); + } + + private void createIndexedDelta(byte[] encodedData, int index, byte update) { ByteBuffer buffer = ByteBuffer.allocate(encodedData.length + 2).order(ByteOrder.LITTLE_ENDIAN); buffer.putShort((short) index); buffer.put(encodedData); diff --git a/src/resources/commands/callbacks/FindFriendCallback.java b/src/resources/commands/callbacks/FindFriendCallback.java new file mode 100644 index 000000000..5782acaf0 --- /dev/null +++ b/src/resources/commands/callbacks/FindFriendCallback.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Holocore 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Holocore. If not, see + ******************************************************************************/ + +package resources.commands.callbacks; + +import intents.chat.ChatBroadcastIntent; +import resources.Location; +import resources.commands.ICmdCallback; +import resources.encodables.ProsePackage; +import resources.objects.SWGObject; +import resources.objects.player.PlayerObject; +import resources.objects.waypoint.WaypointObject; +import resources.player.Player; +import resources.player.PlayerState; +import services.galaxy.GalacticManager; + +import java.util.Map; + +/** + * @author Waverunner + */ +public class FindFriendCallback implements ICmdCallback { + @Override + public void execute(GalacticManager galacticManager, Player player, SWGObject target, String args) { + PlayerObject ghost = player.getPlayerObject(); + + if (ghost == null || args.isEmpty()) + return; + + String friendName = args.split(" ")[0].toLowerCase(); + + if (!ghost.getFriendsList().contains(friendName)) { + new ChatBroadcastIntent(player, "@ui_cmnty:friend_location_failed_noname").broadcast(); + return; + } + + Player friend = galacticManager.getPlayerManager().getPlayerByCreatureFirstName(friendName); + if (friend == null || friend.getPlayerState() != PlayerState.ZONED_IN) { + new ChatBroadcastIntent(player, new ProsePackage("@ui_cmnty:friend_location_failed", "TU", friendName)).broadcast(); + return; + } + + PlayerObject friendGhost = friend.getPlayerObject(); + if (friendGhost == null || !friendGhost.getFriendsList().contains(player.getCharacterName().split(" ")[0].toLowerCase())) { + new ChatBroadcastIntent(player, new ProsePackage("@ui_cmnty:friend_location_failed", "TU", friendName)).broadcast(); + return; + } + + Location location = friend.getCreatureObject().getLocation(); + + WaypointObject waypoint = null; + for (Map.Entry entry : ghost.getWaypoints().entrySet()) { + WaypointObject waypointEntry = entry.getValue(); + if (waypointEntry == null || !waypointEntry.getName().equals(friendName)) + continue; + + waypoint = waypointEntry; + break; + } + + if (waypoint == null) { + waypoint = (WaypointObject) galacticManager.getObjectManager().createObject("object/waypoint/shared_waypoint.iff", location, false); + waypoint.setColor(WaypointObject.WaypointColor.PURPLE); + waypoint.setName(friendName); + ghost.addWaypoint(waypoint); + new ChatBroadcastIntent(player, new ProsePackage("@ui_cmnty:friend_location_create_new_wp", "TU", friendName)).broadcast(); + } else { + waypoint.setLocation(location); + ghost.updateWaypoint(waypoint); + new ChatBroadcastIntent(player, new ProsePackage("@ui_cmnty:friend_location", "TU", friendName)).broadcast(); + } + } +} diff --git a/src/resources/commands/callbacks/WaypointCmdCallback.java b/src/resources/commands/callbacks/WaypointCmdCallback.java index 81a687ac4..b34753d8d 100644 --- a/src/resources/commands/callbacks/WaypointCmdCallback.java +++ b/src/resources/commands/callbacks/WaypointCmdCallback.java @@ -27,6 +27,7 @@ ***********************************************************************************/ package resources.commands.callbacks; +import intents.chat.ChatBroadcastIntent; import resources.Location; import resources.Terrain; import resources.commands.ICmdCallback; @@ -45,61 +46,144 @@ public class WaypointCmdCallback implements ICmdCallback { PlayerObject ghost = player.getPlayerObject(); if (ghost == null) return; - - String[] cmdArgs = args.split(" "); - if (cmdArgs.length > 6) - cmdArgs = args.split(" ", 6); - WaypointColor color = null; + String[] cmdArgs = args.split(" "); + + WaypointColor color = WaypointColor.BLUE; Terrain terrain = null; String name = null; - float x = -1; - float y = -1; + float x = Float.NaN; + float z = Float.NaN; + + // Validate parameters, format for proper command arguments as it's split at whitespace + for (int i = 0; i < cmdArgs.length; i++) { + switch(i) { + // This could be either for just a named waypoint at current spot (1 param) or a planet arg (6 param) + case 0: + if (Terrain.getTerrainFromName(cmdArgs[0]) != null) { + // Terrain's name could also be part of the waypoint name, check to see if next few args are coords + try { + if (cmdArgs.length > 2) { + x = Float.parseFloat(cmdArgs[1]); + if (cmdArgs.length >= 3) { + z = Float.parseFloat(cmdArgs[2]); // Just to be sure.. Maybe someone wanted some numbers in the name. + if (cmdArgs.length != 6) + cmdArgs = args.split(" ", 6); + } + } + } catch (NumberFormatException e) { + // This is just a named waypoint. + cmdArgs = new String[]{args}; + } + } else { + // This is just a named waypoint. + cmdArgs = new String[]{args}; + } + break; + // This could be either for a name (3 param) or a z coordinate (6 param) + case 3: + try { + z = Float.parseFloat(cmdArgs[3]); + // Ensure 100% this is a 6 argument command as the first param MUST be the planet name + if (Terrain.getTerrainFromName(cmdArgs[0]) != null) + if (cmdArgs.length != 6) + cmdArgs = args.split(" ", 6); + else cmdArgs = args.split(" ", 4); + } catch (NumberFormatException e) { + // This is intended for a name, should be 4 params + cmdArgs = args.split(" ", 4); + } + break; + default: break; + } + } + + // Was there an error message saying the format was wrong? switch(cmdArgs.length) { + case 1: // name + name = cmdArgs[0]; + break; case 2: // x y x = floatValue(cmdArgs[0]); - if (x == -1) - return; - y = floatValue(cmdArgs[1]); + if (Float.isNaN(x)) + break; + z = floatValue(cmdArgs[1]); break; - case 4: // x z y name + case 3: // x y z x = floatValue(cmdArgs[0]); - if (x == -1) - return; - //z = floatValue(cmdArgs[1]); - y = floatValue(cmdArgs[2]); + if (Float.isNaN(x)) + break; + //y = floatValue(cmdArgs[1]); + z = floatValue(cmdArgs[2]); + break; + case 4: // x y z name + x = floatValue(cmdArgs[0]); + if (Float.isNaN(x)) + break; + //y = floatValue(cmdArgs[1]); + z = floatValue(cmdArgs[2]); + if (Float.isNaN(z)) + break; name = cmdArgs[3]; break; - case 6: // planet x z y color name + case 6: // planet x y z color name terrain = Terrain.getTerrainFromName(cmdArgs[0]); if (terrain == null) - return; + break; x = floatValue(cmdArgs[1]); - if (x == -1) - return; - //z = floatValue(cmdArgs[2]); - y = floatValue(cmdArgs[3]); - color = colorValue(cmdArgs[4]); + if (Float.isNaN(x)) + break; + //y = floatValue(cmdArgs[2]); + z = floatValue(cmdArgs[3]); + if (Float.isNaN(z)) + break; + color = WaypointColor.fromString(cmdArgs[4]); name = cmdArgs[5]; break; - default: - break; + default: + // Not a valid format for /waypoint command + return; + } + + Location location = new Location(player.getCreatureObject().getLocation()); + + if (!Float.isNaN(x)) + location.setX(x); + + if (!Float.isNaN(z)) + location.setZ(z); + + boolean differentPlanetMessage = false; + if (terrain != null) { + if (terrain != location.getTerrain()) { + location.setTerrain(terrain); + differentPlanetMessage = true; + } + } + + if (name == null) + name = "Waypoint"; + + WaypointObject waypoint = createWaypoint(galacticManager.getObjectManager(), color, name, location); + ghost.addWaypoint(waypoint); + + if (differentPlanetMessage) { + new ChatBroadcastIntent(player, "Waypoint: New waypoint \""+ name + "\" created for location " + + terrain.getName() + " (" + String.format("%.0f", location.getX()) + ", " + + String.format("%.0f", location.getY()) + ", "+ String.format("%.0f", location.getZ()) + ")").broadcast(); + } else { + new ChatBroadcastIntent(player, "Waypoint: New waypoint \""+ name + "\" created for location (" + + String.format("%.0f", location.getX()) + ", "+ String.format("%.0f", location.getY()) + + ", "+ String.format("%.0f", location.getZ()) + ")").broadcast(); } - WaypointObject waypoint = createWaypoint(galacticManager.getObjectManager(), terrain, color, name, x, y, player.getCreatureObject().getLocation()); - ghost.addWaypoint(waypoint); - } - private WaypointObject createWaypoint(ObjectManager objManager, Terrain terrain, WaypointColor color, String name, float x, float y, Location loc) { - WaypointObject waypoint = (WaypointObject) objManager.createObject("object/waypoint/shared_waypoint.iff", false); - - waypoint.setLocation(new Location((x != -1 ? x : loc.getX()), 0, (y != -1 ? y : loc.getZ()), (terrain != null ? terrain : loc.getTerrain()))); - if (color != null) - waypoint.setColor(color); - - waypoint.setName(name == null ? "New Waypoint" : name); - + private WaypointObject createWaypoint(ObjectManager objManager, WaypointColor color, String name, Location location) { + WaypointObject waypoint = (WaypointObject) objManager.createObject("object/waypoint/shared_waypoint.iff", location, false); + waypoint.setColor(color); + waypoint.setName(name); + // TODO: Check if the location collides with a building, and if it does then set the proper cellId return waypoint; } @@ -107,19 +191,7 @@ public class WaypointCmdCallback implements ICmdCallback { try { return Float.parseFloat(str); } catch (NumberFormatException | NullPointerException e) { - return (float) -1; - } - } - - private WaypointColor colorValue(String str) { - switch (str) { - case "blue": return WaypointColor.BLUE; - case "green": return WaypointColor.GREEN; - case "yellow": return WaypointColor.YELLOW; - case "white": return WaypointColor.WHITE; - case "orange": return WaypointColor.ORANGE; - case "purple": return WaypointColor.PURPLE; - default: return WaypointColor.BLUE; + return Float.NaN; } } } diff --git a/src/resources/containers/ContainerPermissions.java b/src/resources/containers/ContainerPermissions.java index 6c0edb3a4..b411cc595 100644 --- a/src/resources/containers/ContainerPermissions.java +++ b/src/resources/containers/ContainerPermissions.java @@ -40,8 +40,8 @@ import java.util.*; public abstract class ContainerPermissions implements Serializable { private static final long serialVersionUID = 1L; - public static WorldPermissions WORLD = new WorldPermissions(); - public static InventoryPermissions INVENTORY = new InventoryPermissions(); + public static final WorldPermissions WORLD = new WorldPermissions(); + public static final InventoryPermissions INVENTORY = new InventoryPermissions(); private Map permissionGroups; private List joinedGroups; @@ -160,7 +160,8 @@ public abstract class ContainerPermissions implements Serializable { } public static int valueOf(EnumSet bitmaskSet) { - return valueOf(bitmaskSet); + Permission[] permissions = bitmaskSet.toArray(new Permission[0]); + return valueOf(permissions); } public static int valueOf(Permission... permissions) { diff --git a/src/resources/encodables/OutOfBand.java b/src/resources/encodables/OutOfBandPackage.java similarity index 52% rename from src/resources/encodables/OutOfBand.java rename to src/resources/encodables/OutOfBandPackage.java index 2b86bf558..83962a2a5 100644 --- a/src/resources/encodables/OutOfBand.java +++ b/src/resources/encodables/OutOfBandPackage.java @@ -29,53 +29,121 @@ package resources.encodables; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import resources.network.BaselineBuilder.Encodable; +import resources.objects.waypoint.WaypointObject; -public class OutOfBand implements Encodable { - // Multiple ProsePackages, need to find an example of it in a packet. Also note that OutOfBand's did not have just ProsePackages. - +public class OutOfBandPackage implements Encodable { private static final long serialVersionUID = 1L; - private ProsePackage prose; - private Type type; + private List packages; + private transient List data; + private transient int dataSize; - public OutOfBand(ProsePackage prose) { - this.prose = prose; - this.type = Type.PROSE_PACKAGE; + public OutOfBandPackage() { + packages = new ArrayList<>(5); } - - public static OutOfBand ProsePackage(Object ... objects) { - return new OutOfBand(new ProsePackage(objects)); + + public OutOfBandPackage(OutOfBandData outOfBandData) { + this(); + packages.add(outOfBandData); } - - public static OutOfBand ProsePackage(Object stf, String key, Object prose) { - return new OutOfBand(new ProsePackage(stf, key, prose)); + + public static OutOfBandPackage createWithProse(Object stf, String key, Object prose) { + return new OutOfBandPackage(new ProsePackage(stf, key, prose)); } - + @Override public byte[] encode() { - byte[] encodedProse = prose.encode(); + if (packages.size() == 0) + return new byte[4]; - ByteBuffer bb = ByteBuffer.allocate(11 + encodedProse.length).order(ByteOrder.LITTLE_ENDIAN); + if (data == null) { + data = new LinkedList<>(); - bb.putInt((7 + encodedProse.length) / 2); - bb.putShort((short) 0); // ?? -- seen as 0 and 1 - bb.put((byte) type.ordinal()); - bb.putInt(-1); // ?? - bb.put(encodedProse); + for (OutOfBandData outOfBandData : packages) { + byte[] bytes = outOfBandData.encodeOutOfBandData(); + dataSize += bytes.length; + data.add(bytes); + } + } + + ByteBuffer bb = ByteBuffer.allocate(4 + dataSize).order(ByteOrder.LITTLE_ENDIAN); + + bb.putInt(dataSize / 2); + + for (byte[] bytes : data) { + bb.put(bytes); + } return bb.array(); } + public void decode(ByteBuffer data) { + int size = data.getInt() * 2; + + for (int read = 0; read < size; read+= 3) { + boolean addedByte = data.getShort() > 0; // ?? Seen as 1 + + Type type = Type.valueOf(data.get()); + switch(type) { + case PROSE_PACKAGE: + ProsePackage prose = new ProsePackage(); + read += prose.decodeOutOfBandData(data); + packages.add(prose); + break; + case WAYPOINT: + WaypointObject waypoint = new WaypointObject(-1); + read += waypoint.decodeOutOfBandData(data); + packages.add(waypoint); + break; + default: + System.err.println("Tried to decode an unsupported OutOfBandData Type: " + type); + break; + } + + if (addedByte) { + data.get(); + read+= 1; + } + } + } + public enum Type { - OBJECT, - PROSE_PACKAGE, - UNKNOWN, - AUCTION_TOKEN, - WAYPOINT, - STRING_ID, - STRING, - UNKNOWN_2 + NONE(-1), + OBJECT(0), + PROSE_PACKAGE(1), + UNKNOWN(2), + AUCTION_TOKEN(3), + WAYPOINT(4), + STRING_ID(5), + STRING(6), + UNKNOWN_2(7); + + byte type; + + Type(int type) { + this.type = (byte) type; + } + + public byte getType() { + return type; + } + + public static Type valueOf(byte typeByte) { + for (Type type : Type.values()) { + if (type.getType() == typeByte) + return type; + } + return Type.NONE; + } + } + + public interface OutOfBandData extends Encodable { + byte[] encodeOutOfBandData(); + int decodeOutOfBandData(ByteBuffer data); } } diff --git a/src/resources/encodables/ProsePackage.java b/src/resources/encodables/ProsePackage.java index 388c33b27..898d5b873 100644 --- a/src/resources/encodables/ProsePackage.java +++ b/src/resources/encodables/ProsePackage.java @@ -34,10 +34,10 @@ import java.nio.charset.Charset; import resources.network.BaselineBuilder.Encodable; -public class ProsePackage implements Encodable { +public class ProsePackage implements OutOfBandPackage.OutOfBandData { private static final long serialVersionUID = 1L; - private Stf base; + private Stf base = new Stf("", ""); private Prose actor = new Prose(); private Prose target = new Prose(); @@ -50,6 +50,8 @@ public class ProsePackage implements Encodable { private boolean grammarFlag = false; + public ProsePackage() {} + /** * Creates a new ProsePackage that contains only 1 parameter for the specified STF object *

@@ -191,10 +193,10 @@ public class ProsePackage implements Encodable { byte[] targetData = target.encode(); byte[] otherData = other.encode(); - int size = 9 + stringData.length + actorData.length + targetData.length + otherData.length; - - ByteBuffer bb = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); - + ByteBuffer bb = ByteBuffer.allocate(16 + stringData.length + actorData.length + targetData.length + otherData.length).order(ByteOrder.LITTLE_ENDIAN); + bb.putShort((short) 0); // ?? + bb.put(OutOfBandPackage.Type.PROSE_PACKAGE.getType()); + bb.putInt(-1); // ?? bb.put(stringData); bb.put(actorData); bb.put(targetData); @@ -208,6 +210,17 @@ public class ProsePackage implements Encodable { return bb.array(); } + + @Override + public byte[] encodeOutOfBandData() { + return encode(); + } + + @Override + public int decodeOutOfBandData(ByteBuffer data) { + return 0; + } + @Override public String toString() { return "[ProsePackage] " + base.toString() + " Actor=" + actor.toString() diff --git a/src/resources/encodables/player/Mail.java b/src/resources/encodables/player/Mail.java index 8557b32ef..4766fa67f 100644 --- a/src/resources/encodables/player/Mail.java +++ b/src/resources/encodables/player/Mail.java @@ -27,7 +27,12 @@ ***********************************************************************************/ package resources.encodables.player; +import resources.encodables.OutOfBandPackage; import resources.network.BaselineBuilder.Encodable; +import utilities.Encoder; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; public class Mail implements Encodable { private static final long serialVersionUID = 1L; @@ -37,9 +42,9 @@ public class Mail implements Encodable { private long receiverId; private String subject; private String message; + private OutOfBandPackage outOfBandPackage; private byte status; private int timestamp; - // TODO: Waypoint attachments public static final byte NEW = 0x4E; public static final byte READ = 0x52; @@ -52,12 +57,6 @@ public class Mail implements Encodable { this.receiverId = receiverId; this.status = NEW; } - - @Override - public byte [] encode() { - // TODO Auto-generated method stub - return new byte[0]; - } public int getId() { return id; @@ -98,4 +97,26 @@ public class Mail implements Encodable { public void setTimestamp(int timestamp) { this.timestamp = timestamp; } + + public void setOutOfBandPackage(OutOfBandPackage outOfBandPackage) { + this.outOfBandPackage = outOfBandPackage; + } + + @Override + public byte[] encode() { + byte[] oob = outOfBandPackage.encode(); + ByteBuffer bb = ByteBuffer.allocate(8 + (message.length() * 2) + (subject.length() * 2) + oob.length).order(ByteOrder.LITTLE_ENDIAN); + bb.put(Encoder.encodeUnicode(message)); + bb.put(Encoder.encodeUnicode(subject)); + bb.put(oob); + return bb.array(); + } + + public byte[] encodeHeader() { + ByteBuffer bb = ByteBuffer.allocate(12 + subject.length() * 2).order(ByteOrder.LITTLE_ENDIAN); + bb.putInt(0); + bb.put(Encoder.encodeUnicode(subject)); + bb.putInt(0); + return bb.array(); + } } diff --git a/src/resources/network/BaselineBuilder.java b/src/resources/network/BaselineBuilder.java index 992218d6c..e412e32bf 100644 --- a/src/resources/network/BaselineBuilder.java +++ b/src/resources/network/BaselineBuilder.java @@ -163,7 +163,7 @@ public class BaselineBuilder { } public interface Encodable extends Serializable { - public byte [] encode(); + byte [] encode(); } } diff --git a/src/resources/objects/SWGObject.java b/src/resources/objects/SWGObject.java index fa05aef2b..e2f84deaf 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -493,7 +493,7 @@ public abstract class SWGObject implements Serializable, Comparable { if (volume == null) return 0; try { - return Integer.valueOf(volume.toString()); + return Integer.parseInt(volume.toString()); } catch (NumberFormatException e) { return 0; } diff --git a/src/resources/objects/waypoint/WaypointObject.java b/src/resources/objects/waypoint/WaypointObject.java index c8fd7dc72..3afd881cc 100644 --- a/src/resources/objects/waypoint/WaypointObject.java +++ b/src/resources/objects/waypoint/WaypointObject.java @@ -30,18 +30,22 @@ package resources.objects.waypoint; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import network.packets.Packet; import network.packets.swg.zone.baselines.Baseline.BaselineType; +import resources.Location; +import resources.Terrain; import resources.common.CRC; +import resources.encodables.OutOfBandPackage; import resources.network.BaselineBuilder.Encodable; import resources.objects.intangible.IntangibleObject; import resources.player.Player; import utilities.Encoder; -public class WaypointObject extends IntangibleObject implements Encodable { +public class WaypointObject extends IntangibleObject implements OutOfBandPackage.OutOfBandData { private static final long serialVersionUID = 1L; - private int cellNumber; + private long cellId; private String name = "New Waypoint"; private WaypointColor color = WaypointColor.BLUE; private boolean active = true; @@ -50,89 +54,136 @@ public class WaypointObject extends IntangibleObject implements Encodable { super(objectId, BaselineType.WAYP); } - - public int getCellNumber() { - return cellNumber; - } - - - public void setCellNumber(int cellNumber) { - this.cellNumber = cellNumber; - } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public WaypointColor getColor() { return color; } - public void setColor(WaypointColor color) { this.color = color; } - public boolean isActive() { return active; } - + public void setActive(boolean active) { this.active = active; } + public long getCellId() { + return cellId; + } + + public void setCellId(long cellId) { + this.cellId = cellId; + } + public void createObject(Player target) { // NOTE: Client is never sent a WAYP baseline in NGE, WaypointObject's just go inside the Waypoint List in PLAY. } @Override public byte[] encode() { + Location loc = getLocation(); ByteBuffer bb = ByteBuffer.allocate(42 + name.length() * 2).order(ByteOrder.LITTLE_ENDIAN); - bb.putInt(cellNumber); - bb.putFloat((float) getLocation().getX()); - bb.putFloat((float) 0); - bb.putFloat((float) getLocation().getZ()); - bb.putLong(0); // Network id, used for clusters - bb.putInt(CRC.getCrc(getLocation().getTerrain().getName())); + bb.putInt(0); + bb.putFloat((float) loc.getX()); + bb.putFloat((float) loc.getY()); + bb.putFloat((float) loc.getZ()); + bb.putLong(cellId); + bb.putInt(CRC.getCrc(loc.getTerrain().getName())); bb.put(Encoder.encodeUnicode(name)); bb.putLong(getObjectId()); bb.put((byte) color.getValue()); bb.put((byte) (active ? 1 : 0)); return bb.array(); } - + + @Override + public byte[] encodeOutOfBandData() { + byte[] encoded = encode(); + ByteBuffer bb = ByteBuffer.allocate(encoded.length + 8).order(ByteOrder.LITTLE_ENDIAN); + bb.putShort((short) 1); // ?? + bb.put(OutOfBandPackage.Type.WAYPOINT.getType()); + bb.putInt(-3); // ?? + bb.put(encoded); + bb.put((byte) 0); + return bb.array(); + } + + @Override + public int decodeOutOfBandData(ByteBuffer data) { + data.getInt(); // -3 + data.getInt(); + setLocation(data.getFloat(), data.getFloat(), data.getFloat()); + cellId = data.getLong(); + getLocation().setTerrain(Terrain.getTerrainFromCrc(data.getInt())); + name = Packet.getUnicode(data); + data.getLong(); + color = WaypointColor.valueOf(data.get()); + active = Packet.getBoolean(data); + return 46 + name.length() * 2; + } + @Override public boolean equals(Object o) { if (!super.equals(o)) return false; if (o instanceof WaypointObject) { WaypointObject wp = (WaypointObject) o; - return wp.name.equals(name) && wp.cellNumber == cellNumber && wp.color == color && wp.active == active; + return wp.name.equals(name) && wp.cellId == cellId && wp.color == color && wp.active == active; } return false; } @Override public int hashCode() { - return ((super.hashCode() * 7 + name.hashCode()) * 13 + color.getValue()) * 17 + cellNumber; + return ((super.hashCode() * 7 + name.hashCode()) * 13 + color.getValue()) * 17 + (int) cellId; } - + + @Override + public String toString() { + return "[WaypointObject] " + getLocation() + " Name: " + name + " Color: " + color + " Active: " + active; + } + public enum WaypointColor{ BLUE(1), GREEN(2), ORANGE(3), YELLOW(4), PURPLE(5), WHITE(6), MULTICOLOR(7); private int i; - private WaypointColor(int i) { + WaypointColor(int i) { this.i = i; } public int getValue() { return i; } + + public static WaypointColor valueOf(int colorId) { + for (WaypointColor color : WaypointColor.values()) { + if (color.getValue() == colorId) + return color; + } + return WaypointColor.BLUE; + } + + public static WaypointColor fromString(String string) { + switch(string) { + case "blue": return WaypointColor.BLUE; + case "green": return WaypointColor.GREEN; + case "orange": return WaypointColor.ORANGE; + case "yellow": return WaypointColor.YELLOW; + case "purple": return WaypointColor.PURPLE; + case "white": return WaypointColor.WHITE; + case "multicolor": return WaypointColor.MULTICOLOR; + default: return WaypointColor.BLUE; + } + } } } diff --git a/src/resources/objects/weapon/WeaponObject.java b/src/resources/objects/weapon/WeaponObject.java index 88f6c78a7..c29691483 100644 --- a/src/resources/objects/weapon/WeaponObject.java +++ b/src/resources/objects/weapon/WeaponObject.java @@ -133,7 +133,7 @@ public class WeaponObject extends TangibleObject implements Encodable{ byte[] data3 = bb.buildAsBaselinePacket(); bb = new BaselineBuilder(this, BaselineType.WEAO, 6); - createBaseline6(null, bb); + createBaseline6(null, bb); // TODO: This needs to have a target otherwise null pointer will be thrown, new encode function in encodable? byte[] data6 = bb.buildAsBaselinePacket(); byte[] ret = new byte[data3.length + data6.length]; diff --git a/src/services/chat/ChatResult.java b/src/services/chat/ChatResult.java new file mode 100644 index 000000000..700d294d9 --- /dev/null +++ b/src/services/chat/ChatResult.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Holocore 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Holocore. If not, see + ******************************************************************************/ + +package services.chat; + +/** + * @author Waverunner + */ +public enum ChatResult { + NONE(-1), + SUCCESS(0), + TARGET_AVATAR_DOESNT_EXIST(4), + IGNORED(23); + + + private final int code; + ChatResult(int code) { + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/src/services/chat/ChatService.java b/src/services/chat/ChatService.java index 5983d25f3..3d662d15e 100644 --- a/src/services/chat/ChatService.java +++ b/src/services/chat/ChatService.java @@ -34,6 +34,7 @@ import intents.chat.ChatBroadcastIntent; import intents.chat.PersistentMessageIntent; import intents.chat.SpatialChatIntent; import intents.network.GalacticPacketIntent; +import intents.player.ZonePlayerSwapIntent; import intents.server.ServerStatusIntent; import network.packets.Packet; import network.packets.swg.SWGPacket; @@ -42,6 +43,9 @@ import network.packets.swg.zone.chat.ChatDeletePersistentMessage; import network.packets.swg.zone.chat.ChatFriendsListUpdate; import network.packets.swg.zone.chat.ChatInstantMessageToCharacter; import network.packets.swg.zone.chat.ChatInstantMessageToClient; +import network.packets.swg.zone.chat.ChatOnAddFriend; +import network.packets.swg.zone.chat.ChatOnChangeFriendStatus; +import network.packets.swg.zone.chat.ChatOnGetFriendsList; import network.packets.swg.zone.chat.ChatOnSendInstantMessage; import network.packets.swg.zone.chat.ChatOnSendPersistentMessage; import network.packets.swg.zone.chat.ChatPersistentMessageToClient; @@ -51,9 +55,10 @@ import network.packets.swg.zone.chat.ChatSystemMessage; import network.packets.swg.zone.chat.ChatSystemMessage.SystemChatType; import network.packets.swg.zone.object_controller.SpatialChat; import resources.Terrain; +import resources.collections.SWGList; import resources.control.Intent; import resources.control.Service; -import resources.encodables.OutOfBand; +import resources.encodables.OutOfBandPackage; import resources.encodables.ProsePackage; import resources.encodables.player.Mail; import resources.objects.SWGObject; @@ -76,7 +81,7 @@ public class ChatService extends Service { private int maxMailId; public ChatService() { - mails = new CachedObjectDatabase("odb/mails.db"); + mails = new CachedObjectDatabase<>("odb/mails.db"); maxMailId = 1; } @@ -89,6 +94,7 @@ public class ChatService extends Service { registerForIntent(ChatBroadcastIntent.TYPE); registerForIntent(ServerStatusIntent.TYPE); registerForIntent(ChatAvatarRequestIntent.TYPE); + registerForIntent(ZonePlayerSwapIntent.TYPE); mails.load(); mails.traverse(new Traverser() { @Override @@ -174,10 +180,13 @@ public class ChatService extends Service { private void handlePlayerEventIntent(PlayerEventIntent intent) { switch (intent.getEvent()) { case PE_FIRST_ZONE: - sendPersistentMessageHeaders(intent.getPlayer(), intent.getGalaxy()); - updateChatAvatarStatus(intent.getPlayer(), intent.getGalaxy(), true); + Player player = intent.getPlayer(); + sendPersistentMessageHeaders(player, intent.getGalaxy()); + updateChatAvatarStatus(player, intent.getGalaxy(), true); break; case PE_LOGGED_OUT: + if (intent.getPlayer() == null || intent.getPlayer().getCreatureObject() == null) + return; updateChatAvatarStatus(intent.getPlayer(), intent.getGalaxy(), false); break; default: @@ -197,7 +206,7 @@ public class ChatService extends Service { broadcastGalaxyMessage(i.getMessage()); break; case PERSONAL: - broadcastPersonalMessage(i.getProse(), i.getBroadcaster()); + broadcastPersonalMessage(i.getProse(), i.getBroadcaster(), i.getMessage()); break; } } @@ -211,9 +220,76 @@ public class ChatService extends Service { break; case IGNORE_ADD_TARGET: break; + case FRIEND_ADD_TARGET: + handleAddFriend(i.getPlayer(), i.getTarget()); + break; + case FRIEND_REMOVE_TARGET: + handleRemoveFriend(i.getPlayer(), i.getTarget()); + break; + case FRIEND_LIST: + handleRequestFriendList(i.getPlayer()); + break; } } + private void handleRequestFriendList(Player player) { + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) + return; + + SWGList friends = (SWGList) ghost.getFriendsList(); + player.sendPacket(new ChatOnGetFriendsList(player.getCreatureObject().getObjectId(), player.getGalaxyName(), friends)); + + friends.sendRefreshedListData(ghost); + } + + private void handleRemoveFriend(Player player, String target) { + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) + return; + + ChatOnChangeFriendStatus friendStatus = new ChatOnChangeFriendStatus( + player.getCreatureObject().getObjectId(), player.getGalaxyName(), target, true); + + player.sendPacket(friendStatus); + + ghost.removeFriend(target); + + new ChatBroadcastIntent(player, new ProsePackage("@cmnty:friend_removed", "TT", target)).broadcast(); + } + + private void handleAddFriend(Player player, String target) { + if (target.equalsIgnoreCase(player.getCharacterName().split(" ")[0])) + return; + + PlayerObject ghost = player.getPlayerObject(); + if (ghost == null) + return; + + if (ghost.getFriendsList().contains(target)) { + new ChatBroadcastIntent(player, new ProsePackage("@cmnty:friend_duplicate", "TT", target)).broadcast(); + return; + } + + if (!player.getPlayerManager().playerExists(target)) { + new ChatBroadcastIntent(player, new ProsePackage("@cmnty:friend_not_found", "TT", target)).broadcast(); + return; + } + + ChatOnChangeFriendStatus friendStatus = new ChatOnChangeFriendStatus( + player.getCreatureObject().getObjectId(), player.getGalaxyName(), target, false); + + player.sendPacket(new ChatOnAddFriend(), friendStatus); + + Player targetPlayer = player.getPlayerManager().getPlayerByCreatureFirstName(target); + if (targetPlayer != null && targetPlayer.getPlayerState() == PlayerState.ZONED_IN) + player.sendPacket(new ChatFriendsListUpdate(player.getGalaxyName(), target, true)); + + ghost.addFriend(target); + + new ChatBroadcastIntent(player, new ProsePackage("@cmnty:friend_added", "TT", target)).broadcast(); + } + private void handleChatRoomListRequest(Player player, ChatRequestRoomList request) { } @@ -262,17 +338,20 @@ public class ChatService extends Service { Player recipient = playerMgr.getPlayerByCreatureFirstName(recipientStr); long recId = (recipient == null ? playerMgr.getCharacterIdByName(request.getRecipient()) : recipient.getCreatureObject().getObjectId()); - int errorCode = 0; + ChatResult result = ChatResult.SUCCESS; if (recId == 0) - errorCode = 4; - - sender.sendPacket(new ChatOnSendPersistentMessage(errorCode, request.getCounter())); - + result = ChatResult.TARGET_AVATAR_DOESNT_EXIST; + + sender.sendPacket(new ChatOnSendPersistentMessage(result, request.getCounter())); + + if (result != ChatResult.SUCCESS) + return; + Mail mail = new Mail(sender.getCharacterName(), request.getSubject(), request.getMessage(), recId); mail.setId(maxMailId++); mail.setTimestamp((int) (new Date().getTime() / 1000)); - + mail.setOutOfBandPackage(request.getOutOfBandPackage()); mails.put(mail.getId(), mail); if (recipient != null) @@ -319,11 +398,10 @@ public class ChatService extends Service { if (online) { PlayerObject playerObject = player.getPlayerObject(); - if (playerObject == null || playerObject.getFriendsList().size() <= 0) - return; - - for (String friend : playerObject.getFriendsList()) { - sendTargetAvatarStatus(player, friend); + if (playerObject != null && playerObject.getFriendsList().size() <= 0) { + for (String friend : playerObject.getFriendsList()) { + sendTargetAvatarStatus(player, friend); + } } } @@ -339,9 +417,7 @@ public class ChatService extends Service { return false; List friends = playerObject.getFriendsList(); - if (friends.contains(update.getFriendName())) - return true; - return false; + return friends.contains(update.getFriendName()); } }, update); } @@ -378,16 +454,18 @@ public class ChatService extends Service { new NotifyPlayersPacketIntent(packet).broadcast(); } - private void broadcastPersonalMessage(ProsePackage prose, Player player) { - OutOfBand pckg = new OutOfBand(prose); - player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, pckg)); + private void broadcastPersonalMessage(ProsePackage prose, Player player, String message) { + if (prose != null) + player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, new OutOfBandPackage(prose))); + else + player.sendPacket(new ChatSystemMessage(SystemChatType.SCREEN_AND_CHAT, message)); } private void sendPersistentMessageHeaders(Player player, String galaxy) { if (player == null || player.getCreatureObject() == null) return; - final List playersMail = new LinkedList(); + final List playersMail = new LinkedList<>(); final long receiverId = player.getCreatureObject().getObjectId(); mails.traverse(new Traverser() { @@ -410,10 +488,10 @@ public class ChatService extends Service { switch (requestType) { case FULL_MESSAGE: - packet = new ChatPersistentMessageToClient(false, mail.getSender(), galaxy, mail.getId(), mail.getSubject(), mail.getMessage(), mail.getTimestamp(), mail.getStatus()); + packet = new ChatPersistentMessageToClient(mail, galaxy, false); break; case HEADER_ONLY: - packet = new ChatPersistentMessageToClient(true, mail.getSender(), galaxy, mail.getId(), mail.getSubject(), "", mail.getTimestamp(), mail.getStatus()); + packet = new ChatPersistentMessageToClient(mail, galaxy, true); break; } @@ -426,6 +504,6 @@ public class ChatService extends Service { private enum MailFlagType { FULL_MESSAGE, - HEADER_ONLY; + HEADER_ONLY } } diff --git a/src/services/commands/CommandService.java b/src/services/commands/CommandService.java index 4e11eb70c..de225a83b 100644 --- a/src/services/commands/CommandService.java +++ b/src/services/commands/CommandService.java @@ -171,6 +171,7 @@ public class CommandService extends Service { registerCallback("kneel", new KneelCmdCallback()); registerCallback("jumpServer", new JumpCmdCallback()); registerCallback("serverDestroyObject", new ServerDestroyObjectCmdCallback()); + registerCallback("findFriend", new FindFriendCallback()); } private void clearCommands() { diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index f1d141088..90a6c6fa1 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -30,7 +30,6 @@ package services.galaxy; import resources.control.Manager; import services.chat.ChatService; import services.commands.CommandService; -import services.player.WeatherService; import services.sui.SuiService; public class GameManager extends Manager { diff --git a/src/services/player/WeatherService.java b/src/services/galaxy/WeatherService.java similarity index 87% rename from src/services/player/WeatherService.java rename to src/services/galaxy/WeatherService.java index c2fa01d1a..8bf2b019b 100644 --- a/src/services/player/WeatherService.java +++ b/src/services/galaxy/WeatherService.java @@ -1,4 +1,4 @@ -package services.player; +package services.galaxy; import java.util.HashMap; import java.util.Map; @@ -28,7 +28,7 @@ public final class WeatherService extends Service { private final Random random; public WeatherService() { - cycleDuration = 900; // Ziggy: 15 minutes, 900 seconds + cycleDuration = 600; // Ziggy: 10 minutes, 600 seconds terrains = Terrain.values(); executor = Executors.newScheduledThreadPool(terrains.length); weatherForTerrain = new HashMap<>(); @@ -44,6 +44,7 @@ public final class WeatherService extends Service { executor.scheduleAtFixedRate( new WeatherChanger(t), 0, cycleDuration, TimeUnit.SECONDS); + } registerForIntent(PlayerEventIntent.TYPE); @@ -90,14 +91,21 @@ public final class WeatherService extends Service { swm.setType(type); swm.setCloudVectorX(random.nextFloat()); // randomised - swm.setCloudVectorZ(0); // Ziggy: Always 0, clouds don't move up/down - swm.setCloudVectorY(random.nextFloat()); // randomised + swm.setCloudVectorZ(random.nextFloat()); // randomised + swm.setCloudVectorY(0); // Ziggy: Always 0, clouds don't move up/down return swm; } private WeatherType randomWeather() { - return weatherTypes[random.nextInt(weatherTypes.length)]; + WeatherType weather = WeatherType.CLEAR; + float roll = random.nextFloat(); + + for(WeatherType candidate : weatherTypes) + if(roll <= candidate.getChance()) + weather = candidate; + + return weather; } private class WeatherChanger implements Runnable { diff --git a/src/utilities/namegen/SWGNameGenerator.java b/src/utilities/namegen/SWGNameGenerator.java index d455f20cb..b1d46ff90 100644 --- a/src/utilities/namegen/SWGNameGenerator.java +++ b/src/utilities/namegen/SWGNameGenerator.java @@ -27,6 +27,9 @@ ***********************************************************************************/ package utilities.namegen; +import resources.Race; +import resources.zone.NameFilter; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; @@ -35,9 +38,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import resources.Race; -import resources.zone.NameFilter; - public class SWGNameGenerator { private Map ruleMap = new HashMap<>(); @@ -158,29 +158,35 @@ public class SWGNameGenerator { } private String getNameByRule(RaceNameRule rule) { - String name = ""; + StringBuffer buffer = new StringBuffer(""); String instructions = getRandomInstruction(rule); int l = instructions.length(); for (int i = 0; i < l; i++) { char x = instructions.charAt(0); - switch (x) { - case 'v': name += removeExcessDuplications(rule.getVowels(), name, getRandomElementFrom(rule.getVowels())); - break; - case 'c': name += removeExcessDuplications(rule.getStartConsonants(), name, getRandomElementFrom(rule.getStartConsonants())); - break; - case 'd': name += removeExcessDuplications(rule.getEndConsonants(), name, getRandomElementFrom(rule.getEndConsonants())); - break; - case '/': name += "'"; - break; + switch (x) { + case 'v': + buffer.append(removeExcessDuplications(rule.getVowels(), buffer.toString(), getRandomElementFrom(rule.getVowels()))); + break; + case 'c': + buffer.append(removeExcessDuplications(rule.getStartConsonants(), buffer.toString(), getRandomElementFrom(rule.getStartConsonants()))); + break; + case 'd': + buffer.append(removeExcessDuplications(rule.getEndConsonants(), buffer.toString(), getRandomElementFrom(rule.getEndConsonants()))); + break; + case '/': + buffer.append("'"); + break; + default: + break; } instructions = instructions.substring(1); } - if (name.isEmpty()) + if (buffer.length() == 0) return getNameByRule(rule); - return name; + return buffer.toString(); } private String getRandomInstruction(RaceNameRule rule) { @@ -251,6 +257,7 @@ public class SWGNameGenerator { case "Instructions": rule.addInstruction(line); break; + default: break; } } } diff --git a/test/resources/TestWeatherType.java b/test/resources/TestWeatherType.java new file mode 100644 index 000000000..b47c81b87 --- /dev/null +++ b/test/resources/TestWeatherType.java @@ -0,0 +1,24 @@ +package resources; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class TestWeatherType { + + /** + * Tests if the chance of each weather type sum up to 100% + */ + @Test + public void verifyChances() { + float expected = 1; + float actual = 0; + + for(WeatherType type : WeatherType.values()) + actual += type.getChance(); + + assertEquals(expected, actual, 0); + + } + +}