Initial commit v2
This commit is contained in:
parent
246fe9783c
commit
380a66e286
17 changed files with 1651 additions and 231 deletions
30
src/main/java/one/eim/gravitycontrol/Compatibility.java
Normal file
30
src/main/java/one/eim/gravitycontrol/Compatibility.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class Compatibility {
|
||||
|
||||
public static Boolean IS_FOLIA = null;
|
||||
|
||||
private static boolean methodExist(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
try {
|
||||
clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||
return true;
|
||||
} catch (Throwable ignored) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Boolean isFolia() {
|
||||
if (IS_FOLIA == null) IS_FOLIA = methodExist(Bukkit.class, "getGlobalRegionScheduler");
|
||||
return IS_FOLIA;
|
||||
}
|
||||
|
||||
public static void runTaskAsync(Plugin plugin, Runnable runnable) {
|
||||
if (isFolia()) {
|
||||
Bukkit.getAsyncScheduler().runNow(plugin, (task) -> runnable.run());
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable);
|
||||
}
|
||||
}
|
||||
173
src/main/java/one/eim/gravitycontrol/Configuration.java
Normal file
173
src/main/java/one/eim/gravitycontrol/Configuration.java
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import com.destroystokyo.paper.MaterialSetTag;
|
||||
import com.destroystokyo.paper.MaterialTags;
|
||||
import net.kyori.adventure.key.InvalidKeyException;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.intellij.lang.annotations.Subst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class Configuration {
|
||||
private final GravityControl plugin;
|
||||
|
||||
final double horizontalCoefficient;
|
||||
final double verticalCoefficient;
|
||||
final Set<Key> worlds;
|
||||
final Set<Material> blocks;
|
||||
final List<String> regions;
|
||||
final boolean updateChecker;
|
||||
|
||||
Configuration(GravityControl plugin, FileConfiguration config) {
|
||||
this.plugin = plugin;
|
||||
|
||||
if (!config.isSet(Fields._version)) {
|
||||
migrate(config);
|
||||
}
|
||||
|
||||
// These seem to make it about match vanilla, but where did these values come from??
|
||||
// magic number much
|
||||
this.horizontalCoefficient = config.getDouble("horizontal-coefficient", 1.46D);
|
||||
this.verticalCoefficient = config.getDouble("vertical-coefficient", -2.4D);
|
||||
|
||||
this.worlds = worlds(config);
|
||||
this.blocks = blocks(config);
|
||||
this.regions = config.getStringList(Fields.regions);
|
||||
this.updateChecker = config.getBoolean(Fields.updateChecker);
|
||||
}
|
||||
|
||||
private Set<Material> blocks(final ConfigurationSection config) {
|
||||
final Set<Material> materials = new HashSet<>();
|
||||
|
||||
for (final String keyString : config.getStringList(Fields.blocks)) {
|
||||
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
||||
if (key == null) {
|
||||
this.plugin.getSLF4JLogger().warn("Invalid key {} supplied in blocks configuration", keyString);
|
||||
continue;
|
||||
}
|
||||
|
||||
final Material material = Registry.MATERIAL.get(key);
|
||||
if (material == null) {
|
||||
this.plugin.getSLF4JLogger().warn("Unknown block {} supplied in blocks configuration", key.asString());
|
||||
}
|
||||
|
||||
materials.add(material);
|
||||
}
|
||||
|
||||
return materials;
|
||||
}
|
||||
|
||||
private Set<Key> worlds(final ConfigurationSection config) {
|
||||
final Set<Key> keys = new HashSet<>();
|
||||
|
||||
for (final @Subst("minecraft:overworld") String keyString : config.getStringList(Fields.worlds)) {
|
||||
if (keyString.equals("*")) {
|
||||
this.plugin.getServer().getWorlds().forEach(world -> keys.add(world.key()));
|
||||
return keys;
|
||||
}
|
||||
|
||||
try {
|
||||
keys.add(Key.key(keyString));
|
||||
} catch (InvalidKeyException e) {
|
||||
this.plugin.getSLF4JLogger().warn("Invalid key {} supplied in worlds configuration", keyString);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
private void migrate(final FileConfiguration config) {
|
||||
this.plugin.getSLF4JLogger().info("Performing config migration");
|
||||
|
||||
final List<String> oldWorlds = config.getStringList("worlds");
|
||||
final List<String> newWorlds = new ArrayList<>();
|
||||
|
||||
for (final String worldName : oldWorlds) {
|
||||
if (worldName.equals("*")) {
|
||||
newWorlds.add(worldName);
|
||||
} else {
|
||||
final World world = this.plugin.getServer().getWorld(worldName);
|
||||
if (world == null) {
|
||||
this.plugin.getSLF4JLogger().warn("Unable to find world {}, discarding from configuration", worldName);
|
||||
continue;
|
||||
}
|
||||
|
||||
newWorlds.add(world.key().asString());
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> newMaterials = new ArrayList<>();
|
||||
|
||||
if (config.getBoolean("enabled-blocks.sand")) {
|
||||
newMaterials.add(Material.SAND.key().asString());
|
||||
}
|
||||
if (config.getBoolean("enabled-blocks.red-sand")) {
|
||||
newMaterials.add(Material.RED_SAND.key().asString());
|
||||
}
|
||||
if (config.getBoolean("enabled-blocks.anvil")) {
|
||||
MaterialSetTag.ANVIL.getValues().forEach(material -> newMaterials.add(material.key().asString()));
|
||||
}
|
||||
if (config.getBoolean("enabled-blocks.dragon-egg")) {
|
||||
newMaterials.add(Material.DRAGON_EGG.key().asString());
|
||||
}
|
||||
if (config.getBoolean("enabled-blocks.gravel")) {
|
||||
newMaterials.add(Material.GRAVEL.key().asString());
|
||||
}
|
||||
if (config.getBoolean("enabled-blocks.concrete-powder")) {
|
||||
MaterialTags.CONCRETE_POWDER.getValues().forEach(material -> newMaterials.add(material.key().asString()));
|
||||
}
|
||||
|
||||
config.set("enabled-blocks", null);
|
||||
config.set(Fields._version, 2);
|
||||
config.set(Fields.worlds, newWorlds);
|
||||
config.set(Fields.blocks, newMaterials);
|
||||
config.set(Fields.regions, List.of("*"));
|
||||
config.set(Fields.updateChecker, true);
|
||||
|
||||
config.setComments(Fields._version, List.of(
|
||||
"Do not touch. Used for configuration migration."
|
||||
));
|
||||
config.setComments(Fields.worlds, List.of(
|
||||
"Worlds where gravity duplication is enabled.",
|
||||
"`*` means all worlds.",
|
||||
"WARNING: World key is used, NOT world name.",
|
||||
"For example: `minecraft:overworld` rather than `world`."
|
||||
));
|
||||
config.setComments(Fields.blocks, List.of(
|
||||
"Blocks for which duping is allowed.",
|
||||
"NOTE: Uses Mojang item names, not bukkit Material names."
|
||||
));
|
||||
config.setComments(Fields.regions, List.of(
|
||||
"List of WorldGuard regions where duping is allowed",
|
||||
"`*` means all regions. Checked based on end portal coordinates."
|
||||
));
|
||||
config.setComments(Fields.updateChecker, List.of(
|
||||
"If GravityControl should check for updates on startup"
|
||||
));
|
||||
|
||||
try {
|
||||
config.save(this.plugin.getDataFolder().toPath().resolve("config.yml").toFile());
|
||||
} catch (final IOException e) {
|
||||
this.plugin.getSLF4JLogger().warn("Failed to migrate configuration", e);
|
||||
this.plugin.getPluginLoader().disablePlugin(this.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Fields {
|
||||
static final String _version = "_version";
|
||||
static final String worlds = "worlds";
|
||||
static final String blocks = "blocks";
|
||||
static final String regions = "regions";
|
||||
static final String updateChecker = "update-checker";
|
||||
}
|
||||
}
|
||||
31
src/main/java/one/eim/gravitycontrol/GravityControl.java
Normal file
31
src/main/java/one/eim/gravitycontrol/GravityControl.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class GravityControl extends JavaPlugin {
|
||||
public Configuration config;
|
||||
|
||||
WorldGuardHook worldGuardHook = null;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
if (this.getServer().getPluginManager().getPlugin("WorldGuard") != null) {
|
||||
this.worldGuardHook = new WorldGuardHook(this);
|
||||
if (!this.worldGuardHook.register()) this.worldGuardHook = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.saveDefaultConfig();
|
||||
|
||||
this.config = new Configuration(this, this.getConfig());
|
||||
|
||||
this.getServer().getPluginManager().registerEvents(new GravityListener(this), this);
|
||||
this.getCommand("gcr").setExecutor(new ReloadCommand(this));
|
||||
|
||||
if (this.config.updateChecker) new UpdateChecker(this).check();
|
||||
new Metrics(this, 13384);
|
||||
}
|
||||
}
|
||||
77
src/main/java/one/eim/gravitycontrol/GravityListener.java
Normal file
77
src/main/java/one/eim/gravitycontrol/GravityListener.java
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag.State;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.FallingBlock;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityChangeBlockEvent;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class GravityListener implements Listener {
|
||||
private static final Set<Vector> DIRECTIONS = Set.of(
|
||||
new Vector(0, 0, -1),
|
||||
new Vector(1, 0, 0),
|
||||
new Vector(0, 0, 1),
|
||||
new Vector(-1, 0, 0)
|
||||
);
|
||||
|
||||
private final GravityControl plugin;
|
||||
|
||||
public GravityListener(final GravityControl plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onEntityChangeBlock(final EntityChangeBlockEvent event) {
|
||||
if (event.getTo() == Material.AIR
|
||||
|| !(event.getEntity() instanceof final FallingBlock falling)
|
||||
|| !this.plugin.config.worlds.contains(event.getBlock().getWorld().key())
|
||||
|| !this.plugin.config.blocks.contains(falling.getBlockData().getMaterial())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BoundingBox boundingBox = falling.getBoundingBox().expand(-0.01D);
|
||||
|
||||
for (Vector direction : DIRECTIONS) {
|
||||
final Location loc = event.getBlock().getLocation().add(direction);
|
||||
if (!loc.getWorld().isChunkLoaded(loc.getBlockX() >> 4, loc.getBlockZ() >> 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Block block = loc.getBlock();
|
||||
if (block.getType() != Material.END_PORTAL || !block.getBoundingBox().overlaps(boundingBox)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check end portal position. This seems to make more sense than checking block or entity location
|
||||
// as this can vary by design
|
||||
if (this.plugin.worldGuardHook != null && this.plugin.worldGuardHook.dupingAllowed(loc) != State.ALLOW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Vector velocity = falling.getVelocity();
|
||||
|
||||
if (velocity.getX() == 0 && velocity.getZ() == 0) {
|
||||
loc.getWorld().spawnFallingBlock(falling.getLocation().add(direction.getX() * 0.25D, 0.05D, direction.getZ() * 0.25D), falling.getBlockData());
|
||||
} else {
|
||||
final Vector endVelocity = velocity
|
||||
.setX(velocity.getX() * this.plugin.config.horizontalCoefficient)
|
||||
.setY(velocity.getY() * this.plugin.config.verticalCoefficient)
|
||||
.setZ(velocity.getZ() * this.plugin.config.horizontalCoefficient);
|
||||
|
||||
falling.getWorld().spawnFallingBlock(
|
||||
falling.getLocation().add(direction.getX() * 0.25D, direction.getY() * 0.25D, direction.getZ() * 0.25D),
|
||||
falling.getBlockData()
|
||||
).setVelocity(endVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/main/java/one/eim/gravitycontrol/ReloadCommand.java
Normal file
28
src/main/java/one/eim/gravitycontrol/ReloadCommand.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class ReloadCommand implements CommandExecutor {
|
||||
private static final Component prefix = Component.text("[", NamedTextColor.GRAY)
|
||||
.append(Component.text("GravityControl", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.text("]", NamedTextColor.GRAY))
|
||||
.append(Component.space());
|
||||
private final GravityControl plugin;
|
||||
|
||||
public ReloadCommand(GravityControl plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
sender.sendMessage(prefix.append(Component.text("Attempting to reload configuration...", NamedTextColor.WHITE)));
|
||||
this.plugin.reloadConfig();
|
||||
this.plugin.config = new Configuration(this.plugin, this.plugin.getConfig());
|
||||
sender.sendMessage(prefix.append(Component.text("Successfully reloaded configuration!", NamedTextColor.GREEN)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
68
src/main/java/one/eim/gravitycontrol/UpdateChecker.java
Normal file
68
src/main/java/one/eim/gravitycontrol/UpdateChecker.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.logging.Level;
|
||||
|
||||
class UpdateChecker {
|
||||
private final HttpClient httpClient;
|
||||
private final HttpRequest request;
|
||||
private final Gson gson;
|
||||
private final GravityControl plugin;
|
||||
|
||||
UpdateChecker(GravityControl plugin) {
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||
.connectTimeout(Duration.ofSeconds(2))
|
||||
.build();
|
||||
this.request = HttpRequest.newBuilder(URI.create("https://gravitycontrol.eim.one/version"))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(plugin.getDescription().getVersion()))
|
||||
.build();
|
||||
this.gson = new Gson();
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
void check() {
|
||||
Compatibility.runTaskAsync(this.plugin, () -> {
|
||||
final Version version;
|
||||
try {
|
||||
version = this.gson.fromJson(
|
||||
this.httpClient.send(this.request, HttpResponse.BodyHandlers.ofString()).body(),
|
||||
Version.class
|
||||
);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
this.plugin.getSLF4JLogger().info("Failed to check for updates", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (version.outdated()) {
|
||||
this.plugin.getLogger().log(Level.INFO, "GravityControl is out of date! Current version: " + this.plugin.getDescription().getVersion() + " Latest version: " + version.latest() + ".");
|
||||
this.plugin.getLogger().log(Level.INFO, "Please download an updated version of GravityControl from https://dev.bukkit.org/projects/gravity-control");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final class Version {
|
||||
private final boolean outdated;
|
||||
private final String latest;
|
||||
|
||||
public Version(final boolean outdated, final String latest) {
|
||||
this.outdated = outdated;
|
||||
this.latest = latest;
|
||||
}
|
||||
|
||||
public boolean outdated() {
|
||||
return outdated;
|
||||
}
|
||||
|
||||
public String latest() {
|
||||
return latest;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/main/java/one/eim/gravitycontrol/WorldGuardHook.java
Normal file
40
src/main/java/one/eim/gravitycontrol/WorldGuardHook.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package one.eim.gravitycontrol;
|
||||
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldguard.WorldGuard;
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag.State;
|
||||
import com.sk89q.worldguard.protection.flags.registry.FlagConflictException;
|
||||
import org.bukkit.Location;
|
||||
|
||||
class WorldGuardHook {
|
||||
private static final String FLAG_NAME = "gravity-dupe";
|
||||
private static final StateFlag FLAG = new StateFlag(FLAG_NAME, true);
|
||||
private final GravityControl plugin;
|
||||
|
||||
WorldGuardHook(final GravityControl plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
WorldGuard wg() {
|
||||
return WorldGuard.getInstance();
|
||||
}
|
||||
|
||||
boolean register() {
|
||||
try {
|
||||
this.wg().getFlagRegistry().register(FLAG);
|
||||
} catch (final FlagConflictException e) {
|
||||
plugin.getSLF4JLogger().warn("Failed to enable WorldGuard hook. Flag with name {} already exists!", FLAG_NAME, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
plugin.getSLF4JLogger().info("Enabled WorldGuard hook");
|
||||
return true;
|
||||
}
|
||||
|
||||
State dupingAllowed(final Location location) {
|
||||
return this.wg().getPlatform().getRegionContainer().createQuery()
|
||||
.getApplicableRegions(BukkitAdapter.adapt(location))
|
||||
.queryState(null, FLAG);
|
||||
}
|
||||
}
|
||||
41
src/main/resources/config.yml
Normal file
41
src/main/resources/config.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Do not touch. Used for configuration migration.
|
||||
_version: 2
|
||||
|
||||
# Worlds where gravity duplication is enabled.
|
||||
# `*` means all worlds.
|
||||
# WARNING: World key is used, NOT world name.
|
||||
# For example: `minecraft:overworld` rather than `world`.
|
||||
worlds:
|
||||
- "*"
|
||||
|
||||
# Blocks for which duping is allowed.
|
||||
blocks:
|
||||
- "sand"
|
||||
- "red_sand"
|
||||
- "gravel"
|
||||
- "anvil"
|
||||
- "dragon_egg"
|
||||
- "white_concrete_powder"
|
||||
- "orange_concrete_powder"
|
||||
- "magenta_concrete_powder"
|
||||
- "light_blue_concrete_powder"
|
||||
- "yellow_concrete_powder"
|
||||
- "lime_concrete_powder"
|
||||
- "pink_concrete_powder"
|
||||
- "gray_concrete_powder"
|
||||
- "light_gray_concrete_powder"
|
||||
- "cyan_concrete_powder"
|
||||
- "purple_concrete_powder"
|
||||
- "blue_concrete_powder"
|
||||
- "brown_concrete_powder"
|
||||
- "green_concrete_powder"
|
||||
- "red_concrete_powder"
|
||||
- "black_concrete_powder"
|
||||
|
||||
# List of WorldGuard regions where duping is allowed
|
||||
# `*` means all regions. Checked based on end portal coordinates.
|
||||
regions:
|
||||
- "*"
|
||||
|
||||
# If GravityControl should check for updates on startup
|
||||
update-checker: true
|
||||
19
src/main/resources/plugin.yml
Normal file
19
src/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: GravityControl
|
||||
version: 1.0
|
||||
author: laymanuel, sulu
|
||||
website: https://github.com/sulu5890/GravityControl
|
||||
main: one.eim.gravitycontrol.GravityControl
|
||||
api-version: 26.1.2
|
||||
folia-supported: true
|
||||
softdepend: [WorldGuard]
|
||||
|
||||
commands:
|
||||
gcr:
|
||||
description: Reload GravityControl configuration
|
||||
permission: gravitycontrol.reload
|
||||
usage: /gcr
|
||||
|
||||
permissions:
|
||||
gravitycontrol.reload:
|
||||
description: Allows use of the /gcr command
|
||||
default: op
|
||||
Loading…
Add table
Add a link
Reference in a new issue