Skip to content

Commit 981d368

Browse files
authored
Merge pull request #3338 from Multiverse/feat/relative-positioning
Add back support for relative position with ~ in destination coordinates
2 parents 7507099 + da5f5de commit 981d368

12 files changed

Lines changed: 802 additions & 46 deletions

File tree

src/main/java/org/mvplugins/multiverse/core/command/MVCommandContexts.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ private ContentFilter parseContentFilter(BukkitCommandExecutionContext context)
116116
}
117117

118118
return destinationsProvider.parseDestination(context.getSender(), destination)
119-
.getOrThrow(failure -> MVInvalidCommandArgument.of(failure.getFailureMessage()));
119+
.getOrThrow(failure ->
120+
new InvalidCommandArgument(failure.getFailureMessage().formatted(context.getIssuer())));
120121
}
121122

122123
private GameRule<?> parseGameRule(BukkitCommandExecutionContext context) {

src/main/java/org/mvplugins/multiverse/core/destination/core/ExactDestination.java

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import org.mvplugins.multiverse.core.config.CoreConfig;
2020
import org.mvplugins.multiverse.core.destination.Destination;
2121
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
22+
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
2223
import org.mvplugins.multiverse.core.locale.MVCorei18n;
2324
import org.mvplugins.multiverse.core.utils.REPatterns;
25+
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
26+
import org.mvplugins.multiverse.core.utils.position.PositionNumber;
27+
import org.mvplugins.multiverse.core.utils.position.VectorPosition;
2428
import org.mvplugins.multiverse.core.utils.result.Attempt;
2529
import org.mvplugins.multiverse.core.utils.result.FailureReason;
2630
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
@@ -62,7 +66,11 @@ public ExactDestination(CoreConfig config, WorldManager worldManager, WorldEntry
6266
* @return A new {@link ExactDestinationInstance}
6367
*/
6468
public @NotNull ExactDestinationInstance fromLocation(@NotNull Location location) {
65-
return new ExactDestinationInstance(this, new UnloadedWorldLocation(location));
69+
return new ExactDestinationInstance(
70+
this,
71+
location.getWorld().getName(),
72+
EntityPosition.ofLocation(location)
73+
);
6674
}
6775

6876
/**
@@ -73,54 +81,38 @@ public ExactDestination(CoreConfig config, WorldManager worldManager, WorldEntry
7381
@NotNull CommandSender sender,
7482
@NotNull String destinationParams
7583
) {
76-
String[] items = REPatterns.COLON.split(destinationParams);
84+
String[] items = REPatterns.COLON.split(destinationParams, 2);
7785
if (items.length < 2) {
7886
if (items[0].equals("@here")) {
7987
return getLocationFromSender(sender)
8088
.map(location -> Attempt.<ExactDestinationInstance, InstanceFailureReason>success(
81-
new ExactDestinationInstance(this, new UnloadedWorldLocation(location))
89+
new ExactDestinationInstance(
90+
this,
91+
location.getWorld().getName(),
92+
EntityPosition.ofLocation(location)
93+
)
8294
))
8395
.getOrElse(() -> Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT)); // todo: specific failure reason for this case
8496
}
8597
return Attempt.failure(InstanceFailureReason.INVALID_FORMAT);
8698
}
8799

88100
String worldName = items[0];
89-
String coordinates = items[1];
90-
String[] coordinatesParams = REPatterns.COMMA.split(coordinates);
91-
if (coordinatesParams.length != 3) {
92-
return Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT);
93-
}
101+
String positionStr = items[1];
94102

95103
World world = getLoadedMultiverseWorld(worldName).flatMap(LoadedMultiverseWorld::getBukkitWorld).getOrNull();
96104
if (world == null) {
97105
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldName));
98106
}
99107

100-
UnloadedWorldLocation location;
108+
EntityPosition position;
101109
try {
102-
location = new UnloadedWorldLocation(
103-
world,
104-
Double.parseDouble(coordinatesParams[0]),
105-
Double.parseDouble(coordinatesParams[1]),
106-
Double.parseDouble(coordinatesParams[2])
107-
);
108-
} catch (NumberFormatException e) {
110+
position = EntityPosition.fromString(positionStr);
111+
} catch (PositionParseException e) {
109112
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
110113
}
111114

112-
if (items.length == 4) {
113-
String pitch = items[2];
114-
String yaw = items[3];
115-
try {
116-
location.setPitch(Float.parseFloat(pitch));
117-
location.setYaw(Float.parseFloat(yaw));
118-
} catch (NumberFormatException e) {
119-
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
120-
}
121-
}
122-
123-
return Attempt.success(new ExactDestinationInstance(this, location));
115+
return Attempt.success(new ExactDestinationInstance(this, worldName, position));
124116
}
125117

126118
//TODO: Extract to a world finder class

src/main/java/org/mvplugins/multiverse/core/destination/core/ExactDestinationInstance.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,52 @@
11
package org.mvplugins.multiverse.core.destination.core;
22

33
import io.vavr.control.Option;
4+
import org.bukkit.Bukkit;
45
import org.bukkit.Location;
56
import org.bukkit.World;
67
import org.bukkit.entity.Entity;
78
import org.bukkit.util.Vector;
89
import org.jetbrains.annotations.NotNull;
910

1011
import org.mvplugins.multiverse.core.destination.DestinationInstance;
12+
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
13+
import org.mvplugins.multiverse.core.utils.position.VectorPosition;
1114
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
1215

1316
/**
1417
* Destination instance implementation for the {@link ExactDestination}.
1518
*/
1619
public final class ExactDestinationInstance extends DestinationInstance<ExactDestinationInstance, ExactDestination> {
17-
private final UnloadedWorldLocation location;
20+
private final String worldName;
21+
private final EntityPosition position;
1822

1923
/**
2024
* Constructor.
2125
*
22-
* @param location The location to teleport to.
26+
* @param destination The parent destination.
27+
* @param worldName The name of the world.
28+
* @param position The position in the world.
2329
*/
24-
ExactDestinationInstance(@NotNull ExactDestination destination, @NotNull UnloadedWorldLocation location) {
30+
ExactDestinationInstance(@NotNull ExactDestination destination,
31+
@NotNull String worldName,
32+
@NotNull EntityPosition position) {
2533
super(destination);
26-
this.location = location;
34+
this.worldName = worldName;
35+
this.position = position;
2736
}
2837

2938
/**
3039
* {@inheritDoc}
3140
*/
3241
@Override
3342
public @NotNull Option<Location> getLocation(@NotNull Entity teleportee) {
34-
if (location.getWorld() == null) {
43+
World world = Bukkit.getWorld(worldName);
44+
if (world == null) {
3545
return Option.none();
3646
}
37-
return Option.of(location.toBukkitLocation());
47+
Location destinationLocation = position.toBukkitLocation(teleportee.getLocation());
48+
destinationLocation.setWorld(world);
49+
return Option.of(destinationLocation);
3850
}
3951

4052
/**
@@ -58,15 +70,14 @@ public boolean checkTeleportSafety() {
5870
*/
5971
@Override
6072
public @NotNull Option<String> getFinerPermissionSuffix() {
61-
return Option.of(location.getWorld()).map(World::getName);
73+
return Option.of(worldName);
6274
}
6375

6476
/**
6577
* {@inheritDoc}
6678
*/
6779
@Override
6880
public @NotNull String serialise() {
69-
return location.getWorldName() + ":" + location.getX() + "," + location.getY()
70-
+ "," + location.getZ() + ":" + location.getPitch() + ":" + location.getYaw();
81+
return worldName + ":" + position.toString();
7182
}
7283
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.mvplugins.multiverse.core.exceptions.utils.position;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
5+
import org.mvplugins.multiverse.core.locale.message.Message;
6+
7+
public class PositionParseException extends MultiverseException {
8+
public PositionParseException(String message) {
9+
super(message);
10+
}
11+
12+
public PositionParseException(@Nullable Message message) {
13+
super(message);
14+
}
15+
16+
public PositionParseException(@Nullable String message, @Nullable Throwable cause) {
17+
super(message, cause);
18+
}
19+
20+
public PositionParseException(@Nullable Message message, @Nullable Throwable cause) {
21+
super(message, cause);
22+
}
23+
}

src/main/java/org/mvplugins/multiverse/core/locale/MVCorei18n.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ public enum MVCorei18n implements MessageKeyProvider {
346346
EXCEPTION_MULTIVERSEWORLD_UNLOADPLAYERSINWORLD,
347347
EXCEPTION_MULTIVERSEWORLD_UNLOADERROR,
348348

349+
// multiverse position parse exception
350+
EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
351+
EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
352+
EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
353+
349354
// generic
350355
GENERIC_SUCCESS,
351356
GENERIC_FAILURE,
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package org.mvplugins.multiverse.core.utils.position;
2+
3+
import org.bukkit.Location;
4+
import org.jetbrains.annotations.ApiStatus;
5+
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
6+
import org.mvplugins.multiverse.core.utils.REPatterns;
7+
8+
/**
9+
* Represents a position for an entity in 3D space, including both coordinates and facing direction.
10+
*
11+
* @since 5.3
12+
*/
13+
@ApiStatus.AvailableSince("5.3")
14+
public class EntityPosition {
15+
16+
/**
17+
* Creates an EntityPosition with absolute coordinates and direction.
18+
*
19+
* @param x The absolute X coordinate.
20+
* @param y The absolute Y coordinate.
21+
* @param z The absolute Z coordinate.
22+
* @param pitch The absolute pitch (vertical angle).
23+
* @param yaw The absolute yaw (horizontal angle).
24+
* @return A new EntityPosition instance with the specified absolute coordinates and direction.
25+
*
26+
* @since 5.3
27+
*/
28+
@ApiStatus.AvailableSince("5.3")
29+
public static EntityPosition ofAbsolute(double x, double y, double z, double pitch, double yaw) {
30+
return new EntityPosition(
31+
VectorPosition.ofAbsolute(x, y, z),
32+
FaceDirection.ofAbsolute(pitch, yaw)
33+
);
34+
}
35+
36+
/**
37+
* Creates an EntityPosition from a Bukkit Location with absolute coordinates and direction.
38+
*
39+
* @param location The Bukkit Location to convert.
40+
* @return A new EntityPosition instance representing the given location.
41+
*
42+
* @since 5.3
43+
*/
44+
@ApiStatus.AvailableSince("5.3")
45+
public static EntityPosition ofLocation(Location location) {
46+
return new EntityPosition(VectorPosition.ofLocation(location), FaceDirection.ofLocation(location));
47+
}
48+
49+
/**
50+
* Parses an EntityPosition from a string representation.
51+
* The expected format is "&lt;x&gt;,&lt;y&gt;,&lt;z&gt;:&lt;pitch&gt;:&lt;yaw&gt;" for absolute coordinates and direction,
52+
* or "&lt;x&gt;,&lt;y&gt;,&lt;z&gt;" for absolute coordinates with default direction (0 pitch, 0 yaw).
53+
* <br>
54+
* Relative coordinates and direction can be specified using the '~' prefix, e.g., "~10,~,~-10:0:90".
55+
*
56+
* @param positionStr The string representation of the position.
57+
* @return A new EntityPosition instance parsed from the string.
58+
* @throws PositionParseException If the string format is invalid.
59+
*
60+
* @since 5.3
61+
*/
62+
@ApiStatus.AvailableSince("5.3")
63+
public static EntityPosition fromString(String positionStr) throws PositionParseException {
64+
String[] parts = REPatterns.COLON.split(positionStr, 2);
65+
return parts.length == 2
66+
? new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.fromString(parts[1]))
67+
: new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.ofAbsolute(0, 0));
68+
}
69+
70+
private final VectorPosition vector;
71+
private final FaceDirection direction;
72+
73+
/**
74+
* Creates a new EntityPosition with the specified vector and direction.
75+
*
76+
* @param vector The vector position (coordinates).
77+
* @param direction The facing direction (pitch and yaw).
78+
*
79+
* @since 5.3
80+
*/
81+
@ApiStatus.AvailableSince("5.3")
82+
public EntityPosition(VectorPosition vector, FaceDirection direction) {
83+
this.vector = vector;
84+
this.direction = direction;
85+
}
86+
87+
/**
88+
* Gets the vector position (coordinates).
89+
*
90+
* @return The vector position.
91+
*
92+
* @since 5.3
93+
*/
94+
@ApiStatus.AvailableSince("5.3")
95+
public VectorPosition getVector() {
96+
return vector;
97+
}
98+
99+
/**
100+
* Gets the facing direction (pitch and yaw).
101+
*
102+
* @return The facing direction.
103+
*
104+
* @since 5.3
105+
*/
106+
@ApiStatus.AvailableSince("5.3")
107+
public FaceDirection getDirection() {
108+
return direction;
109+
}
110+
111+
/**
112+
* Augments a given Bukkit Location by applying the relative components of this EntityPosition.
113+
* This modifies the provided Location in place.
114+
*
115+
* @param base The base Bukkit Location to augment and offset for relative positioning as required.
116+
*
117+
* @since 5.3
118+
*/
119+
@ApiStatus.AvailableSince("5.3")
120+
public void augmentBukkitLocation(Location base) {
121+
vector.augmentBukkitLocation(base);
122+
direction.augmentBukkitLocation(base);
123+
}
124+
125+
/**
126+
* Converts this EntityPosition to a new Bukkit Location based on a given base Location.
127+
* This does not modify the base Location, but returns a new Location instance.
128+
*
129+
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
130+
* @return A new Bukkit Location representing this EntityPosition.
131+
*
132+
* @since 5.3
133+
*/
134+
@ApiStatus.AvailableSince("5.3")
135+
public Location toBukkitLocation(Location base) {
136+
return new Location(
137+
base.getWorld(),
138+
vector.getX().getValue(base.getX()),
139+
vector.getY().getValue(base.getY()),
140+
vector.getZ().getValue(base.getZ()),
141+
(float) direction.getYaw().getValue(base.getYaw()),
142+
(float) direction.getPitch().getValue(base.getPitch())
143+
);
144+
}
145+
146+
@Override
147+
public String toString() {
148+
return vector + ":" + direction;
149+
}
150+
}

0 commit comments

Comments
 (0)