mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-18 14:34:05 +00:00
Command Corrections.
This commit is contained in:
@@ -122,7 +122,7 @@ public class CommandFind implements DirectorExecutor {
|
||||
EngineBukkitOps.gotoPOI(e, type, player(), teleport);
|
||||
}
|
||||
|
||||
@Director(description = "Find an accepted hydrology feature")
|
||||
@Director(description = "Find an accepted hydrology feature", sync = true)
|
||||
public void river(
|
||||
@Param(description = "Feature type: surface, waterfall, underground, grotto, mouth, or a deep-fluid id", customHandler = HydrologyTypeHandler.class)
|
||||
String type,
|
||||
@@ -147,6 +147,7 @@ public class CommandFind implements DirectorExecutor {
|
||||
return;
|
||||
}
|
||||
Location origin = target.getLocation();
|
||||
World originWorld = origin.getWorld();
|
||||
int requestedDistance = runtime == null
|
||||
? 8192
|
||||
: Math.min(8192, runtime.settings().routing().tileSize() * 15);
|
||||
@@ -179,9 +180,9 @@ public class CommandFind implements DirectorExecutor {
|
||||
return;
|
||||
}
|
||||
Location destination = new Location(
|
||||
target.getWorld(), feature.x(), feature.y(), feature.z());
|
||||
originWorld, feature.x(), feature.y(), feature.z());
|
||||
prepareStructureTeleport(
|
||||
target, target.getWorld(), commandSender, label, destination, true);
|
||||
target, originWorld, commandSender, label, destination, true);
|
||||
} catch (Throwable error) {
|
||||
sendStructureMessage(target, commandSender,
|
||||
C.RED + "Could not locate " + type + " hydrology: " + error.getClass().getSimpleName());
|
||||
@@ -737,22 +738,41 @@ public class CommandFind implements DirectorExecutor {
|
||||
|
||||
private void prepareStructureTeleport(Player target, World world, VolmitSender commandSender, String structure,
|
||||
Location at, boolean useLocatedY) {
|
||||
int chunkX = at.getBlockX() >> 4;
|
||||
int chunkZ = at.getBlockZ() >> 4;
|
||||
BukkitPlatform.chunkAtAsync(world, chunkX, chunkZ, true).whenComplete((chunk, error) -> {
|
||||
if (error != null) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not load the destination for " + structure + ".");
|
||||
Iris.reportError("Could not load structure destination '" + structure + "'.", error);
|
||||
J.runEntity(target, () -> {
|
||||
if (!target.isOnline()) {
|
||||
return;
|
||||
}
|
||||
boolean scheduled = J.runRegion(world, chunkX, chunkZ,
|
||||
() -> teleportToStructure(target, world, commandSender, structure, at, useLocatedY));
|
||||
if (!scheduled) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not schedule the destination lookup for " + structure + ".");
|
||||
if (!world.equals(target.getWorld())) {
|
||||
commandSender.sendMessage(C.YELLOW + "Cancelled teleport to " + structure + " because your world changed.");
|
||||
return;
|
||||
}
|
||||
loadStructureDestination(target, world, commandSender, structure, at, useLocatedY);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadStructureDestination(Player target, World world, VolmitSender commandSender, String structure,
|
||||
Location at, boolean useLocatedY) {
|
||||
int chunkX = at.getBlockX() >> 4;
|
||||
int chunkZ = at.getBlockZ() >> 4;
|
||||
try {
|
||||
BukkitPlatform.chunkAtAsync(world, chunkX, chunkZ, true).whenComplete((chunk, error) -> {
|
||||
if (error != null) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not load the destination for " + structure + ".");
|
||||
Iris.reportError("Could not load structure destination '" + structure + "'.", error);
|
||||
return;
|
||||
}
|
||||
boolean scheduled = J.runRegion(world, chunkX, chunkZ,
|
||||
() -> teleportToStructure(target, world, commandSender, structure, at, useLocatedY));
|
||||
if (!scheduled) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not schedule the destination lookup for " + structure + ".");
|
||||
}
|
||||
});
|
||||
} catch (Throwable error) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not load the destination for " + structure + ".");
|
||||
Iris.reportError("Could not load structure destination '" + structure + "'.", error);
|
||||
}
|
||||
}
|
||||
|
||||
private void teleportToStructure(Player target, World world, VolmitSender commandSender, String structure,
|
||||
Location at, boolean useLocatedY) {
|
||||
try {
|
||||
@@ -760,16 +780,41 @@ public class CommandFind implements DirectorExecutor {
|
||||
? Math.max(world.getMinHeight() + 1, Math.min(world.getMaxHeight() - 1, at.getBlockY() + 2))
|
||||
: world.getHighestBlockYAt(at.getBlockX(), at.getBlockZ()) + 2;
|
||||
Location destination = new Location(world, at.getBlockX() + 0.5, y, at.getBlockZ() + 0.5);
|
||||
J.runEntity(target, () -> {
|
||||
BukkitPlatform.teleportAsync(target, destination);
|
||||
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_TELEPORTED, MessageArgument.untrusted("structure", structure), MessageArgument.untrusted("value", at.getBlockX()), MessageArgument.untrusted("y", y), MessageArgument.untrusted("value2", at.getBlockZ())));
|
||||
});
|
||||
J.runEntity(target, () -> completeStructureTeleport(target, world, commandSender, structure, destination));
|
||||
} catch (Throwable t) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not prepare the destination for " + structure + ".");
|
||||
Iris.reportError("Could not prepare structure destination '" + structure + "'.", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeStructureTeleport(Player target, World world, VolmitSender commandSender, String structure,
|
||||
Location destination) {
|
||||
if (!target.isOnline()) {
|
||||
return;
|
||||
}
|
||||
if (!world.equals(target.getWorld())) {
|
||||
commandSender.sendMessage(C.YELLOW + "Cancelled teleport to " + structure + " because your world changed.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
BukkitPlatform.teleportAsync(target, destination).whenComplete((teleported, error) -> {
|
||||
if (error != null) {
|
||||
Iris.reportError("Could not teleport to '" + structure + "'.", error);
|
||||
}
|
||||
sendStructureMessage(target, commandSender, error == null && Boolean.TRUE.equals(teleported)
|
||||
? IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_TELEPORTED,
|
||||
MessageArgument.untrusted("structure", structure),
|
||||
MessageArgument.untrusted("value", destination.getBlockX()),
|
||||
MessageArgument.untrusted("y", destination.getBlockY()),
|
||||
MessageArgument.untrusted("value2", destination.getBlockZ()))
|
||||
: C.RED + "Could not teleport to " + structure + ".");
|
||||
});
|
||||
} catch (Throwable error) {
|
||||
commandSender.sendMessage(C.RED + "Could not teleport to " + structure + ".");
|
||||
Iris.reportError("Could not teleport to '" + structure + "'.", error);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendStructureMessage(Player target, VolmitSender commandSender, String message) {
|
||||
J.runEntity(target, () -> commandSender.sendMessage(message));
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package art.arcane.iris.command;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.studio.view.GuiHost;
|
||||
import art.arcane.iris.studio.view.ImageMapStudioGUI;
|
||||
import art.arcane.iris.studio.view.NoiseExplorerGUI;
|
||||
import art.arcane.iris.studio.view.VisionGUI;
|
||||
@@ -274,10 +275,6 @@ public class CommandStudio implements DirectorExecutor {
|
||||
if (noGUI()) {
|
||||
return;
|
||||
}
|
||||
if (!ImageMapStudioGUI.isAvailable()) {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_YOU_MUST_HAVE_SERVER_LAUNCHED_GUIS_ENABLED_SETTINGS));
|
||||
return;
|
||||
}
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_YOU_NEED_BE_SPECIFY_IRIS_GENERATED_WORLD));
|
||||
return;
|
||||
@@ -883,6 +880,10 @@ public class CommandStudio implements DirectorExecutor {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_YOU_MUST_HAVE_SERVER_LAUNCHED_GUIS_ENABLED_SETTINGS));
|
||||
return true;
|
||||
}
|
||||
if (!GuiHost.isAvailable()) {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_DESKTOP_UNAVAILABLE));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package art.arcane.iris.command;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.generation.hydrology.HydrologyFeatureType;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.localization.IrisLanguage;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
|
||||
import art.arcane.iris.world.history.GenerationSemanticQueries;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
|
||||
public class CommandFindHydrologyTest {
|
||||
@Test
|
||||
public void riverCapturesPlayerStateOnTheOwningThread() throws Exception {
|
||||
assertTrue(CommandFind.class.getMethod("river", String.class, boolean.class)
|
||||
.getAnnotation(Director.class).sync());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void worldChangeDuringSearchDoesNotLoadOrTeleportIntoTheNewWorld() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
fixture.command.river("surface", true);
|
||||
when(fixture.player.getWorld()).thenReturn(mock(World.class));
|
||||
fixture.search.get().run();
|
||||
|
||||
fixture.platform.verify(() -> BukkitPlatform.chunkAtAsync(any(World.class), anyInt(), anyInt(), eq(true)), never());
|
||||
fixture.platform.verify(() -> BukkitPlatform.teleportAsync(any(Player.class), any(Location.class)), never());
|
||||
verify(fixture.sender).sendMessage(contains("world changed"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disconnectDuringSearchDoesNotLoadTheDestination() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
fixture.command.river("surface", true);
|
||||
when(fixture.player.isOnline()).thenReturn(false);
|
||||
fixture.search.get().run();
|
||||
|
||||
fixture.platform.verify(() -> BukkitPlatform.chunkAtAsync(any(World.class), anyInt(), anyInt(), eq(true)), never());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void worldChangeWhileLoadingTheDestinationCancelsTeleport() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
fixture.command.river("surface", true);
|
||||
fixture.search.get().run();
|
||||
when(fixture.player.getWorld()).thenReturn(mock(World.class));
|
||||
fixture.chunk.complete(mock(Chunk.class));
|
||||
|
||||
fixture.platform.verify(() -> BukkitPlatform.teleportAsync(any(Player.class), any(Location.class)), never());
|
||||
verify(fixture.sender).sendMessage(contains("world changed"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successIsReportedOnlyAfterTeleportCompletes() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
fixture.command.river("surface", true);
|
||||
fixture.search.get().run();
|
||||
fixture.chunk.complete(mock(Chunk.class));
|
||||
|
||||
verify(fixture.sender, never()).sendMessage("teleported");
|
||||
fixture.teleport.complete(true);
|
||||
verify(fixture.sender).sendMessage("teleported");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectedTeleportReportsFailureWithoutSuccess() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
fixture.command.river("surface", true);
|
||||
fixture.search.get().run();
|
||||
fixture.chunk.complete(mock(Chunk.class));
|
||||
fixture.teleport.complete(false);
|
||||
|
||||
verify(fixture.sender, never()).sendMessage("teleported");
|
||||
verify(fixture.sender).sendMessage(contains("Could not teleport"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedTeleportReportsTheFullCauseWithoutSuccess() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
IllegalStateException failure = new IllegalStateException("destination unloaded");
|
||||
fixture.command.river("surface", true);
|
||||
fixture.search.get().run();
|
||||
fixture.chunk.complete(mock(Chunk.class));
|
||||
fixture.teleport.completeExceptionally(failure);
|
||||
|
||||
verify(fixture.sender, never()).sendMessage("teleported");
|
||||
verify(fixture.sender).sendMessage(contains("Could not teleport"));
|
||||
fixture.logging.verify(() -> Iris.reportError(any(String.class), eq(failure)));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Fixture implements AutoCloseable {
|
||||
private final CommandFind command = mock(CommandFind.class, CALLS_REAL_METHODS);
|
||||
private final Engine engine = mock(Engine.class);
|
||||
private final IrisComplex complex = mock(IrisComplex.class);
|
||||
private final Player player = mock(Player.class);
|
||||
private final World world = mock(World.class);
|
||||
private final VolmitSender sender = mock(VolmitSender.class);
|
||||
private final AtomicReference<Runnable> search = new AtomicReference<>();
|
||||
private final CompletableFuture<Chunk> chunk = new CompletableFuture<>();
|
||||
private final CompletableFuture<Boolean> teleport = new CompletableFuture<>();
|
||||
private final MockedStatic<J> scheduler = mockStatic(J.class);
|
||||
private final MockedStatic<BukkitPlatform> platform = mockStatic(BukkitPlatform.class);
|
||||
private final MockedStatic<GenerationSemanticQueries> queries = mockStatic(GenerationSemanticQueries.class);
|
||||
private final MockedStatic<IrisLanguage> language = mockStatic(IrisLanguage.class, invocation -> "teleported");
|
||||
private final MockedStatic<Iris> logging = mockStatic(Iris.class);
|
||||
|
||||
private Fixture() {
|
||||
doReturn(engine).when(command).engine();
|
||||
doReturn(player).when(command).player();
|
||||
doReturn(sender).when(command).sender();
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(player.getWorld()).thenReturn(world);
|
||||
when(player.getLocation()).thenReturn(new Location(world, 0, 70, 0));
|
||||
when(player.isOnline()).thenReturn(true);
|
||||
when(world.getMinHeight()).thenReturn(-64);
|
||||
when(world.getMaxHeight()).thenReturn(320);
|
||||
scheduler.when(() -> J.a(any(Runnable.class))).thenAnswer(invocation -> {
|
||||
search.set(invocation.getArgument(0));
|
||||
return null;
|
||||
});
|
||||
scheduler.when(() -> J.runEntity(eq(player), any(Runnable.class))).thenAnswer(invocation -> {
|
||||
invocation.<Runnable>getArgument(1).run();
|
||||
return true;
|
||||
});
|
||||
scheduler.when(() -> J.runRegion(any(World.class), anyInt(), anyInt(), any(Runnable.class))).thenAnswer(invocation -> {
|
||||
invocation.<Runnable>getArgument(3).run();
|
||||
return true;
|
||||
});
|
||||
platform.when(() -> BukkitPlatform.chunkAtAsync(any(World.class), anyInt(), anyInt(), eq(true))).thenReturn(chunk);
|
||||
platform.when(() -> BukkitPlatform.teleportAsync(eq(player), any(Location.class))).thenReturn(teleport);
|
||||
queries.when(() -> GenerationSemanticQueries.nearestRiver(eq(engine), any(), eq(0), eq(0), anyInt(), any()))
|
||||
.thenReturn(Optional.of(new GenerationSemanticQueries.RiverResult(
|
||||
GenerationSemanticQueries.RiverSource.ACTIVE_PREDICTION,
|
||||
HydrologyFeatureType.RIFFLE, Optional.empty(), 1L, 32, 63, 32, OptionalLong.empty())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logging.close();
|
||||
language.close();
|
||||
queries.close();
|
||||
platform.close();
|
||||
scheduler.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package art.arcane.iris.command;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
|
||||
import art.arcane.iris.studio.view.GuiHost;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CommandStudioDesktopTest {
|
||||
private IrisSettings previousSettings;
|
||||
private boolean previousSuppressed;
|
||||
private RecordingSender sender;
|
||||
private CommandStudio command;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
previousSettings = IrisSettings.settings;
|
||||
previousSuppressed = GuiHost.isDesktopSuppressed();
|
||||
IrisSettings.settings = new IrisSettings();
|
||||
IrisSettings.settings.getGui().setUseServerLaunchedGuis(true);
|
||||
GuiHost.suppressDesktop(true);
|
||||
sender = new RecordingSender();
|
||||
command = new TestCommand(sender);
|
||||
}
|
||||
|
||||
@After
|
||||
public void restore() {
|
||||
IrisSettings.settings = previousSettings;
|
||||
GuiHost.suppressDesktop(previousSuppressed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapReportsMissingDisplayBeforeAccessingWorld() {
|
||||
command.map(null);
|
||||
|
||||
assertDisplayMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void imageMapReportsMissingDisplayInsteadOfDisabledSetting() {
|
||||
command.imagemap(null);
|
||||
|
||||
assertDisplayMessage();
|
||||
}
|
||||
|
||||
private void assertDisplayMessage() {
|
||||
assertEquals(1, sender.messages.size());
|
||||
assertTrue(sender.messages.getFirst(), sender.messages.getFirst().contains("display"));
|
||||
}
|
||||
|
||||
private static final class TestCommand extends CommandStudio {
|
||||
private final VolmitSender sender;
|
||||
|
||||
private TestCommand(VolmitSender sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VolmitSender sender() {
|
||||
return sender;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RecordingSender extends VolmitSender {
|
||||
private final List<String> messages = new ArrayList<>();
|
||||
|
||||
private RecordingSender() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user