From b49b544af2d60057cc195234394e4b725e1d9833 Mon Sep 17 00:00:00 2001 From: Waverunner Date: Mon, 5 Oct 2015 17:39:02 -0400 Subject: [PATCH 1/2] Added in basic ability to join groups --- scripts/commands/generic/groupInvite.js | 6 + scripts/commands/generic/groupJoin.js | 8 + src/intents/GroupEventIntent.java | 74 ++++++ src/resources/objects/SWGObject.java | 6 +- .../objects/creature/CreatureObject.java | 19 +- src/resources/objects/group/GroupObject.java | 70 +++++- src/services/galaxy/GameManager.java | 6 +- src/services/group/GroupInviterData.java | 103 ++++++++ src/services/group/GroupService.java | 237 ++++++++++++++++++ src/services/objects/ObjectCreator.java | 2 + src/utilities/IntentFactory.java | 2 +- 11 files changed, 517 insertions(+), 16 deletions(-) create mode 100644 scripts/commands/generic/groupInvite.js create mode 100644 scripts/commands/generic/groupJoin.js create mode 100644 src/intents/GroupEventIntent.java create mode 100644 src/services/group/GroupInviterData.java create mode 100644 src/services/group/GroupService.java 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/scripts/commands/generic/groupJoin.js b/scripts/commands/generic/groupJoin.js new file mode 100644 index 000000000..673019ef1 --- /dev/null +++ b/scripts/commands/generic/groupJoin.js @@ -0,0 +1,8 @@ +function executeCommand(galacticManager, player, target, args) { + var GroupEventIntent = Java.type("intents.GroupEventIntent"); + var GroupEventType = Java.type("intents.GroupEventIntent.GroupEventType"); + + intentFactory.sendSystemMessage("Group: " + args); + + new GroupEventIntent(GroupEventType.GROUP_JOIN, player).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 2e6a630a4..9dab36545 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -791,7 +791,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } - private void awarenessOutOfRange(SWGObject o) { + public void awarenessOutOfRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.remove(o)) { Player owner = o.getOwner(); @@ -803,7 +803,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } - private void awarenessInRange(SWGObject o) { + public void awarenessInRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.add(o)) { Player owner = o.getOwner(); @@ -814,7 +814,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..f2b650f5e 100644 --- a/src/resources/objects/group/GroupObject.java +++ b/src/resources/objects/group/GroupObject.java @@ -27,26 +27,38 @@ package resources.objects.group; +import network.packets.swg.zone.baselines.Baseline; +import resources.collections.SWGMap; 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 { - +public class GroupObject extends SWGObject { // Extends INTO or TANO? private static final long serialVersionUID = 200L; - + + private SWGMap groupMembers = new SWGMap<>(Baseline.BaselineType.GRUP, 6, 2, Encoder.StringType.ASCII); + private long leader; + private short level; + private long lootMaster; + private int lootRule; + + public GroupObject(long objectId) { + super(objectId, Baseline.BaselineType.GRUP); + } + @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 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.addLong(lootMaster); // 7 + bb.addInt(lootRule); // 8 bb.addInt(0); // PickupPointTimer startTime // 9 bb.addInt(0); // endTime bb.addAscii(""); // PickupPoint planetName // 10 @@ -55,4 +67,46 @@ public class GroupObject extends SWGObject { bb.addFloat(0); // z bb.incrementOperandCount(9); } + + public void addMember(CreatureObject object) { + groupMembers.put(object.getObjectId(), object.getName()); + + groupMembers.sendDeltaMessage(this); + + System.out.println("GroupObject: Added member & sent delta:: " + object); + } + + public void removeMember(CreatureObject object) { + groupMembers.remove(object.getObjectId()); + + groupMembers.sendDeltaMessage(this); + } + + public long getLeader() { + return leader; + } + + public void setLeader(long leader) { + this.leader = leader; + } + + 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 SWGMap getGroupMembers() { + return groupMembers; + } } 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..a64a12b3e --- /dev/null +++ b/src/services/group/GroupInviterData.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * 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.nio.ByteBuffer; + +/** + * Created by Waverunner on 10/4/2015 + */ +public class GroupInviterData implements Encodable { + + 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..2aade3c05 --- /dev/null +++ b/src/services/group/GroupService.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * 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.PlayerEventIntent; +import intents.object.ObjectCreateIntent; +import intents.object.ObjectIdRequestIntent; +import intents.object.ObjectIdResponseIntent; +import network.packets.swg.zone.chat.ChatSystemMessage; +import resources.control.Intent; +import resources.control.Service; +import resources.objects.creature.CreatureObject; +import resources.objects.group.GroupObject; +import resources.player.Player; +import resources.server_info.Log; +import services.objects.ObjectCreator; +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 List reservedIds = new ArrayList<>(); + private 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: + break; + } + } + + 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) { + // TODO: Group leader check + 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, groupId, 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(invitation.getSender().getCreatureObject().getObjectId()); + + if (group == null) { + Log.e("GroupService", "Failed to create group"); + creo.updateGroupInviteData(null, 0, ""); + return; + } + + group.addMember(invitation.getSender().getCreatureObject()); + group.awarenessInRange(invitation.getSender().getCreatureObject()); + + sendSystemMessage(invitation.getSender(), "formed_self"); + } else { + // Group already exists + + if (group.getLeader() != sender.getCreatureObject().getObjectId()) { + sendSystemMessage(player, "join_inviter_not_leader"); + creo.updateGroupInviteData(null, 0, ""); + return; + } + + if (group.getGroupMembers().size() == 8) { + sendSystemMessage(player, "join_full"); + creo.updateGroupInviteData(null, 0, ""); + return; + } + } + + group.addMember(creo); + sendSystemMessage(player, "joined_self"); + creo.updateGroupInviteData(null, 0, ""); + } + + private GroupObject createGroup(long leader) { + GroupObject group = (GroupObject) ObjectCreator.createObjectFromTemplate(getNextObjectId(), "object/group/shared_group_object.iff"); + if (group == null) + return null; + + group.setLeader(leader); + group.setLootMaster(leader); + + groups.put(group.getObjectId(), group); + + new ObjectCreateIntent(group, false).broadcast(); + + return group; + } + + private long getNextObjectId() { + if (reservedIds.size() <= 5) + new ObjectIdRequestIntent("GroupService", 50).broadcast(); + + return reservedIds.remove(0); + } + + 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) { + target.sendPacket(new ChatSystemMessage(ChatSystemMessage.SystemChatType.SCREEN_AND_CHAT, "DEBUG: " + id)); + IntentFactory.sendSystemMessage(target, "@group:" + id, objects); + } +} diff --git a/src/services/objects/ObjectCreator.java b/src/services/objects/ObjectCreator.java index d52318df7..102b3e0c6 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 e90d4df35..6ace71dd1 100644 --- a/src/utilities/IntentFactory.java +++ b/src/utilities/IntentFactory.java @@ -75,7 +75,7 @@ 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) { Object [] prose = new Object[objects.length + 2]; prose[0] = "StringId"; prose[1] = new StringId(stf); From 4c1b8952988f834daa99206a729713fc1d8a2ba0 Mon Sep 17 00:00:00 2001 From: Waverunner Date: Tue, 13 Oct 2015 09:35:18 -0400 Subject: [PATCH 2/2] Added core grouping system for CORE-111 - Groups are now implemented --- scripts/commands/generic/cmdGroupDisband.js | 14 ++ .../generic/{groupJoin.js => cmdGroupJoin.js} | 2 - src/resources/objects/SWGObject.java | 4 +- src/resources/objects/group/GroupObject.java | 153 ++++++++++++++++-- src/services/commands/CommandService.java | 11 +- src/services/group/GroupInviterData.java | 3 +- src/services/group/GroupService.java | 131 ++++++++++++--- src/utilities/IntentFactory.java | 3 + 8 files changed, 281 insertions(+), 40 deletions(-) create mode 100644 scripts/commands/generic/cmdGroupDisband.js rename scripts/commands/generic/{groupJoin.js => cmdGroupJoin.js} (83%) 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/groupJoin.js b/scripts/commands/generic/cmdGroupJoin.js similarity index 83% rename from scripts/commands/generic/groupJoin.js rename to scripts/commands/generic/cmdGroupJoin.js index 673019ef1..1daf82dd8 100644 --- a/scripts/commands/generic/groupJoin.js +++ b/scripts/commands/generic/cmdGroupJoin.js @@ -2,7 +2,5 @@ function executeCommand(galacticManager, player, target, args) { var GroupEventIntent = Java.type("intents.GroupEventIntent"); var GroupEventType = Java.type("intents.GroupEventIntent.GroupEventType"); - intentFactory.sendSystemMessage("Group: " + args); - new GroupEventIntent(GroupEventType.GROUP_JOIN, player).broadcast(); } \ No newline at end of file diff --git a/src/resources/objects/SWGObject.java b/src/resources/objects/SWGObject.java index 9dab36545..4e78cbc0c 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -791,7 +791,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } - public void awarenessOutOfRange(SWGObject o) { + protected void awarenessOutOfRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.remove(o)) { Player owner = o.getOwner(); @@ -803,7 +803,7 @@ public abstract class SWGObject implements Serializable, Comparable { } } - public void awarenessInRange(SWGObject o) { + protected void awarenessInRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.add(o)) { Player owner = o.getOwner(); diff --git a/src/resources/objects/group/GroupObject.java b/src/resources/objects/group/GroupObject.java index f2b650f5e..d72eb1b2c 100644 --- a/src/resources/objects/group/GroupObject.java +++ b/src/resources/objects/group/GroupObject.java @@ -27,31 +27,49 @@ package resources.objects.group; +import intents.NotifyPlayersPacketIntent; +import network.packets.Packet; import network.packets.swg.zone.baselines.Baseline; -import resources.collections.SWGMap; +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; +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 SWGMap groupMembers = new SWGMap<>(Baseline.BaselineType.GRUP, 6, 2, Encoder.StringType.ASCII); + 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.addObject(groupMembers); // 2 + bb.addObject(groupMembers); // 2 -- NOTE: First person is the leader bb.addInt(0); // formationmembers // 3 bb.addInt(0); // updateCount bb.addAscii(""); // groupName // 4 @@ -59,35 +77,55 @@ public class GroupObject extends SWGObject { // Extends INTO or TANO? bb.addInt(0); // formationNameCrc // 6 bb.addLong(lootMaster); // 7 bb.addInt(lootRule); // 8 - bb.addInt(0); // PickupPointTimer startTime // 9 - bb.addInt(0); // endTime + 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.put(object.getObjectId(), object.getName()); + groupMembers.add(new GroupMember(object.getObjectId(), object.getName())); groupMembers.sendDeltaMessage(this); - System.out.println("GroupObject: Added member & sent delta:: " + object); + awarenessInRange(object); + object.setGroupId(getObjectId()); + + if (object.getLevel() > level) + setLevel((short) object.getLevel()); } public void removeMember(CreatureObject object) { - groupMembers.remove(object.getObjectId()); + synchronized (groupMembers) { + groupMembers.remove(new GroupMember(object.getObjectId(), object.getName())); - groupMembers.sendDeltaMessage(this); + object.setGroupId(0); + awarenessOutOfRange(object); + + groupMembers.sendDeltaMessage(this); + } } public long getLeader() { return leader; } - public void setLeader(long leader) { - this.leader = 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() { @@ -106,7 +144,96 @@ public class GroupObject extends SWGObject { // Extends INTO or TANO? this.lootMaster = lootMaster; } - public SWGMap getGroupMembers() { - return groupMembers; + 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/group/GroupInviterData.java b/src/services/group/GroupInviterData.java index a64a12b3e..5c0c0136b 100644 --- a/src/services/group/GroupInviterData.java +++ b/src/services/group/GroupInviterData.java @@ -31,12 +31,13 @@ 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 { +public class GroupInviterData implements Encodable, Serializable { private long id; private Player sender; diff --git a/src/services/group/GroupService.java b/src/services/group/GroupService.java index 2aade3c05..0b2fddf4f 100644 --- a/src/services/group/GroupService.java +++ b/src/services/group/GroupService.java @@ -28,18 +28,25 @@ 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; @@ -52,8 +59,8 @@ import java.util.Map; */ public class GroupService extends Service { - private List reservedIds = new ArrayList<>(); - private Map groups = new HashMap<>(); + private final List reservedIds = new ArrayList<>(); + private final Map groups = new HashMap<>(); @Override public boolean initialize() { @@ -97,10 +104,62 @@ public class GroupService extends Service { 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(); @@ -113,7 +172,6 @@ public class GroupService extends Service { long targetId = target.getObjectId(); if (groupId != 0) { - // TODO: Group leader check GroupObject group = getGroup(groupId); if (group.getLeader() != inviterId) { @@ -132,12 +190,11 @@ public class GroupService extends Service { else sendSystemMessage(player, "considering_your_group"); } - } else { sendSystemMessage(targetOwner, "invite_target", "TT", inviterId); sendSystemMessage(player, "invite_leader", "TT", targetId); - target.updateGroupInviteData(player, groupId, player.getCharacterName()); + target.updateGroupInviteData(player, -1, player.getCharacterName()); } } @@ -167,23 +224,24 @@ public class GroupService extends Service { // Group doesn't exist yet if (group == null) { - group = createGroup(invitation.getSender().getCreatureObject().getObjectId()); + group = createGroup(sender); if (group == null) { - Log.e("GroupService", "Failed to create group"); + Log.e("GroupService", "Failed to create group from sender %s for %s", sender, player); creo.updateGroupInviteData(null, 0, ""); return; } - group.addMember(invitation.getSender().getCreatureObject()); - group.awarenessInRange(invitation.getSender().getCreatureObject()); + group.addMember(sender.getCreatureObject()); - sendSystemMessage(invitation.getSender(), "formed_self"); + 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"); + sendSystemMessage(player, "join_inviter_not_leader", sender.getCreatureObject().getObjectId()); creo.updateGroupInviteData(null, 0, ""); return; } @@ -194,34 +252,70 @@ public class GroupService extends Service { 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(long leader) { + private GroupObject createGroup(Player player) { GroupObject group = (GroupObject) ObjectCreator.createObjectFromTemplate(getNextObjectId(), "object/group/shared_group_object.iff"); if (group == null) return null; - group.setLeader(leader); - group.setLootMaster(leader); + 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() { - if (reservedIds.size() <= 5) - new ObjectIdRequestIntent("GroupService", 50).broadcast(); + synchronized (reservedIds) { + if (reservedIds.size() <= 5) + new ObjectIdRequestIntent("GroupService", 50).broadcast(); - return reservedIds.remove(0); + 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); } @@ -231,7 +325,6 @@ public class GroupService extends Service { } private void sendSystemMessage(Player target, String id, Object ... objects) { - target.sendPacket(new ChatSystemMessage(ChatSystemMessage.SystemChatType.SCREEN_AND_CHAT, "DEBUG: " + id)); IntentFactory.sendSystemMessage(target, "@group:" + id, objects); } } diff --git a/src/utilities/IntentFactory.java b/src/utilities/IntentFactory.java index 6ace71dd1..c766506b8 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,6 +77,8 @@ public final class IntentFactory { * Note that the prose key must always come first and the value for that key must always come second. */ 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);