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 11 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
57 changes: 57 additions & 0 deletions src/main/java/ch/njol/skript/bukkitutil/PaperTeleportFlags.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ch.njol.skript.bukkitutil;

import io.papermc.paper.entity.TeleportFlag;

import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* A utility interface to access the Entity::teleport with TeleportFlag vararg in Paper 1.19+.
*/
@FunctionalInterface
public interface PaperTeleportFlags {

public enum SkriptTeleportFlag {
RETAIN_OPEN_INVENTORY(TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY),
RETAIN_PASSENGERS(TeleportFlag.EntityState.RETAIN_PASSENGERS),
RETAIN_VEHICLE(TeleportFlag.EntityState.RETAIN_VEHICLE),
RETAIN_DIRECTION(),
RETAIN_PITCH(TeleportFlag.Relative.PITCH),
RETAIN_YAW(TeleportFlag.Relative.YAW),
RETAIN_X(TeleportFlag.Relative.X),
RETAIN_Y(TeleportFlag.Relative.Y),
RETAIN_Z(TeleportFlag.Relative.Z);

TeleportFlag teleportFlag;

SkriptTeleportFlag() {}

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

@Nullable
public TeleportFlag getTeleportFlag() {
return teleportFlag;
}

}

void teleport(
@NotNull Entity entity, @NotNull Location location, @NotNull TeleportFlag... flags
);

static void teleport(
@NotNull PaperTeleportFlags teleportFlagsInterface, @NotNull Entity entity, @NotNull Location location, @NotNull TeleportFlag... teleportFlags
) {
if (location.getWorld() == null) {
location = location.clone();
location.setWorld(entity.getWorld());
}

teleportFlagsInterface.teleport(entity, location, 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 @@ -86,6 +86,7 @@
import ch.njol.skript.aliases.ItemType;
import ch.njol.skript.bukkitutil.EnchantmentUtils;
import ch.njol.skript.bukkitutil.ItemUtils;
import ch.njol.skript.bukkitutil.PaperTeleportFlags.SkriptTeleportFlag;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.ConfigurationSerializer;
import ch.njol.skript.classes.EnumClassInfo;
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"));
}

}
141 changes: 92 additions & 49 deletions src/main/java/ch/njol/skript/effects/EffTeleport.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,144 +18,168 @@
*/
package ch.njol.skript.effects;

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

import ch.njol.skript.Skript;
import ch.njol.skript.sections.EffSecSpawn.SpawnEvent;
import ch.njol.skript.bukkitutil.EntityUtils;
import ch.njol.skript.bukkitutil.PaperTeleportFlags;
import ch.njol.skript.bukkitutil.PaperTeleportFlags.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 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.Nullable;
import org.jetbrains.annotations.NotNull;

@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(TELEPORT_FLAGS_SUPPORTED ? Entity::teleport : null, 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(TELEPORT_FLAGS_SUPPORTED ? Entity::teleport : null, 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 +191,41 @@ 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(@Nullable PaperTeleportFlags paperTeleportFlags, @NotNull Entity entity, @NotNull Location location, SkriptTeleportFlag... skriptTeleportFlags) {
if (location.getWorld() == null) {
location = location.clone();
location.setWorld(entity.getWorld());
}
if (!TELEPORT_FLAGS_SUPPORTED || paperTeleportFlags == null || skriptTeleportFlags == null) {
entity.teleport(location);
return;
}
Stream<io.papermc.paper.entity.TeleportFlag> teleportFlags = Arrays.stream(skriptTeleportFlags).flatMap(teleportFlag -> {
if (teleportFlag == SkriptTeleportFlag.RETAIN_DIRECTION)
return Stream.of(io.papermc.paper.entity.TeleportFlag.Relative.PITCH, io.papermc.paper.entity.TeleportFlag.Relative.YAW);
return Stream.of(teleportFlag.getTeleportFlag());
}).filter(Objects::nonNull);

paperTeleportFlags.teleport(entity, location, teleportFlags.toArray(io.papermc.paper.entity.TeleportFlag[]::new));
}

}
13 changes: 13 additions & 0 deletions src/main/resources/lang/default.lang
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,18 @@ 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: direction
retain_pitch: pitch
retain_yaw: yaw
retain_x: x coordinate, x-coordinate
retain_y: y coordinate, y-coordinate
retain_z: z coordinate, z-coordinate
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved

# -- Boolean --
boolean:
true:
Expand Down Expand Up @@ -2354,6 +2366,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