diff --git a/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/DanceIntent.java b/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/DanceIntent.java
index eb2f71a8c..0a2bcf5f0 100644
--- a/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/DanceIntent.java
+++ b/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/DanceIntent.java
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2023 /// 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. *
@@ -26,43 +26,43 @@
***********************************************************************************/
package com.projectswg.holocore.intents.gameplay.entertainment.dance;
-import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
+import com.projectswg.holocore.resources.support.global.player.Player;
import me.joshlarson.jlcommon.control.Intent;
public class DanceIntent extends Intent {
private final String danceName;
- private final CreatureObject creatureObject;
+ private final Player player;
private final boolean changeDance;
/**
* Start dancing
*
* @param danceName
- * @param creatureObject
+ * @param player
*/
- public DanceIntent(String danceName, CreatureObject creatureObject, boolean changeDance) {
+ public DanceIntent(String danceName, Player player, boolean changeDance) {
this.danceName = danceName;
- this.creatureObject = creatureObject;
+ this.player = player;
this.changeDance = changeDance;
}
/**
* Stop dancing
*
- * @param creatureObject
+ * @param player
*/
- public DanceIntent(CreatureObject creatureObject) {
- this(null, creatureObject, false);
+ public DanceIntent(Player player) {
+ this(null, player, false);
}
public String getDanceName() {
return danceName;
}
-
- public CreatureObject getCreatureObject() {
- return creatureObject;
+
+ public Player getPlayer() {
+ return player;
}
-
+
public boolean isStartDance() {
return danceName != null;
}
diff --git a/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/WatchIntent.java b/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/WatchIntent.java
index dd412e9c0..e2565064f 100644
--- a/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/WatchIntent.java
+++ b/src/main/java/com/projectswg/holocore/intents/gameplay/entertainment/dance/WatchIntent.java
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2023 /// 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. *
@@ -26,23 +26,23 @@
***********************************************************************************/
package com.projectswg.holocore.intents.gameplay.entertainment.dance;
+import com.projectswg.holocore.resources.support.global.player.Player;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
-import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
import me.joshlarson.jlcommon.control.Intent;
public class WatchIntent extends Intent {
- private final CreatureObject actor;
+ private final Player actor;
private final SWGObject target;
private final boolean startWatch;
- public WatchIntent(CreatureObject actor, SWGObject target, boolean startWatch) {
+ public WatchIntent(Player actor, SWGObject target, boolean startWatch) {
this.actor = actor;
this.target = target;
this.startWatch = startWatch;
}
- public CreatureObject getActor() {
+ public Player getActor() {
return actor;
}
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/KneelCmdCallback.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/KneelCmdCallback.java
index cef3765a2..db19c678d 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/KneelCmdCallback.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/KneelCmdCallback.java
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2023 /// 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. *
@@ -50,7 +50,7 @@ public class KneelCmdCallback implements ICmdCallback {
}
if (creature.isPerforming()) {
- new DanceIntent(player.getCreatureObject()).broadcast();
+ new DanceIntent(player).broadcast();
} else {
creature.setPosture(Posture.CROUCHED);
creature.setMovementPercent(0);
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/ProneCmdCallback.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/ProneCmdCallback.java
index f659edbd1..031bbf6b9 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/ProneCmdCallback.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/ProneCmdCallback.java
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2023 /// 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. *
@@ -51,7 +51,7 @@ public class ProneCmdCallback implements ICmdCallback {
}
if (creature.isPerforming()) {
- new DanceIntent(player.getCreatureObject()).broadcast();
+ new DanceIntent(player).broadcast();
} else {
creature.setPosture(Posture.PRONE);
creature.setTurnScale(1);
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StandCmdCallback.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StandCmdCallback.java
index 5934de006..982d7f62e 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StandCmdCallback.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StandCmdCallback.java
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2023 /// 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. *
@@ -53,7 +53,7 @@ public class StandCmdCallback implements ICmdCallback {
if (creature.isPerforming()) {
// Ziggy: When you move while dancing, the client wants to execute /stand instead of /stopDance. Blame SOE.
- new DanceIntent(player.getCreatureObject()).broadcast();
+ new DanceIntent(player).broadcast();
} else {
creature.clearStatesBitmask(CreatureState.SITTING_ON_CHAIR);
creature.setPosture(Posture.UPRIGHT);
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StartDanceCallback.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StartDanceCallback.java
index 92cf4c7a6..b0b0a0529 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StartDanceCallback.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/StartDanceCallback.java
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2023 /// 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. *
@@ -70,12 +70,12 @@ public class StartDanceCallback implements ICmdCallback {
int selection = SuiListBox.getSelectedRow(parameters);
String selectedDanceName = listBox.getListItem(selection).getName().toLowerCase(Locale.ENGLISH);
- new DanceIntent(selectedDanceName, player.getCreatureObject(), changeDance).broadcast();
+ new DanceIntent(selectedDanceName, player, changeDance).broadcast();
});
listBox.display(player);
} else {
- new DanceIntent(args, player.getCreatureObject(), changeDance).broadcast();
+ new DanceIntent(args, player, changeDance).broadcast();
}
}
}
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopDance.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopDance.java
index 638f377ba..96fac7dd1 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopDance.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopDance.java
@@ -1,3 +1,29 @@
+/***********************************************************************************
+ * Copyright (c) 2023 /// 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 com.projectswg.holocore.resources.support.global.commands.callbacks.generic;
import com.projectswg.holocore.intents.gameplay.entertainment.dance.DanceIntent;
@@ -10,7 +36,7 @@ public final class CmdStopDance implements ICmdCallback {
@Override
public void execute(@NotNull Player player, SWGObject target, @NotNull String args) {
- new DanceIntent(player.getCreatureObject()).broadcast();
+ new DanceIntent(player).broadcast();
}
}
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopWatching.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopWatching.java
index e37d91495..da8b8fb66 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopWatching.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdStopWatching.java
@@ -1,3 +1,29 @@
+/***********************************************************************************
+ * Copyright (c) 2023 /// 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 com.projectswg.holocore.resources.support.global.commands.callbacks.generic;
import com.projectswg.holocore.intents.gameplay.entertainment.dance.WatchIntent;
@@ -19,7 +45,7 @@ public final class CmdStopWatching implements ICmdCallback {
return;
}
- new WatchIntent(player.getCreatureObject(), target, false).broadcast();
+ new WatchIntent(player, target, false).broadcast();
}
}
diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdWatch.java b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdWatch.java
index 445e83195..86f881ce6 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdWatch.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/generic/CmdWatch.java
@@ -1,3 +1,29 @@
+/***********************************************************************************
+ * Copyright (c) 2023 /// 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 com.projectswg.holocore.resources.support.global.commands.callbacks.generic;
import com.projectswg.holocore.intents.gameplay.entertainment.dance.WatchIntent;
@@ -19,7 +45,7 @@ public final class CmdWatch implements ICmdCallback {
return;
}
- new WatchIntent(player.getCreatureObject(), target, true).broadcast();
+ new WatchIntent(player, target, true).broadcast();
}
}
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/entertainment/EntertainmentService.java b/src/main/java/com/projectswg/holocore/services/gameplay/entertainment/EntertainmentService.java
deleted file mode 100644
index b7bce76b7..000000000
--- a/src/main/java/com/projectswg/holocore/services/gameplay/entertainment/EntertainmentService.java
+++ /dev/null
@@ -1,453 +0,0 @@
-/***********************************************************************************
- * Copyright (c) 2018 /// 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 com.projectswg.holocore.services.gameplay.entertainment;
-
-import com.projectswg.common.data.encodables.oob.ProsePackage;
-import com.projectswg.common.data.encodables.oob.StringId;
-import com.projectswg.common.data.encodables.tangible.Posture;
-import com.projectswg.common.data.location.Location;
-import com.projectswg.common.network.packets.swg.zone.object_controller.Animation;
-import com.projectswg.holocore.intents.gameplay.entertainment.dance.DanceIntent;
-import com.projectswg.holocore.intents.gameplay.entertainment.dance.FlourishIntent;
-import com.projectswg.holocore.intents.gameplay.entertainment.dance.WatchIntent;
-import com.projectswg.holocore.intents.gameplay.player.experience.ExperienceIntent;
-import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent;
-import com.projectswg.holocore.intents.support.global.zone.PlayerEventIntent;
-import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent;
-import com.projectswg.holocore.resources.support.data.server_info.StandardLog;
-import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader;
-import com.projectswg.holocore.resources.support.data.server_info.loader.PerformanceLoader.PerformanceInfo;
-import com.projectswg.holocore.resources.support.global.player.Player;
-import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
-import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
-import me.joshlarson.jlcommon.control.IntentHandler;
-import me.joshlarson.jlcommon.control.Service;
-import me.joshlarson.jlcommon.log.Log;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-/**
- * @author Mads
- */
-public class EntertainmentService extends Service {
-
- // TODO: when performing, make NPCs in a radius of x look towards the player (?) and clap. When they stop, turn back (?) and stop clapping
- private static final byte XP_CYCLE_RATE = 10;
- private static final byte WATCH_RADIUS = 20;
-
- private final Map performerMap;
- private final ScheduledExecutorService executorService;
-
- public EntertainmentService() {
- performerMap = new HashMap<>(); // TODO synchronize access?
- executorService = Executors.newSingleThreadScheduledExecutor();
- }
-
- @Override
- public boolean terminate() {
- executorService.shutdownNow();
- return super.terminate();
- }
-
- @IntentHandler
- private void handleDanceIntent(DanceIntent di) {
- CreatureObject dancer = di.getCreatureObject();
- String danceName = di.getDanceName();
-
- if (di.isStartDance()) {
- // This intent wants the creature to start dancing
- // If we're changing dance, allow them to do so
- boolean changeDance = di.isChangeDance();
-
- if (!changeDance && dancer.isPerforming()) {
- new SystemMessageIntent(dancer.getOwner(), "@performance:already_performing_self").broadcast();
- } else if (DataLoader.Companion.performances().getPerformanceByName(danceName) != null) {
- // The dance name is valid.
- if (dancer.hasCommand("startDance+" + danceName)) {
-
- if (changeDance) { // If they're changing dance, we just need to change their animation.
- changeDance(dancer, danceName);
- } else { // Otherwise, they should begin performing now
- startDancing(dancer, danceName);
- }
- } else {
- // This creature doesn't have the ability to perform this dance.
- new SystemMessageIntent(dancer.getOwner(), "@performance:dance_lack_skill_self").broadcast();
- }
- } else {
- // This dance name is invalid
- new SystemMessageIntent(dancer.getOwner(), "@performance:dance_unknown_self").broadcast();
- }
- } else {
- // This intent wants the creature to stop dancing
- stopDancing(dancer);
- }
- }
-
- @IntentHandler
- private void handlePlayerEventIntent(PlayerEventIntent pei) {
- CreatureObject creature = pei.getPlayer().getCreatureObject();
-
- if (creature == null)
- return;
-
- switch (pei.getEvent()) {
- case PE_LOGGED_OUT:
- // Don't keep giving them XP if they log out
- if (isEntertainer(creature) && creature.getPosture().equals(Posture.SKILL_ANIMATING)) {
- cancelExperienceTask(creature);
- }
-
- break;
- case PE_ZONE_IN_SERVER:
- // We need to check if they're dancing in order to start giving them XP
- if (isEntertainer(creature) && creature.getPosture().equals(Posture.SKILL_ANIMATING)) {
- scheduleExperienceTask(creature, DataLoader.Companion.performances().getPerformanceByDanceId(Integer.parseInt(creature.getAnimation().replace("dance_", ""))).getPerformanceName());
- }
-
- break;
- case PE_DISAPPEAR:
- // If a spectator disappears, they need to stop watching and be removed from the audience
- long performerId = creature.getPerformanceListenTarget();
-
- if (performerId != 0 && performerMap.get(performerId).removeSpectator(creature)) {
- stopWatching(creature, false);
- }
-
- // If a performer disappears, the audience needs to be cleared
- // They're also removed from the map of active performers.
- if (isEntertainer(creature) && creature.isPerforming()) {
- performerMap.get(creature.getObjectId()).clearSpectators();
- performerMap.remove(creature.getObjectId());
- }
- break;
- default:
- break;
- }
- }
-
- @IntentHandler
- private void handleFlourishIntent(FlourishIntent fi) {
- Player performer = fi.getPerformer();
- CreatureObject performerObject = performer.getCreatureObject();
-
- if (performerObject.getPerformanceCounter() != 0)
- return;
-
- performerObject.setPerformanceCounter(1);
-
- // Send the flourish animation to the owner of the creature and owners of creatures observing
- performerObject.sendObservers(new Animation(performerObject.getObjectId(), fi.getFlourishName()));
- new SystemMessageIntent(performer, "@performance:flourish_perform").broadcast();
- }
-
- @IntentHandler
- private void handleWatchIntent(WatchIntent wi) {
- SWGObject target = wi.getTarget();
-
- if (target instanceof CreatureObject) {
- CreatureObject actor = wi.getActor();
- CreatureObject creature = (CreatureObject) target;
- Player actorOwner = actor.getOwner();
-
- if (!isEntertainer(creature)) {
- // We can't watch non-entertainers - do nothing
- return;
- }
-
- if (creature.isPlayer()) {
- if (creature.isPerforming()) {
- Performance performance = performerMap.get(creature.getObjectId());
-
- if (wi.isStartWatch()) {
- if (performance.addSpectator(actor)) {
- startWatching(actor, creature);
- }
- } else {
- if (performance.removeSpectator(actor)) {
- stopWatching(actor, true);
- }
- }
- } else {
- // While this is a valid target for watching, the target is currently not performing.
- new SystemMessageIntent(actorOwner, new ProsePackage(new StringId("performance", "dance_watch_not_dancing"), "TT", creature.getObjectName())).broadcast();
- }
- } else {
- // You can't watch NPCs, regardless of whether they're dancing or not
- new SystemMessageIntent(actorOwner, "@performance:dance_watch_npc").broadcast();
- }
- }
- }
-
- @IntentHandler
- private void handlePlayerTransformedIntent(PlayerTransformedIntent pti) {
- CreatureObject movedPlayer = pti.getPlayer();
- long performanceListenTarget = movedPlayer.getPerformanceListenTarget();
-
- if (performanceListenTarget != 0) {
- // They're watching a performer!
-
- Performance performance = performerMap.get(performanceListenTarget);
-
- if (performance == null) {
- Log.e("Couldn't perform range check on %s, because there was no performer with object ID %d", movedPlayer, performanceListenTarget);
- return;
- }
-
- CreatureObject performer = performance.getPerformer();
-
- Location performerLocation = performer.getWorldLocation();
- Location movedPlayerLocation = pti.getPlayer().getWorldLocation(); // Ziggy: The newLocation in PlayerTransformedIntent isn't the world location, which is what we need here
-
- if (!movedPlayerLocation.isWithinDistance(performerLocation, WATCH_RADIUS)) {
- // They moved out of the defined range! Make them stop watching
- if (performance.removeSpectator(movedPlayer)) {
- stopWatching(movedPlayer, true);
- } else {
- Log.w("%s ran out of range of %s, but couldn't stop watching because they weren't watching in the first place", movedPlayer, performer);
- }
- }
- }
-
- }
-
- /**
- * Checks if the {@code CreatureObject} is a Novice Entertainer.
- *
- * @param performer
- * @return true if {@code performer} is a Novice Entertainer and false if not
- */
- private boolean isEntertainer(CreatureObject performer) {
- return performer.hasSkill("social_entertainer_novice"); // First entertainer skillbox
- }
-
- private void scheduleExperienceTask(CreatureObject performer, String performanceName) {
- Log.d("Scheduled %s to receive XP every %d seconds", performer, XP_CYCLE_RATE);
- synchronized (performerMap) {
- long performerId = performer.getObjectId();
- Future> future = executorService.scheduleAtFixedRate(new EntertainerExperience(performer), XP_CYCLE_RATE, XP_CYCLE_RATE, TimeUnit.SECONDS);
-
- // If they went LD but came back before disappearing
- if (performerMap.containsKey(performerId)) {
- Performance performance = performerMap.get(performerId);
- performance.setFuture(future);
- } else {
- performerMap.put(performer.getObjectId(), new Performance(performer, future, performanceName));
- }
- }
- }
-
- private void cancelExperienceTask(CreatureObject performer) {
- Log.d("%s no longer receives XP every %d seconds", performer, XP_CYCLE_RATE);
- synchronized (performerMap) {
- Performance performance = performerMap.get(performer.getObjectId());
-
- if (performance == null) {
- Log.e("Couldn't cancel experience task for %s because they weren't found in performerMap", performer);
- return;
- }
-
- Future> future = performance.getFuture();
-
- // TODO null check?
- // TODO use return result?
- future.cancel(false); // Running tasks are allowed to finish.
- }
- }
-
- private void startDancing(CreatureObject dancer, String danceName) {
- dancer.setAnimation("dance_" + DataLoader.Companion.performances().getPerformanceByName(danceName).getDanceVisualId());
- dancer.setPerformanceId(0); // 0 - anything else will make it look like we're playing music
- dancer.setPerformanceCounter(0);
- dancer.setPerforming(true);
- dancer.setPosture(Posture.SKILL_ANIMATING);
-
- scheduleExperienceTask(dancer, danceName);
- new SystemMessageIntent(dancer.getOwner(), "@performance:dance_start_self").broadcast();
- }
-
- private void stopDancing(CreatureObject dancer) {
- if (dancer.isPerforming()) {
- dancer.setPerforming(false);
- dancer.setPosture(Posture.UPRIGHT);
- dancer.setPerformanceCounter(0);
- dancer.setAnimation("");
-
- // Non-entertainers don't receive XP and have no audience - ignore them
- if (isEntertainer(dancer)) {
- cancelExperienceTask(dancer);
- performerMap.remove(dancer.getObjectId()).clearSpectators();
- }
-
- new SystemMessageIntent(dancer.getOwner(), "@performance:dance_stop_self").broadcast();
- } else {
- new SystemMessageIntent(dancer.getOwner(), "@performance:dance_not_performing").broadcast();
- }
- }
-
- private void changeDance(CreatureObject dancer, String newPerformanceName) {
- performerMap.get(dancer.getObjectId()).setPerformanceName(newPerformanceName);
- dancer.setAnimation("dance_" + DataLoader.Companion.performances().getPerformanceByName(newPerformanceName).getPerformanceName());
- }
-
- private void startWatching(CreatureObject actor, CreatureObject creature) {
- actor.setMoodAnimation("entertained");
- new SystemMessageIntent(actor.getOwner(), new ProsePackage(new StringId("performance", "dance_watch_self"), "TT", creature.getObjectName())).broadcast();
- actor.setPerformanceListenTarget(creature.getObjectId());
- }
-
- private void stopWatching(CreatureObject actor, boolean displaySystemMessage) {
- actor.setMoodAnimation("neutral");
- if (displaySystemMessage)
- new SystemMessageIntent(actor.getOwner(), "@performance:dance_watch_stop_self").broadcast();
- actor.setPerformanceListenTarget(0);
- }
-
- private class Performance {
-
- private final CreatureObject performer;
- private final Set audience;
- private Future> future;
- private String performanceName;
-
- public Performance(CreatureObject performer, Future> future, String performanceName) {
- this.performer = performer;
- this.future = future;
- this.performanceName = performanceName;
- audience = new HashSet<>();
- }
-
- public CreatureObject getPerformer() {
- return performer;
- }
-
- public Future> getFuture() {
- return future;
- }
-
- public void setFuture(Future> future) {
- this.future = future;
- }
-
- public String getPerformanceName() {
- return performanceName;
- }
-
- public boolean addSpectator(CreatureObject spectator) {
- return audience.add(spectator);
- }
-
- public boolean removeSpectator(CreatureObject spectator) {
- return audience.remove(spectator);
- }
-
- public void clearSpectators() {
- audience.forEach(spectator -> stopWatching(spectator, true));
- audience.clear();
- }
-
- public void setPerformanceName(String performanceName) {
- this.performanceName = performanceName;
- }
-
- }
-
- /**
- * Data pulled from the performance.iff table
- */
- private static class PerformanceData {
-
- private final String performanceId;
- private final int flourishXpMod;
-
- public PerformanceData(String performanceId, int flourishXpMod) {
- this.performanceId = performanceId;
- this.flourishXpMod = flourishXpMod;
- }
-
- public String getPerformanceId() {
- return performanceId;
- }
-
- public int getFlourishXpMod() {
- return flourishXpMod;
- }
-
- }
-
- private class EntertainerExperience implements Runnable {
-
- private final CreatureObject performer;
-
- private EntertainerExperience(CreatureObject performer) {
- this.performer = performer;
- }
-
- @Override
- public void run() {
- Performance performance = performerMap.get(performer.getObjectId());
-
- if (performance == null) {
- StandardLog.onPlayerError(this, performer, "is not in performerMap");
- return;
- }
-
- String performanceName = performance.getPerformanceName();
- PerformanceInfo performanceData = DataLoader.Companion.performances().getPerformanceByName(performanceName);
- if (performanceData == null) {
- StandardLog.onPlayerError(this, performer, "was performing unknown performance: '%s'", performanceName);
- return;
- }
-
- int flourishXpMod = performanceData.getFlourishXpMod();
- int performanceCounter = performer.getPerformanceCounter();
- int xpGained = performanceCounter * flourishXpMod;
-
- if (xpGained > 0) {
- if (isEntertainer(performer)) {
- if (isDancing()) {
- new ExperienceIntent(performer, performer, "dance", xpGained, true).broadcast();
- }
- }
- performer.setPerformanceCounter(performanceCounter - 1);
- }
- }
-
- private boolean isDancing() {
- return performer.getPerformanceId() == 0;
- }
-
- }
-
-}
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/entertainment/EntertainmentService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/entertainment/EntertainmentService.kt
new file mode 100644
index 000000000..46d8bbc74
--- /dev/null
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/entertainment/EntertainmentService.kt
@@ -0,0 +1,378 @@
+/***********************************************************************************
+ * Copyright (c) 2023 /// 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 com.projectswg.holocore.services.gameplay.entertainment
+
+import com.projectswg.common.data.encodables.oob.ProsePackage
+import com.projectswg.common.data.encodables.oob.StringId
+import com.projectswg.common.data.encodables.tangible.Posture
+import com.projectswg.common.network.packets.swg.zone.object_controller.Animation
+import com.projectswg.holocore.intents.gameplay.entertainment.dance.DanceIntent
+import com.projectswg.holocore.intents.gameplay.entertainment.dance.FlourishIntent
+import com.projectswg.holocore.intents.gameplay.entertainment.dance.WatchIntent
+import com.projectswg.holocore.intents.gameplay.player.experience.ExperienceIntent
+import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent
+import com.projectswg.holocore.intents.support.global.zone.PlayerEventIntent
+import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent
+import com.projectswg.holocore.resources.support.data.server_info.StandardLog
+import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader.Companion.performances
+import com.projectswg.holocore.resources.support.global.player.Player
+import com.projectswg.holocore.resources.support.global.player.PlayerEvent
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
+import me.joshlarson.jlcommon.control.IntentHandler
+import me.joshlarson.jlcommon.control.Service
+import me.joshlarson.jlcommon.log.Log
+import java.util.concurrent.Executors
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+
+class EntertainmentService : Service() {
+ private val performerMap = mutableMapOf()
+ private val executorService = Executors.newSingleThreadScheduledExecutor()
+
+ override fun terminate(): Boolean {
+ executorService.shutdownNow()
+ return super.terminate()
+ }
+
+ @IntentHandler
+ private fun handleDanceIntent(di: DanceIntent) {
+ val player = di.player
+ val dancer = player.creatureObject
+ val danceName = di.danceName
+ if (di.isStartDance) {
+ // This intent wants the creature to start dancing
+ // If we're changing dance, allow them to do so
+ val changeDance = di.isChangeDance
+ if (!changeDance && dancer.isPerforming) {
+ SystemMessageIntent(player, "@performance:already_performing_self").broadcast()
+ } else if (performances().getPerformanceByName(danceName) != null) {
+ // The dance name is valid.
+ if (dancer.hasCommand("startDance+$danceName")) {
+ if (changeDance) { // If they're changing dance, we just need to change their animation.
+ changeDance(dancer, danceName)
+ } else { // Otherwise, they should begin performing now
+ startDancing(player, danceName)
+ }
+ } else {
+ // This creature doesn't have the ability to perform this dance.
+ SystemMessageIntent(player, "@performance:dance_lack_skill_self").broadcast()
+ }
+ } else {
+ // This dance name is invalid
+ SystemMessageIntent(player, "@performance:dance_unknown_self").broadcast()
+ }
+ } else {
+ // This intent wants the creature to stop dancing
+ stopDancing(player)
+ }
+ }
+
+ @IntentHandler
+ private fun handlePlayerEventIntent(pei: PlayerEventIntent) {
+ val player = pei.player
+ val creature = player.creatureObject ?: return
+ when (pei.event) {
+ PlayerEvent.PE_LOGGED_OUT -> handlePlayerLoggedOut(creature)
+ PlayerEvent.PE_ZONE_IN_SERVER -> handlePlayerZoneIn(creature)
+ PlayerEvent.PE_DISAPPEAR -> handlePlayerDisappear(player)
+ else -> {}
+ }
+ }
+
+ private fun handlePlayerDisappear(player: Player) {
+ val creature = player.creatureObject
+ // If a spectator disappears, they need to stop watching and be removed from the audience
+ val performerId = creature.performanceListenTarget
+ val performance = performerMap[performerId]
+ val spectating = performance?.removeSpectator(creature) == true
+ if (performerId != 0L && spectating) {
+ stopWatching(player, false)
+ }
+
+ // If a performer disappears, the audience needs to be cleared
+ // They're also removed from the map of active performers.
+ if (isEntertainer(creature) && creature.isPerforming) {
+ performerMap[creature.objectId]?.clearSpectators()
+ performerMap.remove(creature.objectId)
+ }
+ }
+
+ private fun handlePlayerZoneIn(creature: CreatureObject) {
+ if (isEntertainer(creature) && creature.posture == Posture.SKILL_ANIMATING) {
+ val danceId = creature.animation.replace("dance_", "").toInt()
+ val performanceByDanceId = performances().getPerformanceByDanceId(danceId)
+ if (performanceByDanceId != null) {
+ scheduleExperienceTask(
+ creature, performanceByDanceId.performanceName
+ )
+ }
+ }
+ }
+
+ private fun handlePlayerLoggedOut(creature: CreatureObject) {
+ if (isEntertainer(creature) && creature.posture == Posture.SKILL_ANIMATING) {
+ cancelExperienceTask(creature)
+ }
+ }
+
+ @IntentHandler
+ private fun handleFlourishIntent(fi: FlourishIntent) {
+ val performer = fi.performer
+ val performerObject = performer.creatureObject
+ if (performerObject.performanceCounter != 0) return
+ performerObject.performanceCounter = 1
+
+ // Send the flourish animation to the owner of the creature and owners of creatures observing
+ performerObject.sendObservers(Animation(performerObject.objectId, fi.flourishName))
+ SystemMessageIntent(performer, "@performance:flourish_perform").broadcast()
+ }
+
+ @IntentHandler
+ private fun handleWatchIntent(wi: WatchIntent) {
+ val target = wi.target
+ if (target is CreatureObject) {
+ val actor = wi.actor
+ if (!isEntertainer(target)) {
+ // We can't watch non-entertainers - do nothing
+ return
+ }
+ if (target.isPlayer) {
+ if (target.isPerforming) {
+ val performance = performerMap[target.objectId] ?: return
+ if (wi.isStartWatch) {
+ if (performance.addSpectator(actor.creatureObject)) {
+ startWatching(actor, target)
+ }
+ } else {
+ if (performance.removeSpectator(actor.creatureObject)) {
+ stopWatching(actor, true)
+ }
+ }
+ } else {
+ // While this is a valid target for watching, the target is currently not performing.
+ SystemMessageIntent(
+ actor, ProsePackage(StringId("performance", "dance_watch_not_dancing"), "TT", target.objectName)
+ ).broadcast()
+ }
+ } else {
+ // You can't watch NPCs, regardless of whether they're dancing or not
+ SystemMessageIntent(actor, "@performance:dance_watch_npc").broadcast()
+ }
+ }
+ }
+
+ @IntentHandler
+ private fun handlePlayerTransformedIntent(pti: PlayerTransformedIntent) {
+ val movedPlayer = pti.player
+ val performanceListenTarget = movedPlayer.performanceListenTarget
+ if (performanceListenTarget != 0L) {
+ // They're watching a performer!
+ val performance = performerMap[performanceListenTarget]
+ if (performance == null) {
+ Log.e("Couldn't perform range check on %s, because there was no performer with object ID %d", movedPlayer, performanceListenTarget)
+ return
+ }
+ val performer = performance.performer
+ val performerLocation = performer.worldLocation
+ val movedPlayerLocation = pti.player.worldLocation // Ziggy: The newLocation in PlayerTransformedIntent isn't the world location, which is what we need here
+ if (!movedPlayerLocation.isWithinDistance(performerLocation, WATCH_RADIUS.toDouble())) {
+ // They moved out of the defined range! Make them stop watching
+ if (performance.removeSpectator(movedPlayer)) {
+ val player = movedPlayer.owner
+ if (player != null) {
+ stopWatching(player, true)
+ }
+ } else {
+ Log.w(
+ "%s ran out of range of %s, but couldn't stop watching because they weren't watching in the first place",
+ movedPlayer,
+ performer
+ )
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the `CreatureObject` is a Novice Entertainer.
+ *
+ * @param performer
+ * @return true if `performer` is a Novice Entertainer and false if not
+ */
+ private fun isEntertainer(performer: CreatureObject): Boolean {
+ return performer.hasSkill("social_entertainer_novice") // First entertainer skillbox
+ }
+
+ private fun scheduleExperienceTask(performer: CreatureObject, performanceName: String) {
+ Log.d("Scheduled %s to receive XP every %d seconds", performer, XP_CYCLE_RATE)
+ synchronized(performerMap) {
+ val performerId = performer.objectId
+ val future = executorService.scheduleAtFixedRate(
+ EntertainerExperience(performer),
+ XP_CYCLE_RATE.toLong(),
+ XP_CYCLE_RATE.toLong(),
+ TimeUnit.SECONDS
+ )
+
+ // If they went LD but came back before disappearing
+ val performance = performerMap[performerId]
+ if (performance != null) {
+ performance.future = future
+ } else {
+ performerMap.put(performer.objectId, Performance(performer, future, performanceName))
+ }
+ }
+ }
+
+ private fun cancelExperienceTask(performer: CreatureObject) {
+ Log.d("%s no longer receives XP every %d seconds", performer, XP_CYCLE_RATE)
+ synchronized(performerMap) {
+ val performance = performerMap[performer.objectId]
+ if (performance == null) {
+ Log.e("Couldn't cancel experience task for %s because they weren't found in performerMap", performer)
+ return
+ }
+ performance.future.cancel(false)
+ }
+ }
+
+ private fun startDancing(player: Player, danceName: String) {
+ val dancer = player.creatureObject
+ val performanceByName = performances().getPerformanceByName(danceName)
+ if (performanceByName == null) {
+ StandardLog.onPlayerEvent(this, dancer, "tried to start unknown dance %s", danceName)
+ return
+ }
+ val danceVisualId = performanceByName.danceVisualId
+ dancer.animation = "dance_$danceVisualId"
+ dancer.performanceId = 0 // 0 - anything else will make it look like we're playing music
+ dancer.performanceCounter = 0
+ dancer.isPerforming = true
+ dancer.posture = Posture.SKILL_ANIMATING
+ scheduleExperienceTask(dancer, danceName)
+ SystemMessageIntent(player, "@performance:dance_start_self").broadcast()
+ }
+
+ private fun stopDancing(player: Player) {
+ val dancer = player.creatureObject
+ if (dancer.isPerforming) {
+ dancer.isPerforming = false
+ dancer.posture = Posture.UPRIGHT
+ dancer.performanceCounter = 0
+ dancer.animation = ""
+
+ // Non-entertainers don't receive XP and have no audience - ignore them
+ if (isEntertainer(dancer)) {
+ cancelExperienceTask(dancer)
+ val performance = performerMap.remove(dancer.objectId)
+ performance?.clearSpectators()
+ }
+ SystemMessageIntent(player, "@performance:dance_stop_self").broadcast()
+ } else {
+ SystemMessageIntent(player, "@performance:dance_not_performing").broadcast()
+ }
+ }
+
+ private fun changeDance(dancer: CreatureObject, newPerformanceName: String) {
+ val performance = performerMap[dancer.objectId]
+ if (performance != null) {
+ performance.performanceName = newPerformanceName
+ val performanceByName = performances().getPerformanceByName(newPerformanceName)
+ if (performanceByName != null) {
+ dancer.animation = "dance_" + performanceByName.performanceName
+ }
+ }
+ }
+
+ private fun startWatching(player: Player, creature: CreatureObject) {
+ val actor = player.creatureObject
+ actor.moodAnimation = "entertained"
+ SystemMessageIntent(player, ProsePackage(StringId("performance", "dance_watch_self"), "TT", creature.objectName)).broadcast()
+ actor.performanceListenTarget = creature.objectId
+ }
+
+ private fun stopWatching(player: Player, displaySystemMessage: Boolean) {
+ val actor = player.creatureObject
+ actor.moodAnimation = "neutral"
+ if (displaySystemMessage) SystemMessageIntent(player, "@performance:dance_watch_stop_self").broadcast()
+ actor.performanceListenTarget = 0
+ }
+
+ private inner class Performance(val performer: CreatureObject, var future: Future<*>, var performanceName: String) {
+ private val audience = mutableSetOf()
+
+ fun addSpectator(spectator: CreatureObject): Boolean {
+ return audience.add(spectator)
+ }
+
+ fun removeSpectator(spectator: CreatureObject): Boolean {
+ return audience.remove(spectator)
+ }
+
+ fun clearSpectators() {
+ audience.mapNotNull { it.owner }.forEach(Consumer { player -> stopWatching(player, true) })
+ audience.clear()
+ }
+ }
+
+ private inner class EntertainerExperience(private val performer: CreatureObject) : Runnable {
+ override fun run() {
+ val performance = performerMap[performer.objectId]
+ if (performance == null) {
+ StandardLog.onPlayerError(this, performer, "is not in performerMap")
+ return
+ }
+ val performanceName = performance.performanceName
+ val performanceData = performances().getPerformanceByName(performanceName)
+ if (performanceData == null) {
+ StandardLog.onPlayerError(this, performer, "was performing unknown performance: '%s'", performanceName)
+ return
+ }
+ val flourishXpMod = performanceData.flourishXpMod
+ val performanceCounter = performer.performanceCounter
+ val xpGained = performanceCounter * flourishXpMod
+ if (xpGained > 0) {
+ if (isEntertainer(performer)) {
+ if (isDancing) {
+ ExperienceIntent(performer, performer, "dance", xpGained, true).broadcast()
+ }
+ }
+ performer.performanceCounter = performanceCounter - 1
+ }
+ }
+
+ private val isDancing: Boolean
+ get() = performer.performanceId == 0
+ }
+
+ companion object {
+ // TODO: when performing, make NPCs in a radius of x look towards the player (?) and clap. When they stop, turn back (?) and stop clapping
+ private const val XP_CYCLE_RATE: Byte = 10
+ private const val WATCH_RADIUS: Byte = 20
+ }
+}