diff --git a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleHandler.java b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleHandler.java index 73d3613b0..d3d88c3d0 100644 --- a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleHandler.java +++ b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleHandler.java @@ -26,146 +26,69 @@ ***********************************************************************************/ package com.projectswg.holocore.resources.gameplay.crafting.survey; -import com.projectswg.common.data.encodables.oob.ProsePackage; -import com.projectswg.common.data.encodables.oob.StringId; -import com.projectswg.common.data.encodables.oob.waypoint.WaypointColor; -import com.projectswg.common.data.encodables.tangible.Posture; import com.projectswg.common.data.location.Location; -import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; -import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage; -import com.projectswg.common.network.packets.swg.zone.chat.ChatSystemMessage; -import com.projectswg.common.network.packets.swg.zone.chat.ChatSystemMessage.SystemChatType; -import com.projectswg.holocore.intents.support.global.network.CloseConnectionIntent; -import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent; -import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent; import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.GalacticResource; -import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.GalacticResourceSpawn; -import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.GalacticResourceStats; -import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.RawResourceType; -import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.storage.GalacticResourceContainer; -import com.projectswg.holocore.resources.gameplay.crafting.resource.raw.RawResource; -import com.projectswg.holocore.resources.support.data.server_info.StandardLog; -import com.projectswg.holocore.resources.support.data.server_info.loader.CollectionLoader.CollectionSlotInfo; -import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader; -import com.projectswg.holocore.resources.support.global.network.DisconnectReason; -import com.projectswg.holocore.resources.support.global.player.Player; -import com.projectswg.holocore.resources.support.global.zone.sui.SuiButtons; -import com.projectswg.holocore.resources.support.global.zone.sui.SuiListBox; -import com.projectswg.holocore.resources.support.global.zone.sui.SuiMessageBox; -import com.projectswg.holocore.resources.support.global.zone.sui.SuiWindow; -import com.projectswg.holocore.resources.support.objects.ObjectCreator; -import com.projectswg.holocore.resources.support.objects.permissions.ContainerResult; -import com.projectswg.holocore.resources.support.objects.permissions.ReadWritePermissions; -import com.projectswg.holocore.resources.support.objects.swg.ServerAttribute; import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject; -import com.projectswg.holocore.resources.support.objects.swg.resource.ResourceContainerObject; import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject; -import com.projectswg.holocore.resources.support.objects.swg.waypoint.WaypointObject; import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool; -import me.joshlarson.jlcommon.control.IntentChain; -import me.joshlarson.jlcommon.log.Log; - -import java.util.List; -import java.util.Map.Entry; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; +import org.jetbrains.annotations.Nullable; public class SampleHandler { - private final AtomicReference sampleLocation; - private final AtomicReference> sampleLoop; private final CreatureObject creature; private final TangibleObject surveyTool; - private final AtomicBoolean running; - private final SampleLoopSession session; private final ScheduledThreadPool executor; + private volatile @Nullable SampleLoopSession sampleSession; + public SampleHandler(CreatureObject creature, TangibleObject surveyTool, ScheduledThreadPool executor) { - this.sampleLocation = new AtomicReference<>(null); - this.sampleLoop = new AtomicReference<>(null); - this.session = new AtomicReference<>(null); this.creature = creature; this.surveyTool = surveyTool; - this.running = new AtomicBoolean(false); this.executor = executor; + + this.sampleSession = null; } - public void startSession() { + public synchronized void startSession() { } - public void stopSession() { - stopSampleLoop(); + public synchronized void stopSession() { + SampleLoopSession session = this.sampleSession; + this.sampleSession = null; + if (session != null) + session.stopSession(); } - public void startSampleLoop(GalacticResource resource) { - Location location = creature.getWorldLocation(); - double concentration = getConcentration(resource, location); + public synchronized void startSampleLoop(GalacticResource resource) { + Location sampleLocation = creature.getWorldLocation(); - if (!isAllowedToSample(resource, concentration)) - return; - - if (!running.getAndSet(true)) { - ScheduledFuture prevLoop = sampleLoop.getAndSet(executor.executeWithFixedRate(5000, 5000, () -> sample(resource, location))); - stopSampleLoop(prevLoop); - sampleLocation.set(location); - creature.setPosture(Posture.CROUCHED); - creature.setMovementPercent(0); - creature.setTurnScale(0); - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, new ProsePackage(new StringId("@survey:start_sampling"), "TO", resource.getName()))); - Log.d("%s started a sample session with %s and concentration %.1f", creature.getObjectName(), resource.getName(), concentration); + SampleLoopSession prevSession = this.sampleSession; + if (prevSession != null && prevSession.isMatching(creature, surveyTool, resource, sampleLocation)) { + if (!prevSession.isSampling()) + prevSession.startSession(executor); + } else { + SampleLoopSession nextSession = new SampleLoopSession(creature, surveyTool, resource, sampleLocation); + if (prevSession != null) + prevSession.stopSession(); + nextSession.startSession(executor); + this.sampleSession = nextSession; } } - public void onPlayerMoved() { - Location oldLocation = sampleLocation.get(); - Location newLocation = creature.getWorldLocation(); - - if (oldLocation == null) - return; - if (oldLocation.distanceTo(newLocation) >= 0.5 || oldLocation.getTerrain() != newLocation.getTerrain()) - stopSampleLoop(); + public synchronized void onPlayerMoved() { + SampleLoopSession session = this.sampleSession; + if (session != null) + session.onPlayerMoved(); } - public void stopSampleLoop() { - if (!running.getAndSet(false)) - return; - stopSampleLoop(sampleLoop.getAndSet(null)); + public synchronized void stopSampleLoop() { + stopSession(); } public boolean isSampling() { - ScheduledFuture loop = this.sampleLoop.get(); - return loop != null && !loop.isDone(); - } - - private void stopSampleLoop(ScheduledFuture loop) { - if (loop != null && loop.cancel(false)) { - creature.setPosture(Posture.UPRIGHT); - creature.setMovementPercent(1); - creature.setTurnScale(1); - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_cancel")); - SuiWindow window = this.sampleWindow.get(); - if (window != null) - window.close(creature.getOwner()); - } - } - - private boolean isAllowedToSample(GalacticResource resource, double concentration) { - if (creature.getSkillModValue("surveying") < 20) { - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "You aren't allowed to sample without a surveying skillmod")); - return false; - } - if (creature.isInCombat()) { - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_cancel_attack")); - return false; - } - if (concentration <= 0.3) { - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, new ProsePackage(new StringId("@survey:density_below_threshold"), "TO", resource.getName()))); - return false; - } - return true; + SampleLoopSession session = this.sampleSession; + return session != null && session.isSampling(); } } diff --git a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleLoopSession.java b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleLoopSession.java index 430791cb1..4a9876e00 100644 --- a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleLoopSession.java +++ b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/survey/SampleLoopSession.java @@ -30,6 +30,7 @@ package com.projectswg.holocore.resources.gameplay.crafting.survey; import com.projectswg.common.data.encodables.oob.ProsePackage; import com.projectswg.common.data.encodables.oob.StringId; import com.projectswg.common.data.encodables.oob.waypoint.WaypointColor; +import com.projectswg.common.data.encodables.tangible.Posture; import com.projectswg.common.data.location.Location; import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage; @@ -58,12 +59,14 @@ import com.projectswg.holocore.resources.support.objects.permissions.ContainerRe import com.projectswg.holocore.resources.support.objects.permissions.ReadWritePermissions; import com.projectswg.holocore.resources.support.objects.swg.ServerAttribute; import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject; +import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureState; import com.projectswg.holocore.resources.support.objects.swg.resource.ResourceContainerObject; import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject; import com.projectswg.holocore.resources.support.objects.swg.waypoint.WaypointObject; import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool; import me.joshlarson.jlcommon.control.IntentChain; import me.joshlarson.jlcommon.log.Log; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Map.Entry; @@ -72,65 +75,96 @@ import java.util.concurrent.ThreadLocalRandom; public class SampleLoopSession { - private final CreatureObject creature; - private final TangibleObject surveyTool; - private final GalacticResource resource; + private final @NotNull CreatureObject creature; + private final @NotNull TangibleObject surveyTool; + private final @NotNull GalacticResource resource; + private final @NotNull Location sampleLocation; private SuiWindow sampleWindow; - private Location sampleLocation; private ScheduledFuture loopFuture; private boolean paused; private int sampleMultiplier; - public SampleLoopSession(CreatureObject creature, TangibleObject surveyTool, GalacticResource resource) { + public SampleLoopSession(@NotNull CreatureObject creature, @NotNull TangibleObject surveyTool, @NotNull GalacticResource resource, @NotNull Location sampleLocation) { this.creature = creature; this.surveyTool = surveyTool; this.resource = resource; + this.sampleLocation = sampleLocation; this.sampleWindow = null; - this.sampleLocation = null; this.loopFuture = null; this.paused = false; this.sampleMultiplier = 1; } - public synchronized void startSession(ScheduledThreadPool executor, GalacticResource resource, Location sampleLocation) { - this.sampleLocation = sampleLocation; + public boolean isMatching(CreatureObject creature, TangibleObject surveyTool, GalacticResource resource, Location sampleLocation) { + if (this.sampleLocation.distanceTo(sampleLocation) >= 0.5 || this.sampleLocation.getTerrain() != sampleLocation.getTerrain()) + return false; // Too far away for the same session + return this.creature.equals(creature) && this.surveyTool.equals(surveyTool) && this.resource.equals(resource); + } + + public synchronized boolean isSampling() { + return loopFuture != null && !loopFuture.isDone(); + } + + public synchronized void onPlayerMoved() { + Location oldLocation = sampleLocation; + Location newLocation = creature.getWorldLocation(); + + if (oldLocation.distanceTo(newLocation) >= 0.5 || oldLocation.getTerrain() != newLocation.getTerrain()) + stopSession(); + } + + /** + * Attempts to start the sample loop session with the specified executor. If the loop is unable to start, this function returns false; otherwise this function returns true. + * @param executor the executor for the loop to run on + * @return TRUE if the sample loop has begin, FALSE otherwise + */ + public synchronized boolean startSession(ScheduledThreadPool executor) { if (loopFuture != null) { loopFuture.cancel(false); } + double concentration = getConcentration(); + if (!isAllowedToSample(concentration)) + return false; + loopFuture = executor.executeWithFixedRate(5000, 5000, this::sample); + creature.setPosture(Posture.CROUCHED); + creature.setMovementPercent(0); + creature.setTurnScale(0); + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, new ProsePackage(new StringId("@survey:start_sampling"), "TO", resource.getName()))); + StandardLog.onPlayerTrace(this, creature, "started a sample session with %s and concentration %.1f", resource.getName(), concentration); + return true; } public synchronized void stopSession() { if (loopFuture != null) loopFuture.cancel(false); + else + return; loopFuture = null; + if (sampleWindow != null) { + Player owner = creature.getOwner(); + if (owner != null) + sampleWindow.close(owner); + } + sampleWindow = null; + + creature.setPosture(Posture.UPRIGHT); + creature.setMovementPercent(1); + creature.setTurnScale(1); + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_cancel")); } - private synchronized void sample() { - if (GalacticResourceContainer.getContainer().getTerrainResourceSpawns(resource, creature.getTerrain()).isEmpty()) { - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_empty")); - stopSession(); - return; - } - if (!creature.getInventory().getContainedObjects().contains(surveyTool)) { - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_gone")); - stopSession(); - return; - } - double concentration = getConcentration(); - if (concentration <= 0.3) { - creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, new ProsePackage(new StringId("@survey:density_below_threshold"), "TO", resource.getName()))); - stopSession(); - return; - } - if (paused) + private void sample() { + final double concentration = getConcentration(); + if (!isAllowedToSample(concentration) || paused) return; ThreadLocalRandom random = ThreadLocalRandom.current(); if (random.nextDouble() > concentration) { creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, new ProsePackage(new StringId("@survey:sample_failed"), "TO", resource.getName()))); + sendSampleEffects(); return; } @@ -167,8 +201,7 @@ public class SampleLoopSession { ObjectCreatedIntent.broadcast(resourceObject); break; } - creature.sendSelf(new PlayMusicMessage(0, getMusicFile(), 1, false)); - creature.sendObservers(new PlayClientEffectObjectMessage(getEffectFile(), "", creature.getObjectId(), "")); + sendSampleEffects(); } private void openConcentrationWindow() { @@ -230,9 +263,6 @@ public class SampleLoopSession { } private void createHighestConcentrationWaypoint() { - if (sampleLocation == null) - return; - double highestX = sampleLocation.getX(); double highestZ = sampleLocation.getZ(); double highest = 0; @@ -258,6 +288,41 @@ public class SampleLoopSession { creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:node_waypoint")); } + private boolean isAllowedToSample(double concentration) { + if (GalacticResourceContainer.getContainer().getTerrainResourceSpawns(resource, creature.getTerrain()).isEmpty()) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_empty")); + stopSession(); + return false; + } + if (!creature.getInventory().getContainedObjects().contains(surveyTool)) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_gone")); + stopSession(); + return false; + } + if (creature.getSkillModValue("surveying") < 20) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "You aren't allowed to sample without a surveying skillmod")); + return false; + } + if (creature.isStatesBitmask(CreatureState.RIDING_MOUNT)) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "You aren't allowed to sample while on a mount")); + return false; + } + if (creature.getParent() != null) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "You aren't allowed to sample while within a building")); + return false; + } + if (creature.isInCombat()) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, "@survey:sample_cancel_attack")); + return false; + } + if (concentration <= 0.3) { + creature.sendSelf(new ChatSystemMessage(SystemChatType.PERSONAL, new ProsePackage(new StringId("@survey:density_below_threshold"), "TO", resource.getName()))); + stopSession(); + return false; + } + return true; + } + private double getConcentration() { List spawns = GalacticResourceContainer.getContainer().getTerrainResourceSpawns(resource, creature.getTerrain()); double concentration = 0; @@ -295,10 +360,15 @@ public class SampleLoopSession { transferStat(obj, "res_toughness", stats.getUnitToughness()); } - private static void transferStat(TangibleObject obj, String name, int attr) { + private void transferStat(TangibleObject obj, String name, int attr) { obj.addAttribute("@obj_attr_n:" + name, String.valueOf(attr)); } + private void sendSampleEffects() { + creature.sendSelf(new PlayMusicMessage(0, getMusicFile(), 1, false)); + creature.sendObservers(new PlayClientEffectObjectMessage(getEffectFile(), "", creature.getObjectId(), "")); + } + private String getMusicFile() { RawResource rawResource = resource.getRawResource(); if (RawResourceType.MINERAL.isResourceType(rawResource))