Mirror von
https://github.com/St3venAU/ArmorStandTools.git
synchronisiert 2024-12-28 20:40:12 +01:00
Tweaks and bugfixes
Dieser Commit ist enthalten in:
Ursprung
d7ef505f49
Commit
03da204f09
@ -33,7 +33,7 @@ class ArmorStandGUI implements Listener {
|
||||
|
||||
ArmorStandGUI(Main plugin, ArmorStand as, Player p) {
|
||||
if(inUse.contains(as.getEntityId())) {
|
||||
p.sendMessage(ChatColor.RED + "This armor stand's GUI is in use");
|
||||
p.sendMessage(ChatColor.RED + Config.guiInUse);
|
||||
return;
|
||||
}
|
||||
if(filler == null) {
|
||||
@ -52,7 +52,7 @@ class ArmorStandGUI implements Listener {
|
||||
this.as = as;
|
||||
String name = as.getCustomName();
|
||||
if(name == null) {
|
||||
name = "Armor Stand";
|
||||
name = Config.armorStand;
|
||||
} else if(name.length() > 32) {
|
||||
name = name.substring(0, 32);
|
||||
}
|
||||
@ -74,7 +74,7 @@ class ArmorStandGUI implements Listener {
|
||||
p.openInventory(i);
|
||||
}
|
||||
|
||||
ItemStack updateLore(ArmorStandTool tool) {
|
||||
private ItemStack updateLore(ArmorStandTool tool) {
|
||||
ItemStack item = tool.getItem();
|
||||
switch (tool) {
|
||||
case INVIS:
|
||||
@ -92,18 +92,18 @@ class ArmorStandGUI implements Listener {
|
||||
case SLOTS:
|
||||
return Utils.setLore(item, ChatColor.AQUA + Config.equip + ": " + (NBT.getDisabledSlots(as) == 2039583 ? (ChatColor.GREEN + Config.locked) : (ChatColor.RED + Config.unLocked)));
|
||||
case NODEL:
|
||||
return Utils.setLore(item, ChatColor.AQUA + "Deletion Protection: " + (as.getMaxHealth() == 50 ? (ChatColor.GREEN + "Enabled") : (ChatColor.RED + "Disabled")));
|
||||
return Utils.setLore(item, ChatColor.AQUA + "Deletion Protection: " + (as.getMaxHealth() == 50 ? (ChatColor.GREEN + Config.enabled) : (ChatColor.RED + Config.disabled)));
|
||||
case NAME:
|
||||
return Utils.setLore(item, ChatColor.AQUA + Config.currently + ": " + (as.getCustomName() == null ? (ChatColor.BLUE + "None") : (ChatColor.GREEN + as.getCustomName())));
|
||||
return Utils.setLore(item, ChatColor.AQUA + Config.currently + ": " + (as.getCustomName() == null ? (ChatColor.BLUE + Config.none) : (ChatColor.GREEN + as.getCustomName())));
|
||||
case PHEAD:
|
||||
String name = plrHeadName(as);
|
||||
return Utils.setLore(item, ChatColor.AQUA + Config.currently + ": " + (name == null ? (ChatColor.BLUE + "None") : (ChatColor.GREEN + name)));
|
||||
return Utils.setLore(item, ChatColor.AQUA + Config.currently + ": " + (name == null ? (ChatColor.BLUE + Config.none) : (ChatColor.GREEN + name)));
|
||||
default:
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
String plrHeadName(ArmorStand as) {
|
||||
private String plrHeadName(ArmorStand as) {
|
||||
if(as.getHelmet() == null) return null;
|
||||
if(!(as.getHelmet().getItemMeta() instanceof SkullMeta)) return null;
|
||||
SkullMeta meta = (SkullMeta) as.getHelmet().getItemMeta();
|
||||
@ -240,7 +240,7 @@ class ArmorStandGUI implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
void updateInventory() {
|
||||
private void updateInventory() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -9,56 +9,58 @@ import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
public enum ArmorStandTool {
|
||||
HEADX ("headX", Material.JACK_O_LANTERN, (short) 0, 12, false, true),
|
||||
HEADY ("headY", Material.JACK_O_LANTERN, (short) 0, 13, false, true),
|
||||
HEADZ ("headZ", Material.JACK_O_LANTERN, (short) 0, 14, false, true),
|
||||
LARMX ("lArmX", Material.TORCH, (short) 0, 27, false, true),
|
||||
LARMY ("lArmY", Material.TORCH, (short) 0, 28, false, true),
|
||||
LARMZ ("lArmZ", Material.TORCH, (short) 0, 29, false, true),
|
||||
RARMX ("rArmX", Material.REDSTONE_TORCH_ON, (short) 0, 30, false, true),
|
||||
RARMY ("rArmY", Material.REDSTONE_TORCH_ON, (short) 0, 31, false, true),
|
||||
RARMZ ("rArmZ", Material.REDSTONE_TORCH_ON, (short) 0, 32, false, true),
|
||||
MOVEX ("moveX", Material.SHEARS, (short) 0, 3, false, true),
|
||||
MOVEY ("moveY", Material.SHEARS, (short) 0, 4, false, true),
|
||||
MOVEZ ("moveZ", Material.SHEARS, (short) 0, 5, false, true),
|
||||
LLEGX ("lLegX", Material.BONE, (short) 0, 18, false, true),
|
||||
LLEGY ("lLegY", Material.BONE, (short) 0, 19, false, true),
|
||||
LLEGZ ("lLegZ", Material.BONE, (short) 0, 20, false, true),
|
||||
RLEGX ("rLegX", Material.BLAZE_ROD, (short) 0, 21, false, true),
|
||||
RLEGY ("rLegY", Material.BLAZE_ROD, (short) 0, 22, false, true),
|
||||
RLEGZ ("rLegZ", Material.BLAZE_ROD, (short) 0, 23, false, true),
|
||||
BODYX ("bodyX", Material.NETHER_BRICK_ITEM, (short) 0, 9, false, true),
|
||||
BODYY ("bodyY", Material.NETHER_BRICK_ITEM, (short) 0, 10, false, true),
|
||||
BODYZ ("bodyZ", Material.NETHER_BRICK_ITEM, (short) 0, 11, false, true),
|
||||
SUMMON ("summon", Material.ARMOR_STAND, (short) 0, 0, false, true),
|
||||
GUI ("gui", Material.NETHER_STAR, (short) 0, 1, false, true),
|
||||
ROTAT ("rotat", Material.MAGMA_CREAM, (short) 0, 2, false, true),
|
||||
CLONE ("gui_clone", Material.GLOWSTONE_DUST, (short) 0, 16, true, true),
|
||||
SAVE ("gui_save", Material.DIAMOND, (short) 0, 17, true, true),
|
||||
INVIS ("gui_invis", Material.GOLD_NUGGET, (short) 0, 14, true, true),
|
||||
SIZE ("gui_size", Material.EMERALD, (short) 0, 23, true, true),
|
||||
BASE ("gui_base", Material.BOOK, (short) 0, 22, true, true),
|
||||
GRAV ("gui_grav", Material.GHAST_TEAR, (short) 0, 24, true, true),
|
||||
ARMS ("gui_arms", Material.ARROW, (short) 0, 21, true, true),
|
||||
NAME ("gui_name", Material.NAME_TAG, (short) 0, 12, true, true),
|
||||
SLOTS ("gui_slots", Material.IRON_HOE, (short) 0, 26, true, true),
|
||||
PHEAD ("gui_pHead", Material.SKULL_ITEM, (short) 3, 13, true, true),
|
||||
INVUL ("gui_invul", Material.GOLDEN_CARROT, (short) 0, 25, true, true),
|
||||
MOVE ("gui_move", Material.FEATHER, (short) 0, 15, true, true),
|
||||
NODEL ("gui_noDel", Material.WOOD_SPADE, (short) 0, 35, true, false); // Developer tool, disabled by default
|
||||
HEADX ("headX", Material.JACK_O_LANTERN, (short) 0, 12, false, true, "astools.use"),
|
||||
HEADY ("headY", Material.JACK_O_LANTERN, (short) 0, 13, false, true, "astools.use"),
|
||||
HEADZ ("headZ", Material.JACK_O_LANTERN, (short) 0, 14, false, true, "astools.use"),
|
||||
LARMX ("lArmX", Material.TORCH, (short) 0, 27, false, true, "astools.use"),
|
||||
LARMY ("lArmY", Material.TORCH, (short) 0, 28, false, true, "astools.use"),
|
||||
LARMZ ("lArmZ", Material.TORCH, (short) 0, 29, false, true, "astools.use"),
|
||||
RARMX ("rArmX", Material.REDSTONE_TORCH_ON, (short) 0, 30, false, true, "astools.use"),
|
||||
RARMY ("rArmY", Material.REDSTONE_TORCH_ON, (short) 0, 31, false, true, "astools.use"),
|
||||
RARMZ ("rArmZ", Material.REDSTONE_TORCH_ON, (short) 0, 32, false, true, "astools.use"),
|
||||
MOVEX ("moveX", Material.SHEARS, (short) 0, 3, false, true, "astools.use"),
|
||||
MOVEY ("moveY", Material.SHEARS, (short) 0, 4, false, true, "astools.use"),
|
||||
MOVEZ ("moveZ", Material.SHEARS, (short) 0, 5, false, true, "astools.use"),
|
||||
LLEGX ("lLegX", Material.BONE, (short) 0, 18, false, true, "astools.use"),
|
||||
LLEGY ("lLegY", Material.BONE, (short) 0, 19, false, true, "astools.use"),
|
||||
LLEGZ ("lLegZ", Material.BONE, (short) 0, 20, false, true, "astools.use"),
|
||||
RLEGX ("rLegX", Material.BLAZE_ROD, (short) 0, 21, false, true, "astools.use"),
|
||||
RLEGY ("rLegY", Material.BLAZE_ROD, (short) 0, 22, false, true, "astools.use"),
|
||||
RLEGZ ("rLegZ", Material.BLAZE_ROD, (short) 0, 23, false, true, "astools.use"),
|
||||
BODYX ("bodyX", Material.NETHER_BRICK_ITEM, (short) 0, 9, false, true, "astools.use"),
|
||||
BODYY ("bodyY", Material.NETHER_BRICK_ITEM, (short) 0, 10, false, true, "astools.use"),
|
||||
BODYZ ("bodyZ", Material.NETHER_BRICK_ITEM, (short) 0, 11, false, true, "astools.use"),
|
||||
SUMMON ("summon", Material.ARMOR_STAND, (short) 0, 0, false, true, "astools.use"),
|
||||
GUI ("gui", Material.NETHER_STAR, (short) 0, 1, false, true, "astools.use"),
|
||||
ROTAT ("rotat", Material.MAGMA_CREAM, (short) 0, 2, false, true, "astools.use"),
|
||||
CLONE ("gui_clone", Material.GLOWSTONE_DUST, (short) 0, 16, true, true, "astools.clone"),
|
||||
SAVE ("gui_save", Material.DIAMOND, (short) 0, 17, true, true, "astools.cmdblock"),
|
||||
INVIS ("gui_invis", Material.GOLD_NUGGET, (short) 0, 14, true, true, "astools.use"),
|
||||
SIZE ("gui_size", Material.EMERALD, (short) 0, 23, true, true, "astools.use"),
|
||||
BASE ("gui_base", Material.BOOK, (short) 0, 22, true, true, "astools.use"),
|
||||
GRAV ("gui_grav", Material.GHAST_TEAR, (short) 0, 24, true, true, "astools.use"),
|
||||
ARMS ("gui_arms", Material.ARROW, (short) 0, 21, true, true, "astools.use"),
|
||||
NAME ("gui_name", Material.NAME_TAG, (short) 0, 12, true, true, "astools.use"),
|
||||
SLOTS ("gui_slots", Material.IRON_HOE, (short) 0, 26, true, true, "astools.use"),
|
||||
PHEAD ("gui_pHead", Material.SKULL_ITEM, (short) 3, 13, true, true, "astools.use"),
|
||||
INVUL ("gui_invul", Material.GOLDEN_CARROT, (short) 0, 25, true, true, "astools.use"),
|
||||
MOVE ("gui_move", Material.FEATHER, (short) 0, 15, true, true, "astools.use"),
|
||||
NODEL ("gui_noDel", Material.WOOD_SPADE, (short) 0, 35, true, false, "astools.use"); // Developer tool, disabled by default
|
||||
|
||||
private final ItemStack item;
|
||||
private final String id;
|
||||
private final int slot;
|
||||
private final boolean enabled;
|
||||
private boolean enabled;
|
||||
private final boolean forGui;
|
||||
private final String permission;
|
||||
|
||||
ArmorStandTool(String id, Material m, short data, int slot, boolean forGui, boolean enabled) {
|
||||
ArmorStandTool(String id, Material m, short data, int slot, boolean forGui, boolean enabled, String permission) {
|
||||
item = new ItemStack(m, 1, data);
|
||||
this.id = id;
|
||||
this.slot = slot;
|
||||
this.forGui = forGui;
|
||||
this.enabled = enabled;
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
ItemStack getItem() {
|
||||
@ -75,10 +77,19 @@ public enum ArmorStandTool {
|
||||
return forGui;
|
||||
}
|
||||
|
||||
void setEnabled(FileConfiguration config) {
|
||||
if(this == NODEL) return;
|
||||
enabled = config.getBoolean("enableTool." + id);
|
||||
}
|
||||
|
||||
boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
@ -94,6 +105,9 @@ public enum ArmorStandTool {
|
||||
|
||||
static void give(Player p) {
|
||||
PlayerInventory i = p.getInventory();
|
||||
for(int slot = 0; slot < 36; slot++) {
|
||||
i.setItem(slot, null);
|
||||
}
|
||||
for(ArmorStandTool t : values()) {
|
||||
if(t.enabled && !t.forGui) {
|
||||
i.setItem(t.slot, t.item);
|
||||
|
@ -31,13 +31,13 @@ class Config {
|
||||
|
||||
public static String
|
||||
invReturned, asDropped, asVisible, isTrue, isFalse,
|
||||
carrying, cbCreated, size, small,
|
||||
normal, basePlate, isOn, isOff, gravity, arms, invul,
|
||||
equip, locked, unLocked, notConsole, giveMsg1,
|
||||
giveMsg2, conReload, noRelPerm, noAirError,
|
||||
pleaseWait, appliedHead, noHead, invalidName,
|
||||
wgNoPerm, currently, headFailed, noCommandPerm,
|
||||
generalNoPerm;
|
||||
carrying, cbCreated, size, small, normal, basePlate,
|
||||
isOn, isOff, gravity, arms, invul, equip, locked,
|
||||
unLocked, notConsole, giveMsg1, giveMsg2, conReload,
|
||||
noRelPerm, noAirError, pleaseWait, appliedHead,
|
||||
noHead, invalidName, wgNoPerm, currently, headFailed,
|
||||
noCommandPerm, generalNoPerm, armorStand, none,
|
||||
enabled, disabled, guiInUse;
|
||||
|
||||
public static void reload(Main main) {
|
||||
plugin = main;
|
||||
@ -83,6 +83,11 @@ class Config {
|
||||
currently = languageConfig.getString("currently");
|
||||
headFailed = languageConfig.getString("headFailed");
|
||||
generalNoPerm = languageConfig.getString("generalNoPerm");
|
||||
armorStand = languageConfig.getString("armorStand");
|
||||
none = languageConfig.getString("none");
|
||||
enabled = languageConfig.getString("enabled");
|
||||
disabled = languageConfig.getString("disabled");
|
||||
guiInUse = languageConfig.getString("guiInUse");
|
||||
}
|
||||
|
||||
private static void reloadMainConfig() {
|
||||
@ -103,6 +108,10 @@ class Config {
|
||||
invulnerable = config.getBoolean("invulnerable");
|
||||
equipmentLock = config.getBoolean("equipmentLock");
|
||||
plugin.carryingArmorStand.clear();
|
||||
|
||||
for(ArmorStandTool tool : ArmorStandTool.values()) {
|
||||
tool.setEnabled(config);
|
||||
}
|
||||
|
||||
Plugin plotSquared = plugin.getServer().getPluginManager().getPlugin("PlotSquared");
|
||||
if (plotSquared != null && plotSquared.isEnabled()) {
|
||||
@ -141,7 +150,7 @@ class Config {
|
||||
}
|
||||
String[] split = s.split(" ");
|
||||
if(split.length > 2) {
|
||||
System.out.println("[ArmorStandTools] Error in Config: Must use the format: MATERIAL_NAME dataValue. Continuing using AIR instead.");
|
||||
System.out.println("[ArmorStandTools] Error in config.yml: Must use the format: MATERIAL_NAME dataValue. Continuing using AIR instead.");
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
byte dataValue = (byte) 0;
|
||||
@ -149,14 +158,14 @@ class Config {
|
||||
try {
|
||||
dataValue = Byte.parseByte(split[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.out.println("[ArmorStandTools] Error in Config: Invalid data value specifed. Continuing using data value 0 instead.");
|
||||
System.out.println("[ArmorStandTools] Error in config.yml: Invalid data value specifed. Continuing using data value 0 instead.");
|
||||
}
|
||||
}
|
||||
Material m;
|
||||
try {
|
||||
m = Material.valueOf(split[0].toUpperCase());
|
||||
} catch(IllegalArgumentException iae) {
|
||||
System.out.println("[ArmorStandTools] Error in Config: Invalid material name specifed. Continuing using AIR instead.");
|
||||
System.out.println("[ArmorStandTools] Error in config.yml: Invalid material name specifed. Continuing using AIR instead.");
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
return new ItemStack(m, 1, dataValue);
|
||||
|
@ -182,40 +182,28 @@ public class Main extends JavaPlugin {
|
||||
b.setMetadata("setSkull", new FixedMetadataValue(this, true));
|
||||
}
|
||||
|
||||
boolean checkPermission(Player player, Block block) {
|
||||
|
||||
if(block == null) return true;
|
||||
|
||||
// Check PlotSquared
|
||||
Location loc = block.getLocation();
|
||||
boolean checkPermission(Player p, Block b) {
|
||||
if(b == null) return true;
|
||||
if (PlotSquaredHook.api != null) {
|
||||
if (PlotSquaredHook.isPlotWorld(loc)) {
|
||||
return PlotSquaredHook.checkPermission(player, loc);
|
||||
Location l = b.getLocation();
|
||||
if(PlotSquaredHook.isPlotWorld(l) && !PlotSquaredHook.checkPermission(p, l)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check WorldGuard
|
||||
if(Config.worldGuardPlugin != null) {
|
||||
return Config.worldGuardPlugin.canBuild(player, block);
|
||||
if(Config.worldGuardPlugin != null && !Config.worldGuardPlugin.canBuild(p, b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use standard permission checking (will support basically any plugin)
|
||||
BlockBreakEvent myBreak = new BlockBreakEvent(block, player);
|
||||
Bukkit.getServer().getPluginManager().callEvent(myBreak);
|
||||
boolean hasPerm = !myBreak.isCancelled();
|
||||
BlockPlaceEvent place = new BlockPlaceEvent(block, block.getState(), block, null, player, true);
|
||||
Bukkit.getServer().getPluginManager().callEvent(place);
|
||||
if (place.isCancelled()) {
|
||||
hasPerm = false;
|
||||
BlockBreakEvent breakEvent = new BlockBreakEvent(b, p);
|
||||
Bukkit.getServer().getPluginManager().callEvent(breakEvent);
|
||||
if(breakEvent.isCancelled()){
|
||||
return false;
|
||||
}
|
||||
return hasPerm;
|
||||
BlockPlaceEvent placeEvent = new BlockPlaceEvent(b, b.getState(), b, null, p, true);
|
||||
Bukkit.getServer().getPluginManager().callEvent(placeEvent);
|
||||
return !placeEvent.isCancelled();
|
||||
}
|
||||
|
||||
boolean playerHasPermission(Player p, Block b, ArmorStandTool tool) {
|
||||
return !(tool != null && !p.isOp()
|
||||
&& (!Utils.hasPermissionNode(p, "astools.use")
|
||||
|| (ArmorStandTool.SAVE == tool && !Utils.hasPermissionNode(p, "astools.cmdblock"))
|
||||
|| (ArmorStandTool.CLONE == tool && !Utils.hasPermissionNode(p, "astools.clone"))))
|
||||
&& checkPermission(p, b);
|
||||
return (tool == null || tool.isEnabled() && Utils.hasPermissionNode(p, tool.getPermission())) && checkPermission(p, b);
|
||||
}
|
||||
}
|
@ -1,6 +1,12 @@
|
||||
# Armor Stand Tools v2.01
|
||||
# Armor Stand Tools
|
||||
# by St3venAU
|
||||
# St3venAU@gmail.com
|
||||
#
|
||||
# by St3venAU (St3venAU@gmail.com)
|
||||
# Main Config
|
||||
#
|
||||
# File generated by: v2.02
|
||||
# (If this is not the version you are running, consider deleting this
|
||||
# config to allow it to be re-created. There may be new config options)
|
||||
#
|
||||
# Features:
|
||||
# - Summon armor stands
|
||||
@ -44,4 +50,41 @@ hasBasePlate: false
|
||||
hasGravity: false
|
||||
name: ''
|
||||
invulnerable: false
|
||||
equipmentLock: false
|
||||
equipmentLock: false
|
||||
enableTool:
|
||||
headX: true
|
||||
headY: true
|
||||
headZ: true
|
||||
lArmX: true
|
||||
lArmY: true
|
||||
lArmZ: true
|
||||
rArmX: true
|
||||
rArmY: true
|
||||
rArmZ: true
|
||||
moveX: true
|
||||
moveY: true
|
||||
moveZ: true
|
||||
lLegX: true
|
||||
lLegY: true
|
||||
lLegZ: true
|
||||
rLegX: true
|
||||
rLegY: true
|
||||
rLegZ: true
|
||||
bodyX: true
|
||||
bodyY: true
|
||||
bodyZ: true
|
||||
summon: true
|
||||
gui: true
|
||||
rotat: true
|
||||
gui_clone: true
|
||||
gui_save: true
|
||||
gui_invis: true
|
||||
gui_size: true
|
||||
gui_base: true
|
||||
gui_grav: true
|
||||
gui_arms: true
|
||||
gui_name: true
|
||||
gui_slots: true
|
||||
gui_pHead: true
|
||||
gui_invul: true
|
||||
gui_move: true
|
@ -1,4 +1,12 @@
|
||||
# Armor Stand Tools Language Config
|
||||
# Armor Stand Tools
|
||||
# by St3venAU
|
||||
# St3venAU@gmail.com
|
||||
#
|
||||
# Language Config
|
||||
#
|
||||
# File generated by: v2.02
|
||||
# (If this is not the version you are running, consider deleting this
|
||||
# config to allow it to be re-created. There may be new config options)
|
||||
#
|
||||
# Edit these strings if you wish to customize the wording
|
||||
# or the language of the text used in this plugin.
|
||||
@ -50,6 +58,11 @@ invalidName: 'is not a valid minecraft username'
|
||||
wgNoPerm: 'No permission to alter an armor stand in this region'
|
||||
headFailed: 'Failed to apply the player head. Try again.'
|
||||
noCommandPerm: 'You do not have permission to use this command'
|
||||
armorStand: 'Armor Stand'
|
||||
none: 'None'
|
||||
enabled: 'Enabled'
|
||||
disabled: 'Disabled'
|
||||
guiInUse: 'This armor stands GUI is in use'
|
||||
#
|
||||
#############################
|
||||
# Tool names & descriptions #
|
||||
|
@ -1,6 +1,6 @@
|
||||
main: com.gmail.St3venAU.plugins.ArmorStandTools.Main
|
||||
name: ArmorStandTools
|
||||
version: 2.01
|
||||
version: 2.02
|
||||
author: St3venAU
|
||||
description: Armor stand manipulation tools
|
||||
softdepend: [WorldGuard, PlotSquared]
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren