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);