Major refactor to handle more edge cases
This commit is contained in:
1 parent
6d8626546c
commit
31070db577
9 files changed
+647
-284
No files matched your search
+1
-1
@@ -25,7 +25,7 @@ kotlin { jvmToolchain(17) }
|
||||
/* ----------------------------- Metadata ------------------------------ */
|
||||
group = "net.trueog.launchpads-og" // Declare bundle identifier.
|
||||
|
||||
version = "1.7" // Declare plugin version (will be in .jar).
|
||||
version = "1.8" // Declare plugin version (will be in .jar).
|
||||
|
||||
val apiVersion = "1.19" // Declare minecraft server target version.
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.trueog.launchpadsog.Commands;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collections;
|
||||
import java.util.Vector;
|
||||
|
||||
@@ -63,7 +65,7 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
// check if there is already a pad in that location
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(world.getName())) {
|
||||
if (world.getName().equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == playerX) {
|
||||
|
||||
@@ -123,14 +125,19 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
int playerX = playerLocation.getBlockX();
|
||||
int playerY = playerLocation.getBlockY();
|
||||
int playerZ = playerLocation.getBlockZ();
|
||||
double padXPower = Double.parseDouble(args[1]);
|
||||
double padYPower = Double.parseDouble(args[2]);
|
||||
double padZPower = Double.parseDouble(args[3]);
|
||||
final Double padXPower = parseDoubleOrWarn(sender, args[1]);
|
||||
final Double padYPower = parseDoubleOrWarn(sender, args[2]);
|
||||
final Double padZPower = parseDoubleOrWarn(sender, args[3]);
|
||||
if (padXPower == null || padYPower == null || padZPower == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// check if there is already a pad in that location
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(world.getName())) {
|
||||
if (world.getName().equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == playerX) {
|
||||
|
||||
@@ -190,17 +197,24 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
else if (args.length == 7) {
|
||||
|
||||
int padNumber = padCoords.getInt("numberOfPads") + 1;
|
||||
int padX = Integer.parseInt(args[1]);
|
||||
int padY = Integer.parseInt(args[2]);
|
||||
int padZ = Integer.parseInt(args[3]);
|
||||
double padXPower = Double.parseDouble(args[4]);
|
||||
double padYPower = Double.parseDouble(args[5]);
|
||||
double padZPower = Double.parseDouble(args[6]);
|
||||
final Integer padX = parseIntOrWarn(sender, args[1]);
|
||||
final Integer padY = parseIntOrWarn(sender, args[2]);
|
||||
final Integer padZ = parseIntOrWarn(sender, args[3]);
|
||||
final Double padXPower = parseDoubleOrWarn(sender, args[4]);
|
||||
final Double padYPower = parseDoubleOrWarn(sender, args[5]);
|
||||
final Double padZPower = parseDoubleOrWarn(sender, args[6]);
|
||||
if (padX == null || padY == null || padZ == null || padXPower == null
|
||||
|| padYPower == null || padZPower == null)
|
||||
{
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// check if there is already a pad in that location
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(world.getName())) {
|
||||
if (world.getName().equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == padX) {
|
||||
|
||||
@@ -250,6 +264,15 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
+ ", " + padZPower).color(TextColor.color(0, 191, 255)))); // light blue
|
||||
|
||||
}
|
||||
// wrong arity: say so instead of doing nothing
|
||||
else {
|
||||
|
||||
sender.sendMessage(Component.text("[LaunchPad]").color(TextColor.color(0, 255, 0))
|
||||
.append(Component.text(
|
||||
" Usage: /launchpad set [<xpower> <ypower> <zpower>] or /launchpad set <x> <y> <z> <xpower> <ypower> <zpower>")
|
||||
.color(TextColor.color(255, 0, 0))));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// if the player doesn't have permission to use this command,
|
||||
@@ -273,75 +296,48 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
LaunchPadsOG.getPlugin(LaunchPadsOG.class).reloadConfig();
|
||||
padCoords = LaunchPadsOG.getConfiguration();
|
||||
|
||||
// check through the config to make sure that
|
||||
// there are as many launchpads as numberOfPads
|
||||
// if there is not, move all of the pad number down
|
||||
// by one starting at the missing number
|
||||
// The live accessor means the pre-reload count no
|
||||
// longer matches the file; re-read before using it.
|
||||
numberOfPads = Math.max(0, padCoords.getInt("numberOfPads"));
|
||||
|
||||
int maxNumberOfPads = numberOfPads + 1;
|
||||
if (numberOfPads > 1) {
|
||||
// Compact pad ids to run 1..n with no gaps, judged
|
||||
// via isSet so jar defaults cannot mask a gap.
|
||||
int writeId = 0;
|
||||
for (int readId = 1; readId <= numberOfPads; readId++) {
|
||||
|
||||
for (int i = 1; i < maxNumberOfPads; i++) {
|
||||
if (!padCoords.isSet(readId + ".world")) {
|
||||
|
||||
// if the world is null, then the pad is not set
|
||||
if (padCoords.getString(i + ".world") == null) {
|
||||
|
||||
// move all other pads up one id number
|
||||
for (int j = i; j < padCoords.getInt("numberOfPads") + 1; j++) {
|
||||
|
||||
padCoords.set("" + j + ".x", padCoords.getInt("" + (j + 1) + ".x"));
|
||||
padCoords.set("" + j + ".y", padCoords.getInt("" + (j + 1) + ".y"));
|
||||
padCoords.set("" + j + ".z", padCoords.getInt("" + (j + 1) + ".z"));
|
||||
padCoords.set("" + j + ".world",
|
||||
padCoords.getString("" + (j + 1) + ".world"));
|
||||
padCoords.set("" + j + ".xpower",
|
||||
padCoords.getDouble("" + (j + 1) + ".xpower"));
|
||||
padCoords.set("" + j + ".ypower",
|
||||
padCoords.getDouble("" + (j + 1) + ".ypower"));
|
||||
padCoords.set("" + j + ".zpower",
|
||||
padCoords.getDouble("" + (j + 1) + ".zpower"));
|
||||
// this makes it easier to search the config file
|
||||
// use CTRL+F and search for the coords of the pad
|
||||
padCoords.set("" + j + ".searchID",
|
||||
padCoords.getString("" + (j + 1) + ".searchID"));
|
||||
|
||||
}
|
||||
|
||||
padCoords.set("" + numberOfPads, null);
|
||||
padCoords.set("numberOfPads", --numberOfPads);
|
||||
|
||||
}
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// check if the last pad exists
|
||||
if (padCoords.getString(numberOfPads + ".world") == null) {
|
||||
writeId++;
|
||||
if (readId != writeId) {
|
||||
|
||||
// decrement numberOfPads by one
|
||||
padCoords.set("numberOfPads", --numberOfPads);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// if numberOfPads is 1, then there can only be one pad
|
||||
// check if it exists. If not, set numberOfPads to 0
|
||||
if (padCoords.getString("1.world") == null) {
|
||||
|
||||
padCoords.set("numberOfPads", 0);
|
||||
padCoords.set(writeId + ".x", padCoords.getInt(readId + ".x"));
|
||||
padCoords.set(writeId + ".y", padCoords.getInt(readId + ".y"));
|
||||
padCoords.set(writeId + ".z", padCoords.getInt(readId + ".z"));
|
||||
padCoords.set(writeId + ".world", padCoords.getString(readId + ".world"));
|
||||
padCoords.set(writeId + ".xpower", padCoords.getDouble(readId + ".xpower"));
|
||||
padCoords.set(writeId + ".ypower", padCoords.getDouble(readId + ".ypower"));
|
||||
padCoords.set(writeId + ".zpower", padCoords.getDouble(readId + ".zpower"));
|
||||
padCoords.set(writeId + ".searchID", padCoords.getString(readId + ".searchID"));
|
||||
padCoords.set("" + readId, null);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if numberOfPads is below 0
|
||||
// if so, set it to 0
|
||||
if (padCoords.getInt("numberOfPads") < 0) {
|
||||
// drop any leftover sections above the compacted count
|
||||
for (int staleId = writeId + 1; staleId <= numberOfPads; staleId++) {
|
||||
|
||||
padCoords.set("numberOfPads", 0);
|
||||
padCoords.set("" + staleId, null);
|
||||
|
||||
}
|
||||
|
||||
padCoords.set("numberOfPads", writeId);
|
||||
numberOfPads = writeId;
|
||||
|
||||
/* check that everything has what it is supposed to have */
|
||||
|
||||
// check that numberOfPads is an integer
|
||||
@@ -694,7 +690,7 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
boolean found = false;
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(world.getName())) {
|
||||
if (world.getName().equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == block.getX()) {
|
||||
|
||||
@@ -774,14 +770,19 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
else if (args.length == 4) {
|
||||
|
||||
// check for a launchpad at the coords given in the player's world
|
||||
int blockX = Integer.parseInt(args[1]);
|
||||
int blockY = Integer.parseInt(args[2]);
|
||||
int blockZ = Integer.parseInt(args[3]);
|
||||
final Integer blockX = parseIntOrWarn(sender, args[1]);
|
||||
final Integer blockY = parseIntOrWarn(sender, args[2]);
|
||||
final Integer blockZ = parseIntOrWarn(sender, args[3]);
|
||||
if (blockX == null || blockY == null || blockZ == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(world.getName())) {
|
||||
if (world.getName().equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == blockX) {
|
||||
|
||||
@@ -828,6 +829,10 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
+ blockY + ", " + blockZ)
|
||||
.color(TextColor.color(255, 255, 0))));
|
||||
|
||||
// stop here: the ids just compacted, so
|
||||
// continuing the scan reads dead sections
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -871,14 +876,19 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
|
||||
}
|
||||
|
||||
int blockX = Integer.parseInt(args[2]);
|
||||
int blockY = Integer.parseInt(args[3]);
|
||||
int blockZ = Integer.parseInt(args[4]);
|
||||
final Integer blockX = parseIntOrWarn(sender, args[2]);
|
||||
final Integer blockY = parseIntOrWarn(sender, args[3]);
|
||||
final Integer blockZ = parseIntOrWarn(sender, args[4]);
|
||||
if (blockX == null || blockY == null || blockZ == null) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(worldName)) {
|
||||
if (worldName.equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == blockX) {
|
||||
|
||||
@@ -925,6 +935,10 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
+ blockY + ", " + blockZ)
|
||||
.color(TextColor.color(255, 255, 0))));
|
||||
|
||||
// stop here: the ids just compacted, so
|
||||
// continuing the scan reads dead sections
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -944,6 +958,15 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
}
|
||||
|
||||
}
|
||||
// wrong arity: say so instead of doing nothing
|
||||
else {
|
||||
|
||||
sender.sendMessage(Component.text("[LaunchPad]").color(TextColor.color(0, 255, 0))
|
||||
.append(Component.text(
|
||||
" Usage: /launchpad delete [<x> <y> <z>] or /launchpad delete <world> <x> <y> <z>")
|
||||
.color(TextColor.color(255, 0, 0))));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// if the player doesn't have permission to use this command,
|
||||
@@ -1072,6 +1095,18 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
|
||||
}
|
||||
|
||||
} else if (args[0].equalsIgnoreCase("reloadconfig")) {
|
||||
|
||||
// console path: reload needs no player context, and index
|
||||
// recovery must not depend on a logged-in op
|
||||
if (sender.hasPermission("launchpad.canreload")) {
|
||||
|
||||
LaunchPadsOG.getPlugin(LaunchPadsOG.class).reloadConfig();
|
||||
sender.sendMessage(Component.text("[LaunchPad]").color(TextColor.color(0, 255, 0))
|
||||
.append(Component.text(" Config Reloaded").color(TextColor.color(255, 255, 0))));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1088,18 +1123,69 @@ public class LaunchPadCommands implements CommandExecutor {
|
||||
|
||||
}
|
||||
|
||||
// Null when the argument is not a number; the caller bails after the warning.
|
||||
private Integer parseIntOrWarn(CommandSender sender, String arg) {
|
||||
|
||||
try {
|
||||
|
||||
return Integer.parseInt(arg);
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
warnNotANumber(sender, arg);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Double parseDoubleOrWarn(CommandSender sender, String arg) {
|
||||
|
||||
try {
|
||||
|
||||
return Double.parseDouble(arg);
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
warnNotANumber(sender, arg);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void warnNotANumber(CommandSender sender, String arg) {
|
||||
|
||||
sender.sendMessage(Component.text("[LaunchPad]").color(TextColor.color(0, 255, 0))
|
||||
.append(Component.text(" '" + arg + "' is not a number").color(TextColor.color(255, 0, 0))));
|
||||
|
||||
}
|
||||
|
||||
private void restoreOldConfig(FileConfiguration oldPadCoords) {
|
||||
|
||||
try {
|
||||
|
||||
File configFile = new File(LaunchPadsOG.getPlugin(LaunchPadsOG.class).getDataFolder(), "config.yml");
|
||||
// keep the admin's hand-edited file recoverable before clobbering it
|
||||
if (configFile.exists()) {
|
||||
|
||||
Files.copy(configFile.toPath(), configFile.toPath().resolveSibling("config.yml.bak"),
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
}
|
||||
|
||||
oldPadCoords.save(configFile);
|
||||
LaunchPadsOG.getPlugin(LaunchPadsOG.class).reloadConfig();
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
|
||||
} finally {
|
||||
|
||||
// reload whatever is on disk so memory, index, and file agree
|
||||
// even when the rollback write itself failed
|
||||
LaunchPadsOG.getPlugin(LaunchPadsOG.class).reloadConfig();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.trueog.launchpadsog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@@ -15,19 +17,27 @@ import net.trueog.launchpadsog.Commands.LaunchPadCommands;
|
||||
import net.trueog.launchpadsog.Listeners.OnBlockBreak;
|
||||
import net.trueog.launchpadsog.Listeners.OnBlockPlace;
|
||||
import net.trueog.launchpadsog.Listeners.OnEntityDamage;
|
||||
import net.trueog.launchpadsog.Listeners.OnLaunchEnd;
|
||||
import net.trueog.launchpadsog.Listeners.OnPlayerInteract;
|
||||
import net.trueog.launchpadsog.Listeners.OnPlayerMove;
|
||||
|
||||
public final class LaunchPadsOG extends JavaPlugin {
|
||||
|
||||
private static LaunchPadsOG plugin;
|
||||
private static FileConfiguration config;
|
||||
|
||||
// This list will hold the players that have used a launchpad and are flying
|
||||
// through the air
|
||||
// then, when they land, I remove them from the list, and stop them from taking
|
||||
// fall damage.
|
||||
private static final ArrayList<Player> launchedPlayers = new ArrayList<>();
|
||||
// Only an AIRBORNE protection can be spent or dropped, so a slow client
|
||||
// still reporting the pad position cannot burn it before liftoff.
|
||||
public enum LaunchPhase {
|
||||
AWAITING_LIFTOFF, AIRBORNE
|
||||
}
|
||||
|
||||
// Players inside a live launch arc, tracked by id so relogs and repeated
|
||||
// pad hits can neither stack protections nor strand stale entries.
|
||||
private static final Map<UUID, LaunchPhase> launchedPlayers = new HashMap<>();
|
||||
|
||||
// Pad ids indexed by "world:x:y:z", so the move handler does one map
|
||||
// lookup instead of scanning every configured pad on every step.
|
||||
private static final Map<String, Integer> padsByBlock = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -35,12 +45,10 @@ public final class LaunchPadsOG extends JavaPlugin {
|
||||
plugin = this;
|
||||
|
||||
saveDefaultConfig();
|
||||
config = this.getConfig();
|
||||
|
||||
reloadConfig();
|
||||
|
||||
// If the launchpad is craftable, add the recipe to the server.
|
||||
if ((boolean) config.get("cancraft")) {
|
||||
if (getConfig().getBoolean("cancraft")) {
|
||||
|
||||
// Create the recipe for the launchpad
|
||||
// make it really expensive by locking it
|
||||
@@ -63,6 +71,7 @@ public final class LaunchPadsOG extends JavaPlugin {
|
||||
getServer().getPluginManager().registerEvents(new OnBlockBreak(), this);
|
||||
getServer().getPluginManager().registerEvents(new OnBlockPlace(), this);
|
||||
getServer().getPluginManager().registerEvents(new OnEntityDamage(), this);
|
||||
getServer().getPluginManager().registerEvents(new OnLaunchEnd(), this);
|
||||
getCommand("launchpad").setExecutor(new LaunchPadCommands());
|
||||
getCommand("launchpad").setTabCompleter(new LaunchPadCommandCompleter());
|
||||
|
||||
@@ -70,49 +79,146 @@ public final class LaunchPadsOG extends JavaPlugin {
|
||||
// to signify that they are launchpads.
|
||||
Bukkit.getScheduler().runTaskTimer(this, () -> {
|
||||
|
||||
for (int i = 1; i < config.getInt("numberOfPads") + 1; i++) {
|
||||
final FileConfiguration padConfig = getConfig();
|
||||
final int idleParticleCount = padConfig.getInt("idleParticleCount");
|
||||
final double idleParticlePower = padConfig.getDouble("idleParticlePower");
|
||||
for (int i = 1; i < padConfig.getInt("numberOfPads") + 1; i++) {
|
||||
|
||||
final String worldName = config.getString(i + ".world");
|
||||
final World world = Bukkit.getWorld(worldName);
|
||||
final double padX = config.getInt(i + ".x") + 0.5;
|
||||
final double padY = config.getInt(i + ".y");
|
||||
final double padZ = config.getInt(i + ".z") + 0.5;
|
||||
final Location padLocation = new Location(world, padX, padY, padZ);
|
||||
final int idleParticleCount = config.getInt("idleParticleCount");
|
||||
final double idleParticlePower = config.getDouble("idleParticlePower");
|
||||
final String worldName = padConfig.getString(i + ".world");
|
||||
final World world = worldName == null ? null : Bukkit.getWorld(worldName);
|
||||
if (world == null) {
|
||||
|
||||
// Spawn particles on the pad if the pad is loaded.
|
||||
if (world.isChunkLoaded(padLocation.getChunk())) {
|
||||
|
||||
// Use PORTAL, what ViaRewind downgrades DRAGON_BREATH to, so 1.8 and modern
|
||||
// clients match.
|
||||
world.spawnParticle(Particle.PORTAL, padLocation, idleParticleCount, 0, 0, 0,
|
||||
0.05 * idleParticlePower);
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
final int padX = padConfig.getInt(i + ".x");
|
||||
final int padY = padConfig.getInt(i + ".y");
|
||||
final int padZ = padConfig.getInt(i + ".z");
|
||||
|
||||
// Asked by chunk coordinates on purpose: getChunk() on a
|
||||
// location loads and pins the chunk just to answer.
|
||||
if (!world.isChunkLoaded(padX >> 4, padZ >> 4)) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
final Location padLocation = new Location(world, padX + 0.5, padY, padZ + 0.5);
|
||||
|
||||
// Use PORTAL, what ViaRewind downgrades DRAGON_BREATH to, so 1.8 and modern
|
||||
// clients match.
|
||||
world.spawnParticle(Particle.PORTAL, padLocation, idleParticleCount, 0, 0, 0, 0.05 * idleParticlePower);
|
||||
|
||||
}
|
||||
|
||||
}, 1, 30);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
|
||||
launchedPlayers.clear();
|
||||
padsByBlock.clear();
|
||||
|
||||
}
|
||||
|
||||
// Every save and reload funnels through these, so the pad index can never
|
||||
// go stale against the configuration.
|
||||
@Override
|
||||
public void saveConfig() {
|
||||
|
||||
super.saveConfig();
|
||||
rebuildPadIndex();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
|
||||
super.reloadConfig();
|
||||
rebuildPadIndex();
|
||||
|
||||
}
|
||||
|
||||
public static LaunchPadsOG getPlugin() {
|
||||
|
||||
return plugin;
|
||||
|
||||
}
|
||||
|
||||
// Always the live configuration: the old cached object was silently
|
||||
// swapped out by reloadConfig(), losing /launchpad set edits on restart.
|
||||
public static FileConfiguration getConfiguration() {
|
||||
|
||||
return config;
|
||||
return plugin.getConfig();
|
||||
|
||||
}
|
||||
|
||||
public static ArrayList<Player> getLaunchedPlayers() {
|
||||
public static void markLaunched(Player player) {
|
||||
|
||||
return launchedPlayers;
|
||||
launchedPlayers.put(player.getUniqueId(), LaunchPhase.AWAITING_LIFTOFF);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
public static LaunchPhase launchPhase(Player player) {
|
||||
|
||||
return launchedPlayers.get(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
public static void markAirborne(Player player) {
|
||||
|
||||
launchedPlayers.replace(player.getUniqueId(), LaunchPhase.AIRBORNE);
|
||||
|
||||
}
|
||||
|
||||
// Spends the protection of an arc in flight; damage before liftoff is the
|
||||
// arrival fall onto the pad, which stays the player's own to eat.
|
||||
public static boolean consumeAirborneLaunch(Player player) {
|
||||
|
||||
if (launchedPlayers.get(player.getUniqueId()) != LaunchPhase.AIRBORNE)
|
||||
return false;
|
||||
launchedPlayers.remove(player.getUniqueId());
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public static void clearLaunch(Player player) {
|
||||
|
||||
launchedPlayers.remove(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
// The pad id at a block position, or null when no pad sits there.
|
||||
public static Integer padAt(String worldName, int x, int y, int z) {
|
||||
|
||||
return padsByBlock.get(worldName + ":" + x + ":" + y + ":" + z);
|
||||
|
||||
}
|
||||
|
||||
private void rebuildPadIndex() {
|
||||
|
||||
padsByBlock.clear();
|
||||
final FileConfiguration padConfig = getConfig();
|
||||
for (int i = 1; i < padConfig.getInt("numberOfPads") + 1; i++) {
|
||||
|
||||
// isSet ignores the jar defaults; plain getters resolve through
|
||||
// them and would resurrect every hand-deleted pad.
|
||||
if (!padConfig.isSet(i + ".world")) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// putIfAbsent keeps the old scan's first-match-wins order when two
|
||||
// entries share one block.
|
||||
padsByBlock.putIfAbsent(padConfig.getString(i + ".world") + ":" + padConfig.getInt(i + ".x") + ":"
|
||||
+ padConfig.getInt(i + ".y") + ":" + padConfig.getInt(i + ".z"), i);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,8 @@ public class OnBlockBreak implements Listener {
|
||||
|
||||
for (int i = 1; i < numberOfPads + 1; i++) {
|
||||
|
||||
if (padCoords.getString("" + i + ".world").equals(world.getName())) {
|
||||
// null-safe order: a gap in the ids must not abort every break event
|
||||
if (world.getName().equals(padCoords.getString("" + i + ".world"))) {
|
||||
|
||||
if (padCoords.getInt("" + i + ".x") == block.getX()) {
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.trueog.launchpadsog.Listeners;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -14,12 +15,21 @@ import net.trueog.launchpadsog.LaunchPadsOG;
|
||||
|
||||
public class OnBlockPlace implements Listener {
|
||||
|
||||
@EventHandler
|
||||
// ignoreCancelled: a placement a region plugin blocked must not become a
|
||||
// working pad on disk anyway
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
|
||||
ItemStack item = event.getItemInHand();
|
||||
Block block = event.getBlockPlaced();
|
||||
|
||||
// the pad item is a pressure plate; lore alone must not qualify
|
||||
if (item.getType() != Material.STONE_PRESSURE_PLATE) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
Vector<Component> loreList = new Vector<>();
|
||||
loreList.add(Component.text("Launches you in the"));
|
||||
loreList.add(Component.text("direction you are facing"));
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
package net.trueog.launchpadsog.Listeners;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import net.trueog.launchpadsog.LaunchPadsOG;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByBlockEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
|
||||
import net.trueog.launchpadsog.LaunchPadsOG;
|
||||
|
||||
public class OnEntityDamage implements Listener {
|
||||
|
||||
// @EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler
|
||||
// HIGHEST + ignoreCancelled: a cancelled event spends nothing, and little
|
||||
// runs after us that could un-cancel our own cancellation.
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onEntityDamage(EntityDamageEvent event) {
|
||||
|
||||
// check if the damage was fall damage
|
||||
if (event.getCause().equals(EntityDamageEvent.DamageCause.FALL)) {
|
||||
// check that this is a landing hurting a player
|
||||
if (!(event.getEntity() instanceof Player player) || !endsALaunch(event)) {
|
||||
|
||||
// check if the entity was a player
|
||||
if (event.getEntity() instanceof Player) {
|
||||
return;
|
||||
|
||||
// check the entity was in the launchedPlayers list
|
||||
ArrayList<Player> launchedList = LaunchPadsOG.getLaunchedPlayers();
|
||||
Player eventPlayer = (Player) event.getEntity();
|
||||
if (launchedList.contains(eventPlayer)) {
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
// Cancelled even at zero final damage, so the landing costs no hurt
|
||||
// animation or absorption; pre-liftoff damage is never consumed.
|
||||
if (LaunchPadsOG.consumeAirborneLaunch(player)) {
|
||||
|
||||
launchedList.remove(eventPlayer);
|
||||
event.setCancelled(true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Landing on a stalagmite reports CONTACT rather than FALL, but it is
|
||||
// still the launch arc ending, so both count.
|
||||
private static boolean endsALaunch(EntityDamageEvent event) {
|
||||
|
||||
switch (event.getCause()) {
|
||||
|
||||
case FALL:
|
||||
return true;
|
||||
|
||||
case CONTACT:
|
||||
if (event instanceof EntityDamageByBlockEvent blockDamage) {
|
||||
|
||||
final Block damager = blockDamage.getDamager();
|
||||
return damager != null && damager.getType() == Material.POINTED_DRIPSTONE;
|
||||
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package net.trueog.launchpadsog.Listeners;
|
||||
|
||||
import net.trueog.launchpadsog.LaunchPadsOG;
|
||||
import org.bukkit.GameEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.GenericGameEvent;
|
||||
|
||||
public class OnGenericGameEvent implements Listener {
|
||||
|
||||
// use this event to see if the player hit the ground
|
||||
// if they did, then we can check if they were launched
|
||||
// this way, even if they don't take fall damage from the fall
|
||||
// say, they were using an elytra,
|
||||
// this will ensure that the OnEntityDamage event is still called
|
||||
// to remove them from the launched player List
|
||||
@EventHandler
|
||||
public void onGenericGameEvent(GenericGameEvent event) {
|
||||
|
||||
// check if the event was an entity hitting the ground
|
||||
if (event.getEvent().equals(GameEvent.HIT_GROUND)) {
|
||||
|
||||
// if the entity was a player
|
||||
if (event.getEntity() instanceof Player) {
|
||||
|
||||
Player player = (Player) event.getEntity();
|
||||
|
||||
// if the player was in the launched list
|
||||
if (LaunchPadsOG.getLaunchedPlayers().contains(player)) {
|
||||
|
||||
// set the player's fall distance to 4
|
||||
player.setFallDistance(4.0f);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package net.trueog.launchpadsog.Listeners;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityToggleGlideEvent;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.PlayerPortalEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.spigotmc.event.entity.EntityMountEvent;
|
||||
|
||||
import net.trueog.launchpadsog.LaunchPadsOG;
|
||||
|
||||
// Every way of leaving a launch arc drops its protection, so normal
|
||||
// fall damage applies again afterwards.
|
||||
public class OnLaunchEnd implements Listener {
|
||||
|
||||
// opening an elytra turns the arc into controlled flight
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onGlide(EntityToggleGlideEvent event) {
|
||||
|
||||
if (event.isGliding() && event.getEntity() instanceof Player player) {
|
||||
|
||||
LaunchPadsOG.clearLaunch(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// a mount catches the player; later falls belong to the ride
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onMount(EntityMountEvent event) {
|
||||
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
|
||||
LaunchPadsOG.clearLaunch(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// a teleport abandons the arc entirely
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onTeleport(PlayerTeleportEvent event) {
|
||||
|
||||
LaunchPadsOG.clearLaunch(event.getPlayer());
|
||||
|
||||
}
|
||||
|
||||
// portal travel is dispatched on its own handler list, so the teleport
|
||||
// handler above never sees it
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPortal(PlayerPortalEvent event) {
|
||||
|
||||
LaunchPadsOG.clearLaunch(event.getPlayer());
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onDeath(PlayerDeathEvent event) {
|
||||
|
||||
LaunchPadsOG.clearLaunch(event.getEntity());
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
|
||||
LaunchPadsOG.clearLaunch(event.getPlayer());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package net.trueog.launchpadsog.Listeners;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -16,22 +22,13 @@ import net.trueog.launchpadsog.LaunchPadsOG;
|
||||
|
||||
public class OnPlayerMove implements Listener {
|
||||
|
||||
// One boost per pass: a glider re-arms a pad only by leaving its radius.
|
||||
private final Map<UUID, Integer> glidePads = new HashMap<>();
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerMoveEvent(PlayerMoveEvent event) {
|
||||
|
||||
if (!event.hasChangedBlock()) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Loop through every launch pad and look for one that has the same coordinates
|
||||
// as
|
||||
// the player.
|
||||
final FileConfiguration padConfig = LaunchPadsOG.getConfiguration();
|
||||
final Player player = event.getPlayer();
|
||||
final String worldName = player.getWorld().getName();
|
||||
|
||||
final Location playerLocation = event.getTo();
|
||||
if (playerLocation == null) {
|
||||
|
||||
@@ -39,147 +36,254 @@ public class OnPlayerMove implements Listener {
|
||||
|
||||
}
|
||||
|
||||
for (int i = 1; i < padConfig.getInt("numberOfPads") + 1; i++) {
|
||||
final LaunchPadsOG.LaunchPhase phase = LaunchPadsOG.launchPhase(player);
|
||||
if (phase == LaunchPadsOG.LaunchPhase.AWAITING_LIFTOFF) {
|
||||
|
||||
final String padWorld = padConfig.getString(i + ".world");
|
||||
if (!worldName.equals(padWorld)) {
|
||||
// A slow client reports the pad position for a full round trip, so
|
||||
// the protection only arms once liftoff is actually seen.
|
||||
if (player.getFallDistance() > 0.0F || !hasLandedAt(playerLocation))
|
||||
LaunchPadsOG.markAirborne(player);
|
||||
|
||||
continue; // wrong world -> skip this pad
|
||||
} else if (phase == LaunchPadsOG.LaunchPhase.AIRBORNE) {
|
||||
|
||||
// Arc over: held up again, not ascending (fallDistance stays 0 all
|
||||
// ascent), no fall live. getTo(): this step's damage fires after us.
|
||||
if (!player.isFlying() && player.getFallDistance() <= 0.0F
|
||||
&& playerLocation.getY() <= event.getFrom().getY() && hasLandedAt(playerLocation))
|
||||
LaunchPadsOG.clearLaunch(player);
|
||||
|
||||
}
|
||||
|
||||
if (!event.hasChangedBlock()) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Riders are skipped: their move events carry the vehicle's position,
|
||||
// and setVelocity cannot move a mounted player anyway.
|
||||
final Integer padId = player.isInsideVehicle() ? null : padNear(player, playerLocation);
|
||||
|
||||
if (padId != null) {
|
||||
|
||||
// A glider skimming the radius matches on several packets in a
|
||||
// row; only the first one fires the boost.
|
||||
if (player.isGliding() && padId.equals(glidePads.put(player.getUniqueId(), padId))) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final boolean condition = padConfig.getInt(Integer.toString(i) + ".x") == playerLocation.getBlockX()
|
||||
&& padConfig.getInt(Integer.toString(i) + ".y") == playerLocation.getBlockY()
|
||||
&& padConfig.getInt(Integer.toString(i) + ".z") == playerLocation.getBlockZ();
|
||||
// check y location
|
||||
// check z location
|
||||
// check each cardinal direction individually so that it can run faster
|
||||
// check x location
|
||||
if (condition) {
|
||||
final FileConfiguration padConfig = LaunchPadsOG.getConfiguration();
|
||||
final int i = padId;
|
||||
|
||||
// calculate the player's y-speed using getFrom() and getTo()
|
||||
// y speed is the only one calculated so that a jump will affect
|
||||
// how far you get launched
|
||||
final Location from = event.getFrom();
|
||||
final Location to = playerLocation;
|
||||
// calculate the player's y-speed using getFrom() and getTo()
|
||||
// y speed is the only one calculated so that a jump will affect
|
||||
// how far you get launched
|
||||
final Location from = event.getFrom();
|
||||
final Location to = playerLocation;
|
||||
|
||||
// speed = distance/time
|
||||
// distance = abs(from-to)
|
||||
// time = 1 tick
|
||||
double ySpeed = to.getY() - from.getY();
|
||||
// speed = distance/time
|
||||
// distance = abs(from-to)
|
||||
// time = 1 tick
|
||||
double ySpeed = to.getY() - from.getY();
|
||||
|
||||
// if the ySpeed is below a threshold of 0.15,
|
||||
// then set the ySpeed to 0.15 so that there is some y movement
|
||||
if (ySpeed < 0.15) {
|
||||
// if the ySpeed is below a threshold of 0.15,
|
||||
// then set the ySpeed to 0.15 so that there is some y movement
|
||||
if (ySpeed < 0.15) {
|
||||
|
||||
ySpeed = 0.15;
|
||||
ySpeed = 0.15;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// add 0.25 to the ySpeed so that there is more movement,
|
||||
// without reducing the effect of a jump
|
||||
ySpeed += 0.25;
|
||||
// add 0.25 to the ySpeed so that there is more movement,
|
||||
// without reducing the effect of a jump
|
||||
ySpeed += 0.25;
|
||||
|
||||
// if the player is crouching, reduce the velocity to 1/3
|
||||
if (player.isSneaking()) {
|
||||
// if the player is crouching, reduce the velocity to 1/3
|
||||
if (player.isSneaking()) {
|
||||
|
||||
ySpeed /= 3;
|
||||
ySpeed /= 3;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply the velocity by how far the player is looking up or down.
|
||||
final double velocityMult;
|
||||
final double lookPitch = Location.normalizePitch(playerLocation.getPitch());
|
||||
velocityMult = Math.abs(lookPitch - 90) / 90;
|
||||
// Multiply the velocity by how far the player is looking up or down.
|
||||
final double velocityMult;
|
||||
final double lookPitch = Location.normalizePitch(playerLocation.getPitch());
|
||||
velocityMult = Math.abs(lookPitch - 90) / 90;
|
||||
|
||||
// Get the unit vector for the x,z directions.
|
||||
final Vector directionVector = playerLocation.getDirection();
|
||||
// Get the unit vector for the x,z directions.
|
||||
final Vector directionVector = playerLocation.getDirection();
|
||||
|
||||
// Get the xpower, ypower, and zpower.
|
||||
final double xPower = padConfig.getDouble(i + ".xpower");
|
||||
final double yPower = padConfig.getDouble(i + ".ypower");
|
||||
final double zPower = padConfig.getDouble(i + ".zpower");
|
||||
// Get the xpower, ypower, and zpower.
|
||||
final double xPower = padConfig.getDouble(i + ".xpower");
|
||||
final double yPower = padConfig.getDouble(i + ".ypower");
|
||||
final double zPower = padConfig.getDouble(i + ".zpower");
|
||||
|
||||
final double vx = xPower * directionVector.getX() * velocityMult;
|
||||
final double vy = yPower * ySpeed;
|
||||
final double vz = zPower * directionVector.getZ() * velocityMult;
|
||||
final double vx = xPower * directionVector.getX() * velocityMult;
|
||||
final double vy = yPower * ySpeed;
|
||||
final double vz = zPower * directionVector.getZ() * velocityMult;
|
||||
|
||||
player.setVelocity(new Vector(/* x */ vx, /* y */ vy, /* z */ vz));
|
||||
player.setVelocity(new Vector(/* x */ vx, /* y */ vy, /* z */ vz));
|
||||
|
||||
// Spawn particles at the launch pad.
|
||||
final Location padLocation = new Location(player.getWorld(), padConfig.getInt(i + ".x") + 0.5,
|
||||
padConfig.getInt(i + ".y"), padConfig.getInt(i + ".z") + 0.5);
|
||||
// Spawn particles at the launch pad.
|
||||
final Location padLocation = new Location(player.getWorld(), padConfig.getInt(i + ".x") + 0.5,
|
||||
padConfig.getInt(i + ".y"), padConfig.getInt(i + ".z") + 0.5);
|
||||
|
||||
// Hive java style launchpad woosh (audible to nearby players, fades with
|
||||
// distance).
|
||||
final float basePitch = (float) Math.max(1.62, Math.min(1.99, 1.80 + (velocityMult * 0.18)));
|
||||
final float treblePitch = Math.min(2.0f, basePitch + 0.22f);
|
||||
// Hive java style launchpad woosh (audible to nearby players, fades with
|
||||
// distance).
|
||||
final float basePitch = (float) Math.max(1.62, Math.min(1.99, 1.80 + (velocityMult * 0.18)));
|
||||
final float treblePitch = Math.min(2.0f, basePitch + 0.22f);
|
||||
|
||||
for (int t = 0; t < 6; t++) {
|
||||
for (int t = 0; t < 6; t++) {
|
||||
|
||||
final int delay = t;
|
||||
final int delay = t;
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(LaunchPadsOG.getPlugin(), () -> {
|
||||
|
||||
final float falloff = Math.max(0.0f, 1.0f - (delay * 0.16f));
|
||||
|
||||
final float jitter = ((delay & 1) == 0) ? 0.06f : -0.03f;
|
||||
final float pMain = Math.max(0.5f, Math.min(2.0f, basePitch + jitter));
|
||||
final float pTreble = Math.max(0.5f, Math.min(2.0f, treblePitch + jitter));
|
||||
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_WITHER_SHOOT, 0.34f * falloff, pMain);
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_GHAST_SHOOT, 0.20f * falloff, pTreble);
|
||||
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_BAT_TAKEOFF, 0.16f * falloff, 2.0f);
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_BLAZE_SHOOT, 0.10f * falloff, 2.0f);
|
||||
|
||||
}, delay);
|
||||
|
||||
}
|
||||
|
||||
final double padParticleCount = padConfig.getDouble("padLaunchParticleCount");
|
||||
final double padParticlePower = padConfig.getDouble("padLaunchParticlePower");
|
||||
player.spawnParticle(Particle.FIREWORKS_SPARK, padLocation, (int) padParticleCount, 0, 0, 0,
|
||||
0.2 * padParticlePower);
|
||||
|
||||
final double playerParticleMult = padConfig.getDouble("playerLaunchParticleCountMultipier");
|
||||
|
||||
// Spawn particles on the player as they fly through the air
|
||||
// This equation was gotten with a linear regression on different amounts of
|
||||
// yPower
|
||||
// to find what the vertex of the player's jump is
|
||||
// maxes out at 25 ticks
|
||||
// \/ --------- \/
|
||||
for (int j = 0; j < Math.min(yPower * 8 - 15, 25); j++) {
|
||||
|
||||
// Max out the number of particles at 50
|
||||
Bukkit.getScheduler().runTaskLater(LaunchPadsOG.getPlugin(),
|
||||
() -> player.getWorld().spawnParticle(Particle.SPELL, player.getLocation(),
|
||||
(int) Math.min(xPower * zPower * playerParticleMult, 50)),
|
||||
j);
|
||||
|
||||
}
|
||||
|
||||
// Add the player to the launchedPlayers list
|
||||
// 1 tick later so that the player is off the ground
|
||||
// and isn't removed immediately,
|
||||
Bukkit.getScheduler().runTaskLater(LaunchPadsOG.getPlugin(), () -> {
|
||||
|
||||
// Check if the player is in survival. Otherwise, don't add them
|
||||
if (player.getGameMode().equals(GameMode.SURVIVAL)) {
|
||||
final float falloff = Math.max(0.0f, 1.0f - (delay * 0.16f));
|
||||
|
||||
LaunchPadsOG.getLaunchedPlayers().add(player);
|
||||
final float jitter = ((delay & 1) == 0) ? 0.06f : -0.03f;
|
||||
final float pMain = Math.max(0.5f, Math.min(2.0f, basePitch + jitter));
|
||||
final float pTreble = Math.max(0.5f, Math.min(2.0f, treblePitch + jitter));
|
||||
|
||||
}
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_WITHER_SHOOT, 0.34f * falloff, pMain);
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_GHAST_SHOOT, 0.20f * falloff, pTreble);
|
||||
|
||||
}, 1);
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_BAT_TAKEOFF, 0.16f * falloff, 2.0f);
|
||||
player.getWorld().playSound(padLocation, Sound.ENTITY_BLAZE_SHOOT, 0.10f * falloff, 2.0f);
|
||||
|
||||
break;
|
||||
}, delay);
|
||||
|
||||
}
|
||||
|
||||
final double padParticleCount = padConfig.getDouble("padLaunchParticleCount");
|
||||
final double padParticlePower = padConfig.getDouble("padLaunchParticlePower");
|
||||
// Visible to everyone nearby, matching the in-air trail below.
|
||||
player.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, padLocation, (int) padParticleCount, 0, 0, 0,
|
||||
0.2 * padParticlePower);
|
||||
|
||||
final double playerParticleMult = padConfig.getDouble("playerLaunchParticleCountMultipier");
|
||||
|
||||
// Spawn particles on the player as they fly through the air
|
||||
// This equation was gotten with a linear regression on different amounts of
|
||||
// yPower
|
||||
// to find what the vertex of the player's jump is
|
||||
// maxes out at 25 ticks
|
||||
// \/ --------- \/
|
||||
for (int j = 0; j < Math.min(yPower * 8 - 15, 25); j++) {
|
||||
|
||||
// Max out the number of particles at 50
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(
|
||||
LaunchPadsOG.getPlugin(), () -> player.getWorld().spawnParticle(Particle.SPELL,
|
||||
player.getLocation(), (int) Math.min(xPower * zPower * playerParticleMult, 50)),
|
||||
j);
|
||||
|
||||
}
|
||||
|
||||
// Eligibility sampled at launch: adventure falls like survival,
|
||||
// and an already-gliding player is in controlled flight.
|
||||
final GameMode launchGamemode = player.getGameMode();
|
||||
final boolean protectable = (launchGamemode == GameMode.SURVIVAL || launchGamemode == GameMode.ADVENTURE)
|
||||
&& !player.isGliding();
|
||||
|
||||
if (protectable) {
|
||||
|
||||
if (phase == LaunchPadsOG.LaunchPhase.AIRBORNE && player.getFallDistance() > 0.0F) {
|
||||
|
||||
// Landing here out of a previous arc: that arc's landing
|
||||
// damage spends first, so the new arc arms a tick later.
|
||||
Bukkit.getScheduler().runTaskLater(LaunchPadsOG.getPlugin(), () -> {
|
||||
|
||||
if (player.isOnline() && !player.isDead()) {
|
||||
|
||||
LaunchPadsOG.markLaunched(player);
|
||||
|
||||
}
|
||||
|
||||
}, 1);
|
||||
|
||||
} else {
|
||||
|
||||
// Immediate: the pre-liftoff phase can neither be spent
|
||||
// nor dropped, so there is nothing to wait for.
|
||||
LaunchPadsOG.markLaunched(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// out of every pad's radius again: the glide debounce re-arms
|
||||
glidePads.remove(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Gliders skim rather than stand, so a pad within one block still fires;
|
||||
// everyone else must be inside the pad's own block, as always.
|
||||
private static Integer padNear(Player player, Location location) {
|
||||
|
||||
final String worldName = location.getWorld().getName();
|
||||
final int x = location.getBlockX();
|
||||
final int y = location.getBlockY();
|
||||
final int z = location.getBlockZ();
|
||||
|
||||
final Integer exact = LaunchPadsOG.padAt(worldName, x, y, z);
|
||||
if (exact != null || !player.isGliding()) {
|
||||
|
||||
return exact;
|
||||
|
||||
}
|
||||
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
|
||||
final Integer pad = LaunchPadsOG.padAt(worldName, x + dx, y + dy, z + dz);
|
||||
if (pad != null) {
|
||||
|
||||
return pad;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// Whether something at or right under this spot ends a fall: standing
|
||||
// room on a block, water, or a climbable the player has grabbed.
|
||||
private static boolean hasLandedAt(Location location) {
|
||||
|
||||
final Block feet = location.getBlock();
|
||||
if (feet.isLiquid() || Tag.CLIMBABLE.isTagged(feet.getType())) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// 0.4 covers blocks, slabs, and carpets; 1.6 covers fences and walls,
|
||||
// whose standing surface sits above the block that holds them.
|
||||
final Block shallow = location.clone().subtract(0.0, 0.4, 0.0).getBlock();
|
||||
if (shallow.isLiquid() || !shallow.isPassable()) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return !location.clone().subtract(0.0, 1.6, 0.0).getBlock().isPassable();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user