General cleanup

- Moved XSeries to Maven
- Updated UpdateManager
- New ConfigUpdater
This commit is contained in:
stijnb1234 2021-04-07 10:41:01 +02:00
parent 346f9bcb00
commit 4e9ac799a2
23 changed files with 616 additions and 3886 deletions

38
pom.xml
View file

@ -53,7 +53,30 @@
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>nl.SBDeveloper.V10Lift.utils.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>com.cryptomorin.xseries</pattern>
<shadedPattern>nl.SBDeveloper.V10Lift.utils.xseries</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>com/cryptomorin/xseries/XPotion*</exclude>
<exclude>com/cryptomorin/xseries/XItemStack*</exclude>
<exclude>com/cryptomorin/xseries/XEntity*</exclude>
<exclude>com/cryptomorin/xseries/XEnchantment*</exclude>
<exclude>com/cryptomorin/xseries/XBlock*</exclude>
<exclude>com/cryptomorin/xseries/XBiome*</exclude>
<exclude>com/cryptomorin/xseries/SkullUtils*</exclude>
<exclude>com/cryptomorin/xseries/ReflectionUtils*</exclude>
<exclude>com/cryptomorin/xseries/NMSExtras*</exclude>
<exclude>com/cryptomorin/xseries/NoteBlockMusic*</exclude>
<exclude>com/cryptomorin/xseries/particles/*</exclude>
<exclude>com/cryptomorin/xseries/messages/*</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
@ -161,6 +184,14 @@
<scope>provided</scope>
</dependency>
<!-- SnakeYAML - Included in Spigot and used for the ConfigUpdater -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
<scope>provided</scope>
</dependency>
<!-- Lombok, used for the objects -->
<dependency>
<groupId>org.projectlombok</groupId>
@ -185,6 +216,13 @@
<scope>provided</scope>
</dependency>
<!-- XSeries, used for multi version support -->
<dependency>
<groupId>com.github.cryptomorin</groupId>
<artifactId>XSeries</artifactId>
<version>7.9.1.1</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>

View file

@ -10,15 +10,16 @@ import nl.SBDeveloper.V10Lift.listeners.SignChangeListener;
import nl.SBDeveloper.V10Lift.managers.DBManager;
import nl.SBDeveloper.V10Lift.managers.DataManager;
import nl.SBDeveloper.V10Lift.managers.VaultManager;
import nl.SBDeveloper.V10Lift.sbutils.ConfigUpdater;
import nl.SBDeveloper.V10Lift.sbutils.UpdateManager;
import nl.SBDeveloper.V10Lift.sbutils.YamlFile;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Objects;
import java.util.Collections;
public class V10LiftPlugin extends JavaPlugin {
@ -37,6 +38,14 @@ public class V10LiftPlugin extends JavaPlugin {
config = new YamlFile("config");
config.loadDefaults();
//And update config
try {
ConfigUpdater.update(this, "config.yml", config.getJavaFile(), Collections.emptyList());
} catch (IOException e) {
Bukkit.getLogger().warning("[V10Lift] Couldn't update the config.yml. Please check the stacktrace below.");
e.printStackTrace();
}
//Load the messages
messages = new YamlFile("messages");
messages.loadDefaults();
@ -46,6 +55,7 @@ public class V10LiftPlugin extends JavaPlugin {
try {
dbManager.load();
} catch (SQLException e) {
Bukkit.getLogger().warning("[V10Lift] Couldn't connect to the SQLite database. Please check the stacktrace below.");
e.printStackTrace();
}
@ -53,10 +63,9 @@ public class V10LiftPlugin extends JavaPlugin {
api = new V10LiftAPI();
//Load vault if found
if (Bukkit.getServer().getPluginManager().getPlugin("Vault") != null) {
if (VaultManager.setupPermissions()) {
Bukkit.getLogger().info("[V10Lift] Loading Vault hook for group whitelist support.");
vault = true;
VaultManager.setupPermissions();
}
//Load the command
@ -75,27 +84,40 @@ public class V10LiftPlugin extends JavaPlugin {
metrics.addCustomChart(new Metrics.SingleLineChart("lifts", () -> DataManager.getLifts().size()));
//Load the update checker
if (!getSConfig().getFile().contains("CheckUpdates") || getSConfig().getFile().getBoolean("CheckUpdates")) {
UpdateManager manager = new UpdateManager(this, 72317, UpdateManager.CheckType.SPIGOT);
if (getSConfig().getFile().getBoolean("UpdateChecker.Enabled")) {
UpdateManager updateManager = new UpdateManager(this, 72317);
manager.handleResponse((versionResponse, version) -> {
if (versionResponse == UpdateManager.VersionResponse.FOUND_NEW) {
Bukkit.getLogger().warning("[V10Lift] There is a new version available! Current: " + this.getDescription().getVersion() + " New: " + version);
Bukkit.getLogger().info("[V10Lift] Trying to download...");
updateManager.handleResponse((versionResponse, version) -> {
switch (versionResponse) {
case FOUND_NEW:
Bukkit.getLogger().warning("[V10Lift] There is a new version available! Current: " + this.getDescription().getVersion() + " New: " + version.get());
if (getSConfig().getFile().getBoolean("UpdateChecker.DownloadOnUpdate")) {
Bukkit.getLogger().info("[V10Lift] Trying to download the update. This could take some time...");
manager.handleDownloadResponse((downloadResponse, path) -> {
if (downloadResponse == UpdateManager.DownloadResponse.DONE) {
Bukkit.getLogger().info("[V10Lift] Update done! After a restart, it should be loaded.");
} else if (downloadResponse == UpdateManager.DownloadResponse.UNAVAILABLE) {
Bukkit.getLogger().warning("[V10Lift] Couldn't download the update, because it's not a Spigot resource.");
} else if (downloadResponse == UpdateManager.DownloadResponse.ERROR) {
Bukkit.getLogger().severe("[V10Lift] Unable to download the newest file.");
updateManager.handleDownloadResponse((downloadResponse, fileName) -> {
switch (downloadResponse) {
case DONE:
Bukkit.getLogger().info("[V10Lift] Update downloaded! If you restart your server, it will be loaded. Filename: " + fileName);
break;
case ERROR:
Bukkit.getLogger().severe("[V10Lift] Something went wrong when trying downloading the latest version.");
break;
case UNAVAILABLE:
Bukkit.getLogger().warning("[V10Lift] Unable to download the latest version.");
break;
}
}).runUpdate();
}
}).runUpdate();
} else if (versionResponse == UpdateManager.VersionResponse.LATEST) {
Bukkit.getLogger().info("[V10Lift] You are running the latest version [" + this.getDescription().getVersion() + "]!");
} else if (versionResponse == UpdateManager.VersionResponse.UNAVAILABLE) {
Bukkit.getLogger().severe("[V10Lift] Unable to perform an update check.");
break;
case LATEST:
Bukkit.getLogger().info("[V10Lift] You are running the latest version [" + this.getDescription().getVersion() + "]!");
break;
case THIS_NEWER:
Bukkit.getLogger().info("[V10Lift] You are running a newer version [" + this.getDescription().getVersion() + "]! This is probably fine.");
break;
case UNAVAILABLE:
Bukkit.getLogger().severe("[V10Lift] Unable to perform an update check.");
break;
}
}).check();
}
@ -105,7 +127,7 @@ public class V10LiftPlugin extends JavaPlugin {
@Override
public void onDisable() {
V10LiftPlugin.getDBManager().removeFromData();
dbManager.removeFromData(); //TODO Find a better way, override?
dbManager.save();
dbManager.closeConnection();

View file

@ -1,5 +1,6 @@
package nl.SBDeveloper.V10Lift.api;
import com.cryptomorin.xseries.XMaterial;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import nl.SBDeveloper.V10Lift.api.objects.*;
import nl.SBDeveloper.V10Lift.api.runnables.DoorCloser;
@ -11,7 +12,6 @@ import nl.SBDeveloper.V10Lift.sbutils.LocationSerializer;
import nl.SBDeveloper.V10Lift.utils.ConfigUtil;
import nl.SBDeveloper.V10Lift.utils.DirectionUtil;
import nl.SBDeveloper.V10Lift.utils.DoorUtil;
import nl.SBDeveloper.V10Lift.utils.XMaterial;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@ -405,13 +405,7 @@ public class V10LiftAPI {
lift.setDoorOpen(f);
if (lift.isRealistic()) {
lift.setDoorCloser(new DoorCloser(liftName));
long doorCloseTime = V10LiftPlugin.getSConfig().getFile().getLong("DoorCloseTime");
int pid = Bukkit.getScheduler().scheduleSyncRepeatingTask(V10LiftPlugin.getInstance(), lift.getDoorCloser(), doorCloseTime, doorCloseTime);
lift.getDoorCloser().setPid(pid);
}
if (lift.isRealistic()) lift.setDoorCloser(new DoorCloser(liftName));
return true;
}
@ -439,13 +433,7 @@ public class V10LiftAPI {
lift.setDoorOpen(f);
if (lift.isRealistic()) {
lift.setDoorCloser(new DoorCloser(liftName));
long doorCloseTime = V10LiftPlugin.getSConfig().getFile().getLong("DoorCloseTime");
int pid = Bukkit.getScheduler().scheduleSyncRepeatingTask(V10LiftPlugin.getInstance(), lift.getDoorCloser(), doorCloseTime, doorCloseTime);
lift.getDoorCloser().setPid(pid);
}
if (lift.isRealistic()) lift.setDoorCloser(new DoorCloser(liftName));
return true;
}
@ -502,10 +490,12 @@ public class V10LiftAPI {
}
state.update(true);
}
for (LiftBlock lb : lift.getDoorOpen().getRealDoorBlocks()) {
Block block = Objects.requireNonNull(Bukkit.getWorld(lb.getWorld()), "World is null at closeDoor").getBlockAt(lb.getX(), lb.getY(), lb.getZ());
DoorUtil.closeDoor(block);
}
lift.setDoorOpen(null);
if (lift.getDoorCloser() != null) lift.getDoorCloser().stop();
}

View file

@ -3,13 +3,14 @@ package nl.SBDeveloper.V10Lift.api.objects;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.UUID;
/** A floor object, for a floor in the lift. */
@Getter @Setter @NoArgsConstructor
@Getter @Setter @NoArgsConstructor @ToString
public class Floor {
private String world;
private int y;
@ -49,16 +50,4 @@ public class Floor {
result = prime * result + y;
return result;
}
@Override
public String toString() {
return "Floor{" +
"world='" + world + '\'' +
", y=" + y +
", doorBlocks=" + doorBlocks +
", realDoorBlocks=" + realDoorBlocks +
", userWhitelist=" + userWhitelist +
", groupWhitelist=" + groupWhitelist +
'}';
}
}

View file

@ -3,12 +3,13 @@ package nl.SBDeveloper.V10Lift.api.objects;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import nl.SBDeveloper.V10Lift.api.runnables.DoorCloser;
import java.util.*;
/** A lift object, to create a lift. */
@NoArgsConstructor
@NoArgsConstructor @ToString
public class Lift {
@Getter @Setter private String worldName;
@Getter @Setter private int y;
@ -59,30 +60,4 @@ public class Lift {
this.speed = speed;
this.realistic = realistic;
}
@Override
public String toString() {
return "Lift{" +
"worldName='" + worldName + '\'' +
", y=" + y +
", owners=" + owners +
", blocks=" + blocks +
", floors=" + floors +
", signs=" + signs +
", inputs=" + inputs +
", offlineInputs=" + offlineInputs +
", queue=" + queue +
", ropes=" + ropes +
", toMove=" + toMove +
", speed=" + speed +
", realistic=" + realistic +
", offline=" + offline +
", sound=" + sound +
", defective=" + defective +
", signText='" + signText + '\'' +
", counter=" + counter +
", doorOpen=" + doorOpen +
", doorCloser=" + doorCloser +
'}';
}
}

View file

@ -1,16 +1,19 @@
package nl.SBDeveloper.V10Lift.api.runnables;
import nl.SBDeveloper.V10Lift.managers.DataManager;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import nl.SBDeveloper.V10Lift.managers.DataManager;
import org.bukkit.Bukkit;
/** The doorcloser runnable, used for checking if the door can be closed. */
/** The DoorCloser runnable, used for checking if the door can be closed. */
public class DoorCloser implements Runnable {
private final String liftName;
private int pid;
private final int taskID;
public DoorCloser(String liftName) {
this.liftName = liftName;
final long doorCloseTime = V10LiftPlugin.getSConfig().getFile().getLong("DoorCloseTime");
this.taskID = Bukkit.getScheduler().runTaskTimer(V10LiftPlugin.getInstance(), this, doorCloseTime, doorCloseTime).getTaskId();
}
@Override
@ -18,12 +21,8 @@ public class DoorCloser implements Runnable {
if (V10LiftPlugin.getAPI().closeDoor(liftName)) stop();
}
public void setPid(int pid) {
this.pid = pid;
}
public void stop() {
Bukkit.getScheduler().cancelTask(pid);
Bukkit.getScheduler().cancelTask(taskID);
if (DataManager.containsLift(liftName)) DataManager.getLift(liftName).setDoorCloser(null);
}
}

View file

@ -1,13 +1,13 @@
package nl.SBDeveloper.V10Lift.api.runnables;
import com.cryptomorin.xseries.XMaterial;
import com.cryptomorin.xseries.XSound;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import nl.SBDeveloper.V10Lift.api.objects.*;
import nl.SBDeveloper.V10Lift.managers.DataManager;
import nl.SBDeveloper.V10Lift.sbutils.LocationSerializer;
import nl.SBDeveloper.V10Lift.utils.ConfigUtil;
import nl.SBDeveloper.V10Lift.utils.DirectionUtil;
import nl.SBDeveloper.V10Lift.utils.XMaterial;
import nl.SBDeveloper.V10Lift.utils.XSound;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;

View file

@ -1,5 +1,6 @@
package nl.SBDeveloper.V10Lift.commands;
import com.cryptomorin.xseries.XMaterial;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import nl.SBDeveloper.V10Lift.api.objects.Floor;
import nl.SBDeveloper.V10Lift.api.objects.Lift;
@ -9,7 +10,6 @@ import nl.SBDeveloper.V10Lift.managers.DataManager;
import nl.SBDeveloper.V10Lift.managers.VaultManager;
import nl.SBDeveloper.V10Lift.sbutils.LocationSerializer;
import nl.SBDeveloper.V10Lift.utils.ConfigUtil;
import nl.SBDeveloper.V10Lift.utils.XMaterial;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;

View file

@ -1,5 +1,7 @@
package nl.SBDeveloper.V10Lift.listeners;
import com.cryptomorin.xseries.XMaterial;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import nl.SBDeveloper.V10Lift.api.objects.Floor;
import nl.SBDeveloper.V10Lift.api.objects.Lift;
import nl.SBDeveloper.V10Lift.api.objects.LiftBlock;
@ -7,8 +9,6 @@ import nl.SBDeveloper.V10Lift.managers.DataManager;
import nl.SBDeveloper.V10Lift.managers.VaultManager;
import nl.SBDeveloper.V10Lift.utils.ConfigUtil;
import nl.SBDeveloper.V10Lift.utils.DoorUtil;
import nl.SBDeveloper.V10Lift.utils.XMaterial;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
@ -29,8 +29,7 @@ import org.bukkit.inventory.ItemStack;
import java.util.*;
public class PlayerInteractListener implements Listener {
private ArrayList<UUID> rightClicked = new ArrayList<>();
private final ArrayList<UUID> rightClicked = new ArrayList<>();
//BUTTON CLICK
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)

View file

@ -1,14 +1,14 @@
package nl.SBDeveloper.V10Lift.managers;
import nl.SBDeveloper.V10Lift.utils.XMaterial;
import com.cryptomorin.xseries.XMaterial;
import org.bukkit.Material;
import java.util.HashSet;
public class AntiCopyBlockManager {
private HashSet<XMaterial> antiCopy = new HashSet<>();
private static final HashSet<XMaterial> antiCopy = new HashSet<>();
public AntiCopyBlockManager() {
static {
//TODO Add more anti copy materials
//TODO Add to config
antiCopy.add(XMaterial.REDSTONE_TORCH);
@ -79,7 +79,7 @@ public class AntiCopyBlockManager {
antiCopy.add(XMaterial.JUKEBOX);
}
public boolean isAntiCopy(Material mat) {
public static boolean isAntiCopy(Material mat) {
XMaterial xmat = XMaterial.matchXMaterial(mat);
return antiCopy.contains(xmat);
}

View file

@ -12,7 +12,6 @@ import java.sql.SQLException;
import java.util.Map;
public class DBManager {
private static SQLiteDB data;
private static Connection con;
@ -121,5 +120,4 @@ public class DBManager {
public void closeConnection() {
data.closeSource();
}
}

View file

@ -1,5 +1,6 @@
package nl.SBDeveloper.V10Lift.managers;
import lombok.Getter;
import nl.SBDeveloper.V10Lift.api.objects.Lift;
import nl.SBDeveloper.V10Lift.api.objects.LiftBlock;
@ -7,19 +8,19 @@ import java.util.*;
public class DataManager {
/* A manager for general HashMaps */
private static LinkedHashMap<String, Lift> lifts = new LinkedHashMap<>();
private static LinkedHashMap<UUID, TreeSet<LiftBlock>> builds = new LinkedHashMap<>();
private static LinkedHashMap<UUID, String> editors = new LinkedHashMap<>();
private static LinkedHashMap<UUID, String> inputEdits = new LinkedHashMap<>();
private static ArrayList<UUID> inputRemoves = new ArrayList<>();
private static ArrayList<UUID> offlineEdits = new ArrayList<>();
private static ArrayList<UUID> offlineRemoves = new ArrayList<>();
private static ArrayList<UUID> builder = new ArrayList<>();
private static LinkedHashMap<UUID, LiftBlock> ropeEdits = new LinkedHashMap<>();
private static ArrayList<UUID> ropeRemoves = new ArrayList<>();
private static HashMap<UUID, String> doorEdits = new HashMap<>();
private static ArrayList<UUID> whoisReq = new ArrayList<>();
private static HashMap<String, Integer> movingTasks = new HashMap<>();
@Getter private static final Map<String, Lift> lifts = new LinkedHashMap<>();
private static final Map<UUID, TreeSet<LiftBlock>> builds = new LinkedHashMap<>();
@Getter private static final Map<UUID, String> editors = new LinkedHashMap<>();
private static final Map<UUID, String> inputEdits = new LinkedHashMap<>();
private static final List<UUID> inputRemoves = new ArrayList<>();
private static final List<UUID> offlineEdits = new ArrayList<>();
private static final List<UUID> offlineRemoves = new ArrayList<>();
private static final List<UUID> builder = new ArrayList<>();
private static final Map<UUID, LiftBlock> ropeEdits = new LinkedHashMap<>();
private static final List<UUID> ropeRemoves = new ArrayList<>();
private static final Map<UUID, String> doorEdits = new HashMap<>();
private static final List<UUID> whoisReq = new ArrayList<>();
private static final Map<String, Integer> movingTasks = new HashMap<>();
/* HashMap methods */
@ -40,8 +41,6 @@ public class DataManager {
return lifts.get(liftName);
}
public static LinkedHashMap<String, Lift> getLifts() { return lifts; }
// //
public static boolean containsPlayer(UUID player) {
return builds.containsKey(player);
@ -114,8 +113,6 @@ public class DataManager {
return editors.get(player);
}
public static LinkedHashMap<UUID, String> getEditors() { return editors; }
// //
public static void addMovingTask(String liftName, int taskid) {
movingTasks.put(liftName, taskid);

View file

@ -1,14 +1,14 @@
package nl.SBDeveloper.V10Lift.managers;
import nl.SBDeveloper.V10Lift.utils.XMaterial;
import com.cryptomorin.xseries.XMaterial;
import org.bukkit.Material;
import java.util.HashSet;
public class ForbiddenBlockManager {
private HashSet<XMaterial> forbidden = new HashSet<>();
private static final HashSet<XMaterial> forbidden = new HashSet<>();
public ForbiddenBlockManager() {
static {
//TODO Add more forbidden materials
//TODO Add to config
forbidden.add(XMaterial.BLACK_BED);
@ -41,7 +41,7 @@ public class ForbiddenBlockManager {
forbidden.add(XMaterial.STICKY_PISTON);
}
public boolean isForbidden(Material mat) {
public static boolean isForbidden(Material mat) {
XMaterial xmat = XMaterial.matchXMaterial(mat);
return forbidden.contains(xmat);
}

View file

@ -2,10 +2,10 @@ package nl.SBDeveloper.V10Lift.managers;
import net.milkbowl.vault.permission.Permission;
import nl.SBDeveloper.V10Lift.V10LiftPlugin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@ -14,12 +14,14 @@ public class VaultManager {
private static Permission perms = null;
public static boolean setupPermissions() {
if (Bukkit.getServer().getPluginManager().getPlugin("Vault") == null) {
return false;
}
RegisteredServiceProvider<Permission> rsp = V10LiftPlugin.getInstance().getServer().getServicesManager().getRegistration(Permission.class);
perms = rsp.getProvider();
return perms != null;
return true;
}
@Nonnull
public static List<String> getGroups() {
return Arrays.asList(perms.getGroups());
}
@ -28,7 +30,7 @@ public class VaultManager {
return Arrays.asList(perms.getGroups()).contains(groupName);
}
public static boolean userHasAnyGroup(Player player, @Nonnull HashSet<String> whitelistSet) {
public static boolean userHasAnyGroup(Player player, HashSet<String> whitelistSet) {
boolean found = false;
for (String group : whitelistSet) {
found = Arrays.asList(perms.getPlayerGroups(player)).contains(group);

View file

@ -0,0 +1,331 @@
package nl.SBDeveloper.V10Lift.sbutils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.plugin.Plugin;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/**
* A class to update/add new sections/keys to your config while keeping your current values and keeping your comments
* Algorithm:
* Read the new file and scan for comments and ignored sections, if ignored section is found it is treated as a comment.
* Read and write each line of the new config, if the old config has value for the given key it writes that value in the new config.
* If a key has an attached comment above it, it is written first.
* @author tchristofferson
*/
public class ConfigUpdater {
/**
* Update a yaml file from a resource inside your plugin jar
* @param plugin You plugin
* @param resourceName The yaml file name to update from, typically config.yml
* @param toUpdate The yaml file to update
* @param ignoredSections List of sections to ignore and copy from the current config
* @throws IOException If an IOException occurs
*/
public static void update(Plugin plugin, String resourceName, File toUpdate, List<String> ignoredSections) throws IOException {
BufferedReader newReader = new BufferedReader(new InputStreamReader(plugin.getResource(resourceName), StandardCharsets.UTF_8));
List<String> newLines = newReader.lines().collect(Collectors.toList());
newReader.close();
FileConfiguration oldConfig = YamlConfiguration.loadConfiguration(toUpdate);
FileConfiguration newConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(plugin.getResource(resourceName)));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(toUpdate), StandardCharsets.UTF_8));
List<String> ignoredSectionsArrayList = new ArrayList<>(ignoredSections);
//ignoredSections can ONLY contain configurations sections
ignoredSectionsArrayList.removeIf(ignoredSection -> !newConfig.isConfigurationSection(ignoredSection));
DumperOptions options = new DumperOptions();
options.setSplitLines(false);
Yaml yaml = new Yaml(options);
Map<String, String> comments = parseComments(newLines, ignoredSectionsArrayList, oldConfig, yaml);
write(newConfig, oldConfig, comments, ignoredSectionsArrayList, writer, yaml);
}
//Write method doing the work.
//It checks if key has a comment associated with it and writes comment then the key and value
private static void write(FileConfiguration newConfig, FileConfiguration oldConfig, Map<String, String> comments, List<String> ignoredSections, BufferedWriter writer, Yaml yaml) throws IOException {
outer: for (String key : newConfig.getKeys(true)) {
String[] keys = key.split("\\.");
String actualKey = keys[keys.length - 1];
String comment = comments.remove(key);
StringBuilder prefixBuilder = new StringBuilder();
int indents = keys.length - 1;
appendPrefixSpaces(prefixBuilder, indents);
String prefixSpaces = prefixBuilder.toString();
if (comment != null) {
writer.write(comment);//No \n character necessary, new line is automatically at end of comment
}
for (String ignoredSection : ignoredSections) {
if (key.startsWith(ignoredSection)) {
continue outer;
}
}
Object newObj = newConfig.get(key);
Object oldObj = oldConfig.get(key);
if (newObj instanceof ConfigurationSection && oldObj instanceof ConfigurationSection) {
//write the old section
writeSection(writer, actualKey, prefixSpaces, (ConfigurationSection) oldObj);
} else if (newObj instanceof ConfigurationSection) {
//write the new section, old value is no more
writeSection(writer, actualKey, prefixSpaces, (ConfigurationSection) newObj);
} else if (oldObj != null) {
//write the old object
write(oldObj, actualKey, prefixSpaces, yaml, writer);
} else {
//write new object
write(newObj, actualKey, prefixSpaces, yaml, writer);
}
}
String danglingComments = comments.get(null);
if (danglingComments != null) {
writer.write(danglingComments);
}
writer.close();
}
//Doesn't work with configuration sections, must be an actual object
//Auto checks if it is serializable and writes to file
private static void write(Object obj, String actualKey, String prefixSpaces, Yaml yaml, BufferedWriter writer) throws IOException {
if (obj instanceof ConfigurationSerializable) {
writer.write(prefixSpaces + actualKey + ": " + yaml.dump(((ConfigurationSerializable) obj).serialize()));
} else if (obj instanceof String || obj instanceof Character) {
if (obj instanceof String) {
String s = (String) obj;
obj = s.replace("\n", "\\n");
}
writer.write(prefixSpaces + actualKey + ": " + yaml.dump(obj));
} else if (obj instanceof List) {
writeList((List<?>) obj, actualKey, prefixSpaces, yaml, writer);
} else {
writer.write(prefixSpaces + actualKey + ": " + yaml.dump(obj));
}
}
//Writes a configuration section
private static void writeSection(BufferedWriter writer, String actualKey, String prefixSpaces, ConfigurationSection section) throws IOException {
if (section.getKeys(false).isEmpty()) {
writer.write(prefixSpaces + actualKey + ": {}");
} else {
writer.write(prefixSpaces + actualKey + ":");
}
writer.write("\n");
}
//Writes a list of any object
private static void writeList(List<?> list, String actualKey, String prefixSpaces, Yaml yaml, BufferedWriter writer) throws IOException {
writer.write(getListAsString(list, actualKey, prefixSpaces, yaml));
}
private static String getListAsString(List<?> list, String actualKey, String prefixSpaces, Yaml yaml) {
StringBuilder builder = new StringBuilder(prefixSpaces).append(actualKey).append(":");
if (list.isEmpty()) {
builder.append(" []\n");
return builder.toString();
}
builder.append("\n");
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
if (o instanceof String || o instanceof Character) {
builder.append(prefixSpaces).append("- '").append(o).append("'");
} else if (o instanceof List) {
builder.append(prefixSpaces).append("- ").append(yaml.dump(o));
} else {
builder.append(prefixSpaces).append("- ").append(o);
}
if (i != list.size()) {
builder.append("\n");
}
}
return builder.toString();
}
//Key is the config key, value = comment and/or ignored sections
//Parses comments, blank lines, and ignored sections
private static Map<String, String> parseComments(List<String> lines, List<String> ignoredSections, FileConfiguration oldConfig, Yaml yaml) {
Map<String, String> comments = new HashMap<>();
StringBuilder builder = new StringBuilder();
StringBuilder keyBuilder = new StringBuilder();
int lastLineIndentCount = 0;
outer: for (int i = 0; i<lines.size(); i++) {
String line = lines.get(i);
if (line != null && line.trim().startsWith("-"))
continue;
if (line == null || line.trim().equals("") || line.trim().startsWith("#")) {
builder.append(line).append("\n");
} else {
lastLineIndentCount = setFullKey(keyBuilder, i, lines, lastLineIndentCount);
for (String ignoredSection : ignoredSections) {
if (keyBuilder.toString().equals(ignoredSection)) {
Object value = oldConfig.get(keyBuilder.toString());
if (value instanceof ConfigurationSection)
appendSection(builder, (ConfigurationSection) value, new StringBuilder(getPrefixSpaces(lastLineIndentCount)), yaml);
continue outer;
}
}
if (keyBuilder.length() > 0) {
comments.put(keyBuilder.toString(), builder.toString());
builder.setLength(0);
}
}
}
if (builder.length() > 0) {
comments.put(null, builder.toString());
}
return comments;
}
private static void appendSection(StringBuilder builder, ConfigurationSection section, StringBuilder prefixSpaces, Yaml yaml) {
builder.append(prefixSpaces).append(getKeyFromFullKey(section.getCurrentPath())).append(":");
Set<String> keys = section.getKeys(false);
if (keys.isEmpty()) {
builder.append(" {}\n");
return;
}
builder.append("\n");
prefixSpaces.append(" ");
for (String key : keys) {
Object value = section.get(key);
String actualKey = getKeyFromFullKey(key);
if (value instanceof ConfigurationSection) {
appendSection(builder, (ConfigurationSection) value, prefixSpaces, yaml);
prefixSpaces.setLength(prefixSpaces.length() - 2);
} else if (value instanceof List) {
builder.append(getListAsString((List<?>) value, actualKey, prefixSpaces.toString(), yaml));
} else {
builder.append(prefixSpaces.toString()).append(actualKey).append(": ").append(yaml.dump(value));
}
}
}
//Check how many times spaces make a jump of at least two
private static int countIndents(int index, List<String> lines) {
int currentSpaces = getSpaces(lines.get(index));
int indents = 0;
for (int i = index-1; i >= 0; i--){
String line = lines.get(i);
if (line == null || line.trim().equals("")) continue;
int newSpaces = getSpaces(lines.get(i));
// differs at least two
if (newSpaces < currentSpaces - 1){
indents++;
currentSpaces = newSpaces;
}
if (currentSpaces <= 1) break;
}
return indents;
}
private static int getSpaces(String line){
int spaces = 0;
for (char c : line.toCharArray()) {
if (c == ' ') {
spaces += 1;
} else {
break;
}
}
return spaces;
}
//Ex. keyBuilder = key1.key2.key3 --> key1.key2
private static void removeLastKey(StringBuilder keyBuilder) {
String temp = keyBuilder.toString();
String[] keys = temp.split("\\.");
if (keys.length == 1) {
keyBuilder.setLength(0);
return;
}
temp = temp.substring(0, temp.length() - keys[keys.length - 1].length() - 1);
keyBuilder.setLength(temp.length());
}
private static String getKeyFromFullKey(String fullKey) {
String[] keys = fullKey.split("\\.");
return keys[keys.length - 1];
}
//Updates the keyBuilder and returns configLines number of indents
private static int setFullKey(StringBuilder keyBuilder, int index, List<String> configLines, int lastLineIndentCount) {
int currentIndents = countIndents(index, configLines);
String key = configLines.get(index).trim().split(":")[0];
if (keyBuilder.length() == 0) {
keyBuilder.append(key);
} else if (currentIndents == lastLineIndentCount) {
//Replace the last part of the key with current key
removeLastKey(keyBuilder);
if (keyBuilder.length() > 0) {
keyBuilder.append(".");
}
keyBuilder.append(key);
} else if (currentIndents > lastLineIndentCount) {
//Append current key to the keyBuilder
keyBuilder.append(".").append(key);
} else {
int difference = lastLineIndentCount - currentIndents;
for (int i = 0; i < difference + 1; i++) {
removeLastKey(keyBuilder);
}
if (keyBuilder.length() > 0) {
keyBuilder.append(".");
}
keyBuilder.append(key);
}
return currentIndents;
}
private static String getPrefixSpaces(int indents) {
return new String(new char[Math.max(0, indents)]).replace("\0", " ");
}
private static void appendPrefixSpaces(StringBuilder builder, int indents) {
builder.append(getPrefixSpaces(indents));
}
}

View file

@ -7,7 +7,6 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class LocationSerializer {
/**
* Deserialize a serialized location, without {@link Location#getYaw()} and {@link Location#getPitch()}
*
@ -77,5 +76,4 @@ public class LocationSerializer {
if (loc.getWorld() == null) return null;
return loc.getWorld().getName() + "_" + loc.getX() + "_" + loc.getY() + "_" + loc.getZ() + "_" + loc.getYaw() + "_" + loc.getPitch();
}
}

View file

@ -12,8 +12,7 @@ import java.sql.SQLException;
import java.util.Properties;
public class SQLiteDB {
private String dbName;
private final String dbName;
private HikariDataSource source;
private Connection con;

View file

@ -1,59 +1,75 @@
package nl.SBDeveloper.V10Lift.sbutils;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.function.BiConsumer;
/**
* Update class for SBDevelopment
* @author Stijn [SBDeveloper]
* @since 05-03-2020
* @version 1.4
* @version 2.0 [26-12-2020] - This class supports the v2 Update API
*
* © Stijn Bannink <stijnbannink23@gmail.com> - All rights reserved.
* <p>&copy; Stijn Bannink [stijnbannink23@gmail.com] - All rights reserved.</p>
*/
public class UpdateManager {
private static final String SPIGOT_API = "https://api.spigotmc.org/legacy/update.php?resource=%d";
private static final String SPIGOT_DOWNLOAD = "http://api.spiget.org/v2/resources/%s/download";
/* Port 4000 is now legacy, 4443 has a SSL cert */
/* As of 24-05-2020, using the legacy port because of SSL errors */
private static final String SBDPLUGINS_API = "http://updates.sbdplugins.nl:4000/api/resources/%d";
private static final String SBDPLUGINS_API = "https://updates.sbdplugins.nl/api/v2/plugins/%d";
private static final String SBDPLUGINS_DOWNLOAD = "https://updates.sbdplugins.nl/api/v2/download/%d";
private static final String RESOURCE_DOWNLOAD = "http://api.spiget.org/v2/resources/%s/download";
private final Plugin plugin;
private final Version currentVersion;
private final int resourceID;
private final CheckType type;
private final String license;
private Plugin plugin;
private Version currentVersion;
private int resourceID;
private CheckType type;
private BiConsumer<VersionResponse, Version> versionResponse;
private BiConsumer<DownloadResponse, String> downloadResponse;
/**
* Construct a new UpdateManager
* Construct a new UpdateManager for Spigot
*
* @param plugin The javaplugin (Main class)
* @param resourceID The resourceID on spigot/sbdplugins
* @param type The check type
*/
public UpdateManager(@Nonnull Plugin plugin, int resourceID, CheckType type) {
public UpdateManager(Plugin plugin, int resourceID) {
this.plugin = plugin;
this.currentVersion = new Version(plugin.getDescription().getVersion());
this.resourceID = resourceID;
this.type = type;
this.type = CheckType.SPIGOT;
this.license = null;
}
/**
* Construct a new UpdateManager for SBDPlugins
*
* @param plugin The javaplugin (Main class)
* @param resourceID The resourceID on spigot/sbdplugins
* @param license The license for the download
*/
public UpdateManager(Plugin plugin, int resourceID, String license) {
this.plugin = plugin;
this.currentVersion = new Version(plugin.getDescription().getVersion());
this.resourceID = resourceID;
this.type = CheckType.SBDPLUGINS;
this.license = license;
}
/**
@ -85,7 +101,7 @@ public class UpdateManager {
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else if (type == CheckType.SBDPLUGINS) {
HttpURLConnection con = (HttpURLConnection) new URL(String.format(SBDPLUGINS_API, this.resourceID)).openConnection();
HttpsURLConnection con = (HttpsURLConnection) new URL(String.format(SBDPLUGINS_API, this.resourceID)).openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
@ -94,7 +110,7 @@ public class UpdateManager {
if (in == null) return;
String version = null;
String version;
String inputLine;
StringBuilder response = new StringBuilder();
@ -103,22 +119,25 @@ public class UpdateManager {
}
in.close();
JsonParser parser = new JsonParser();
if (type == CheckType.SPIGOT) {
version = response.toString();
} else if (type == CheckType.SBDPLUGINS) {
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(response.toString()).getAsJsonArray();
version = array.get(0).getAsJsonObject().get("name").getAsString();
} else {
JsonObject object = parser.parse(response.toString()).getAsJsonObject();
version = object.get("data").getAsJsonObject().get("version").getAsString();
version = object.get("version").getAsString();
}
if (version == null) return;
Version onlineVersion = new Version(version);
boolean latestVersion = this.currentVersion.compareTo(onlineVersion) < 0;
VersionResponse verRes = this.currentVersion.check(onlineVersion);
Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(latestVersion ? VersionResponse.LATEST : VersionResponse.FOUND_NEW, latestVersion ? this.currentVersion : onlineVersion));
Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(verRes, onlineVersion));
} catch (IOException | NullPointerException e) {
e.printStackTrace();
Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null));
@ -127,54 +146,108 @@ public class UpdateManager {
}
public void runUpdate() {
File pluginFile = getPluginFile();// /plugins/XXX.jar
File pluginFile = getPluginFile(); // /plugins/XXX.jar
if (pluginFile == null) {
this.downloadResponse.accept(DownloadResponse.ERROR, null);
Bukkit.getLogger().info("Pluginfile is null");
return;
}
File updateFolder = Bukkit.getUpdateFolderFile();
if (!updateFolder.exists()) {
if (!updateFolder.mkdirs()) {
this.downloadResponse.accept(DownloadResponse.ERROR, null);
Bukkit.getLogger().info("Updatefolder doesn't exists, and can't be made");
return;
}
}
final File updateFile = new File(updateFolder, pluginFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
if (this.type == CheckType.SBDPLUGINS) {
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.UNAVAILABLE, null));
return;
}
ReadableByteChannel channel;
try {
//https://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(RESOURCE_DOWNLOAD, this.resourceID)).openConnection();
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
if (connection.getResponseCode() != 200) {
throw new RuntimeException("Download returned status #" + connection.getResponseCode());
int response;
InputStream stream;
if (type == CheckType.SBDPLUGINS) {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(SBDPLUGINS_DOWNLOAD, this.resourceID)).openConnection();
String urlParameters = "license=" + license + "&port=" + Bukkit.getPort();
byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
int postDataLength = postData.length;
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("charset", "utf-8");
connection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
connection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.write(postData);
wr.close();
response = connection.getResponseCode();
stream = connection.getInputStream();
} else {
HttpsURLConnection connection = (HttpsURLConnection) new URL(String.format(SPIGOT_DOWNLOAD, this.resourceID)).openConnection();
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
response = connection.getResponseCode();
stream = connection.getInputStream();
}
channel = Channels.newChannel(connection.getInputStream());
if (response != 200) {
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
String inputLine;
StringBuilder responsestr = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
responsestr.append(inputLine);
}
in.close();
throw new RuntimeException("Download returned status #" + response, new Throwable(responsestr.toString()));
}
channel = Channels.newChannel(stream);
} catch (IOException e) {
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.ERROR, null));
e.printStackTrace();
return;
}
FileChannel fileChannel = null;
try {
FileOutputStream output = new FileOutputStream(updateFile);
output.getChannel().transferFrom(channel, 0, Long.MAX_VALUE);
output.flush();
output.close();
FileOutputStream fosForDownloadedFile = new FileOutputStream(updateFile);
fileChannel = fosForDownloadedFile.getChannel();
fileChannel.transferFrom(channel, 0, Long.MAX_VALUE);
} catch (IOException e) {
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.ERROR, null));
e.printStackTrace();
return;
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException ioe) {
System.out.println("Error while closing response body channel");
}
}
if (fileChannel != null) {
try {
fileChannel.close();
} catch (IOException ioe) {
System.out.println("Error while closing file channel for downloaded file");
}
}
}
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.DONE, updateFile.getPath()));
});
}
@Nullable
private File getPluginFile() {
if (!(this.plugin instanceof JavaPlugin)) { return null; }
try {
@ -186,27 +259,30 @@ public class UpdateManager {
}
}
public enum CheckType {
private enum CheckType {
SPIGOT, SBDPLUGINS
}
public enum VersionResponse {
LATEST, FOUND_NEW, UNAVAILABLE
LATEST, //Latest version
FOUND_NEW, //Newer available
THIS_NEWER, //Local version is newer?
UNAVAILABLE //Error
}
public enum DownloadResponse {
DONE, ERROR, UNAVAILABLE
}
public static class Version implements Comparable<Version> {
public static class Version {
private String version;
private final String version;
public final String get() {
return this.version;
}
public Version(String version) {
private Version(String version) {
if(version == null)
throw new IllegalArgumentException("Version can not be null");
if(!version.matches("[0-9]+(\\.[0-9]+)*"))
@ -214,8 +290,7 @@ public class UpdateManager {
this.version = version;
}
@Override
public int compareTo(@Nonnull Version that) {
private VersionResponse check(Version that) {
String[] thisParts = this.get().split("\\.");
String[] thatParts = that.get().split("\\.");
@ -224,19 +299,11 @@ public class UpdateManager {
int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0;
int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0;
if(thisPart < thatPart)
return -1;
return VersionResponse.FOUND_NEW;
if(thisPart > thatPart)
return 1;
return VersionResponse.THIS_NEWER;
}
return 0;
}
@Override
public boolean equals(Object that) {
if (this == that) return true;
if (that == null) return false;
if (this.getClass() != that.getClass()) return false;
return this.compareTo((UpdateManager.Version) that) == 0;
return VersionResponse.LATEST;
}
}
}

View file

@ -13,11 +13,9 @@ import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class YamlFile {
//SBYamlFile file = new SBYamlFile(this, "data");
private final String name;
private FileConfiguration fileConfiguration;
private File file;
private String name;
public YamlFile(String name) {
this.name = name;
@ -53,6 +51,10 @@ public class YamlFile {
saveFile();
}
public File getJavaFile() {
return this.file;
}
public FileConfiguration getFile() {
return this.fileConfiguration;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,35 @@
#General sign texts
SignText: "[v10lift]"
DefectText: "&kDefect!"
DisabledText: "&cDisabled"
MaintenanceText: "&cMaintenance"
UpText: "&aup"
DownText: "&adown"
#The change for a lift to get defective. Disabled by default.
DefectRate: 0.0
#The repair item and amount required
RepairItem: REDSTONE
RepairAmount: 5
#The master repair item and amount required
MasterRepairItem: DIAMOND
MasterRepairAmount: 10
#The default speed of a lift
DefaultSpeed: 16
#Create lifts with realistic mode enabled default?
DefaultRealistic: true
#The time for the door to close, in ticks. 20 ticks = 1 sec
DoorCloseTime: 100
CheckUpdates: true
PacketTeleport: false
#Teleport with packets (no Bukkit events will be fired then)
PacketTeleport: false
#The updater checker - It will check for new versions, and install automatically if found.
UpdateChecker:
Enabled: true
DownloadOnUpdate: true

View file

@ -1,22 +1,18 @@
General:
#General
NoPermission: '&cYou don''t have the permission to do this.'
PlayerOnly: '&cOnly players can do this.'
IncorrectUsage: '&cPlease use %Command% instead'
InternalError: '&cSomething went wrong internally.'
#Lift general
DoesntExists: '&cThere are no lifts with that name.'
AlreadyExists: '&cA lift with that name already exists.'
SwitchOnEdit: '&cEnable editor mode before doing this.'
DetectionFailed: '&cAutomatic floor detection failed!'
FloorDoesntExists: '&cThe floor %Name% doesn''t exists!'
#Lift control
NoWhitelistPermission: '&cYou can''t go to that floor!'
NoFloors: '&cThis elevator has no floors!'
#Protection
RemoveLiftFirst: '&cYou can''t do this! Remove the lift first.'
RemoveRopeFirst: '&cYou can''t do this! Remove the rope first.'
RemoveDoorFirst: '&cYou can''t do this! Remove the door first.'