Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add teleport flags #6498

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cf39490
Add teleport flags to EffTeleport
TheLimeGlass Mar 16, 2024
bac451c
Add unmerged commits
TheLimeGlass Mar 16, 2024
c6e1085
Apply changes
TheLimeGlass Mar 16, 2024
74041ba
Merge branch 'dev/feature' into feature/teleport-flags
sovdeeth Mar 30, 2024
43a5366
Conflicts
TheLimeGlass Jul 16, 2024
891a562
Merge branch 'feature/teleport-flags' of https://github.com/TheLimeGl…
TheLimeGlass Jul 16, 2024
d44ac35
Set aliases to master
TheLimeGlass Jul 16, 2024
3865993
Merge branch 'dev/feature' into feature/teleport-flags
Moderocky Aug 15, 2024
74d3add
Update src/main/java/ch/njol/skript/bukkitutil/TeleportFlags.java
TheLimeGlass Sep 18, 2024
7021b23
Simplify functional interface
TheLimeGlass Sep 18, 2024
74320ad
Conflicts
TheLimeGlass Sep 18, 2024
08f138c
add tests, remove functional interface, do requested changes
sovdeeth Sep 18, 2024
ecaf79e
Update src/main/java/ch/njol/skript/effects/EffTeleport.java
TheLimeGlass Sep 19, 2024
dff6230
Merge branch 'dev/feature' into feature/teleport-flags
sovdeeth Sep 20, 2024
0e79c75
Merge branch 'dev/feature' into feature/teleport-flags
sovdeeth Oct 14, 2024
3a1c894
Merge branch 'dev/feature' into feature/teleport-flags
TheLimeGlass Nov 10, 2024
1f94770
Merge remote-tracking branch 'upstream/dev/feature' into feature/tele…
sovdeeth Jan 1, 2025
e75807b
requested changes
sovdeeth Jan 1, 2025
eda628b
Merge branch 'dev/feature' into feature/teleport-flags
Moderocky Jan 1, 2025
63984e2
fix merge mistake
sovdeeth Jan 1, 2025
8a3cee2
Merge branch 'feature/teleport-flags' of https://github.com/TheLimeGl…
sovdeeth Jan 1, 2025
26541a3
Update default.lang
sovdeeth Jan 1, 2025
a5e9424
Merge branch 'dev/feature' into feature/teleport-flags
APickledWalrus Jan 1, 2025
dc793b6
Merge branch 'dev/feature' into feature/teleport-flags
APickledWalrus Jan 1, 2025
8fa3673
Fix messy imports.
Moderocky Jan 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@
import org.bukkit.entity.Piglin;
import org.bukkit.entity.Zoglin;
import org.bukkit.entity.Zombie;
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval;
import org.jetbrains.annotations.NotNull;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import io.papermc.paper.entity.TeleportFlag;

import ch.njol.skript.Skript;
import ch.njol.skript.entity.EntityData;

Expand Down Expand Up @@ -156,7 +160,10 @@ public static EntityData<?> toSkriptEntityData(EntityType e) {
/**
* Teleports the given entity to the given location.
* Teleports to the given location in the entity's world if the location's world is null.
* @deprecated this method is only used by EffTeleport, and with the recent additions of TeleportFlag, this method should be moved within that effect.
*/
@Deprecated
@ScheduledForRemoval
public static void teleport(Entity entity, Location location) {
if (location.getWorld() == null) {
location = location.clone();
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/ch/njol/skript/bukkitutil/SkriptTeleportFlag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ch.njol.skript.bukkitutil;

import io.papermc.paper.entity.TeleportFlag;
import org.jetbrains.annotations.Nullable;

/**
* A utility enum for accessing Paper's teleport flags (1.19.4+)
*/
public enum SkriptTeleportFlag {
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved
RETAIN_OPEN_INVENTORY(TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY),
RETAIN_PASSENGERS(TeleportFlag.EntityState.RETAIN_PASSENGERS),
RETAIN_VEHICLE(TeleportFlag.EntityState.RETAIN_VEHICLE),
RETAIN_DIRECTION(TeleportFlag.Relative.PITCH, TeleportFlag.Relative.YAW),
RETAIN_PITCH(TeleportFlag.Relative.PITCH),
RETAIN_YAW(TeleportFlag.Relative.YAW),
RETAIN_MOVEMENT(TeleportFlag.Relative.X, TeleportFlag.Relative.Y, TeleportFlag.Relative.Z),
RETAIN_X(TeleportFlag.Relative.X),
RETAIN_Y(TeleportFlag.Relative.Y),
RETAIN_Z(TeleportFlag.Relative.Z);

final TeleportFlag[] teleportFlags;

SkriptTeleportFlag(TeleportFlag... teleportFlags) {
this.teleportFlags = teleportFlags;
}

@Nullable
public TeleportFlag[] getTeleportFlags() {
return teleportFlags;
}

}
9 changes: 9 additions & 0 deletions src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.stream.Collectors;

import ch.njol.skript.bukkitutil.BukkitUtils;
import ch.njol.skript.bukkitutil.SkriptTeleportFlag;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Difficulty;
Expand Down Expand Up @@ -1526,6 +1527,14 @@ public String toVariableNameString(EnchantmentOffer eo) {
.name("Transform Reason")
.description("Represents a transform reason of an <a href='events.html#entity transform'>entity transform event</a>.")
.since("2.8.0"));

if (Skript.classExists("io.papermc.paper.entity.TeleportFlag"))
Classes.registerClass(new EnumClassInfo<>(SkriptTeleportFlag.class, "teleportflag", "teleport flags")
.user("teleport ?flags?")
.name("Teleport Flag")
.description("Teleport Flags are settings to retain during a teleport.")
.requiredPlugins("Paper 1.19+")
.since("INSERT VERSION"));
}

}
138 changes: 89 additions & 49 deletions src/main/java/ch/njol/skript/effects/EffTeleport.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,143 +19,165 @@
package ch.njol.skript.effects;

import ch.njol.skript.Skript;
import ch.njol.skript.sections.EffSecSpawn.SpawnEvent;
import ch.njol.skript.bukkitutil.EntityUtils;
import ch.njol.skript.bukkitutil.SkriptTeleportFlag;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.RequiredPlugins;
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.Trigger;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.skript.sections.EffSecSpawn.SpawnEvent;
import ch.njol.skript.timings.SkriptTimings;
import ch.njol.skript.util.Direction;
import ch.njol.skript.variables.Variables;
import ch.njol.util.Kleenean;
import io.papermc.lib.PaperLib;
import io.papermc.lib.environments.PaperEnvironment;
import io.papermc.paper.entity.TeleportFlag;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;

@Name("Teleport")
@Description({
"Teleport an entity to a specific location. ",
"This effect is delayed by default on Paper, meaning certain syntax such as the return effect for functions cannot be used after this effect.",
"The keyword 'force' indicates this effect will not be delayed, ",
"which may cause lag spikes or server crashes when using this effect to teleport entities to unloaded chunks."
"which may cause lag spikes or server crashes when using this effect to teleport entities to unloaded chunks.",
"Teleport flags are settings to retain during a teleport. Such as direction, passengers, x coordinate, etc."
})
@Examples({
"teleport the player to {homes::%player%}",
"teleport the attacker to the victim"
"teleport the player to {home::%uuid of player%}",
"teleport the attacker to the victim",
"",
"on dismount:",
"\tcancel event",
"\tteleport the player to {server::spawn} retaining vehicle and passengers"
})
@Since("1.0")
@RequiredPlugins("Paper 1.19+ (teleport flags)")
@Since("1.0, INSERT VERSION")
public class EffTeleport extends Effect {

private static final boolean TELEPORT_FLAGS_SUPPORTED = Skript.classExists("io.papermc.paper.entity.TeleportFlag");
private static final boolean CAN_RUN_ASYNC = PaperLib.getEnvironment() instanceof PaperEnvironment;

static {
Skript.registerEffect(EffTeleport.class, "[(1¦force)] teleport %entities% (to|%direction%) %location%");
String extra = "";
if (TELEPORT_FLAGS_SUPPORTED)
extra = " [[while] retaining %-teleportflags%]";
Skript.registerEffect(EffTeleport.class, "[:force] teleport %entities% (to|%direction%) %location%" + extra);
}

@Nullable
private Expression<SkriptTeleportFlag> teleportFlags;

@SuppressWarnings("NotNullFieldNotInitialized")
private Expression<Entity> entities;

@SuppressWarnings("NotNullFieldNotInitialized")
private Expression<Location> location;

private boolean isAsync;
private boolean async;
AyhamAl-Ali marked this conversation as resolved.
Show resolved Hide resolved

@Override
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
entities = (Expression<Entity>) exprs[0];
location = Direction.combine((Expression<? extends Direction>) exprs[1], (Expression<? extends Location>) exprs[2]);
isAsync = CAN_RUN_ASYNC && parseResult.mark == 0;
async = CAN_RUN_ASYNC && !parseResult.hasTag("force");
if (TELEPORT_FLAGS_SUPPORTED)
teleportFlags = (Expression<SkriptTeleportFlag>) exprs[3];

if (getParser().isCurrentEvent(SpawnEvent.class)) {
Skript.error("You cannot be teleporting an entity that hasn't spawned yet. Ensure you're using the location expression from the spawn section pattern.");
return false;
}

if (isAsync)
if (async)
getParser().setHasDelayBefore(Kleenean.UNKNOWN); // UNKNOWN because it isn't async if the chunk is already loaded.
return true;
}

@Nullable
@Override
protected TriggerItem walk(Event e) {
debug(e, true);

protected TriggerItem walk(Event event) {
debug(event, true);
TriggerItem next = getNext();

boolean delayed = Delay.isDelayed(e);

Location loc = location.getSingle(e);
if (loc == null)
boolean delayed = Delay.isDelayed(event);
Location location = this.location.getSingle(event);
if (location == null)
return next;
boolean unknownWorld = !loc.isWorldLoaded();
boolean unknownWorld = !location.isWorldLoaded();

Entity[] entityArray = entities.getArray(e); // We have to fetch this before possible async execution to avoid async local variable access.
Entity[] entityArray = entities.getArray(event); // We have to fetch this before possible async execution to avoid async local variable access.
if (entityArray.length == 0)
return next;

if (!delayed) {
if (e instanceof PlayerRespawnEvent && entityArray.length == 1 && entityArray[0].equals(((PlayerRespawnEvent) e).getPlayer())) {
if (event instanceof PlayerRespawnEvent playerRespawnEvent && entityArray.length == 1 && entityArray[0].equals(playerRespawnEvent.getPlayer())) {
if (unknownWorld)
return next;
((PlayerRespawnEvent) e).setRespawnLocation(loc);
playerRespawnEvent.setRespawnLocation(location);
return next;
}

if (e instanceof PlayerMoveEvent && entityArray.length == 1 && entityArray[0].equals(((PlayerMoveEvent) e).getPlayer())) {
if (event instanceof PlayerMoveEvent playerMoveEvent && entityArray.length == 1 && entityArray[0].equals(playerMoveEvent.getPlayer())) {
if (unknownWorld) { // we can approximate the world
loc = loc.clone();
loc.setWorld(((PlayerMoveEvent) e).getFrom().getWorld());
location = location.clone();
location.setWorld(playerMoveEvent.getFrom().getWorld());
}
((PlayerMoveEvent) e).setTo(loc);
playerMoveEvent.setTo(location);
return next;
}
}

if (!isAsync) {
for (Entity entity : entityArray) {
EntityUtils.teleport(entity, loc);
}
return next;
}

if (unknownWorld) { // we can't fetch the chunk without a world
if (entityArray.length == 1) { // if there's 1 thing we can borrow its world
Entity entity = entityArray[0];
if (entity == null)
return next;
// assume it's a local teleport, use the first entity we find as a reference
loc = loc.clone();
loc.setWorld(entity.getWorld());
location = location.clone();
location.setWorld(entity.getWorld());
} else {
return next; // no entities = no chunk = nobody teleporting
}
}
final Location fixed = loc;
Delay.addDelayedEvent(e);
Object localVars = Variables.removeLocals(e);


if (!async) {
SkriptTeleportFlag[] teleportFlags = this.teleportFlags == null ? null : this.teleportFlags.getArray(event);
for (Entity entity : entityArray) {
teleport(entity, location, teleportFlags);
}
return next;
}

final Location fixed = location;
Delay.addDelayedEvent(event);
Object localVars = Variables.removeLocals(event);

// This will either fetch the chunk instantly if on Spigot or already loaded or fetch it async if on Paper.
PaperLib.getChunkAtAsync(loc).thenAccept(chunk -> {
PaperLib.getChunkAtAsync(location).thenAccept(chunk -> {
// The following is now on the main thread
SkriptTeleportFlag[] teleportFlags = this.teleportFlags == null ? null : this.teleportFlags.getArray(event);
for (Entity entity : entityArray) {
EntityUtils.teleport(entity, fixed);
teleport(entity, fixed, teleportFlags);
}

// Re-set local variables
if (localVars != null)
Variables.setLocalVariables(e, localVars);
Variables.setLocalVariables(event, localVars);

// Continue the rest of the trigger if there is one
Object timing = null;
Expand All @@ -167,22 +189,40 @@ protected TriggerItem walk(Event e) {
}
}

TriggerItem.walk(next, e);
TriggerItem.walk(next, event);
}
Variables.removeLocals(e); // Clean up local vars, we may be exiting now
Variables.removeLocals(event); // Clean up local vars, we may be exiting now
SkriptTimings.stop(timing);
});
return null;
}

@Override
protected void execute(Event e) {
// Nothing needs to happen here, we're executing in walk
protected void execute(Event event) {
assert false;
}

@Override
public String toString(@Nullable Event e, boolean debug) {
return "teleport " + entities.toString(e, debug) + " to " + location.toString(e, debug);
public String toString(@Nullable Event event, boolean debug) {
return "teleport " + entities.toString(event, debug) + " to " + location.toString(event, debug) +
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved
(teleportFlags == null ? "" : " retaining " + teleportFlags.toString(event, debug));
}

private void teleport(@NotNull Entity entity, @NotNull Location location, SkriptTeleportFlag... skriptTeleportFlags) {
if (location.getWorld() == null) {
location = location.clone();
location.setWorld(entity.getWorld());
}

if (!TELEPORT_FLAGS_SUPPORTED || skriptTeleportFlags == null) {
entity.teleport(location);
return;
}

Stream<TeleportFlag> teleportFlags = Arrays.stream(skriptTeleportFlags)
.flatMap(teleportFlag -> Stream.of(teleportFlag.getTeleportFlags()))
.filter(Objects::nonNull);
entity.teleport(location, teleportFlags.toArray(TeleportFlag[]::new));
}

}
14 changes: 14 additions & 0 deletions src/main/resources/lang/default.lang
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,19 @@ transform reasons:
unknown: unknown
infection: infection, villager infection

# -- Teleport Flags --
teleport flags:
TheLimeGlass marked this conversation as resolved.
Show resolved Hide resolved
retain_open_inventory: opened inventory, open inventory, inventory
retain_passengers: passengers, passenger
retain_vehicle: vehicle
retain_direction: directional velocity, pitch and yaw velocity, yaw and pitch velocity
retain_pitch: pitch velocity
retain_yaw: yaw velocity
retain_movement: xyz velocity
retain_x: x velocity
retain_y: y velocity
retain_z: z velocity

# -- Boolean --
boolean:
true:
Expand Down Expand Up @@ -2354,6 +2367,7 @@ types:
quitreason: quit reason¦s @a
inventoryclosereason: inventory close reason¦s @an
transformreason: transform reason¦s @a
teleportflag: teleport flag¦s @a

# Skript
weathertype: weather type¦s @a
Expand Down
19 changes: 19 additions & 0 deletions src/test/skript/tests/syntaxes/effects/EffTeleport.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
test "teleport":
spawn pig at location(0, 0, 0):
set {_pig} to entity
teleport {_pig} to location(1, 30, 1)
assert location of {_pig} is location(1, 30, 1) with "failed to teleport entity"
delete entity within {_pig}

test "teleport flags":
spawn pig at location(0, 0, 0):
set {_pig} to entity
spawn pig at location(0, 0, 0):
make entity ride {_pig}
set {_pig2} to entity
teleport {_pig} to location(1, 30, 1) retaining passengers, xyz velocity, and yaw and pitch velocity
assert location of {_pig} is location(1, 30, 1) with "failed to teleport entity"
assert passenger of {_pig} is {_pig2} with "failed to retain passenger"

delete entity within {_pig2}
delete entity within {_pig}