added anti sign edit
This commit is contained in:
		| @@ -22,13 +22,13 @@ repositories { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT' |     compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT' | ||||||
|     compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' |     compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' | ||||||
|     implementation 'org.apache.httpcomponents:httpclient:4.5.14' |     implementation 'org.apache.httpcomponents:httpclient:4.5.14' | ||||||
|     implementation 'com.sparkjava:spark-core:2.9.4' |     implementation 'com.sparkjava:spark-core:2.9.4' | ||||||
| } | } | ||||||
|  |  | ||||||
| def targetJavaVersion = 17 | def targetJavaVersion = 21 | ||||||
| java { | java { | ||||||
|     def javaVersion = JavaVersion.toVersion(targetJavaVersion) |     def javaVersion = JavaVersion.toVersion(targetJavaVersion) | ||||||
|     sourceCompatibility = javaVersion |     sourceCompatibility = javaVersion | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn; | |||||||
|  |  | ||||||
| import eu.mhsl.craftattack.spawn.api.HttpServer; | import eu.mhsl.craftattack.spawn.api.HttpServer; | ||||||
| import eu.mhsl.craftattack.spawn.appliance.Appliance; | import eu.mhsl.craftattack.spawn.appliance.Appliance; | ||||||
|  | import eu.mhsl.craftattack.spawn.appliances.AntiSignEdit.AntiSignEdit; | ||||||
| import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; | import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; | ||||||
| import eu.mhsl.craftattack.spawn.appliances.autoShulker.AutoShulker; | import eu.mhsl.craftattack.spawn.appliances.autoShulker.AutoShulker; | ||||||
| import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages; | import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages; | ||||||
| @@ -31,9 +32,11 @@ import org.bukkit.plugin.java.JavaPlugin; | |||||||
|  |  | ||||||
| import java.lang.reflect.ParameterizedType; | import java.lang.reflect.ParameterizedType; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.logging.Logger; | ||||||
|  |  | ||||||
| public final class Main extends JavaPlugin { | public final class Main extends JavaPlugin { | ||||||
|     private static Main instance; |     private static Main instance; | ||||||
|  |     private static Logger logger; | ||||||
|  |  | ||||||
|     private List<Appliance> appliances; |     private List<Appliance> appliances; | ||||||
|     private HttpServer httpApi; |     private HttpServer httpApi; | ||||||
| @@ -41,6 +44,7 @@ public final class Main extends JavaPlugin { | |||||||
|     @Override |     @Override | ||||||
|     public void onEnable() { |     public void onEnable() { | ||||||
|         instance = this; |         instance = this; | ||||||
|  |         logger = instance().getLogger(); | ||||||
|         saveDefaultConfig(); |         saveDefaultConfig(); | ||||||
|         Configuration.readConfig(); |         Configuration.readConfig(); | ||||||
|  |  | ||||||
| @@ -66,18 +70,19 @@ public final class Main extends JavaPlugin { | |||||||
|             new CustomAdvancements(), |             new CustomAdvancements(), | ||||||
|             new Settings(), |             new Settings(), | ||||||
|             new PortableCrafting(), |             new PortableCrafting(), | ||||||
|             new AutoShulker() |             new AutoShulker(), | ||||||
|  |             new AntiSignEdit() | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         Bukkit.getLogger().info("Loading appliances..."); |         Main.logger.info("Loading appliances..."); | ||||||
|         appliances.forEach(appliance -> { |         appliances.forEach(appliance -> { | ||||||
|             Bukkit.getLogger().info("Enabling " + appliance.getClass().getSimpleName()); |             Main.logger().info("Enabling " + appliance.getClass().getSimpleName()); | ||||||
|             appliance.onEnable(); |             appliance.onEnable(); | ||||||
|             appliance.initialize(this); |             appliance.initialize(this); | ||||||
|         }); |         }); | ||||||
|         Bukkit.getLogger().info("Loaded " + appliances.size() + " appliances!"); |         Main.logger().info("Loaded " + appliances.size() + " appliances!"); | ||||||
|  |  | ||||||
|         Bukkit.getLogger().info("Starting HTTP API"); |         Main.logger().info("Starting HTTP API"); | ||||||
|         this.httpApi = new HttpServer(); |         this.httpApi = new HttpServer(); | ||||||
|  |  | ||||||
|         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); |         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); | ||||||
| @@ -85,16 +90,16 @@ public final class Main extends JavaPlugin { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onDisable() { |     public void onDisable() { | ||||||
|         Bukkit.getLogger().info("Disabling appliances..."); |         Main.logger().info("Disabling appliances..."); | ||||||
|         appliances.forEach(appliance -> { |         appliances.forEach(appliance -> { | ||||||
|             Bukkit.getLogger().info("Disabling " + appliance.getClass().getSimpleName()); |             Main.logger().info("Disabling " + appliance.getClass().getSimpleName()); | ||||||
|             appliance.onDisable(); |             appliance.onDisable(); | ||||||
|             appliance.destruct(this); |             appliance.destruct(this); | ||||||
|         }); |         }); | ||||||
|         this.httpApi.stop(); |         this.httpApi.stop(); | ||||||
|         HandlerList.unregisterAll(this); |         HandlerList.unregisterAll(this); | ||||||
|         Bukkit.getScheduler().cancelTasks(this); |         Bukkit.getScheduler().cancelTasks(this); | ||||||
|         Bukkit.getLogger().info("Disabled " + appliances.size() + " appliances!"); |         Main.logger().info("Disabled " + appliances.size() + " appliances!"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public <T extends Appliance> T getAppliance(Class<T> clazz) { |     public <T extends Appliance> T getAppliance(Class<T> clazz) { | ||||||
| @@ -117,4 +122,8 @@ public final class Main extends JavaPlugin { | |||||||
|     public static Main instance() { |     public static Main instance() { | ||||||
|         return instance; |         return instance; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static Logger logger() { | ||||||
|  |         return logger; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ public abstract class Appliance { | |||||||
|             command.setExecutor(executor); |             command.setExecutor(executor); | ||||||
|             command.setTabCompleter(executor); |             command.setTabCompleter(executor); | ||||||
|         } else { |         } else { | ||||||
|             Bukkit.getLogger().warning("Command " + name + " is not specified in plugin.yml!"); |             Main.logger().warning("Command " + name + " is not specified in plugin.yml!"); | ||||||
|             throw new RuntimeException("All commands must be registered in plugin.yml. Missing command: " + name); |             throw new RuntimeException("All commands must be registered in plugin.yml. Missing command: " + name); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package eu.mhsl.craftattack.spawn.appliance; | package eu.mhsl.craftattack.spawn.appliance; | ||||||
|  |  | ||||||
|  | import eu.mhsl.craftattack.spawn.Main; | ||||||
| import net.kyori.adventure.text.Component; | import net.kyori.adventure.text.Component; | ||||||
| import net.kyori.adventure.text.format.NamedTextColor; | import net.kyori.adventure.text.format.NamedTextColor; | ||||||
| import org.bukkit.Bukkit; |  | ||||||
| import org.bukkit.command.Command; | import org.bukkit.command.Command; | ||||||
| import org.bukkit.command.CommandExecutor; | import org.bukkit.command.CommandExecutor; | ||||||
| import org.bukkit.command.CommandSender; | import org.bukkit.command.CommandSender; | ||||||
| @@ -38,7 +38,7 @@ public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSup | |||||||
|             sender.sendMessage(errorMessage.append(Component.text(e.getMessage()))); |             sender.sendMessage(errorMessage.append(Component.text(e.getMessage()))); | ||||||
|         } catch(Exception e) { |         } catch(Exception e) { | ||||||
|             sender.sendMessage(errorMessage.append(Component.text("Interner Fehler"))); |             sender.sendMessage(errorMessage.append(Component.text("Interner Fehler"))); | ||||||
|             Bukkit.getLogger().warning("Error executing appliance command " + commandName + ": " + e.getMessage()); |             Main.logger().warning("Error executing appliance command " + commandName + ": " + e.getMessage()); | ||||||
|             e.printStackTrace(System.err); |             e.printStackTrace(System.err); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package eu.mhsl.craftattack.spawn.appliances.AntiSignEdit; | ||||||
|  |  | ||||||
|  | import eu.mhsl.craftattack.spawn.appliance.Appliance; | ||||||
|  | import eu.mhsl.craftattack.spawn.appliances.settings.Settings; | ||||||
|  | import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; | ||||||
|  | import eu.mhsl.craftattack.spawn.appliances.settings.settings.SignEditSetting; | ||||||
|  | import net.kyori.adventure.text.Component; | ||||||
|  | import net.kyori.adventure.text.format.NamedTextColor; | ||||||
|  | import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; | ||||||
|  | import org.bukkit.block.sign.SignSide; | ||||||
|  | import org.bukkit.entity.Player; | ||||||
|  | import org.bukkit.event.Listener; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | public class AntiSignEdit extends Appliance { | ||||||
|  |     public boolean preventSignEdit(Player p, SignSide sign) { | ||||||
|  |         SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class); | ||||||
|  |         if(setting.is(SignEditSetting.editable)) return false; | ||||||
|  |         if(setting.is(SignEditSetting.readOnly)) { | ||||||
|  |             p.sendActionBar(Component.text("Das Bearbeiten von Schildern ist in deinen Einstellungen deaktiviert.", NamedTextColor.RED)); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(setting.is(SignEditSetting.editableWhenEmpty)) { | ||||||
|  |             boolean hasText = sign.lines().stream() | ||||||
|  |                 .anyMatch(line -> !PlainTextComponentSerializer.plainText().serialize(line).isBlank()); | ||||||
|  |  | ||||||
|  |             if(hasText) { | ||||||
|  |                 p.sendActionBar(Component.text("Das Bearbeiten von Schildern, welch bereits beschrieben sind, ist bei dir deaktiviert.", NamedTextColor.RED)); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected @NotNull List<Listener> eventHandlers() { | ||||||
|  |         return List.of(new OnSignEditListener()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package eu.mhsl.craftattack.spawn.appliances.AntiSignEdit; | ||||||
|  |  | ||||||
|  | import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; | ||||||
|  | import io.papermc.paper.event.player.PlayerOpenSignEvent; | ||||||
|  | import org.bukkit.block.sign.SignSide; | ||||||
|  | import org.bukkit.event.EventHandler; | ||||||
|  |  | ||||||
|  | public class OnSignEditListener extends ApplianceListener<AntiSignEdit> { | ||||||
|  |     @EventHandler | ||||||
|  |     public void onEdit(PlayerOpenSignEvent event) { | ||||||
|  |         if(event.getCause().equals(PlayerOpenSignEvent.Cause.PLACE)) return; | ||||||
|  |         SignSide signSide = event.getSign().getSide(event.getSide()); | ||||||
|  |         event.setCancelled(getAppliance().preventSignEdit(event.getPlayer(), signSide)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.appliances.autoShulker; | |||||||
|  |  | ||||||
| import eu.mhsl.craftattack.spawn.appliance.Appliance; | import eu.mhsl.craftattack.spawn.appliance.Appliance; | ||||||
| import net.kyori.adventure.text.Component; | import net.kyori.adventure.text.Component; | ||||||
|  | import net.kyori.adventure.text.format.NamedTextColor; | ||||||
| import org.bukkit.Material; | import org.bukkit.Material; | ||||||
| import org.bukkit.block.ShulkerBox; | import org.bukkit.block.ShulkerBox; | ||||||
| import org.bukkit.entity.Item; | import org.bukkit.entity.Item; | ||||||
| @@ -26,7 +27,7 @@ public class AutoShulker extends Appliance { | |||||||
|         HashMap<Integer, ItemStack> leftOver = shulkerBox.getInventory().addItem(itemStack); |         HashMap<Integer, ItemStack> leftOver = shulkerBox.getInventory().addItem(itemStack); | ||||||
|         if(leftOver.size() > 1) throw new IllegalStateException("Multiple ItemStacks cannot be processed by AutoShulker!"); |         if(leftOver.size() > 1) throw new IllegalStateException("Multiple ItemStacks cannot be processed by AutoShulker!"); | ||||||
|         if(itemStack.equals(leftOver.get(0))) { |         if(itemStack.equals(leftOver.get(0))) { | ||||||
|             p.sendActionBar(Component.text("Die Shulkerbox ist voll!")); |             p.sendActionBar(Component.text("Die Shulkerbox ist voll!", NamedTextColor.RED)); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         if(leftOver.isEmpty()) { |         if(leftOver.isEmpty()) { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package eu.mhsl.craftattack.spawn.appliances.report; | package eu.mhsl.craftattack.spawn.appliances.report; | ||||||
|  |  | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
|  | import eu.mhsl.craftattack.spawn.Main; | ||||||
| import eu.mhsl.craftattack.spawn.appliance.Appliance; | import eu.mhsl.craftattack.spawn.appliance.Appliance; | ||||||
| import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; | import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; | ||||||
| import net.kyori.adventure.text.Component; | import net.kyori.adventure.text.Component; | ||||||
| @@ -66,8 +67,7 @@ public class Report extends Appliance { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void issueReport(Player issuer, Request reportRequest) { |     private void issueReport(Player issuer, Request reportRequest) { | ||||||
|         try { |         try(HttpClient client = HttpClient.newHttpClient()) { | ||||||
|             HttpClient client = HttpClient.newHttpClient(); |  | ||||||
|             HttpRequest httpRequest = HttpRequest.newBuilder() |             HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||
|                 .uri(this.apiEndpoint) |                 .uri(this.apiEndpoint) | ||||||
|                 .header("Content-Type", "application/json") |                 .header("Content-Type", "application/json") | ||||||
| @@ -128,7 +128,7 @@ public class Report extends Appliance { | |||||||
|  |  | ||||||
|             case 401: |             case 401: | ||||||
|             default: |             default: | ||||||
|                 Bukkit.getLogger().warning("Failed to request Report: " + httpResponse.statusCode()); |                 Main.logger().warning("Failed to request Report: " + httpResponse.statusCode()); | ||||||
|                 issuer.sendMessage( |                 issuer.sendMessage( | ||||||
|                     Component.text() |                     Component.text() | ||||||
|                         .append(Component.text("Interner Serverfehler beim anlegen des Reports.", NamedTextColor.RED)) |                         .append(Component.text("Interner Serverfehler beim anlegen des Reports.", NamedTextColor.RED)) | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ public class Settings extends Appliance { | |||||||
|         EnablePortableCrafting, |         EnablePortableCrafting, | ||||||
|         EnableSettingsShortcut, |         EnableSettingsShortcut, | ||||||
|         AutoShulker, |         AutoShulker, | ||||||
|  |         SignEdit, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Settings instance() { |     public static Settings instance() { | ||||||
| @@ -49,7 +50,8 @@ public class Settings extends Appliance { | |||||||
|             new ShowJoinAndLeaveMessagesSetting(), |             new ShowJoinAndLeaveMessagesSetting(), | ||||||
|             new EnablePortableCraftingSetting(), |             new EnablePortableCraftingSetting(), | ||||||
|             new EnableSettingsShortcutSetting(), |             new EnableSettingsShortcutSetting(), | ||||||
|             new AutoShulkerSetting() |             new AutoShulkerSetting(), | ||||||
|  |             new SignEditSetting() | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         settings.forEach(setting -> setting.initializeFromPlayer(player)); |         settings.forEach(setting -> setting.initializeFromPlayer(player)); | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package eu.mhsl.craftattack.spawn.appliances.settings.settings; | ||||||
|  |  | ||||||
|  | import eu.mhsl.craftattack.spawn.appliances.settings.Settings; | ||||||
|  | import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; | ||||||
|  | import org.bukkit.Material; | ||||||
|  | import org.bukkit.NamespacedKey; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
|  |  | ||||||
|  | public class SignEditSetting extends SelectSetting { | ||||||
|  |     private static final String namespace = SignEditSetting.class.getSimpleName().toLowerCase(Locale.ROOT); | ||||||
|  |     public static Options.Option editable = new Options.Option("Bearbeitbar", new NamespacedKey(namespace, "editable")); | ||||||
|  |     public static Options.Option editableWhenEmpty = new Options.Option("Bearbeitbar wenn leer", new NamespacedKey(namespace, "emptyeditable")); | ||||||
|  |     public static Options.Option readOnly = new Options.Option("Nicht bearbeitbar", new NamespacedKey(namespace, "readonly")); | ||||||
|  |  | ||||||
|  |     public SignEditSetting() { | ||||||
|  |         super( | ||||||
|  |             Settings.Key.SignEdit, | ||||||
|  |             new Options(List.of(editable, editableWhenEmpty, readOnly)) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected String title() { | ||||||
|  |         return "Bearbeiten von Schildern"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected String description() { | ||||||
|  |         return "Bei einem Klick auf ein Schild, kann dieses Bearbeitet werden"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected Material icon() { | ||||||
|  |         return Material.OAK_SIGN; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected Options.Option defaultValue() { | ||||||
|  |         return editableWhenEmpty; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| package eu.mhsl.craftattack.spawn.appliances.worldmuseum; | package eu.mhsl.craftattack.spawn.appliances.worldmuseum; | ||||||
|  |  | ||||||
|  | import eu.mhsl.craftattack.spawn.Main; | ||||||
| import eu.mhsl.craftattack.spawn.appliance.Appliance; | import eu.mhsl.craftattack.spawn.appliance.Appliance; | ||||||
| import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; | import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; | ||||||
| import eu.mhsl.craftattack.spawn.util.entity.DisplayVillager; | import eu.mhsl.craftattack.spawn.util.entity.DisplayVillager; | ||||||
| @@ -9,7 +10,6 @@ import eu.mhsl.craftattack.spawn.util.server.Floodgate; | |||||||
| import eu.mhsl.craftattack.spawn.util.server.PluginMessage; | import eu.mhsl.craftattack.spawn.util.server.PluginMessage; | ||||||
| import net.kyori.adventure.text.Component; | import net.kyori.adventure.text.Component; | ||||||
| import net.kyori.adventure.text.format.NamedTextColor; | import net.kyori.adventure.text.format.NamedTextColor; | ||||||
| import org.bukkit.Bukkit; |  | ||||||
| import org.bukkit.Location; | import org.bukkit.Location; | ||||||
| import org.bukkit.entity.Player; | import org.bukkit.entity.Player; | ||||||
| import org.bukkit.entity.Villager; | import org.bukkit.entity.Villager; | ||||||
| @@ -56,7 +56,7 @@ public class WorldMuseum extends Appliance { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Bukkit.getLogger().info("Sending" + player.getName() + " to WorldMuseum"); |         Main.logger().info("Sending" + player.getName() + " to WorldMuseum"); | ||||||
|         PluginMessage.connect(player, localConfig().getString("connect-server-name")); |         PluginMessage.connect(player, localConfig().getString("connect-server-name")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package eu.mhsl.craftattack.spawn.config; | package eu.mhsl.craftattack.spawn.config; | ||||||
|  |  | ||||||
| import eu.mhsl.craftattack.spawn.Main; | import eu.mhsl.craftattack.spawn.Main; | ||||||
| import org.bukkit.Bukkit; |  | ||||||
| import org.bukkit.configuration.file.FileConfiguration; | import org.bukkit.configuration.file.FileConfiguration; | ||||||
| import org.bukkit.configuration.file.YamlConfiguration; | import org.bukkit.configuration.file.YamlConfiguration; | ||||||
|  |  | ||||||
| @@ -20,7 +19,7 @@ public class Configuration { | |||||||
|         try { |         try { | ||||||
|             cfg.save(configFile); |             cfg.save(configFile); | ||||||
|         } catch(Exception e) { |         } catch(Exception e) { | ||||||
|             Bukkit.getLogger().warning("Could not save configuration: " + e.getMessage()); |             Main.logger().warning("Could not save configuration: " + e.getMessage()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ name: spawn | |||||||
| author: olischma, muelleel | author: olischma, muelleel | ||||||
| version: '1.0' | version: '1.0' | ||||||
| main: eu.mhsl.craftattack.spawn.Main | main: eu.mhsl.craftattack.spawn.Main | ||||||
| api-version: '1.20' | api-version: '1.21' | ||||||
| commands: | commands: | ||||||
|   moveWorldMuseumVillager: |   moveWorldMuseumVillager: | ||||||
|     description: Moves world museum villager to current player location and persists location to config |     description: Moves world museum villager to current player location and persists location to config | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user