DREAMFIRE Docs ← Back to site
Loading...
Searching...
No Matches
DreamTeleport.java
Go to the documentation of this file.
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Dreamfire Studio
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24package com.dreamfirestudios.dreamcore.DreamTeleport;
25
26import net.kyori.adventure.text.Component;
27import org.bukkit.Bukkit;
28import org.bukkit.Location;
29import org.bukkit.World;
30import org.bukkit.entity.LivingEntity;
31import org.bukkit.entity.Player;
32import org.bukkit.plugin.Plugin;
33import org.bukkit.scheduler.BukkitTask;
34
35import java.lang.ref.WeakReference;
36import java.util.*;
37import java.util.concurrent.ConcurrentHashMap;
38
46public final class DreamTeleport implements Runnable {
47
49 public static final long DEFAULT_PERIOD_TICKS = 20L;
50
51 private final Plugin plugin;
52 private final Map<UUID, Location> startLocations = new ConcurrentHashMap<>();
53 private final WeakReference<LivingEntity> liveTarget; // may be null
54 private final Location fixedTarget; // may be null
55 private final boolean showCountdown;
56 private final boolean cancelOnMove;
57 private final double moveToleranceSq;
58 private final long periodTicks;
59
60 private final int totalSeconds;
61 private int secondsLeft;
62
63 private BukkitTask task;
64
77 Plugin plugin,
78 Collection<Player> players,
79 LivingEntity liveTarget,
80 Location fixedTarget,
81 int seconds,
82 long periodTicks,
83 boolean showCountdown,
84 boolean cancelOnMove,
85 double moveTolerance
86 ) {
87 this.plugin = Objects.requireNonNull(plugin, "plugin");
88 Objects.requireNonNull(players, "players");
89 if (liveTarget == null && fixedTarget == null) {
90 throw new IllegalArgumentException("Either liveTarget or fixedTarget must be provided");
91 }
92
93 for (Player p : players) {
94 if (p != null && p.isOnline()) {
95 startLocations.put(p.getUniqueId(), p.getLocation().clone());
96 }
97 }
98
99 this.liveTarget = liveTarget == null ? null : new WeakReference<>(liveTarget);
100 this.fixedTarget = fixedTarget == null ? null : fixedTarget.clone();
101 this.totalSeconds = Math.max(0, seconds);
102 this.secondsLeft = this.totalSeconds;
103 this.periodTicks = Math.max(1L, periodTicks);
104 this.showCountdown = showCountdown;
105 this.cancelOnMove = cancelOnMove;
106 this.moveToleranceSq = Math.max(0.0, moveTolerance) * Math.max(0.0, moveTolerance);
107 }
108
110 public void start() {
111 if (task != null) return;
112 task = Bukkit.getScheduler().runTaskTimer(plugin, this, 0L, periodTicks);
113 }
114
116 public void cancel() {
117 if (task != null) task.cancel();
118 task = null;
119 startLocations.clear();
120 }
121
123 public boolean isEmpty() {
124 return startLocations.isEmpty();
125 }
126
128 public boolean contains(Player player) {
129 return player != null && startLocations.containsKey(player.getUniqueId());
130 }
131
133 public boolean remove(Player player) {
134 if (player == null) return isEmpty();
135 startLocations.remove(player.getUniqueId());
136 return isEmpty();
137 }
138
139 @Override
140 public void run() {
141 // if no players remain, stop the task
142 if (startLocations.isEmpty()) {
143 cancel();
144 return;
145 }
146
147 // Per-tick housekeeping & countdown display
148 if (secondsLeft > 0) {
149 // show actionbar countdown (optional)
150 if (showCountdown) {
151 forEachOnlinePlayer(p -> p.sendActionBar(Component.text(
152 "Teleporting in " + secondsLeft + " / " + totalSeconds + "…"
153 )));
154 }
155
156 // cancel on move (if enabled)
157 if (cancelOnMove) {
158 final List<UUID> toRemove = new ArrayList<>();
159 for (Map.Entry<UUID, Location> e : startLocations.entrySet()) {
160 Player p = Bukkit.getPlayer(e.getKey());
161 if (p == null || !p.isOnline()) {
162 toRemove.add(e.getKey());
163 continue;
164 }
165 Location start = e.getValue();
166 Location now = p.getLocation();
167
168 if (!sameWorld(start, now) || start.toVector().distanceSquared(now.toVector()) > moveToleranceSq) {
169 p.sendActionBar(Component.text("Teleport cancelled (you moved)."));
170 toRemove.add(e.getKey());
171 }
172 }
173 toRemove.forEach(startLocations::remove);
174 if (startLocations.isEmpty()) {
175 cancel();
176 return;
177 }
178 }
179
180 secondsLeft--;
181 return;
182 }
183
184 // Time's up: teleport remaining players
185 Location target = resolveTarget();
186 if (target == null) {
187 // live target vanished AND no fixed fallback; cancel safely
188 forEachOnlinePlayer(p -> p.sendActionBar(Component.text("Teleport failed: no valid target.")));
189 cancel();
190 return;
191 }
192
193 forEachOnlinePlayer(p -> p.teleport(target));
194 cancel(); // finished
195 }
196
197 private void forEachOnlinePlayer(java.util.function.Consumer<Player> consumer) {
198 for (UUID id : new ArrayList<>(startLocations.keySet())) {
199 Player p = Bukkit.getPlayer(id);
200 if (p != null && p.isOnline()) {
201 consumer.accept(p);
202 } else {
203 startLocations.remove(id);
204 }
205 }
206 }
207
208 private static boolean sameWorld(Location a, Location b) {
209 World wa = a.getWorld();
210 World wb = b.getWorld();
211 return wa != null && wa.equals(wb);
212 }
213
214 private Location resolveTarget() {
215 if (liveTarget != null) {
216 LivingEntity e = liveTarget.get();
217 if (e != null && e.isValid()) {
218 return e.getLocation();
219 }
220 }
221 return fixedTarget;
222 }
223
224 // --- Getters ---
225 public int getSecondsLeft() { return secondsLeft; }
226 public int getTotalSeconds() { return totalSeconds; }
227}
A scheduled teleport task that can handle one or more players counting down to a target (either a liv...
boolean contains(Player player)
Returns true if the given player is queued in this task.
DreamTeleport(Plugin plugin, Collection< Player > players, LivingEntity liveTarget, Location fixedTarget, int seconds, long periodTicks, boolean showCountdown, boolean cancelOnMove, double moveTolerance)
void cancel()
Cancels the task immediately without teleporting remaining players.
boolean isEmpty()
Returns true if this task still has players queued.
static final long DEFAULT_PERIOD_TICKS
Default tick period; 20 ticks = 1 second updates.