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