added anti sign edit
This commit is contained in:
parent
772687b15d
commit
62703ffd12
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user