From 88db1c86e0edca9b9d1e2194ddb75a47fd1faf6e Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 1 Jun 2026 14:45:06 +0100 Subject: [PATCH 1/5] feat: add API for SavedTeleportManager Events for adding & deleting homes/warps and event for teleport to a saved home/warp, allowing dest modification or teleport cancellation --- .../api/TeleportDestination.java | 42 ++++++++++ .../ftbessentials/api/TeleportResult.java | 56 +++++++++++++ .../api/event/SavedTeleportEvent.java | 67 +++++++++++++++ .../api/event/TeleportEvent.java | 8 ++ .../commands/impl/teleporting/TPACommand.java | 5 +- .../util/SavedTeleportManager.java | 37 ++++++++- .../mods/ftbessentials/util/TeleportPos.java | 81 ++++++------------- .../util/WarmupCooldownTeleporter.java | 4 +- 8 files changed, 235 insertions(+), 65 deletions(-) create mode 100644 common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java create mode 100644 common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportResult.java create mode 100644 common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java new file mode 100644 index 0000000..74fc9bf --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java @@ -0,0 +1,42 @@ +package dev.ftb.mods.ftbessentials.api; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; + +import java.util.Optional; +import java.util.UUID; + +/** + * Represents a teleport destination used by the various teleportation commands in the mod. + * + * @param dimension the dimension + * @param pos the block position + * @param yRot the player's Y rotation (yaw) on arrival (if empty, use player's current yaw) + * @param xRot the player's X rotation (pitch) on arrival (if empty, use player's current pitch) + */ +public record TeleportDestination(ResourceKey dimension, BlockPos pos, Optional yRot, Optional xRot) { + /** + * Return a successful outcome for this destination. See also {@link dev.ftb.mods.ftbessentials.api.event.SavedTeleportEvent.PreTeleport#onTeleport(String, ServerPlayer, TeleportDestination, UUID)}. + * + * @return a successful outcome + */ + public Outcome success() { + return new Outcome(true, this, Component.empty()); + } + + /** + * Return a successful outcome for this destination. See also {@link dev.ftb.mods.ftbessentials.api.event.SavedTeleportEvent.PreTeleport#onTeleport(String, ServerPlayer, TeleportDestination, UUID)}. + * + * @param reason the failure reason to report to the player + * @return a failed outcome + */ + public Outcome failed(Component reason) { + return new Outcome(false, this, reason); + } + + public record Outcome(boolean success, TeleportDestination dest, Component reason) { + } +} diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportResult.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportResult.java new file mode 100644 index 0000000..948214c --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportResult.java @@ -0,0 +1,56 @@ +package dev.ftb.mods.ftbessentials.api; + +import dev.ftb.mods.ftblibrary.util.TimeUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; + +import java.util.function.LongSupplier; + +@FunctionalInterface +public interface TeleportResult { + int runCommand(ServerPlayer player); + + default boolean isSuccess() { + return false; + } + + static TeleportResult failed(Component msg) { + return player -> { + player.displayClientMessage(msg, false); + return 0; + }; + } + + TeleportResult DIMENSION_NOT_FOUND = failed(Component.translatable("ftbessentials.dimension_not_found")); + TeleportResult UNKNOWN_DESTINATION = failed(Component.translatable("ftbessentials.unknown_dest")); + TeleportResult PREVENTED = failed(Component.translatable("ftbessentials.teleport_prevented")); + TeleportResult DIMENSION_NOT_ALLOWED_FROM = failed(Component.translatable("ftbessentials.teleport.not_from_here")); + TeleportResult DIMENSION_NOT_ALLOWED_TO = failed(Component.translatable("ftbessentials.teleport.not_to_here")); + TeleportResult SUCCESS = new TeleportResult() { + @Override + public int runCommand(ServerPlayer player) { + return 1; + } + + @Override + public boolean isSuccess() { + return true; + } + }; + + @FunctionalInterface + interface OnCooldown extends TeleportResult { + long getCooldown(); + + @Override + default int runCommand(ServerPlayer player) { + String secStr = TimeUtils.prettyTimeString(getCooldown() / 1000L); + player.displayClientMessage(Component.translatable("ftbessentials.teleport.on_cooldown", secStr), false); + return 0; + } + + static OnCooldown create(LongSupplier longSupplier) { + return longSupplier::getAsLong; + } + } +} diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java new file mode 100644 index 0000000..3ed561e --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java @@ -0,0 +1,67 @@ +package dev.ftb.mods.ftbessentials.api.event; + +import dev.architectury.event.CompoundEventResult; +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import dev.ftb.mods.ftbessentials.api.TeleportDestination; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public interface SavedTeleportEvent { + /** + * See {@link #onAdded(String, TeleportDestination, ServerPlayer, UUID)} + */ + Event ADDED = EventFactory.createLoop(); + /** + * See {@link #onDeleted(String, TeleportDestination, UUID)} (String, TeleportDestination, ServerPlayer, UUID)} + */ + Event DELETED = EventFactory.createLoop(); + /** + * See {@link PreTeleport#onTeleport(String, ServerPlayer, TeleportDestination, UUID)} + */ + Event PRE_TELEPORT = EventFactory.createCompoundEventResult(); + + /** + * Fired after a saved destination (home or warp) has been added. + * + * @param name the name of the destination + * @param dest the destination that was added + * @param player the player who added the destination + * @param owningPlayer the player's UUID for a home destination, or null for a global warp destination + */ + void onAdded(String name, TeleportDestination dest, ServerPlayer player, @Nullable UUID owningPlayer); + + /** + * Fired after a saved destination (home or warp) has been deleted. + * + * @param name the name of the destination + * @param destination the destination that was removed + * @param owningPlayer the player's UUID for a home destination, or null for a global warp destination + */ + void onDeleted(String name, TeleportDestination destination, @Nullable UUID owningPlayer); + + interface PreTeleport { + /** + * Fired when a player is about to teleport to a saved destination (home or warp). This event allows + * the destination to be modified, or the teleportation to be prevented entirely. + *
    + *
  • To modify the destination, return {@link CompoundEventResult#interruptTrue(Object)} with a new + * {@link TeleportDestination.Outcome} - see {@link TeleportDestination#success()}
  • + *
  • To prevent teleportation, return {@link CompoundEventResult#interruptFalse(Object)} - see {@link TeleportDestination#failed(Component)}
  • + *
  • To proceed with the default behavior, return {@link CompoundEventResult#pass()}
  • + *
+ * See also {@link TeleportEvent}, which is fired after this event, and provides a second opportunity to + * prevent teleportation. + * + * @param name the name of the saved destination + * @param player the player about to teleport + * @param dest the planned teleport destination + * @param owningPlayer UUID of the player who owns the destination; non-null for a player home, null for a global warp + * @return the event result (see above) + */ + CompoundEventResult onTeleport(String name, ServerPlayer player, TeleportDestination dest, @Nullable UUID owningPlayer); + } +} diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java index 0545aba..88e2dd1 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java @@ -10,6 +10,14 @@ public class TeleportEvent { public static Event TELEPORT = EventFactory.createCompoundEventResult(); public interface Teleport { + /** + * Fired just before a player teleports (but before any warmup is started). This can be canceled to prevent + * teleportation by returning {@link CompoundEventResult#interruptFalse(Object)} with a message to supply to the + * player about why teleportation was prevented. + * + * @param player the player about to teleport + * @return the event result + */ CompoundEventResult teleport(ServerPlayer player); } } diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/commands/impl/teleporting/TPACommand.java b/common/src/main/java/dev/ftb/mods/ftbessentials/commands/impl/teleporting/TPACommand.java index 1f321e9..ec65fa4 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/commands/impl/teleporting/TPACommand.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/commands/impl/teleporting/TPACommand.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import dev.ftb.mods.ftbessentials.api.TeleportResult; import dev.ftb.mods.ftbessentials.api.records.TPARequest; import dev.ftb.mods.ftbessentials.commands.FTBCommand; import dev.ftb.mods.ftbessentials.config.FTBEConfig; @@ -62,7 +63,7 @@ public int tpa(ServerPlayer player, ServerPlayer target, boolean here) { return 0; } - TeleportPos.TeleportResult result = here ? + TeleportResult result = here ? dataTarget.tpaTeleporter.checkCooldown(target) : dataSource.tpaTeleporter.checkCooldown(player); @@ -127,7 +128,7 @@ public int tpaccept(ServerPlayer player, String id) { return 0; } - TeleportPos.TeleportResult result = request.here() ? + TeleportResult result = request.here() ? request.target().tpaTeleporter.teleport(player, p -> new TeleportPos(sourcePlayer)) : request.source().tpaTeleporter.teleport(sourcePlayer, p -> new TeleportPos(player)); diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java index 506cf0c..5f22580 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java @@ -1,13 +1,17 @@ package dev.ftb.mods.ftbessentials.util; +import dev.ftb.mods.ftbessentials.api.TeleportResult; +import dev.ftb.mods.ftbessentials.api.event.SavedTeleportEvent; import dev.ftb.mods.ftbessentials.config.FTBEConfig; import dev.ftb.mods.ftblibrary.snbt.SNBTCompoundTag; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.stream.Stream; public abstract class SavedTeleportManager { @@ -19,20 +23,34 @@ public void addDestination(String name, TeleportPos dest, ServerPlayer player) { throw new TooManyDestinationsException(); } destinations.put(nameLower, dest); + SavedTeleportEvent.ADDED.invoker().onAdded(name, dest.asDestination(), player, owningPlayer()); onChanged(); } public boolean deleteDestination(String name) { - if (destinations.remove(name.toLowerCase()) != null) { + TeleportPos removed = destinations.remove(name.toLowerCase()); + if (removed != null) { + SavedTeleportEvent.DELETED.invoker().onDeleted(name, removed.asDestination(), owningPlayer()); onChanged(); return true; } return false; } - public TeleportPos.TeleportResult teleportTo(String name, ServerPlayer player, WarmupCooldownTeleporter teleporter) { + public TeleportResult teleportTo(String name, ServerPlayer player, WarmupCooldownTeleporter teleporter) { TeleportPos pos = destinations.get(name.toLowerCase()); - return pos != null ? teleporter.teleport(player, p -> pos) : TeleportPos.TeleportResult.UNKNOWN_DESTINATION; + if (pos == null) { + return TeleportResult.UNKNOWN_DESTINATION; + } + + var result = SavedTeleportEvent.PRE_TELEPORT.invoker().onTeleport(name, player, pos.asDestination(), owningPlayer()); + if (result.isFalse()) { + return TeleportResult.failed(result.object().reason()); + } + + TeleportPos newPos = result.isEmpty() ? pos : TeleportPos.fromDestination(result.object().dest()); + + return newPos != null ? teleporter.teleport(player, p -> newPos) : TeleportResult.UNKNOWN_DESTINATION; } public Stream destinations() { @@ -62,6 +80,9 @@ protected int getMaxSize(ServerPlayer player) { protected abstract void onChanged(); + @Nullable + protected abstract UUID owningPlayer(); + public static class HomeManager extends SavedTeleportManager { private final FTBEPlayerData playerData; @@ -78,6 +99,11 @@ protected int getMaxSize(ServerPlayer player) { protected void onChanged() { playerData.markDirty(); } + + @Override + protected UUID owningPlayer() { + return playerData.getUuid(); + } } public static class WarpManager extends SavedTeleportManager { @@ -91,6 +117,11 @@ public WarpManager(FTBEWorldData worldData) { protected void onChanged() { worldData.markDirty(); } + + @Override + protected UUID owningPlayer() { + return null; + } } public record DestinationEntry(String name, TeleportPos destination) { diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java index 2bfce4e..fd3b521 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java @@ -1,12 +1,12 @@ package dev.ftb.mods.ftbessentials.util; +import dev.ftb.mods.ftbessentials.api.TeleportDestination; +import dev.ftb.mods.ftbessentials.api.TeleportResult; import dev.ftb.mods.ftblibrary.snbt.SNBTCompoundTag; -import dev.ftb.mods.ftblibrary.util.TimeUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; @@ -28,14 +28,6 @@ public TeleportPos(ResourceKey d, BlockPos p) { this(d, p, null, null); } - public TeleportPos(ResourceKey d, BlockPos p, Float yRot, Float xRot) { - dimension = d; - pos = p; - this.yRot = yRot; - this.xRot = xRot; - time = System.currentTimeMillis(); - } - public TeleportPos(Level world, BlockPos p, Float yRot, Float xRot) { this(world.dimension(), p, yRot, xRot); } @@ -52,6 +44,27 @@ public TeleportPos(CompoundTag tag) { time = tag.getLong("time"); } + private TeleportPos(ResourceKey d, BlockPos p, Float yRot, Float xRot) { + this(d, p, yRot, xRot, System.currentTimeMillis()); + } + + private TeleportPos(ResourceKey dimension, BlockPos pos, Float yRot, Float xRot, long time) { + this.dimension = dimension; + this.pos = pos; + this.yRot = yRot; + this.xRot = xRot; + this.time = time; + } + + @Nullable + public static TeleportPos fromDestination(@Nullable TeleportDestination dest) { + return dest == null ? null : new TeleportPos(dest.dimension(), dest.pos(), dest.yRot().orElse(null), dest.xRot().orElse(null)); + } + + TeleportDestination asDestination() { + return new TeleportDestination(dimension, pos, Optional.ofNullable(yRot), Optional.ofNullable(xRot)); + } + public TeleportPos safeForPlayer(ServerPlayer player) { ServerLevel level = player.getServer().getLevel(dimension); if (level == null) return this; // shouldn't happen @@ -149,52 +162,4 @@ public String posAsString() { // Normal shortString would be 1, 2, 3 so we remove the commas return pos.toShortString().replaceAll(",", ""); } - - @FunctionalInterface - public interface TeleportResult { - TeleportResult SUCCESS = new TeleportResult() { - @Override - public int runCommand(ServerPlayer player) { - return 1; - } - - @Override - public boolean isSuccess() { - return true; - } - }; - - static TeleportResult failed(Component msg) { - return player -> { - player.displayClientMessage(msg, false); - return 0; - }; - } - - TeleportResult DIMENSION_NOT_FOUND = failed(Component.translatable("ftbessentials.dimension_not_found")); - - TeleportResult UNKNOWN_DESTINATION = failed(Component.translatable("ftbessentials.unknown_dest")); - - TeleportResult DIMENSION_NOT_ALLOWED_FROM = failed(Component.translatable("ftbessentials.teleport.not_from_here")); - - TeleportResult DIMENSION_NOT_ALLOWED_TO = failed(Component.translatable("ftbessentials.teleport.not_to_here")); - - int runCommand(ServerPlayer player); - - default boolean isSuccess() { - return false; - } - } - - @FunctionalInterface - public interface CooldownTeleportResult extends TeleportResult { - long getCooldown(); - - @Override - default int runCommand(ServerPlayer player) { - String secStr = TimeUtils.prettyTimeString(getCooldown() / 1000L); - player.displayClientMessage(Component.translatable("ftbessentials.teleport.on_cooldown", secStr), false); - return 0; - } - } } diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/WarmupCooldownTeleporter.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/WarmupCooldownTeleporter.java index afd2373..9e4cd8b 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/WarmupCooldownTeleporter.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/WarmupCooldownTeleporter.java @@ -4,7 +4,7 @@ import dev.architectury.injectables.annotations.ExpectPlatform; import dev.ftb.mods.ftbessentials.api.event.TeleportEvent; import dev.ftb.mods.ftbessentials.config.FTBEConfig; -import dev.ftb.mods.ftbessentials.util.TeleportPos.TeleportResult; +import dev.ftb.mods.ftbessentials.api.TeleportResult; import net.minecraft.ChatFormatting; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; @@ -45,7 +45,7 @@ public TeleportResult checkCooldown(ServerPlayer player) { long nextRun = lastRun + Math.max(0L, cooldownConfig.applyAsInt(player) * 1000L); if (now < nextRun) { - return (TeleportPos.CooldownTeleportResult) () -> nextRun - now; + return TeleportResult.OnCooldown.create(() -> nextRun - now); } return TeleportResult.SUCCESS; From b2fd26a55efb227a5a346d31cb1e8b4881062910 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 2 Jun 2026 12:37:43 +0100 Subject: [PATCH 2/5] feat: add more teleporting API --- .github/workflows/build.yml | 2 +- .../mods/ftbessentials/FTBEEventHandler.java | 5 ++ .../api/TeleportDestination.java | 29 +++++++++-- .../api/event/SavedTeleportEvent.java | 3 +- .../api/event/TeleportEvent.java | 3 +- .../api/event/TeleportImmediateEvent.java | 25 ++++++++++ .../ftbessentials/util/FTBEPlayerData.java | 4 +- .../util/SavedTeleportManager.java | 2 +- .../mods/ftbessentials/util/TeleportPos.java | 48 ++++++++++++++----- 9 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eae06a6..5ded99c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Java CI - Build on Push on: push: - branches: [ main, dev, "1.**" ] + branches: [ main, dev, "1.**", "teleporting-api" ] workflow_dispatch: inputs: skip_maven_publish: diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java b/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java index 18a8291..53fa5d3 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java @@ -4,6 +4,8 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.architectury.event.EventResult; import dev.architectury.event.events.common.*; +import dev.ftb.mods.ftbessentials.api.TeleportDestination; +import dev.ftb.mods.ftbessentials.api.event.TeleportImmediateEvent; import dev.ftb.mods.ftbessentials.api.records.TPARequest; import dev.ftb.mods.ftbessentials.commands.FTBCommands; import dev.ftb.mods.ftbessentials.commands.impl.teleporting.TPACommand; @@ -54,6 +56,9 @@ public static void init() { EntityEvent.LIVING_HURT.register(FTBEEventHandler::playerHurt); ChatEvent.RECEIVED.register(FTBEEventHandler::playerChat); + + TeleportImmediateEvent.TELEPORT.register((player, dest) -> + dest.success(dest.withPos(dest.pos().above(10)))); } private static void serverAboutToStart(MinecraftServer minecraftServer) { diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java index 74fc9bf..a53de92 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java @@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; import java.util.Optional; import java.util.UUID; @@ -13,11 +14,22 @@ * Represents a teleport destination used by the various teleportation commands in the mod. * * @param dimension the dimension - * @param pos the block position - * @param yRot the player's Y rotation (yaw) on arrival (if empty, use player's current yaw) - * @param xRot the player's X rotation (pitch) on arrival (if empty, use player's current pitch) + * @param pos the block position + * @param yRot the player's Y rotation (yaw) on arrival (if empty, use player's current yaw) + * @param xRot the player's X rotation (pitch) on arrival (if empty, use player's current pitch) + * @param playerId the player's UUID, only if this destination was created from a player, null otherwise */ -public record TeleportDestination(ResourceKey dimension, BlockPos pos, Optional yRot, Optional xRot) { +public record TeleportDestination( + ResourceKey dimension, + BlockPos pos, + Optional yRot, + Optional xRot, + @Nullable UUID playerId +) { + public TeleportDestination withPos(BlockPos newPos) { + return new TeleportDestination(dimension, newPos, yRot, xRot, playerId); + } + /** * Return a successful outcome for this destination. See also {@link dev.ftb.mods.ftbessentials.api.event.SavedTeleportEvent.PreTeleport#onTeleport(String, ServerPlayer, TeleportDestination, UUID)}. * @@ -27,6 +39,15 @@ public Outcome success() { return new Outcome(true, this, Component.empty()); } + /** + * Return a successful outcome with a new destination. + * + * @return a successful outcome + */ + public Outcome success(TeleportDestination newDest) { + return new Outcome(true, newDest, Component.empty()); + } + /** * Return a successful outcome for this destination. See also {@link dev.ftb.mods.ftbessentials.api.event.SavedTeleportEvent.PreTeleport#onTeleport(String, ServerPlayer, TeleportDestination, UUID)}. * diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java index 3ed561e..751a345 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/SavedTeleportEvent.java @@ -16,7 +16,7 @@ public interface SavedTeleportEvent { */ Event ADDED = EventFactory.createLoop(); /** - * See {@link #onDeleted(String, TeleportDestination, UUID)} (String, TeleportDestination, ServerPlayer, UUID)} + * See {@link #onDeleted(String, TeleportDestination, UUID)} */ Event DELETED = EventFactory.createLoop(); /** @@ -43,6 +43,7 @@ public interface SavedTeleportEvent { */ void onDeleted(String name, TeleportDestination destination, @Nullable UUID owningPlayer); + @FunctionalInterface interface PreTeleport { /** * Fired when a player is about to teleport to a saved destination (home or warp). This event allows diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java index 88e2dd1..e61873c 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportEvent.java @@ -9,9 +9,10 @@ public class TeleportEvent { public static Event TELEPORT = EventFactory.createCompoundEventResult(); + @FunctionalInterface public interface Teleport { /** - * Fired just before a player teleports (but before any warmup is started). This can be canceled to prevent + * Fired when the player is ready to teleport, but before any warmup is started. This can be canceled to prevent * teleportation by returning {@link CompoundEventResult#interruptFalse(Object)} with a message to supply to the * player about why teleportation was prevented. * diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java new file mode 100644 index 0000000..a971242 --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java @@ -0,0 +1,25 @@ +package dev.ftb.mods.ftbessentials.api.event; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import dev.ftb.mods.ftbessentials.api.TeleportDestination; +import net.minecraft.server.level.ServerPlayer; + +public class TeleportImmediateEvent { + public static Event TELEPORT = EventFactory.createLoop(); + + @FunctionalInterface + public interface TeleportImmediate { + /** + * Fired when the player is just about to teleport, when any possible teleportation warmup is complete. + *

+ * Teleportation can be prevented by returning a failed outcome e.g. {@code destination.failed(reason)}, + * or a modified destination e.g. {@code detination.success(newdest)}. + * + * @param player the player about to teleport + * @param destination the teleport destination + * @return the event outcome + */ + TeleportDestination.Outcome teleport(ServerPlayer player, TeleportDestination destination); + } +} diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/FTBEPlayerData.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/FTBEPlayerData.java index ce7d7ad..66b4b4f 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/FTBEPlayerData.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/FTBEPlayerData.java @@ -247,14 +247,14 @@ public void read(CompoundTag tag) { god = tag.getBoolean("god"); nick = tag.getString("nick"); recording = RecordingStatus.NAME_MAP.map.getOrDefault(tag.getString("recording"), RecordingStatus.NONE); - lastSeenPos = tag.contains("last_seen") ? new TeleportPos(tag.getCompound("last_seen")) : null; + lastSeenPos = tag.contains("last_seen") ? new TeleportPos(tag.getCompound("last_seen"), uuid) : null; teleportHistory.clear(); ListTag th = tag.getList("teleport_history", Tag.TAG_COMPOUND); for (int i = 0; i < th.size(); i++) { - teleportHistory.add(new TeleportPos(th.getCompound(i))); + teleportHistory.add(new TeleportPos(th.getCompound(i), uuid)); } kitUseTimes.clear(); diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java index 5f22580..0c80bd9 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/SavedTeleportManager.java @@ -66,7 +66,7 @@ public CompoundTag writeNBT() { public void readNBT(CompoundTag tag) { destinations.clear(); for (String key : tag.getAllKeys()) { - destinations.put(key, new TeleportPos(tag.getCompound(key))); + destinations.put(key, new TeleportPos(tag.getCompound(key), owningPlayer())); } } diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java index fd3b521..6c9ba28 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java @@ -2,6 +2,7 @@ import dev.ftb.mods.ftbessentials.api.TeleportDestination; import dev.ftb.mods.ftbessentials.api.TeleportResult; +import dev.ftb.mods.ftbessentials.api.event.TeleportImmediateEvent; import dev.ftb.mods.ftblibrary.snbt.SNBTCompoundTag; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -17,55 +18,66 @@ import org.jetbrains.annotations.Nullable; import java.util.Optional; +import java.util.UUID; public class TeleportPos { private final ResourceKey dimension; private final BlockPos pos; + @Nullable public final Float yRot, xRot; - private final long time; + @Nullable + private final UUID playerId; public TeleportPos(ResourceKey d, BlockPos p) { this(d, p, null, null); } - public TeleportPos(Level world, BlockPos p, Float yRot, Float xRot) { + public TeleportPos(Level world, BlockPos p, @Nullable Float yRot, @Nullable Float xRot) { this(world.dimension(), p, yRot, xRot); } public TeleportPos(Entity entity) { - this(entity.level(), entity.blockPosition(), entity.getYRot(), entity.getXRot()); + this(entity.level().dimension(), entity.blockPosition(), entity.getYRot(), entity.getXRot(), + entity instanceof Player p ? p.getUUID() : null); } - public TeleportPos(CompoundTag tag) { + public TeleportPos(CompoundTag tag, @Nullable UUID playerId) { dimension = ResourceKey.create(Registries.DIMENSION, ResourceLocation.tryParse(tag.getString("dim"))); pos = new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); this.yRot = (tag.getTagType("yRot") == CompoundTag.TAG_FLOAT) ? tag.getFloat("yRot") : null; this.xRot = (tag.getTagType("xRot") == CompoundTag.TAG_FLOAT) ? tag.getFloat("xRot") : null; - time = tag.getLong("time"); + this.playerId = playerId; } - private TeleportPos(ResourceKey d, BlockPos p, Float yRot, Float xRot) { - this(d, p, yRot, xRot, System.currentTimeMillis()); + private TeleportPos(ResourceKey d, BlockPos p, @Nullable Float yRot, @Nullable Float xRot) { + this(d, p, yRot, xRot, null); } - private TeleportPos(ResourceKey dimension, BlockPos pos, Float yRot, Float xRot, long time) { + // canonical ctor + private TeleportPos(ResourceKey dimension, BlockPos pos, @Nullable Float yRot, @Nullable Float xRot, @Nullable UUID playerId) { this.dimension = dimension; this.pos = pos; this.yRot = yRot; this.xRot = xRot; - this.time = time; + this.playerId = playerId; } @Nullable public static TeleportPos fromDestination(@Nullable TeleportDestination dest) { - return dest == null ? null : new TeleportPos(dest.dimension(), dest.pos(), dest.yRot().orElse(null), dest.xRot().orElse(null)); + return dest == null ? + null : + new TeleportPos(dest.dimension(), dest.pos(), + dest.yRot().orElse(null), dest.xRot().orElse(null), + dest.playerId() + ); } TeleportDestination asDestination() { - return new TeleportDestination(dimension, pos, Optional.ofNullable(yRot), Optional.ofNullable(xRot)); + return new TeleportDestination(dimension, pos, Optional.ofNullable(yRot), Optional.ofNullable(xRot), playerId); } public TeleportPos safeForPlayer(ServerPlayer player) { + assert player.getServer() != null; ServerLevel level = player.getServer().getLevel(dimension); if (level == null) return this; // shouldn't happen @@ -107,6 +119,17 @@ public TeleportResult checkDimensionBlacklist(Player player) { } public TeleportResult teleport(ServerPlayer player) { + TeleportDestination oldDest = asDestination(); + var outcome = TeleportImmediateEvent.TELEPORT.invoker().teleport(player, oldDest); + if (outcome != null && !outcome.success()) { + return TeleportResult.failed(outcome.reason()); + } + + TeleportPos newPos = outcome == null || outcome.dest().equals(oldDest) ? this : TeleportPos.fromDestination(outcome.dest()); + return newPos.actuallyTeleport(player); + } + + private TeleportResult actuallyTeleport(ServerPlayer player) { ServerLevel level = player.server.getLevel(dimension); if (level == null) { return TeleportResult.DIMENSION_NOT_FOUND; @@ -127,7 +150,6 @@ public CompoundTag write() { tag.putInt("x", pos.getX()); tag.putInt("y", pos.getY()); tag.putInt("z", pos.getZ()); - tag.putLong("time", time); if (this.xRot != null) tag.putFloat("xRot", this.xRot); if (this.yRot != null) tag.putFloat("yRot", this.yRot); return tag; @@ -160,6 +182,6 @@ public BlockPos getPos() { public String posAsString() { // Normal shortString would be 1, 2, 3 so we remove the commas - return pos.toShortString().replaceAll(",", ""); + return pos.toShortString().replace(",", ""); } } From 2e114a999b8b9fa684ac0d478b93aa18773036aa Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 2 Jun 2026 17:00:33 +0100 Subject: [PATCH 3/5] fix: fix up teleport immediate event (now compoundeventresult) --- .../java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java | 3 ++- .../ftb/mods/ftbessentials/api/TeleportDestination.java | 8 ++++---- .../ftbessentials/api/event/TeleportImmediateEvent.java | 5 +++-- .../java/dev/ftb/mods/ftbessentials/util/TeleportPos.java | 7 ++++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java b/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java index 53fa5d3..cc4061c 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.architectury.event.CompoundEventResult; import dev.architectury.event.EventResult; import dev.architectury.event.events.common.*; import dev.ftb.mods.ftbessentials.api.TeleportDestination; @@ -58,7 +59,7 @@ public static void init() { ChatEvent.RECEIVED.register(FTBEEventHandler::playerChat); TeleportImmediateEvent.TELEPORT.register((player, dest) -> - dest.success(dest.withPos(dest.pos().above(10)))); + CompoundEventResult.pass()); } private static void serverAboutToStart(MinecraftServer minecraftServer) { diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java index a53de92..5ee0672 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/TeleportDestination.java @@ -36,7 +36,7 @@ public TeleportDestination withPos(BlockPos newPos) { * @return a successful outcome */ public Outcome success() { - return new Outcome(true, this, Component.empty()); + return new Outcome(this, Component.empty()); } /** @@ -45,7 +45,7 @@ public Outcome success() { * @return a successful outcome */ public Outcome success(TeleportDestination newDest) { - return new Outcome(true, newDest, Component.empty()); + return new Outcome(newDest, Component.empty()); } /** @@ -55,9 +55,9 @@ public Outcome success(TeleportDestination newDest) { * @return a failed outcome */ public Outcome failed(Component reason) { - return new Outcome(false, this, reason); + return new Outcome(this, reason); } - public record Outcome(boolean success, TeleportDestination dest, Component reason) { + public record Outcome(TeleportDestination dest, Component reason) { } } diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java index a971242..df8ac70 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/api/event/TeleportImmediateEvent.java @@ -1,12 +1,13 @@ package dev.ftb.mods.ftbessentials.api.event; +import dev.architectury.event.CompoundEventResult; import dev.architectury.event.Event; import dev.architectury.event.EventFactory; import dev.ftb.mods.ftbessentials.api.TeleportDestination; import net.minecraft.server.level.ServerPlayer; public class TeleportImmediateEvent { - public static Event TELEPORT = EventFactory.createLoop(); + public static Event TELEPORT = EventFactory.createCompoundEventResult(TeleportImmediate.class); @FunctionalInterface public interface TeleportImmediate { @@ -20,6 +21,6 @@ public interface TeleportImmediate { * @param destination the teleport destination * @return the event outcome */ - TeleportDestination.Outcome teleport(ServerPlayer player, TeleportDestination destination); + CompoundEventResult teleport(ServerPlayer player, TeleportDestination destination); } } diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java b/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java index 6c9ba28..00a4bb2 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/util/TeleportPos.java @@ -121,11 +121,12 @@ public TeleportResult checkDimensionBlacklist(Player player) { public TeleportResult teleport(ServerPlayer player) { TeleportDestination oldDest = asDestination(); var outcome = TeleportImmediateEvent.TELEPORT.invoker().teleport(player, oldDest); - if (outcome != null && !outcome.success()) { - return TeleportResult.failed(outcome.reason()); + if (outcome.isFalse()) { + return TeleportResult.failed(outcome.object().reason()); } - TeleportPos newPos = outcome == null || outcome.dest().equals(oldDest) ? this : TeleportPos.fromDestination(outcome.dest()); + var newDest = outcome.isEmpty() ? oldDest : outcome.object().dest(); + TeleportPos newPos = newDest.equals(oldDest) ? this : TeleportPos.fromDestination(newDest); return newPos.actuallyTeleport(player); } From 5b836547a108e53e2d132dcda58ad9bbee427595 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 2 Jun 2026 17:00:59 +0100 Subject: [PATCH 4/5] build: don't need build.yml change --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ded99c..eae06a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Java CI - Build on Push on: push: - branches: [ main, dev, "1.**", "teleporting-api" ] + branches: [ main, dev, "1.**" ] workflow_dispatch: inputs: skip_maven_publish: From 4562df9e58eb1e6dceaa339c27c1142dbec2695d Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 2 Jun 2026 17:06:27 +0100 Subject: [PATCH 5/5] build: remove test listener! --- .../main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java b/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java index cc4061c..0f18d64 100644 --- a/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java +++ b/common/src/main/java/dev/ftb/mods/ftbessentials/FTBEEventHandler.java @@ -57,9 +57,6 @@ public static void init() { EntityEvent.LIVING_HURT.register(FTBEEventHandler::playerHurt); ChatEvent.RECEIVED.register(FTBEEventHandler::playerChat); - - TeleportImmediateEvent.TELEPORT.register((player, dest) -> - CompoundEventResult.pass()); } private static void serverAboutToStart(MinecraftServer minecraftServer) {