diff --git a/java plugin/addons/README.md b/java plugin/addons/README.md index 2e0464b..26fa916 100644 --- a/java plugin/addons/README.md +++ b/java plugin/addons/README.md @@ -8,7 +8,7 @@ Plugins that extend MCConnect through MCDataLink (3.22+). Background and securit | BetterJoinMessages | MCConnect's join/leave messages (reward levels, `/profil`), first-join message, greeting, fixed lines per player, silent players, no message on quick reconnects | `/joinmessage` | | EasyHeadDrop | player heads (date, killer, weapon) with chance, looting bonus and anti-farming cooldown; optional mob heads and chat announcement | – | | EasyNewCraftingRecipes | own shaped, shapeless and furnace recipes, applied without a restart, problems shown on the website | – | -| SpawnElytra | double jump near the spawn or in rectangles with their own boost starts a glide without an elytra, no fall damage | `/spawnelytra` | +| SpawnElytra | double jump in rectangles and circles, each with its own boost, starts a glide without an elytra, no fall damage | `/spawnelytra` | | SpawnProtect | protected regions (box or circle around the spawn) with 13 protections, bypass, greeting | `/spawnprotect` (`/sprot`) | Build: `mvn package` in `java plugin/` (builds MCDataLink and all addons). The Dockerfile copies the addon jars to diff --git a/java plugin/addons/SpawnElytra/pom.xml b/java plugin/addons/SpawnElytra/pom.xml index 155bf86..4af1d3d 100644 --- a/java plugin/addons/SpawnElytra/pom.xml +++ b/java plugin/addons/SpawnElytra/pom.xml @@ -11,6 +11,6 @@ SpawnElytra - 2.2 + 2.3 jar diff --git a/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/Area.java b/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/Area.java index 96b8011..9c94f13 100644 --- a/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/Area.java +++ b/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/Area.java @@ -4,17 +4,25 @@ import org.bukkit.World; import org.tobias.mcdatalink.api.AddonConfig; -/** One rectangle from the settings: between two corners (in any order), with its own boost. */ +/** One area from the settings, with its own boost: a box between two corners (in any order) or a circle. */ final class Area { final String name; /** empty: the main world */ final String world; + final boolean circle, aroundSpawn; final int minX, minY, minZ, maxX, maxY, maxZ; + final int centerX, centerY, centerZ; + final int radius; + /** circle: blocks above the center that still count, 0 = no limit */ + final int height; final double boost; private Area(AddonConfig row) { name = row.getString("name", "bereich"); world = row.getString("world", "").trim(); + String shape = row.getString("shape", "rectangle"); + aroundSpawn = "spawn".equals(shape); + circle = aroundSpawn || "circle".equals(shape); int[] a = row.getPosition("pos1"); int[] b = row.getPosition("pos2"); if (a == null) a = new int[]{0, 0, 0}; @@ -25,6 +33,13 @@ private Area(AddonConfig row) { maxX = Math.max(a[0], b[0]); maxY = Math.max(a[1], b[1]); maxZ = Math.max(a[2], b[2]); + int[] center = row.getPosition("center"); + if (center == null) center = new int[]{0, 0, 0}; + centerX = center[0]; + centerY = center[1]; + centerZ = center[2]; + radius = Math.max(1, row.getInt("radius", 20)); + height = row.getInt("height", 0); boost = row.getDouble("boost", 1.0); } @@ -33,19 +48,32 @@ static Area of(AddonConfig row) { return row.getBoolean("enabled", true) ? new Area(row) : null; } - boolean contains(Location location, World main, int height) { + boolean contains(Location location, World main) { World candidate = location.getWorld(); if (candidate == null) return false; if (!(world.isEmpty() ? candidate.equals(main) : candidate.getName().equalsIgnoreCase(world))) return false; - return covers(location.getBlockX(), location.getBlockY(), location.getBlockZ(), height); + if (!circle) return inBox(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + if (aroundSpawn) { + Location spawn = candidate.getSpawnLocation(); + return inCircle(location.getX() - spawn.getX(), location.getZ() - spawn.getZ(), location.getBlockY(), spawn.getBlockY()); + } + return inCircle(location.getX() - (centerX + 0.5), location.getZ() - (centerZ + 0.5), location.getBlockY(), centerY); } - /** Both corners included; in the height from the lower corner up to height blocks above the higher one (0 = no limit). */ - boolean covers(int x, int y, int z, int height) { - return x >= minX && x <= maxX && z >= minZ && z <= maxZ && (height <= 0 || (y >= minY && y <= maxY + height)); + /** Both corners included. */ + boolean inBox(int x, int y, int z) { + return x >= minX && x <= maxX && y >= minY && y <= maxY && z >= minZ && z <= maxZ; + } + + /** dx, dz: horizontal distance to the center; in the height from its level up to height blocks above, both included. */ + boolean inCircle(double dx, double dz, int y, int levelOfCenter) { + if (height > 0 && (y < levelOfCenter || y > levelOfCenter + height)) return false; + return dx * dx + dz * dz <= (double) radius * radius; } String size() { - return (maxX - minX + 1) + " × " + (maxZ - minZ + 1) + " Blöcke, Schub " + boost; + if (!circle) return "Rechteck " + (maxX - minX + 1) + " × " + (maxZ - minZ + 1) + ", Y " + minY + " bis " + maxY + ", Schub " + boost; + return "Kreis mit Radius " + radius + " um " + (aroundSpawn ? "den Weltspawn" : centerX + " " + centerY + " " + centerZ) + + (height > 0 ? ", " + height + " Blöcke hoch" : "") + ", Schub " + boost; } } diff --git a/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/SpawnElytra.java b/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/SpawnElytra.java index 9b94259..e941f79 100644 --- a/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/SpawnElytra.java +++ b/java plugin/addons/SpawnElytra/src/main/java/org/tobias/mcconnect/spawnelytra/SpawnElytra.java @@ -43,9 +43,9 @@ import java.util.regex.Pattern; /** - * Gliding without an elytra around the spawn (the old SpawnElytra as an MCConnect addon): a double jump in the zone - * (the circle or one of the rectangles, these with their own boost) starts a glide, the landing is without fall damage. Fixed against the old plugin: the world is configurable (it was - * fixed to "world"), only players in the zone get the right to fly and only that right is taken back (it used to + * Gliding without an elytra around the spawn (the old SpawnElytra as an MCConnect addon): a double jump in one of the + * areas (rectangles and circles, each with its own boost) starts a glide, the landing is without fall damage. Fixed + * against the old plugin: the world is configurable (it was fixed to "world"), only players in the zone get the right to fly and only that right is taken back (it used to * switch off /fly of other plugins for every survival player), the right is taken back on quit (the game stores it), * no isAir() (1.14+), the action bar is not sent every 3 ticks and players who left are forgotten. */ @@ -101,14 +101,14 @@ public void onActivate(AddonConfig config) { if (area == null) continue; compiled.add(area); if (!area.world.isEmpty() && getServer().getWorld(area.world) == null) { - problems.add("Rechteck »" + area.name + "«: die Welt »" + area.world + "« gibt es auf dem Server nicht."); + problems.add("Bereich »" + area.name + "«: die Welt »" + area.world + "« gibt es auf dem Server nicht."); } } areas = Collections.unmodifiableList(compiled); handle.reportProblems(problems); if (task == null) task = getServer().getScheduler().runTaskTimer(this, this::tick, TICK_PERIOD, TICK_PERIOD); Player actor = savedBy == null ? null : getServer().getPlayer(savedBy); - if (actor != null) actor.sendMessage(ChatColor.GREEN + "SpawnElytra: " + (savedWhat != null ? savedWhat : "gespeichert. " + ChatColor.GRAY + describe(config))); + if (actor != null) actor.sendMessage(ChatColor.GREEN + "SpawnElytra: " + savedWhat); savedBy = null; savedWhat = null; } @@ -135,46 +135,13 @@ private void stop() { // ------------------------------------------------------------------ zone - private World zoneWorld(AddonConfig config) { - String name = config.getString("world", "").trim(); - if (name.isEmpty()) return getServer().getWorlds().isEmpty() ? null : getServer().getWorlds().get(0); - return getServer().getWorld(name); - } - - private double[] center(AddonConfig config, World world) { - int[] custom = config.getPosition("center"); - if ("custom".equals(config.getString("center_mode", "spawn")) && custom != null) { - return new double[]{custom[0] + 0.5, custom[1], custom[2] + 0.5}; - } - Location spawn = world.getSpawnLocation(); - return new double[]{spawn.getX(), spawn.getBlockY(), spawn.getZ()}; - } - - /** From the level of the center up to height blocks above it, both included; 0 = no limit. */ - static boolean inHeightBand(int y, int centerY, int height) { - return height <= 0 || (y >= centerY && y <= centerY + height); - } - - /** The boost at the player's place, or null outside of the zone. A rectangle goes before the circle, the first one wins. */ - private Double zoneBoost(Player player, AddonConfig config) { + /** The boost at the player's place, or null outside of all areas. The first area of the list wins. */ + private Double zoneBoost(Player player) { World main = getServer().getWorlds().isEmpty() ? null : getServer().getWorlds().get(0); - int height = config.getInt("height", 2); for (Area area : areas) { - if (area.contains(player.getLocation(), main, height)) return area.boost; + if (area.contains(player.getLocation(), main)) return area.boost; } - return inCircle(player, config) ? config.getDouble("boost", 1.0) : null; - } - - private boolean inCircle(Player player, AddonConfig config) { - if (!config.getBoolean("circle", true)) return false; - World world = zoneWorld(config); - if (world == null || !player.getWorld().equals(world)) return false; - double[] center = center(config, world); - if (!inHeightBand(player.getLocation().getBlockY(), (int) center[1], config.getInt("height", 2))) return false; - double dx = player.getLocation().getX() - center[0]; - double dz = player.getLocation().getZ() - center[2]; - double radius = config.getDouble("radius", 20); - return dx * dx + dz * dz <= radius * radius; + return null; } private static boolean eligible(Player player, AddonConfig config) { @@ -196,7 +163,7 @@ private void tick() { } continue; } - if (eligible(player, config) && zoneBoost(player, config) != null) { + if (eligible(player, config) && zoneBoost(player) != null) { if (!player.getAllowFlight()) { player.setAllowFlight(true); granted.add(uuid); @@ -239,7 +206,7 @@ public void onToggleFlight(PlayerToggleFlightEvent event) { AddonConfig config = settings; Player player = event.getPlayer(); if (config == null || !event.isFlying() || !granted.contains(player.getUniqueId())) return; - Double zone = zoneBoost(player, config); + Double zone = zoneBoost(player); if (!eligible(player, config) || zone == null) return; event.setCancelled(true); revoke(player); // no second double jump while gliding; given again after the landing @@ -308,16 +275,15 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } String action = args.length == 0 ? "info" : args[0].toLowerCase(Locale.ROOT); if (action.equals("info")) { - sender.sendMessage(ChatColor.GOLD + "SpawnElytra " + ChatColor.GRAY + describe(config)); - sender.sendMessage(ChatColor.GRAY + "/spawnelytra setcenter " + ChatColor.DARK_GRAY + "– Mittelpunkt hierher"); - sender.sendMessage(ChatColor.GRAY + "/spawnelytra radius " + ChatColor.DARK_GRAY + "– Radius ändern"); + sender.sendMessage(ChatColor.GOLD + "SpawnElytra " + ChatColor.GRAY + areas.size() + (areas.size() == 1 ? " aktiver Bereich" : " aktive Bereiche")); sender.sendMessage(ChatColor.GRAY + "/spawnelytra pos1 | pos2 " + ChatColor.DARK_GRAY + "– Ecken eines Rechtecks hierher"); sender.sendMessage(ChatColor.GRAY + "/spawnelytra create [schub] " + ChatColor.DARK_GRAY + "– Rechteck anlegen"); - sender.sendMessage(ChatColor.GRAY + "/spawnelytra delete | list " + ChatColor.DARK_GRAY + "– Rechteck löschen, alle zeigen"); + sender.sendMessage(ChatColor.GRAY + "/spawnelytra circle [schub] [höhe] " + ChatColor.DARK_GRAY + "– Kreis um deine Position"); + sender.sendMessage(ChatColor.GRAY + "/spawnelytra delete | list " + ChatColor.DARK_GRAY + "– Bereich löschen, alle zeigen"); return true; } if (action.equals("list")) { - if (areas.isEmpty()) sender.sendMessage(ChatColor.GRAY + "Keine aktiven Rechtecke."); + if (areas.isEmpty()) sender.sendMessage(ChatColor.GRAY + "Keine aktiven Bereiche."); for (Area area : areas) sender.sendMessage(ChatColor.GOLD + area.name + ChatColor.GRAY + " – " + area.size()); return true; } @@ -325,28 +291,14 @@ public boolean onCommand(CommandSender sender, Command command, String label, St setCorner((Player) sender, action.equals("pos1") ? 0 : 1); return true; } - JsonObject changed = config.toJson(); - String what = null; - if ((action.equals("create") && sender instanceof Player || action.equals("delete")) && args.length > 1) { - what = changeAreas(sender, changed, action, args); - if (what == null) return true; - } else if (action.equals("setcenter") && sender instanceof Player) { - Location at = ((Player) sender).getLocation(); - changed.addProperty("center_mode", "custom"); - changed.add("center", AddonConfig.position(at.getBlockX(), at.getBlockY(), at.getBlockZ())); - World main = getServer().getWorlds().get(0); - changed.addProperty("world", at.getWorld().equals(main) ? "" : at.getWorld().getName()); - } else if (action.equals("radius") && args.length > 1) { - try { - changed.addProperty("radius", Integer.parseInt(args[1])); - } catch (NumberFormatException e) { - sender.sendMessage(ChatColor.RED + "Der Radius muss eine Zahl sein."); - return true; - } - } else { - sender.sendMessage(ChatColor.GRAY + "Benutzung: /spawnelytra [info|setcenter|radius |pos1|pos2|create [schub]|delete |list]"); + boolean ingameOnly = action.equals("create") || action.equals("circle"); + if (!(ingameOnly && sender instanceof Player || action.equals("delete")) || args.length < 2) { + sender.sendMessage(ChatColor.GRAY + "Benutzung: /spawnelytra [info|pos1|pos2|create [schub]|circle [schub] [höhe]|delete |list]"); return true; } + JsonObject changed = config.toJson(); + String what = changeAreas(sender, changed, action, args); + if (what == null) return true; Player actor = sender instanceof Player ? (Player) sender : null; if (handle.saveConfig(changed, actor)) { savedBy = actor == null ? null : actor.getUniqueId(); @@ -379,73 +331,91 @@ private String changeAreas(CommandSender sender, JsonObject changed, String acti } if (action.equals("delete")) { if (index < 0) { - sender.sendMessage(ChatColor.RED + "Das Rechteck " + name + " gibt es nicht. " + ChatColor.GRAY + "/spawnelytra list"); + sender.sendMessage(ChatColor.RED + "Den Bereich " + name + " gibt es nicht. " + ChatColor.GRAY + "/spawnelytra list"); return null; } rows.remove(index); changed.add("regions", rows); - return "Rechteck " + name + " gelöscht."; - } - Location[] selection = selections.get(((Player) sender).getUniqueId()); - if (selection == null || selection[0] == null || selection[1] == null) { - sender.sendMessage(ChatColor.RED + "Setze zuerst beide Ecken mit /spawnelytra pos1 und pos2."); - return null; + return "Bereich " + name + " gelöscht."; } if (!NAME.matcher(name).matches()) { sender.sendMessage(ChatColor.RED + "Der Name darf nur Buchstaben, Ziffern, _ und - haben (höchstens 32)."); return null; } if (index >= 0) { - sender.sendMessage(ChatColor.RED + "Das Rechteck " + name + " gibt es schon."); + sender.sendMessage(ChatColor.RED + "Den Bereich " + name + " gibt es schon."); return null; } - double boost = 1.0; - if (args.length > 2) { - try { - boost = Double.parseDouble(args[2]); - } catch (NumberFormatException e) { - boost = -1; + Player player = (Player) sender; + boolean circle = action.equals("circle"); + JsonObject row = new JsonObject(); + row.addProperty("name", name); + row.addProperty("enabled", true); + String flatHint = ""; + if (circle) { + Double radius = args.length > 2 ? number(args[2], 2, 500) : null; + Double boost = args.length > 3 ? number(args[3], 0, MAX_BOOST) : Double.valueOf(1.0); + Double height = args.length > 4 ? number(args[4], 0, 384) : Double.valueOf(0); + if (radius == null || boost == null || height == null) { + sender.sendMessage(ChatColor.RED + "Radius 2 bis 500, Schub 0 bis 5, Höhe 0 bis 384 (0 = ohne Grenze)."); + return null; + } + Location at = player.getLocation(); + row.addProperty("shape", "circle"); + row.addProperty("world", worldName(at.getWorld())); + row.add("center", AddonConfig.position(at.getBlockX(), at.getBlockY(), at.getBlockZ())); + row.addProperty("radius", radius.intValue()); + row.addProperty("height", height.intValue()); + row.addProperty("boost", boost); + } else { + Location[] selection = selections.get(player.getUniqueId()); + if (selection == null || selection[0] == null || selection[1] == null) { + sender.sendMessage(ChatColor.RED + "Setze zuerst beide Ecken mit /spawnelytra pos1 und pos2."); + return null; } - if (!(boost >= 0 && boost <= MAX_BOOST)) { + Double boost = args.length > 2 ? number(args[2], 0, MAX_BOOST) : Double.valueOf(1.0); + if (boost == null) { sender.sendMessage(ChatColor.RED + "Der Schub muss eine Zahl von 0 bis 5 sein."); return null; } + row.addProperty("shape", "rectangle"); + row.addProperty("world", worldName(selection[0].getWorld())); + row.add("pos1", AddonConfig.position(selection[0].getBlockX(), selection[0].getBlockY(), selection[0].getBlockZ())); + row.add("pos2", AddonConfig.position(selection[1].getBlockX(), selection[1].getBlockY(), selection[1].getBlockZ())); + row.addProperty("boost", boost); + if (selection[0].getBlockY() == selection[1].getBlockY()) { + flatHint = ChatColor.GRAY + " Beide Ecken liegen auf einer Höhe: zum Abspringen muss eine mindestens 2 Blöcke höher sein."; + } } - JsonObject row = new JsonObject(); - row.addProperty("name", name); - row.addProperty("enabled", true); - World world = selection[0].getWorld(); - row.addProperty("world", world.equals(getServer().getWorlds().get(0)) ? "" : world.getName()); - row.add("pos1", AddonConfig.position(selection[0].getBlockX(), selection[0].getBlockY(), selection[0].getBlockZ())); - row.add("pos2", AddonConfig.position(selection[1].getBlockX(), selection[1].getBlockY(), selection[1].getBlockZ())); - row.addProperty("boost", boost); rows.add(row); changed.add("regions", rows); - return "Rechteck " + name + " angelegt."; + return "Bereich " + name + " angelegt." + flatHint; + } + + private String worldName(World world) { + return world.equals(getServer().getWorlds().get(0)) ? "" : world.getName(); + } + + /** The number in [min, max], or null. */ + private static Double number(String text, double min, double max) { + try { + double value = Double.parseDouble(text); + return value >= min && value <= max ? Double.valueOf(value) : null; + } catch (NumberFormatException e) { + return null; + } } @Override public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { if (args.length != 1) return Collections.emptyList(); List matches = new ArrayList<>(); - for (String option : Arrays.asList("info", "setcenter", "radius", "pos1", "pos2", "create", "delete", "list")) { + for (String option : Arrays.asList("info", "pos1", "pos2", "create", "circle", "delete", "list")) { if (option.startsWith(args[0].toLowerCase(Locale.ROOT))) matches.add(option); } return matches; } - private String describe(AddonConfig config) { - String rectangles = areas.isEmpty() ? "" : " · " + areas.size() + (areas.size() == 1 ? " Rechteck" : " Rechtecke"); - if (!config.getBoolean("circle", true)) return "Kreis aus" + rectangles; - World world = zoneWorld(config); - if (world == null) return "Die Welt »" + config.getString("world", "") + "« gibt es nicht."; - double[] center = center(config, world); - int height = config.getInt("height", 2); - return "Radius " + config.getInt("radius", 20) + " um " + (int) Math.floor(center[0]) + " / " + (int) Math.floor(center[2]) - + (height > 0 ? ", Höhe Y " + (int) center[1] + " bis " + ((int) center[1] + height) : "") - + " in " + world.getName() + ("custom".equals(config.getString("center_mode", "spawn")) ? "" : " (Weltspawn)") + rectangles; - } - private static String color(String text) { return ChatColor.translateAlternateColorCodes('&', text); } diff --git a/java plugin/addons/SpawnElytra/src/main/resources/mcconnect-addon.json b/java plugin/addons/SpawnElytra/src/main/resources/mcconnect-addon.json index 1cc7de0..45ada58 100644 --- a/java plugin/addons/SpawnElytra/src/main/resources/mcconnect-addon.json +++ b/java plugin/addons/SpawnElytra/src/main/resources/mcconnect-addon.json @@ -2,47 +2,43 @@ "id": "spawnelytra", "name": "SpawnElytra", "summary": "Am Spawn zweimal Leertaste – und losgleiten, ganz ohne Elytra.", - "description": "Rund um den Spawn können Spieler mit doppeltem Sprung losgleiten wie mit einer Elytra. Die Landung ist ohne Fallschaden. Bereich, Startschub, Hinweistext und wer darf, stellst du hier ein; den Mittelpunkt setzt du auch im Spiel mit /spawnelytra setcenter.", + "description": "In den Bereichen, die du anlegst, können Spieler mit doppeltem Sprung losgleiten wie mit einer Elytra. Die Landung ist ohne Fallschaden. Rechtecke und Kreise mit eigenem Startschub, Hinweistext und wer darf, stellst du hier ein; Bereiche legst du auch im Spiel an.", "features": [ - "Doppelsprung am Spawn startet einen Gleitflug", - "Einstellbarer Startschub", + "Doppelsprung in deinen Bereichen startet einen Gleitflug", + "Beliebig viele Rechtecke und Kreise, jeweils mit eigenem Startschub", "Landen ohne Fallschaden", - "Mittelpunkt: Weltspawn oder eigener Punkt (/spawnelytra setcenter)", - "Bereich in der Höhe begrenzbar", - "Mehrere rechteckige Bereiche mit eigenem Startschub (/spawnelytra pos1, pos2, create)", + "Kreise optional in der Höhe begrenzbar", + "Im Spiel anlegen: /spawnelytra pos1, pos2, create, circle", "Hinweis in der Actionbar", "Lässt /fly anderer Plugins in Ruhe" ], "settings": [ { - "title": "Bereich", + "title": "Bereiche", + "description": "Ohne Bereich kann niemand losgleiten. Im Spiel: Rechteck mit /spawnelytra pos1 und pos2 an den Ecken, dann /spawnelytra create [schub]. Kreis um deine Position mit /spawnelytra circle [schub] [höhe]. Liegt ein Spieler in mehreren Bereichen, gilt der erste der Liste.", "fields": [ - {"key": "circle", "type": "toggle", "default": true, "label": "Kreis um den Mittelpunkt", - "help": "Aus = nur die Rechtecke weiter unten."}, - {"key": "world", "type": "text", "max": 64, "default": "", "label": "Welt", "help": "Leer = die Hauptwelt.", "suggest": "worlds", - "show_if": {"key": "circle", "is": true}}, - {"key": "center_mode", "type": "select", "default": "spawn", "label": "Mittelpunkt", "show_if": {"key": "circle", "is": true}, - "options": [{"value": "spawn", "label": "Spawnpunkt der Welt"}, {"value": "custom", "label": "Eigener Punkt"}]}, - {"key": "center", "type": "position", "default": {"x": 0, "y": 64, "z": 0}, "label": "Eigener Punkt", - "help": "Im Spiel: /spawnelytra setcenter an der gewünschten Stelle.", "show_if": {"key": "center_mode", "in": ["custom"]}}, - {"key": "radius", "type": "number", "default": 20, "min": 2, "max": 500, "step": 1, "unit": "Blöcke", "label": "Radius", - "help": "Gezählt wird der Abstand in der Waagerechten.", "show_if": {"key": "circle", "is": true}}, - {"key": "height", "type": "number", "default": 2, "min": 0, "max": 384, "step": 1, "unit": "Blöcke", "label": "Höhe", - "help": "Kreis: von der Y-Koordinate des Mittelpunkts bis so viele Blöcke darüber. Rechteck: von der tieferen Ecke bis so viele Blöcke über der höheren. Darunter nie. 0 = ohne Höhengrenze."} - ] - }, - { - "title": "Rechtecke", - "description": "Im Spiel: stell dich auf die Ecken und setze sie mit /spawnelytra pos1 und pos2, dann /spawnelytra create [schub]. Liegt ein Spieler in mehreren Bereichen, gilt das erste Rechteck der Liste, danach der Kreis.", - "fields": [ - {"key": "regions", "type": "list", "label": "Rechtecke", "item_label": "Rechteck", "title_key": "name", "max_items": 50, + {"key": "regions", "type": "list", "label": "Bereiche", "item_label": "Bereich", "title_key": "name", "max_items": 50, "fields": [ {"key": "name", "type": "text", "max": 32, "required": true, "default": "bereich", "label": "Name", "help": "Für die Befehle im Spiel, ohne Leerzeichen."}, {"key": "enabled", "type": "toggle", "default": true, "label": "Aktiv"}, + {"key": "shape", "type": "select", "default": "rectangle", "label": "Form", + "options": [{"value": "rectangle", "label": "Rechteck zwischen zwei Ecken"}, + {"value": "circle", "label": "Kreis um einen Punkt"}, + {"value": "spawn", "label": "Kreis um den Weltspawn"}]}, {"key": "world", "type": "text", "max": 64, "default": "", "label": "Welt", "help": "Leer = die Hauptwelt.", "suggest": "worlds"}, - {"key": "pos1", "type": "position", "default": {"x": -10, "y": 64, "z": -10}, "label": "Ecke 1"}, - {"key": "pos2", "type": "position", "default": {"x": 10, "y": 64, "z": 10}, "label": "Ecke 2"}, + {"key": "pos1", "type": "position", "default": {"x": -10, "y": 64, "z": -10}, "label": "Ecke 1", + "show_if": {"key": "shape", "in": ["rectangle"]}}, + {"key": "pos2", "type": "position", "default": {"x": 10, "y": 66, "z": 10}, "label": "Ecke 2", + "help": "Der Bereich reicht genau von der Höhe der einen Ecke bis zu der der anderen.", + "show_if": {"key": "shape", "in": ["rectangle"]}}, + {"key": "center", "type": "position", "default": {"x": 0, "y": 64, "z": 0}, "label": "Mittelpunkt", + "show_if": {"key": "shape", "in": ["circle"]}}, + {"key": "radius", "type": "number", "default": 20, "min": 2, "max": 500, "step": 1, "unit": "Blöcke", "label": "Radius", + "show_if": {"key": "shape", "in": ["circle", "spawn"]}}, + {"key": "height", "type": "number", "default": 0, "min": 0, "max": 384, "step": 1, "unit": "Blöcke", "label": "Höhe (optional)", + "help": "Gilt von der Y-Koordinate des Mittelpunkts bis so viele Blöcke darüber, darunter nie. 0 = ohne Höhengrenze.", + "show_if": {"key": "shape", "in": ["circle", "spawn"]}}, {"key": "boost", "type": "number", "default": 1.0, "min": 0, "max": 5, "step": 0.1, "label": "Startschub", "help": "0 = kein Schub, nur losgleiten."} ]} @@ -51,8 +47,6 @@ { "title": "Flug", "fields": [ - {"key": "boost", "type": "number", "default": 1.0, "min": 0, "max": 5, "step": 0.1, "label": "Startschub im Kreis", - "help": "0 = kein Schub, nur losgleiten. Rechtecke haben ihren eigenen."}, {"key": "no_fall_damage", "type": "toggle", "default": true, "label": "Landen ohne Fallschaden"}, {"key": "allow_rockets", "type": "toggle", "default": false, "label": "Raketen zum Beschleunigen erlauben"}, {"key": "gamemodes", "type": "multiselect", "default": ["survival", "adventure"], "label": "Für Spieler im Modus", @@ -63,7 +57,7 @@ { "title": "Hinweis", "fields": [ - {"key": "actionbar", "type": "toggle", "default": true, "label": "Hinweis in der Actionbar am Spawn"}, + {"key": "actionbar", "type": "toggle", "default": true, "label": "Hinweis in der Actionbar im Bereich"}, {"key": "actionbar_text", "type": "text", "colors": true, "max": 80, "default": "&bSpawnElytra bereit &8| &3zweimal Leertaste zum Fliegen", "label": "Text", "show_if": {"key": "actionbar", "is": true}} diff --git a/java plugin/addons/SpawnElytra/src/main/resources/plugin.yml b/java plugin/addons/SpawnElytra/src/main/resources/plugin.yml index 12f00df..75384b4 100644 --- a/java plugin/addons/SpawnElytra/src/main/resources/plugin.yml +++ b/java plugin/addons/SpawnElytra/src/main/resources/plugin.yml @@ -4,17 +4,17 @@ main: org.tobias.mcconnect.spawnelytra.SpawnElytra api-version: '1.13' depend: [MCDataLink] authors: [Tobias Auer] -description: Gleiten ohne Elytra rund um den Spawn (MCConnect-Erweiterung) +description: Gleiten ohne Elytra in festgelegten Bereichen (MCConnect-Erweiterung) website: mc.tobisit.de commands: spawnelytra: - description: Mittelpunkt, Radius und Rechtecke von SpawnElytra setzen - usage: /spawnelytra [info|setcenter|radius |pos1|pos2|create [schub]|delete |list] + description: Bereiche von SpawnElytra anlegen und löschen + usage: /spawnelytra [info|pos1|pos2|create [schub]|circle [schub] [höhe]|delete |list] permission: spawnelytra.admin permissions: spawnelytra.admin: description: SpawnElytra im Spiel einstellen default: op spawnelytra.use: - description: Am Spawn gleiten (wenn in MCConnect "Nur mit Berechtigung" an ist) + description: In den Bereichen gleiten (wenn in MCConnect "Nur mit Berechtigung" an ist) default: true diff --git a/tests/test_addons.py b/tests/test_addons.py index 4103bb3..d30a010 100644 --- a/tests/test_addons.py +++ b/tests/test_addons.py @@ -411,8 +411,17 @@ def spawnelytra_settings(): return addons.check_manifest(json.load(file))["settings"] -def test_spawnelytra_rectangle_keeps_its_own_boost_in_range(): - row = {"name": "start", "pos1": {"x": 0, "y": 64, "z": 0}, "pos2": {"x": 5, "y": 64, "z": 5}} - values, errors = addons.normalize(spawnelytra_settings(), {"regions": [dict(row, boost=2.5), dict(row, name="b", boost=99)]}) - assert [r["boost"] for r in values["regions"]] == [2.5, 5] and len(errors) == 1 - assert values["circle"] is True and values["height"] == 2 # settings saved before these fields keep working +def test_spawnelytra_areas_keep_their_own_boost_in_range(): + rectangle = {"name": "start", "pos1": {"x": 0, "y": 64, "z": 0}, "pos2": {"x": 5, "y": 66, "z": 5}} + circle = {"name": "kreis", "shape": "circle", "center": {"x": 0, "y": 64, "z": 0}, "radius": 9, "boost": 2, "height": 3} + values, errors = addons.normalize(spawnelytra_settings(), {"regions": [dict(rectangle, boost=2.5), dict(rectangle, name="b", boost=99), circle]}) + assert [r["boost"] for r in values["regions"]] == [2.5, 5, 2] and len(errors) == 1 + assert values["regions"][0]["shape"] == "rectangle" # rectangles saved before the form existed + assert values["regions"][2]["height"] == 3 + + +def test_spawnelytra_has_no_area_by_default(): + values, errors = addons.normalize(spawnelytra_settings(), {}) + assert values["regions"] == [] and not errors + assert not {"radius", "center", "height", "boost"} & set(values) +