diff --git a/scripts/commands/generic/cmdGroupDisband.js b/scripts/commands/generic/cmdGroupDisband.js new file mode 100644 index 000000000..d52a8ccbf --- /dev/null +++ b/scripts/commands/generic/cmdGroupDisband.js @@ -0,0 +1,14 @@ +function executeCommand(galacticManager, player, target, args) { + var GroupEventIntent = Java.type("intents.GroupEventIntent"); + var GroupEventType = Java.type("intents.GroupEventIntent.GroupEventType"); + + if (target != null && args == null || args.length == 0) { + new GroupEventIntent(GroupEventType.GROUP_DISBAND, player, target).broadcast(); + } else { + var kickingPlayer = galacticManager.getPlayerManager().getPlayerByCreatureFirstName(args); + if (kickingPlayer != null) + new GroupEventIntent(GroupEventType.GROUP_DISBAND, player, kickingPlayer.getCreatureObject()).broadcast(); + else + new GroupEventIntent(GroupEventType.GROUP_DISBAND, player, null).broadcast(); + } +} \ No newline at end of file diff --git a/scripts/commands/generic/cmdGroupJoin.js b/scripts/commands/generic/cmdGroupJoin.js new file mode 100644 index 000000000..1daf82dd8 --- /dev/null +++ b/scripts/commands/generic/cmdGroupJoin.js @@ -0,0 +1,6 @@ +function executeCommand(galacticManager, player, target, args) { + var GroupEventIntent = Java.type("intents.GroupEventIntent"); + var GroupEventType = Java.type("intents.GroupEventIntent.GroupEventType"); + + new GroupEventIntent(GroupEventType.GROUP_JOIN, player).broadcast(); +} \ No newline at end of file diff --git a/scripts/commands/generic/groupInvite.js b/scripts/commands/generic/groupInvite.js new file mode 100644 index 000000000..a91a82928 --- /dev/null +++ b/scripts/commands/generic/groupInvite.js @@ -0,0 +1,6 @@ +function executeCommand(galacticManager, player, target, args) { + var GroupEventIntent = Java.type("intents.GroupEventIntent"); + var GroupEventType = Java.type("intents.GroupEventIntent.GroupEventType"); + + new GroupEventIntent(GroupEventType.GROUP_INVITE, player, target).broadcast(); +} \ No newline at end of file diff --git a/src/intents/GroupEventIntent.java b/src/intents/GroupEventIntent.java new file mode 100644 index 000000000..7f00ac965 --- /dev/null +++ b/src/intents/GroupEventIntent.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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 intents; + +import resources.control.Intent; +import resources.objects.creature.CreatureObject; +import resources.player.Player; + +/** + * Created by Waverunner on 10/4/2015 + */ +public class GroupEventIntent extends Intent { + public static final String TYPE = "GroupEventIntent"; + + private GroupEventType eventType; + private Player player; + private CreatureObject target; + + public GroupEventIntent(GroupEventType eventType, Player player, CreatureObject target) { + super(TYPE); + this.eventType = eventType; + this.player = player; + this.target = target; + } + + public GroupEventIntent(GroupEventType eventType, Player player) { + super(TYPE); + this.eventType = eventType; + this.player = player; + } + + public GroupEventType getEventType() { + return eventType; + } + + public Player getPlayer() { + return player; + } + + public CreatureObject getTarget() { + return target; + } + + public enum GroupEventType { + GROUP_INVITE, + GROUP_JOIN, + GROUP_DISBAND + } +} \ No newline at end of file diff --git a/src/resources/objects/SWGObject.java b/src/resources/objects/SWGObject.java index ce4b09440..0788d5ead 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -804,7 +804,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } - private void awarenessOutOfRange(SWGObject o) { + protected void awarenessOutOfRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.remove(o)) { Player owner = o.getOwner(); @@ -816,7 +816,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } - private void awarenessInRange(SWGObject o) { + protected void awarenessInRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.add(o)) { Player owner = o.getOwner(); @@ -827,7 +827,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } } - + private void createObjectObservers(SWGObject obj) { Set observers = new HashSet<>(); getChildrenObservers(observers, obj); diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 9f2c7034d..49ec60c33 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -45,6 +45,7 @@ import resources.objects.tangible.OptionFlag; import resources.objects.tangible.TangibleObject; import resources.objects.weapon.WeaponObject; import resources.player.Player; +import services.group.GroupInviterData; import utilities.Encoder.StringType; public class CreatureObject extends TangibleObject { @@ -89,6 +90,7 @@ public class CreatureObject extends TangibleObject { private boolean shownOnRadar = true; private boolean beast = false; private long groupId = 0; + private GroupInviterData inviterData = new GroupInviterData(0, null, "", 0); private byte factionRank = 0; private long ownerId = 0; private int battleFatigue = 0; @@ -521,6 +523,19 @@ public class CreatureObject extends TangibleObject { return groupId; } + public void updateGroupInviteData(Player sender, long groupId, String name) { + inviterData.setName(name); + inviterData.setSender(sender); + inviterData.setId(groupId); + inviterData.incrementCounter(); + + sendDelta(6, 14, inviterData); + } + + public GroupInviterData getInviterData() { + return inviterData; + } + public void setGroupId(long groupId) { this.groupId = groupId; sendDelta(6, 13, groupId); @@ -807,9 +822,7 @@ public class CreatureObject extends TangibleObject { bb.addAscii(moodAnimation); // 11 bb.addLong(equippedWeaponId); // 12 bb.addLong(groupId); // 13 - bb.addLong(0); // Group Inviter ID -- 14 - bb.addAscii(""); // Group Inviter Name - bb.addLong(0); // Invite counter + bb.addObject(inviterData); // TODO: Check structure -- 14 bb.addInt(guildId); // 15 bb.addLong(lookAtTargetId); // 16 bb.addLong(intendedTargetId); // 17 diff --git a/src/resources/objects/group/GroupObject.java b/src/resources/objects/group/GroupObject.java index e251cb8eb..d72eb1b2c 100644 --- a/src/resources/objects/group/GroupObject.java +++ b/src/resources/objects/group/GroupObject.java @@ -27,32 +27,213 @@ package resources.objects.group; +import intents.NotifyPlayersPacketIntent; +import network.packets.Packet; +import network.packets.swg.zone.baselines.Baseline; +import resources.collections.SWGList; +import resources.encodables.Encodable; import resources.network.BaselineBuilder; import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; import resources.player.Player; +import utilities.Encoder; -public class GroupObject extends SWGObject { - +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GroupObject extends SWGObject { // Extends INTO or TANO? private static final long serialVersionUID = 200L; - + + private final SWGList groupMembers = new SWGList<>(Baseline.BaselineType.GRUP, 6, 2, Encoder.StringType.ASCII); + private long leader; + private short level; + private long lootMaster; + private int lootRule; + + private transient PickupPointTimer pickupPointTimer; + + public GroupObject(long objectId) { + super(objectId, Baseline.BaselineType.GRUP); + } + + @Override + public void createBaseline3(Player target, BaselineBuilder bb) { + super.createBaseline3(target, bb); + } + @Override public void createBaseline6(Player target, BaselineBuilder bb) { super.createBaseline6(target, bb); // BASE06 -- 2 variables - bb.addInt(0); // groupMembers // 2 - bb.addInt(0); // updateCount + bb.addObject(groupMembers); // 2 -- NOTE: First person is the leader bb.addInt(0); // formationmembers // 3 bb.addInt(0); // updateCount bb.addAscii(""); // groupName // 4 - bb.addShort(0); // groupLevel // 5 + bb.addShort(level); // 5 bb.addInt(0); // formationNameCrc // 6 - bb.addLong(0); // lootMaster // 7 - bb.addInt(0); // lootRule // 8 - bb.addInt(0); // PickupPointTimer startTime // 9 - bb.addInt(0); // endTime + bb.addLong(lootMaster); // 7 + bb.addInt(lootRule); // 8 + bb.addObject(pickupPointTimer); // 9 bb.addAscii(""); // PickupPoint planetName // 10 bb.addFloat(0); // x bb.addFloat(0); // y bb.addFloat(0); // z + bb.incrementOperandCount(9); } + + public void addMember(CreatureObject object) { + groupMembers.add(new GroupMember(object.getObjectId(), object.getName())); + + groupMembers.sendDeltaMessage(this); + + awarenessInRange(object); + object.setGroupId(getObjectId()); + + if (object.getLevel() > level) + setLevel((short) object.getLevel()); + } + + public void removeMember(CreatureObject object) { + synchronized (groupMembers) { + groupMembers.remove(new GroupMember(object.getObjectId(), object.getName())); + + object.setGroupId(0); + awarenessOutOfRange(object); + + groupMembers.sendDeltaMessage(this); + } + } + + public long getLeader() { + return leader; + } + + public void setLeader(CreatureObject object) { + this.leader = object.getObjectId(); + + GroupMember member = new GroupMember(object.getObjectId(), object.getName()); + if (groupMembers.size() > 0) { + synchronized (groupMembers) { + GroupMember previous = groupMembers.set(0, member); + groupMembers.add(previous); + } + } else { + groupMembers.add(member); + } + groupMembers.sendDeltaMessage(this); + } + + public short getLevel() { + return level; + } + + public void setLevel(short level) { + this.level = level; + } + + public long getLootMaster() { + return lootMaster; + } + + public void setLootMaster(long lootMaster) { + this.lootMaster = lootMaster; + } + + public int getLootRule() { + return lootRule; + } + + public void setLootRule(int lootRule) { + this.lootRule = lootRule; + sendDelta(6, 8, lootRule); + } + + public Map getGroupMembers() { + Map members = new HashMap<>(); + + synchronized (groupMembers) { + for (GroupMember groupMember : groupMembers) { + members.put(groupMember.getName(), groupMember.getId()); + } + } + + return members; + } + + private static class PickupPointTimer implements Serializable, Encodable { + public int start; + public int end; + + @Override + public byte[] encode() { + return ByteBuffer.allocate(8).putInt(start).putInt(end).array(); + } + + @Override + public void decode(ByteBuffer data) { + start = Packet.getInt(data); + end = Packet.getInt(data); + } + + public int decrement() { + return --end; + } + } + + private static class GroupMember implements Serializable, Encodable { + private long id; + private String name; + + public GroupMember(long id, String name) { + this.id = id; + this.name = name; + } + + @Override + public byte[] encode() { + ByteBuffer bb = ByteBuffer.allocate(10 + name.length()); + Packet.addLong(bb, id); + Packet.addAscii(bb, name); + return bb.array(); + } + + @Override + public void decode(ByteBuffer data) { + id = Packet.getLong(data); + name = Packet.getAscii(data); + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + GroupMember that = (GroupMember) o; + + return id == that.id && name.equals(that.name); + + } + + @Override + public int hashCode() { + int result = (int) (id ^ (id >>> 32)); + result = 31 * result + name.hashCode(); + return result; + } + } } diff --git a/src/services/commands/CommandService.java b/src/services/commands/CommandService.java index afee2de04..569b197a3 100644 --- a/src/services/commands/CommandService.java +++ b/src/services/commands/CommandService.java @@ -100,6 +100,7 @@ public class CommandService extends Service { } Command command = getCommand(request.getCommandCrc()); + System.out.println(command + "\t\t\tscriptHook: " + command.getScriptHook() + "\t\t:cppHook " + command.getDefaultScriptCallback()); String [] arguments = request.getArguments().split(" "); SWGObject target = null; if (request.getTargetId() != 0) { @@ -151,7 +152,8 @@ public class CommandService extends Service { } private void loadBaseCommands() { - final String [] commandTables = new String [] {"command_table", "client_command_table", "command_table_ground"}; + // First = Higher Priority, Last = Lower Priority ---- Some tables contain duplicates, ORDER MATTERS! + final String [] commandTables = new String [] {"command_table", "command_table_ground", "client_command_table", }; clearCommands(); for (String table : commandTables) { @@ -259,7 +261,10 @@ public class CommandService extends Service { } } - private void addCommand(Command command) { + private boolean addCommand(Command command) { + if (commands.containsKey(command.getCrc())) + return false; + synchronized (commands) { commands.put(command.getCrc(), command); } @@ -276,6 +281,6 @@ public class CommandService extends Service { } commands.add(command); } + return true; } - } diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index a0b7747da..5aed2ab25 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -31,6 +31,7 @@ import resources.control.Manager; import services.commands.CommandService; import services.faction.FactionService; import services.galaxy.terminals.TerminalService; +import services.group.GroupService; import services.sui.SuiService; public class GameManager extends Manager { @@ -41,7 +42,8 @@ public class GameManager extends Manager { private final WeatherService weatherService; private final TerminalService terminalManager; private final FactionService factionService; - + private final GroupService groupService; + public GameManager() { commandService = new CommandService(); connectionService = new ConnectionService(); @@ -49,6 +51,7 @@ public class GameManager extends Manager { weatherService = new WeatherService(); terminalManager = new TerminalService(); factionService = new FactionService(); + groupService = new GroupService(); addChildService(commandService); addChildService(connectionService); @@ -56,5 +59,6 @@ public class GameManager extends Manager { addChildService(weatherService); addChildService(terminalManager); addChildService(factionService); + addChildService(groupService); } } diff --git a/src/services/group/GroupInviterData.java b/src/services/group/GroupInviterData.java new file mode 100644 index 000000000..5c0c0136b --- /dev/null +++ b/src/services/group/GroupInviterData.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * 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.group; + +import network.packets.Packet; +import resources.encodables.Encodable; +import resources.player.Player; + +import java.io.Serializable; +import java.nio.ByteBuffer; + +/** + * Created by Waverunner on 10/4/2015 + */ +public class GroupInviterData implements Encodable, Serializable { + + private long id; + private Player sender; + private String name; + private long counter; + + public GroupInviterData(long id, Player sender, String name, int counter) { + this.id = id; + this.sender = sender; + this.name = name; + this.counter = counter; + } + + @Override + public byte[] encode() { + ByteBuffer bb = ByteBuffer.allocate(name.length() + 18); + + Packet.addLong(bb, id); + Packet.addAscii(bb, name); + Packet.addLong(bb, counter); + + return bb.array(); + } + + @Override + public void decode(ByteBuffer data) { + id = Packet.getLong(data); + name = Packet.getAscii(data); + counter = Packet.getInt(data); + } + + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getCounter() { + return counter; + } + + public void incrementCounter() { + counter++; + } + + public Player getSender() { + return sender; + } + + public void setSender(Player sender) { + this.sender = sender; + } +} diff --git a/src/services/group/GroupService.java b/src/services/group/GroupService.java new file mode 100644 index 000000000..0b2fddf4f --- /dev/null +++ b/src/services/group/GroupService.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * 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.group; + +import intents.GroupEventIntent; +import intents.NotifyPlayersPacketIntent; +import intents.PlayerEventIntent; +import intents.chat.ChatRoomUpdateIntent; +import intents.object.ObjectCreateIntent; +import intents.object.ObjectIdRequestIntent; +import intents.object.ObjectIdResponseIntent; +import network.packets.swg.zone.chat.ChatSystemMessage; +import resources.chat.ChatAvatar; +import resources.control.Intent; +import resources.control.Service; +import resources.encodables.OutOfBandPackage; +import resources.encodables.ProsePackage; +import resources.encodables.StringId; +import resources.objects.creature.CreatureObject; +import resources.objects.group.GroupObject; +import resources.player.Player; +import resources.server_info.Log; +import services.objects.ObjectCreator; +import services.player.PlayerManager; +import utilities.IntentFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by Waverunner on 10/4/2015 + */ +public class GroupService extends Service { + + private final List reservedIds = new ArrayList<>(); + private final Map groups = new HashMap<>(); + + @Override + public boolean initialize() { + registerForIntent(GroupEventIntent.TYPE); + registerForIntent(PlayerEventIntent.TYPE); + registerForIntent(ObjectIdResponseIntent.TYPE); + return super.initialize(); + } + + @Override + public boolean start() { + new ObjectIdRequestIntent("GroupService", 50).broadcast(); + return super.start(); + } + + @Override + public void onIntentReceived(Intent i) { + String type = i.getType(); + switch(type) { + case GroupEventIntent.TYPE: + if (i instanceof GroupEventIntent) + handleGroupEventIntent((GroupEventIntent) i); + break; + case ObjectIdResponseIntent.TYPE: + if (!(i instanceof ObjectIdResponseIntent) || !((ObjectIdResponseIntent) i).getIdentifier().equals("GroupService")) + break; + reservedIds.addAll(((ObjectIdResponseIntent)i).getReservedIds()); + break; + case PlayerEventIntent.TYPE: + break; + default: break; + } + } + + private void handleGroupEventIntent(GroupEventIntent intent) { + switch(intent.getEventType()) { + case GROUP_INVITE: + handleGroupInvite(intent.getPlayer(), intent.getTarget()); + break; + case GROUP_JOIN: + handleGroupJoin(intent.getPlayer()); + break; + case GROUP_DISBAND: + handleGroupDisband(intent.getPlayer(), intent.getTarget()); + break; + } + } + + private void handleGroupDisband(Player player, CreatureObject target) { + CreatureObject creo = player.getCreatureObject(); + + if (creo == null) + return; + + GroupObject group = getGroup(creo.getGroupId()); + if (group == null) + return; + + if (group.getLeader() != creo.getObjectId()) { + sendSystemMessage(player, "must_be_leader"); + return; + } + + if (target == null) { + // Disband Group + + String galaxy = player.getGalaxyName(); + new ChatRoomUpdateIntent(getGroupChatPath(group.getObjectId(), galaxy), String.valueOf(group.getObjectId()), null, + ChatAvatar.getSystemAvatar(galaxy), null, ChatRoomUpdateIntent.UpdateType.DESTROY).broadcast(); + + Map members = group.getGroupMembers(); + PlayerManager playerManager = player.getPlayerManager(); + + sendGroupSystemMessage(group, "disbanded"); + for (String name : members.keySet()) { + Player memPlayer = playerManager.getPlayerByCreatureName(name); + if (memPlayer == null) + continue; + + CreatureObject memCreo = memPlayer.getCreatureObject(); + if (memCreo == null) + continue; + + group.removeMember(memCreo); + } + + // TODO: Send object destroy intent for object manager + } else { + // Disband player + + group.removeMember(target); + + sendGroupSystemMessage(group, "other_left_prose", "TU", target.getObjectId()); + sendSystemMessage(target.getOwner(), "removed"); + + // TODO: Leave group chat room + } + } + + private void handleGroupInvite(Player player, CreatureObject target) { + CreatureObject playerCreo = player.getCreatureObject(); + + Player targetOwner = target.getOwner(); + if (targetOwner == null) + return; + + long groupId = playerCreo.getGroupId(); + long inviterId = playerCreo.getObjectId(); + long targetId = target.getObjectId(); + + if (groupId != 0) { + GroupObject group = getGroup(groupId); + + if (group.getLeader() != inviterId) { + sendSystemMessage(player, "must_be_leader"); + return; + } + + if (target.getGroupId() != 0) { + sendSystemMessage(player, "already_grouped", "TT", targetId); + return; + } + + if (target.getInviterData().getId() != 0 ) { + if(target.getInviterData().getId() != groupId) + sendSystemMessage(player, "considering_other_group"); + else + sendSystemMessage(player, "considering_your_group"); + } + } else { + sendSystemMessage(targetOwner, "invite_target", "TT", inviterId); + sendSystemMessage(player, "invite_leader", "TT", targetId); + + target.updateGroupInviteData(player, -1, player.getCharacterName()); + } + } + + private void handleGroupJoin(Player player) { + CreatureObject creo = player.getCreatureObject(); + + GroupInviterData invitation = creo.getInviterData(); + + long groupId = invitation.getId(); + + if (groupId == 0) { + sendSystemMessage(player, "must_be_invited"); + return; + } + + GroupObject group = getGroup(groupId); + + Player sender = invitation.getSender(); + + if (invitation.getSender() == null) { + // Inviter logged out, invitation is no good + sendSystemMessage(player, "must_be_invited"); + creo.updateGroupInviteData(null, 0, ""); + return; + } + + // Group doesn't exist yet + if (group == null) { + + group = createGroup(sender); + + if (group == null) { + Log.e("GroupService", "Failed to create group from sender %s for %s", sender, player); + creo.updateGroupInviteData(null, 0, ""); + return; + } + + group.addMember(sender.getCreatureObject()); + + sendSystemMessage(sender, "formed_self", "TT", sender.getCreatureObject().getObjectId()); + + // TODO: Join group chat room + } else { + // Group already exists + + if (group.getLeader() != sender.getCreatureObject().getObjectId()) { + sendSystemMessage(player, "join_inviter_not_leader", sender.getCreatureObject().getObjectId()); + creo.updateGroupInviteData(null, 0, ""); + return; + } + + if (group.getGroupMembers().size() == 8) { + sendSystemMessage(player, "join_full"); + creo.updateGroupInviteData(null, 0, ""); + return; + } + } + sendGroupSystemMessage(group, "other_joined_prose", "TU", creo.getObjectId()); + group.addMember(creo); + sendSystemMessage(player, "joined_self"); + creo.updateGroupInviteData(null, 0, ""); + // TODO: Join group chat room + } + + private GroupObject createGroup(Player player) { + GroupObject group = (GroupObject) ObjectCreator.createObjectFromTemplate(getNextObjectId(), "object/group/shared_group_object.iff"); + if (group == null) + return null; + + group.setLeader(player.getCreatureObject()); + + groups.put(group.getObjectId(), group); + + new ObjectCreateIntent(group, false).broadcast(); + + String galaxy = player.getGalaxyName(); + new ChatRoomUpdateIntent(getGroupChatPath(group.getObjectId(), galaxy), String.valueOf(group.getObjectId()), null, + ChatAvatar.getSystemAvatar(galaxy), null, ChatRoomUpdateIntent.UpdateType.CREATE).broadcast(); + + return group; + } + + private long getNextObjectId() { + synchronized (reservedIds) { + if (reservedIds.size() <= 5) + new ObjectIdRequestIntent("GroupService", 50).broadcast(); + + return reservedIds.remove(0); + } + } + + private void sendGroupSystemMessage(GroupObject group, String id) { + Map members = group.getGroupMembers(); + + List ids = new ArrayList<>(members.values()); + + new NotifyPlayersPacketIntent(new ChatSystemMessage(ChatSystemMessage.SystemChatType.SCREEN_AND_CHAT, "@group:" + id), ids).broadcast(); + } + + private void sendGroupSystemMessage(GroupObject group, String id, Object ... objects) { + Map members = group.getGroupMembers(); + + List ids = new ArrayList<>(members.values()); + + if (objects.length % 2 != 0) + Log.e("ProsePackage", "Sent a ProsePackage chat message with an uneven number of object arguments for StringId %s", "@group:" + id); + Object [] prose = new Object[objects.length + 2]; + prose[0] = "StringId"; + prose[1] = new StringId("@group:" + id); + System.arraycopy(objects, 0, prose, 2, objects.length); + + new NotifyPlayersPacketIntent( + new ChatSystemMessage(ChatSystemMessage.SystemChatType.SCREEN_AND_CHAT, + new OutOfBandPackage(new ProsePackage(prose))), ids).broadcast(); + } + + private String getGroupChatPath(long groupId, String galaxy) { + // SWG.serverName.group.GroupObjectId.GroupChat || title = groupid + String groupIdString = String.valueOf(groupId); + return "SWG." + galaxy + ".group." + groupIdString + ".GroupChat"; + } + private GroupObject getGroup(long groupId) { + return groups.get(groupId); + } + + private void sendSystemMessage(Player target, String id) { + target.sendPacket(new ChatSystemMessage(ChatSystemMessage.SystemChatType.SCREEN_AND_CHAT, id)); + } + + private void sendSystemMessage(Player target, String id, Object ... objects) { + IntentFactory.sendSystemMessage(target, "@group:" + id, objects); + } +} diff --git a/src/services/objects/ObjectCreator.java b/src/services/objects/ObjectCreator.java index 99561fc80..1e4681ae7 100644 --- a/src/services/objects/ObjectCreator.java +++ b/src/services/objects/ObjectCreator.java @@ -37,6 +37,7 @@ import resources.objects.SWGObject; import resources.objects.building.BuildingObject; import resources.objects.cell.CellObject; import resources.objects.creature.CreatureObject; +import resources.objects.group.GroupObject; import resources.objects.installation.InstallationObject; import resources.objects.intangible.IntangibleObject; import resources.objects.player.PlayerObject; @@ -80,6 +81,7 @@ public final class ObjectCreator { case "installation": return new InstallationObject(objectId); case "ship": return new ShipObject(objectId); case "soundobject": return new SoundObject(objectId); + case "group": return new GroupObject(objectId); } return null; } diff --git a/src/utilities/IntentFactory.java b/src/utilities/IntentFactory.java index 07fca1c1b..59bc2e495 100644 --- a/src/utilities/IntentFactory.java +++ b/src/utilities/IntentFactory.java @@ -13,6 +13,7 @@ import resources.player.Player; import intents.chat.ChatBroadcastIntent; import intents.chat.ChatBroadcastIntent.BroadcastType; import intents.chat.PersistentMessageIntent; +import resources.server_info.Log; import resources.sui.SuiWindow; /** @@ -76,7 +77,9 @@ public final class IntentFactory { * set the %DI to the value of 500 for the StringId. * Note that the prose key must always come first and the value for that key must always come second. */ - public void sendSystemMessage(Player target, String stf, Object ... objects) { + public static void sendSystemMessage(Player target, String stf, Object ... objects) { + if (objects.length % 2 != 0) + Log.e("ProsePackage", "Sent a ProsePackage chat message with an uneven number of object arguments for StringId %s", stf); Object [] prose = new Object[objects.length + 2]; prose[0] = "StringId"; prose[1] = new StringId(stf);