From d08efea2c02754b10cfb779907f908f03c5bb905 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Mon, 12 Jan 2026 21:32:36 +0100 Subject: [PATCH 01/17] Abstraction layer for these properties --- .../main/java/at/pavlov/cannons/Cannons.java | 9 +- .../hooks/movecraft/MovecraftUtils.java | 18 ++++ .../listener/CraftDetectListener.java | 39 +------- .../hooks/movecraft/type/CannonCheck.java | 10 ++ .../movecraft/type/CannonProperties.java | 34 +++++++ .../hooks/movecraft/type/CraftKeys.java | 7 ++ .../hooks/movecraft/type/MaxCannonsEntry.java | 17 +++- .../movecraft/type/MaxCannonsProperty.java | 50 ---------- .../hooks/movecraft/type/PropertyWrapper.java | 94 +++++++++++++++++++ 9 files changed, 183 insertions(+), 95 deletions(-) create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonCheck.java create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java delete mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsProperty.java create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java index 8d0ebc69..3a60ac92 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java @@ -16,7 +16,7 @@ import at.pavlov.cannons.exchange.ExchangeLoader; import at.pavlov.cannons.hooks.VaultHook; import at.pavlov.cannons.hooks.movecraft.MovecraftHook; -import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsProperty; +import at.pavlov.cannons.hooks.movecraft.type.CannonProperties; import at.pavlov.cannons.hooks.movecraftcombat.MovecraftCombatHook; import at.pavlov.cannons.hooks.papi.PlaceholderAPIHook; import at.pavlov.cannons.listener.BlockListener; @@ -34,15 +34,12 @@ import at.pavlov.cannons.utils.CannonSelector; import at.pavlov.cannons.utils.TimeUtils; import at.pavlov.internal.CLogger; -import at.pavlov.internal.Hook; import at.pavlov.internal.HookManager; import at.pavlov.internal.Key; import at.pavlov.internal.ModrinthUpdateChecker; import at.pavlov.internal.key.registries.Registries; import at.pavlov.internal.projectile.definition.KeyedDefaultProjectile; import lombok.Getter; -import org.bstats.bukkit.Metrics; -import org.bstats.charts.AdvancedPie; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; @@ -59,8 +56,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.text.DecimalFormat; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; import java.util.logging.Logger; @@ -127,7 +122,7 @@ public void onLoad() { if (config.isMovecraftEnabled()) { try { Class.forName("net.countercraft.movecraft.craft.type.property.Property"); - MaxCannonsProperty.register(); + CannonProperties.register(); } catch (Exception ignored) { } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java index b36753bc..27ec34d4 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java @@ -6,13 +6,17 @@ import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PilotedCraft; import net.countercraft.movecraft.craft.SubCraft; +import net.countercraft.movecraft.util.Pair; import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; +import static net.countercraft.movecraft.craft.type.TypeData.NUMERIC_PREFIX; + public class MovecraftUtils { public static Set getCannons(Craft craft) { @@ -49,4 +53,18 @@ public static UUID getPlayerFromCraft(Craft craft) { // Return null if all else fails return null; } + + public static @NotNull Pair parseLimit(@NotNull Object input) { + if (!(input instanceof String str)) { + return new Pair<>(false, (double) input); + } + + if (!str.contains(NUMERIC_PREFIX)) { + return new Pair<>(false, Double.valueOf(str)); + } + + String[] parts = str.split(NUMERIC_PREFIX); + int val = Integer.parseInt(parts[1]); + return new Pair<>(true, val); + } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index ed05c327..f500123b 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -1,16 +1,14 @@ package at.pavlov.cannons.hooks.movecraft.listener; import at.pavlov.cannons.Cannons; -import at.pavlov.cannons.Enum.BreakCause; import at.pavlov.cannons.cannon.Cannon; -import at.pavlov.cannons.cannon.CannonManager; import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; +import at.pavlov.cannons.hooks.movecraft.type.CannonProperties; import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PlayerCraft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftDetectEvent; -import net.countercraft.movecraft.events.CraftSinkEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -19,8 +17,6 @@ import java.util.Map; import java.util.Set; -import static at.pavlov.cannons.hooks.movecraft.type.MaxCannonsProperty.MAX_CANNONS; - public class CraftDetectListener implements Listener { private static final Set notifyError = new HashSet<>(); private static final Cannons cannon = Cannons.getPlugin(); @@ -35,7 +31,7 @@ public void onCraftDetect(CraftDetectEvent e) { if (!(craft instanceof PlayerCraft)) return; - Set maxCannons = getCannonProperty(type); + Set maxCannons = CannonProperties.MAX_CANNONS.get(type); if (maxCannons.isEmpty()) return; // Return if empty set to improve performance @@ -49,22 +45,17 @@ public void onCraftDetect(CraftDetectEvent e) { printCannonCount(cannonCount); // Check designs against maxCannons - int size = craft.getOrigBlockCount(); - for (var entry : maxCannons) { - if (!(entry instanceof MaxCannonsEntry max)) - throw new IllegalStateException("maxCannons must be a set of MaxCannonsEntry."); - + for (var max : maxCannons) { var cannonName = max.getName(); var count = cannonCount.get(cannonName.toLowerCase()); if (count == null) continue; - var result = max.detect(count, size); + var result = max.check(craft, cannonCount); result.ifPresent( error -> { e.setCancelled(true); - e.setFailMessage("Detection Failed! You have too many cannons of the following type on this craft: " - + cannonName + ": " + error); + e.setFailMessage("Detection Failed! " + error); }); } } @@ -74,24 +65,4 @@ private void printCannonCount(Map cannonCount) { cannon.logDebug("Cannon found: " + entry.getKey() + " | " + entry.getValue()); } } - - private Set getCannonProperty(CraftType type) { - try { - Object objectProperty = type.getObjectProperty(MAX_CANNONS); - if (objectProperty instanceof Set property) { - return (Set) property; - } else { - throw new IllegalStateException("maxCannons must be a set."); - } - } catch (Exception exception) { - notifyError.add(type); - cannon.logSevere( - "Failed to get maxCannons property from craft " + - type.getStringProperty(CraftType.NAME) + - " maxCannons won't be applied. - Cause: " + - exception.getMessage() - ); - return Set.of(); - } - } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonCheck.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonCheck.java new file mode 100644 index 00000000..ce6973b2 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonCheck.java @@ -0,0 +1,10 @@ +package at.pavlov.cannons.hooks.movecraft.type; + +import net.countercraft.movecraft.craft.Craft; + +import java.util.Map; +import java.util.Optional; + +public interface CannonCheck { + Optional check(Craft craft, Map cannonCountMap); +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java new file mode 100644 index 00000000..97070070 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java @@ -0,0 +1,34 @@ +package at.pavlov.cannons.hooks.movecraft.type; + +import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; +import net.countercraft.movecraft.craft.type.TypeData; + +import java.util.HashSet; +import java.util.Set; + +public class CannonProperties { + public static final PropertyWrapper MAX_CANNONS = new PropertyWrapper<>( + CraftKeys.MAX_CANNONS, + Set.class, + Set::of + ); + + public static void register() { + MAX_CANNONS.register((data, type, fileKey, namespacedKey) -> { + var map = data.getData(fileKey).getBackingData(); + if (map.isEmpty()) + throw new TypeData.InvalidValueException("Value for " + fileKey + " must not be an empty map"); + + Set maxCannons = new HashSet<>(); + for (var entry : map.entrySet()) { + if (entry.getKey() == null) + throw new TypeData.InvalidValueException("Keys for " + fileKey + " must be a string cannon name."); + + String name = entry.getKey(); + var limit = MovecraftUtils.parseLimit(entry.getValue()); + maxCannons.add(new MaxCannonsEntry(name, limit)); + } + return maxCannons; + }, (type -> new HashSet<>())); + } +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java new file mode 100644 index 00000000..9d330703 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java @@ -0,0 +1,7 @@ +package at.pavlov.cannons.hooks.movecraft.type; + +import org.bukkit.NamespacedKey; + +public class CraftKeys { + public static NamespacedKey MAX_CANNONS = new NamespacedKey("cannons-revamped", "max_cannons"); +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java index 71433468..5e76a84a 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java @@ -1,13 +1,15 @@ package at.pavlov.cannons.hooks.movecraft.type; import lombok.Getter; +import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.util.Pair; import org.jetbrains.annotations.NotNull; +import java.util.Map; import java.util.Optional; -public class MaxCannonsEntry { - @Getter +@Getter +public class MaxCannonsEntry implements CannonCheck { private final String name; private final double max; private final boolean numericMax; @@ -34,13 +36,20 @@ public boolean check(int count, int size) { public Optional detect(int count, int size) { if (numericMax) { if (count > max) - return Optional.of(String.format("%d > %d", count, (int) max)); + return Optional.of(String.format("You have too many %s cannon types : %d > %d", name, count, (int) max)); } else { double blockPercent = 100D * count / size; if (blockPercent > max) - return Optional.of(String.format("%.2f%% > %.2f%%", blockPercent, max)); + return Optional.of(String.format("You have too many %s cannon types : %.2f%% > %.2f%%", name, blockPercent, max)); } return Optional.empty(); } + + @Override + public Optional check(Craft craft, Map cannonCountMap) { + Integer count = cannonCountMap.get(name); + if (count == null) return Optional.empty(); + return detect(count, craft.getOrigBlockCount()); + } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsProperty.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsProperty.java deleted file mode 100644 index 3a5523ce..00000000 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsProperty.java +++ /dev/null @@ -1,50 +0,0 @@ -package at.pavlov.cannons.hooks.movecraft.type; - -import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.craft.type.TypeData; -import net.countercraft.movecraft.craft.type.property.ObjectPropertyImpl; -import net.countercraft.movecraft.util.Pair; -import org.bukkit.NamespacedKey; -import org.jetbrains.annotations.NotNull; - -import java.util.HashSet; -import java.util.Set; - -import static net.countercraft.movecraft.craft.type.TypeData.NUMERIC_PREFIX; - -public class MaxCannonsProperty { - public static NamespacedKey MAX_CANNONS = new NamespacedKey("cannons-revamped", "max_cannons"); - - private static @NotNull Pair parseLimit(@NotNull Object input) { - if (!(input instanceof String str)) { - return new Pair<>(false, (double) input); - } - - if (!str.contains(NUMERIC_PREFIX)) { - return new Pair<>(false, Double.valueOf(str)); - } - - String[] parts = str.split(NUMERIC_PREFIX); - int val = Integer.parseInt(parts[1]); - return new Pair<>(true, val); - } - - public static void register() { - CraftType.registerProperty(new ObjectPropertyImpl("maxCannons", MAX_CANNONS, (data, type, fileKey, namespacedKey) -> { - var map = data.getData(fileKey).getBackingData(); - if (map.isEmpty()) - throw new TypeData.InvalidValueException("Value for " + fileKey + " must not be an empty map"); - - Set maxCannons = new HashSet<>(); - for (var entry : map.entrySet()) { - if (entry.getKey() == null) - throw new TypeData.InvalidValueException("Keys for " + fileKey + " must be a string cannon name."); - - String name = entry.getKey(); - var limit = parseLimit(entry.getValue()); - maxCannons.add(new MaxCannonsEntry(name, limit)); - } - return maxCannons; - }, (type -> new HashSet<>()))); - } -} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java new file mode 100644 index 00000000..f98046e2 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java @@ -0,0 +1,94 @@ +package at.pavlov.cannons.hooks.movecraft.type; + +import at.pavlov.cannons.Cannons; +import lombok.AllArgsConstructor; +import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.craft.type.TypeData; +import net.countercraft.movecraft.craft.type.property.ObjectPropertyImpl; +import net.countercraft.movecraft.util.functions.QuadFunction; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +@AllArgsConstructor +public class PropertyWrapper { + public static final Set notifyError = new HashSet<>(); + + public final NamespacedKey key; + public final Class clazz; + public final Supplier errorFallback; + + private static String snakeToCamel(String input) { + if (input == null || input.isEmpty()) { + return input; + } + + StringBuilder result = new StringBuilder(); + boolean capitalizeNext = false; + + for (char c : input.toCharArray()) { + if (c == '_') { + capitalizeNext = true; + } else if (capitalizeNext) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + result.append(c); + } + } + + return result.toString(); + } + + private record ShutUp(QuadFunction otherType) implements QuadFunction { + @Override + public Object apply(TypeData typeData, CraftType craftType, String s, NamespacedKey namespacedKey) { + return otherType.apply(typeData, craftType, s, namespacedKey); + } + } + + private record ShutUp2(Function otherType) implements Function { + @Override + public Object apply(CraftType craftType) { + return otherType.apply(craftType); + } + } + + public void register(@NotNull QuadFunction loadProvider) { + register(loadProvider, null); + } + + public void register(@NotNull QuadFunction loadProvider, @Nullable Function defaultProvider) { + String configName = snakeToCamel(key.getKey()); + if (defaultProvider == null) { + CraftType.registerProperty(new ObjectPropertyImpl(configName, key, new ShutUp<>(loadProvider))); + } else { + CraftType.registerProperty(new ObjectPropertyImpl(configName, key, new ShutUp<>(loadProvider), new ShutUp2<>(defaultProvider))); + } + } + + public T get(CraftType type) { + try { + Object objectProperty = type.getObjectProperty(key); + if (clazz.isInstance(objectProperty)) { + return clazz.cast(objectProperty); + } else { + throw new IllegalStateException("maxCannons must be a set."); + } + } catch (Exception exception) { + notifyError.add(type); + Cannons.getPlugin().logSevere( + "Failed to get maxCannons property from craft " + + type.getStringProperty(CraftType.NAME) + + " maxCannons won't be applied. - Cause: " + + exception.getMessage() + ); + return errorFallback.get(); + } + } +} From 42703a3b04f997c0485ec0c940325481e81e1013 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Mon, 12 Jan 2026 21:47:19 +0100 Subject: [PATCH 02/17] Use PropertyWrapper#notifyError and fix error handling --- .../hooks/movecraft/listener/CraftDetectListener.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index f500123b..77929902 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -5,6 +5,7 @@ import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; import at.pavlov.cannons.hooks.movecraft.type.CannonProperties; import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; +import at.pavlov.cannons.hooks.movecraft.type.PropertyWrapper; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PlayerCraft; import net.countercraft.movecraft.craft.type.CraftType; @@ -13,19 +14,17 @@ import org.bukkit.event.Listener; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; public class CraftDetectListener implements Listener { - private static final Set notifyError = new HashSet<>(); private static final Cannons cannon = Cannons.getPlugin(); @EventHandler public void onCraftDetect(CraftDetectEvent e) { Craft craft = e.getCraft(); CraftType type = craft.getType(); - if (notifyError.contains(type)) + if (PropertyWrapper.notifyError.contains(type)) return; if (!(craft instanceof PlayerCraft)) @@ -53,10 +52,12 @@ public void onCraftDetect(CraftDetectEvent e) { var result = max.check(craft, cannonCount); - result.ifPresent( error -> { + if (result.isPresent()) { + String error = result.get(); e.setCancelled(true); e.setFailMessage("Detection Failed! " + error); - }); + return; + } } } From 5a56c4846438538475f4fd38d54056d2e5db5df2 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Mon, 12 Jan 2026 23:53:49 +0100 Subject: [PATCH 03/17] Fix lowercase bug --- .../cannons/hooks/movecraft/listener/CraftDetectListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index 77929902..8ee2c19d 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -38,7 +38,7 @@ public void onCraftDetect(CraftDetectEvent e) { Set cannons = MovecraftUtils.getCannons(craft); Map cannonCount = new HashMap<>(); for (var cannon : cannons) { - String design = cannon.getCannonDesign().getDesignName().toLowerCase(); + String design = cannon.getCannonDesign().getDesignID(); cannonCount.compute(design, (key, value) -> (value == null) ? 1 : value + 1); } printCannonCount(cannonCount); From 4a49fa61dc0ef8ae6dd23c06e60ace37d9d64460 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Mon, 12 Jan 2026 23:55:47 +0100 Subject: [PATCH 04/17] Allow list as keys --- .../main/java/at/pavlov/cannons/Cannons.java | 2 +- .../hooks/movecraft/MovecraftUtils.java | 14 +++++++++++ .../listener/CraftDetectListener.java | 9 ++------ .../hooks/movecraft/type/MaxCannonsEntry.java | 23 +++++++++++++------ .../{ => properties}/CannonProperties.java | 8 ++++--- .../{ => properties}/PropertyWrapper.java | 2 +- 6 files changed, 39 insertions(+), 19 deletions(-) rename cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/{ => properties}/CannonProperties.java (77%) rename cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/{ => properties}/PropertyWrapper.java (98%) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java index 3a60ac92..7f0ffacd 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java @@ -16,7 +16,7 @@ import at.pavlov.cannons.exchange.ExchangeLoader; import at.pavlov.cannons.hooks.VaultHook; import at.pavlov.cannons.hooks.movecraft.MovecraftHook; -import at.pavlov.cannons.hooks.movecraft.type.CannonProperties; +import at.pavlov.cannons.hooks.movecraft.type.properties.CannonProperties; import at.pavlov.cannons.hooks.movecraftcombat.MovecraftCombatHook; import at.pavlov.cannons.hooks.papi.PlaceholderAPIHook; import at.pavlov.cannons.listener.BlockListener; diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java index 27ec34d4..88ed09ab 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java @@ -67,4 +67,18 @@ public static UUID getPlayerFromCraft(Craft craft) { int val = Integer.parseInt(parts[1]); return new Pair<>(true, val); } + + public static @NotNull List<@NotNull String> parseKey(Object key) { + if (key instanceof String string) { + return List.of(string); + } else if (key instanceof ArrayList array) { + if (array.get(0) instanceof String) { + return (ArrayList) array; + } + + throw new IllegalArgumentException("Invalid parsed key"); + } else { + throw new IllegalArgumentException("Invalid parsed key"); + } + } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index 8ee2c19d..df7b2c93 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -3,9 +3,9 @@ import at.pavlov.cannons.Cannons; import at.pavlov.cannons.cannon.Cannon; import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; -import at.pavlov.cannons.hooks.movecraft.type.CannonProperties; +import at.pavlov.cannons.hooks.movecraft.type.properties.CannonProperties; import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; -import at.pavlov.cannons.hooks.movecraft.type.PropertyWrapper; +import at.pavlov.cannons.hooks.movecraft.type.properties.PropertyWrapper; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PlayerCraft; import net.countercraft.movecraft.craft.type.CraftType; @@ -45,11 +45,6 @@ public void onCraftDetect(CraftDetectEvent e) { // Check designs against maxCannons for (var max : maxCannons) { - var cannonName = max.getName(); - var count = cannonCount.get(cannonName.toLowerCase()); - if (count == null) - continue; - var result = max.check(craft, cannonCount); if (result.isPresent()) { diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java index 5e76a84a..a1c44662 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java @@ -1,21 +1,23 @@ package at.pavlov.cannons.hooks.movecraft.type; +import lombok.AllArgsConstructor; import lombok.Getter; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.util.Pair; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.Map; import java.util.Optional; @Getter public class MaxCannonsEntry implements CannonCheck { - private final String name; + private final List names; private final double max; private final boolean numericMax; - public MaxCannonsEntry(String name, @NotNull Pair max) { - this.name = name; + public MaxCannonsEntry(List names, @NotNull Pair max) { + this.names = names; this.max = max.getRight().doubleValue(); this.numericMax = max.getLeft(); } @@ -36,20 +38,27 @@ public boolean check(int count, int size) { public Optional detect(int count, int size) { if (numericMax) { if (count > max) - return Optional.of(String.format("You have too many %s cannon types : %d > %d", name, count, (int) max)); + return Optional.of(String.format("You have too many %s cannon types : %d > %d", allNames(), count, (int) max)); } else { double blockPercent = 100D * count / size; if (blockPercent > max) - return Optional.of(String.format("You have too many %s cannon types : %.2f%% > %.2f%%", name, blockPercent, max)); + return Optional.of(String.format("You have too many %s cannon types : %.2f%% > %.2f%%", allNames(), blockPercent, max)); } return Optional.empty(); } + public String allNames() { + return String.join(", ", names); + } + @Override public Optional check(Craft craft, Map cannonCountMap) { - Integer count = cannonCountMap.get(name); - if (count == null) return Optional.empty(); + int count = 0; + for (String name : names) { + count += cannonCountMap.getOrDefault(name, 0); + } + return detect(count, craft.getOrigBlockCount()); } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java similarity index 77% rename from cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java rename to cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java index 97070070..713bfe46 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CannonProperties.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java @@ -1,6 +1,8 @@ -package at.pavlov.cannons.hooks.movecraft.type; +package at.pavlov.cannons.hooks.movecraft.type.properties; import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; +import at.pavlov.cannons.hooks.movecraft.type.CraftKeys; +import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; import net.countercraft.movecraft.craft.type.TypeData; import java.util.HashSet; @@ -24,9 +26,9 @@ public static void register() { if (entry.getKey() == null) throw new TypeData.InvalidValueException("Keys for " + fileKey + " must be a string cannon name."); - String name = entry.getKey(); + Object entryKey = entry.getKey(); var limit = MovecraftUtils.parseLimit(entry.getValue()); - maxCannons.add(new MaxCannonsEntry(name, limit)); + maxCannons.add(new MaxCannonsEntry(MovecraftUtils.parseKey(entryKey), limit)); } return maxCannons; }, (type -> new HashSet<>())); diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java similarity index 98% rename from cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java rename to cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java index f98046e2..a225d398 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/PropertyWrapper.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java @@ -1,4 +1,4 @@ -package at.pavlov.cannons.hooks.movecraft.type; +package at.pavlov.cannons.hooks.movecraft.type.properties; import at.pavlov.cannons.Cannons; import lombok.AllArgsConstructor; From eeea90926e60303476854e362f2b1208287eacf7 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 00:00:00 +0100 Subject: [PATCH 05/17] Remove dead code --- .../cannons/hooks/movecraft/type/MaxCannonsEntry.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java index a1c44662..75079b23 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java @@ -22,15 +22,6 @@ public MaxCannonsEntry(List names, @NotNull Pair max); - } - - double percent = 100D * count / size; - return !(percent > max); - } - /** * * @return Empty if no error, otherwise return the error From 8b804e7f15fd6a67dc68df5d07f70600773a2a20 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 00:00:18 +0100 Subject: [PATCH 06/17] Add MinCannons --- .../hooks/movecraft/type/CraftKeys.java | 1 + .../hooks/movecraft/type/MinCannonsEntry.java | 54 +++++++++++++++++++ .../type/properties/CannonProperties.java | 24 +++++++++ 3 files changed, 79 insertions(+) create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java index 9d330703..f151dec1 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java @@ -4,4 +4,5 @@ public class CraftKeys { public static NamespacedKey MAX_CANNONS = new NamespacedKey("cannons-revamped", "max_cannons"); + public static NamespacedKey MIN_CANNONS = new NamespacedKey("cannons-revamped", "min_cannons"); } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java new file mode 100644 index 00000000..8539bb7b --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java @@ -0,0 +1,54 @@ +package at.pavlov.cannons.hooks.movecraft.type; + +import lombok.Getter; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.util.Pair; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Getter +public class MinCannonsEntry implements CannonCheck { + private final List names; + private final double min; + private final boolean numericMax; + + public MinCannonsEntry(List names, @NotNull Pair min) { + this.names = names; + this.min = min.getRight().doubleValue(); + this.numericMax = min.getLeft(); + } + + /** + * + * @return Empty if no error, otherwise return the error + */ + public Optional detect(int count, int size) { + if (numericMax) { + if (count < min) + return Optional.of(String.format("You have too many %s cannon types : %d > %d", allNames(), count, (int) min)); + } else { + double blockPercent = 100D * count / size; + if (blockPercent < min) + return Optional.of(String.format("You have too many %s cannon types : %.2f%% > %.2f%%", allNames(), blockPercent, min)); + } + + return Optional.empty(); + } + + public String allNames() { + return String.join(", ", names); + } + + @Override + public Optional check(Craft craft, Map cannonCountMap) { + int count = 0; + for (String name : names) { + count += cannonCountMap.getOrDefault(name, 0); + } + + return detect(count, craft.getOrigBlockCount()); + } +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java index 713bfe46..29d3c2c9 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java @@ -3,6 +3,7 @@ import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; import at.pavlov.cannons.hooks.movecraft.type.CraftKeys; import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; +import at.pavlov.cannons.hooks.movecraft.type.MinCannonsEntry; import net.countercraft.movecraft.craft.type.TypeData; import java.util.HashSet; @@ -15,6 +16,12 @@ public class CannonProperties { Set::of ); + public static final PropertyWrapper MIN_CANNONS = new PropertyWrapper<>( + CraftKeys.MIN_CANNONS, + Set.class, + Set::of + ); + public static void register() { MAX_CANNONS.register((data, type, fileKey, namespacedKey) -> { var map = data.getData(fileKey).getBackingData(); @@ -32,5 +39,22 @@ public static void register() { } return maxCannons; }, (type -> new HashSet<>())); + + MIN_CANNONS.register((data, type, fileKey, namespacedKey) -> { + var map = data.getData(fileKey).getBackingData(); + if (map.isEmpty()) + throw new TypeData.InvalidValueException("Value for " + fileKey + " must not be an empty map"); + + Set maxCannons = new HashSet<>(); + for (var entry : map.entrySet()) { + if (entry.getKey() == null) + throw new TypeData.InvalidValueException("Keys for " + fileKey + " must be a string cannon name."); + + Object entryKey = entry.getKey(); + var limit = MovecraftUtils.parseLimit(entry.getValue()); + maxCannons.add(new MinCannonsEntry(MovecraftUtils.parseKey(entryKey), limit)); + } + return maxCannons; + }, (type -> new HashSet<>())); } } From 2a5779785eff570f3ac9d6bf10c32f5da40392aa Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 09:34:26 +0100 Subject: [PATCH 07/17] Fix min cannons output --- .../pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java index 8539bb7b..f38d0b87 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MinCannonsEntry.java @@ -28,11 +28,11 @@ public MinCannonsEntry(List names, @NotNull Pair detect(int count, int size) { if (numericMax) { if (count < min) - return Optional.of(String.format("You have too many %s cannon types : %d > %d", allNames(), count, (int) min)); + return Optional.of(String.format("You have too few %s cannon types : %d < %d", allNames(), count, (int) min)); } else { double blockPercent = 100D * count / size; if (blockPercent < min) - return Optional.of(String.format("You have too many %s cannon types : %.2f%% > %.2f%%", allNames(), blockPercent, min)); + return Optional.of(String.format("You have too few %s cannon types : %.2f%% < %.2f%%", allNames(), blockPercent, min)); } return Optional.empty(); From 20fdcb57fc4146546b3c1cf1c9f64f3163ec6e24 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 09:34:51 +0100 Subject: [PATCH 08/17] Extract PropertyUtils --- .../type/properties/PropertyUtils.java | 47 +++++++++++++++++ .../type/properties/PropertyWrapper.java | 51 ++++--------------- 2 files changed, 57 insertions(+), 41 deletions(-) create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyUtils.java diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyUtils.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyUtils.java new file mode 100644 index 00000000..9cebdb2e --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyUtils.java @@ -0,0 +1,47 @@ +package at.pavlov.cannons.hooks.movecraft.type.properties; + +import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.craft.type.TypeData; +import net.countercraft.movecraft.util.functions.QuadFunction; +import org.bukkit.NamespacedKey; + +import java.util.function.Function; + +public class PropertyUtils { + public static String snakeToCamel(String input) { + if (input == null || input.isEmpty()) { + return input; + } + + StringBuilder result = new StringBuilder(); + boolean capitalizeNext = false; + + for (char c : input.toCharArray()) { + if (c == '_') { + capitalizeNext = true; + } else if (capitalizeNext) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + result.append(c); + } + } + + return result.toString(); + } + + public record ShutUp( + QuadFunction otherType) implements QuadFunction { + @Override + public Object apply(TypeData typeData, CraftType craftType, String s, NamespacedKey namespacedKey) { + return otherType.apply(typeData, craftType, s, namespacedKey); + } + } + + public record ShutUp2(Function otherType) implements Function { + @Override + public Object apply(CraftType craftType) { + return otherType.apply(craftType); + } + } +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java index a225d398..b8acaf23 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java @@ -1,7 +1,6 @@ package at.pavlov.cannons.hooks.movecraft.type.properties; import at.pavlov.cannons.Cannons; -import lombok.AllArgsConstructor; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.craft.type.TypeData; import net.countercraft.movecraft.craft.type.property.ObjectPropertyImpl; @@ -15,48 +14,19 @@ import java.util.function.Function; import java.util.function.Supplier; -@AllArgsConstructor public class PropertyWrapper { public static final Set notifyError = new HashSet<>(); public final NamespacedKey key; public final Class clazz; public final Supplier errorFallback; + public final String fileEntryName; - private static String snakeToCamel(String input) { - if (input == null || input.isEmpty()) { - return input; - } - - StringBuilder result = new StringBuilder(); - boolean capitalizeNext = false; - - for (char c : input.toCharArray()) { - if (c == '_') { - capitalizeNext = true; - } else if (capitalizeNext) { - result.append(Character.toUpperCase(c)); - capitalizeNext = false; - } else { - result.append(c); - } - } - - return result.toString(); - } - - private record ShutUp(QuadFunction otherType) implements QuadFunction { - @Override - public Object apply(TypeData typeData, CraftType craftType, String s, NamespacedKey namespacedKey) { - return otherType.apply(typeData, craftType, s, namespacedKey); - } - } - - private record ShutUp2(Function otherType) implements Function { - @Override - public Object apply(CraftType craftType) { - return otherType.apply(craftType); - } + public PropertyWrapper(NamespacedKey key, Class clazz, Supplier errorFallback) { + this.key = key; + this.clazz = clazz; + this.errorFallback = errorFallback; + this.fileEntryName = PropertyUtils.snakeToCamel(key.getKey()); } public void register(@NotNull QuadFunction loadProvider) { @@ -64,11 +34,10 @@ public void register(@NotNull QuadFunction loadProvider, @Nullable Function defaultProvider) { - String configName = snakeToCamel(key.getKey()); if (defaultProvider == null) { - CraftType.registerProperty(new ObjectPropertyImpl(configName, key, new ShutUp<>(loadProvider))); + CraftType.registerProperty(new ObjectPropertyImpl(fileEntryName, key, new PropertyUtils.ShutUp<>(loadProvider))); } else { - CraftType.registerProperty(new ObjectPropertyImpl(configName, key, new ShutUp<>(loadProvider), new ShutUp2<>(defaultProvider))); + CraftType.registerProperty(new ObjectPropertyImpl(fileEntryName, key, new PropertyUtils.ShutUp<>(loadProvider), new PropertyUtils.ShutUp2<>(defaultProvider))); } } @@ -83,9 +52,9 @@ public T get(CraftType type) { } catch (Exception exception) { notifyError.add(type); Cannons.getPlugin().logSevere( - "Failed to get maxCannons property from craft " + + "Failed to get " + fileEntryName +" property from craft " + type.getStringProperty(CraftType.NAME) + - " maxCannons won't be applied. - Cause: " + + " so it won't be applied. - Cause: " + exception.getMessage() ); return errorFallback.get(); From 01e87b50b2c6a8cc6e2af5eb5fa3f39fd4e9889d Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 09:35:22 +0100 Subject: [PATCH 09/17] Add MaxMass & MinMass properties --- .../listener/CraftDetectListener.java | 52 ++++++++++++++----- .../hooks/movecraft/type/CraftKeys.java | 2 + .../type/properties/CannonProperties.java | 6 +++ .../type/properties/PropertyWrapperInt.java | 52 +++++++++++++++++++ 4 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index df7b2c93..efedfa83 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -3,8 +3,8 @@ import at.pavlov.cannons.Cannons; import at.pavlov.cannons.cannon.Cannon; import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; +import at.pavlov.cannons.hooks.movecraft.type.CannonCheck; import at.pavlov.cannons.hooks.movecraft.type.properties.CannonProperties; -import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; import at.pavlov.cannons.hooks.movecraft.type.properties.PropertyWrapper; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PlayerCraft; @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; public class CraftDetectListener implements Listener { @@ -24,28 +25,29 @@ public class CraftDetectListener implements Listener { public void onCraftDetect(CraftDetectEvent e) { Craft craft = e.getCraft(); CraftType type = craft.getType(); - if (PropertyWrapper.notifyError.contains(type)) - return; - if (!(craft instanceof PlayerCraft)) - return; + if (PropertyWrapper.notifyError.contains(type)) return; + + if (!(craft instanceof PlayerCraft)) return; + + Set cannonMaxMinChecks = CannonProperties.MAX_CANNONS.get(type); + cannonMaxMinChecks.addAll(CannonProperties.MIN_CANNONS.get(type)); - Set maxCannons = CannonProperties.MAX_CANNONS.get(type); - if (maxCannons.isEmpty()) - return; // Return if empty set to improve performance + if (cannonMaxMinChecks.isEmpty()) return; // Sum up counts of each cannon design Set cannons = MovecraftUtils.getCannons(craft); + Map cannonCount = new HashMap<>(); - for (var cannon : cannons) { + for (Cannon cannon : cannons) { String design = cannon.getCannonDesign().getDesignID(); cannonCount.compute(design, (key, value) -> (value == null) ? 1 : value + 1); } + printCannonCount(cannonCount); - // Check designs against maxCannons - for (var max : maxCannons) { - var result = max.check(craft, cannonCount); + for (CannonCheck check : cannonMaxMinChecks) { + Optional result = check.check(craft, cannonCount); if (result.isPresent()) { String error = result.get(); @@ -54,6 +56,32 @@ public void onCraftDetect(CraftDetectEvent e) { return; } } + + int cannonsMassCount = 0; + for (Cannon cannon : cannons) { + cannonsMassCount += cannon.getCannonDesign().getMassOfCannon(); + } + + Integer maxMass = CannonProperties.MAX_MASS.get(type); + if (maxMass != null && maxMass < cannonsMassCount) { + e.setCancelled(true); + e.setFailMessage( + String.format( + "Detection Failed! Too much cannon mass on board! %d > %d", cannonsMassCount, maxMass + ) + ); + return; + } + + Integer minMass = CannonProperties.MIN_MASS.get(type); + if (minMass != null && minMass > cannonsMassCount) { + e.setCancelled(true); + e.setFailMessage( + String.format( + "Detection Failed! Not enough cannon mass on board! %d < %d", cannonsMassCount, minMass + ) + ); + } } private void printCannonCount(Map cannonCount) { diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java index f151dec1..40b406e6 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java @@ -5,4 +5,6 @@ public class CraftKeys { public static NamespacedKey MAX_CANNONS = new NamespacedKey("cannons-revamped", "max_cannons"); public static NamespacedKey MIN_CANNONS = new NamespacedKey("cannons-revamped", "min_cannons"); + public static NamespacedKey MAX_MASS = new NamespacedKey("cannons-revamped", "max_mass"); + public static NamespacedKey MIN_MASS = new NamespacedKey("cannons-revamped", "min_mass"); } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java index 29d3c2c9..95824d0a 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java @@ -22,6 +22,9 @@ public class CannonProperties { Set::of ); + public static final PropertyWrapperInt MAX_MASS = new PropertyWrapperInt(CraftKeys.MAX_MASS, (type) -> null); + public static final PropertyWrapperInt MIN_MASS = new PropertyWrapperInt(CraftKeys.MIN_MASS, (type) -> null); + public static void register() { MAX_CANNONS.register((data, type, fileKey, namespacedKey) -> { var map = data.getData(fileKey).getBackingData(); @@ -56,5 +59,8 @@ public static void register() { } return maxCannons; }, (type -> new HashSet<>())); + + MAX_MASS.register(); + MIN_MASS.register(); } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java new file mode 100644 index 00000000..c6bcd301 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java @@ -0,0 +1,52 @@ +package at.pavlov.cannons.hooks.movecraft.type.properties; + +import at.pavlov.cannons.Cannons; +import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.craft.type.property.IntegerProperty; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; +import java.util.function.Supplier; + +public class PropertyWrapperInt { + + public final NamespacedKey key; + public final Function defaultProvider; + public final String fileEntryName; + + public PropertyWrapperInt(NamespacedKey key, Function defaultProvider) { + this.key = key; + this.defaultProvider = defaultProvider; + this.fileEntryName = PropertyUtils.snakeToCamel(key.getKey()); + } + + public void register() { + String configName = PropertyUtils.snakeToCamel(key.getKey()); + if (defaultProvider == null) { + CraftType.registerProperty(new IntegerProperty(configName, key)); + } else { + CraftType.registerProperty(new IntegerProperty(configName, key, defaultProvider)); + } + } + + public Integer get(CraftType type) { + try { + // movecraft treats it as Integer but returns a not null int primitive... whatever + return type.getIntProperty(key); + } catch (Exception exception) { + if (defaultProvider != null) return defaultProvider.apply(type); + + PropertyWrapper.notifyError.add(type); + Cannons.getPlugin().logSevere( + "Failed to get " + fileEntryName + " property from craft " + + type.getStringProperty(CraftType.NAME) + + " so it won't be applied. - Cause: " + + exception.getMessage() + ); + + return null; + } + } +} From b820dd222d5b7ce7ae7b0d2a26c2aee4bea240b2 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 09:35:52 +0100 Subject: [PATCH 10/17] Optimize imports --- .../java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java | 1 - .../pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java | 1 - .../hooks/movecraft/type/properties/PropertyWrapperInt.java | 3 --- 3 files changed, 5 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java index 226f7644..ad7ba0cc 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java @@ -9,7 +9,6 @@ import at.pavlov.internal.Hook; import net.countercraft.movecraft.Movecraft; import org.bukkit.ChatColor; -import org.bukkit.event.HandlerList; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java index 75079b23..9b380e2f 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/MaxCannonsEntry.java @@ -1,6 +1,5 @@ package at.pavlov.cannons.hooks.movecraft.type; -import lombok.AllArgsConstructor; import lombok.Getter; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.util.Pair; diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java index c6bcd301..9f9e73c5 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java @@ -4,11 +4,8 @@ import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.craft.type.property.IntegerProperty; import org.bukkit.NamespacedKey; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.function.Function; -import java.util.function.Supplier; public class PropertyWrapperInt { From fc708e000cbbe4f937cdef070905aa19bab12452 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 10:40:31 +0100 Subject: [PATCH 11/17] Add excludeFromMass --- .../listener/CraftDetectListener.java | 60 ++++++++++++------- .../hooks/movecraft/type/CraftKeys.java | 1 + .../type/properties/CannonProperties.java | 15 +++++ 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index efedfa83..2ee936a6 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -2,6 +2,7 @@ import at.pavlov.cannons.Cannons; import at.pavlov.cannons.cannon.Cannon; +import at.pavlov.cannons.cannon.CannonDesign; import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; import at.pavlov.cannons.hooks.movecraft.type.CannonCheck; import at.pavlov.cannons.hooks.movecraft.type.properties.CannonProperties; @@ -30,38 +31,28 @@ public void onCraftDetect(CraftDetectEvent e) { if (!(craft instanceof PlayerCraft)) return; - Set cannonMaxMinChecks = CannonProperties.MAX_CANNONS.get(type); - cannonMaxMinChecks.addAll(CannonProperties.MIN_CANNONS.get(type)); - - if (cannonMaxMinChecks.isEmpty()) return; - // Sum up counts of each cannon design Set cannons = MovecraftUtils.getCannons(craft); - Map cannonCount = new HashMap<>(); - for (Cannon cannon : cannons) { - String design = cannon.getCannonDesign().getDesignID(); - cannonCount.compute(design, (key, value) -> (value == null) ? 1 : value + 1); - } + if (cannons.isEmpty()) return; - printCannonCount(cannonCount); + if (checkMaxMin(e, type, cannons, craft)) return; - for (CannonCheck check : cannonMaxMinChecks) { - Optional result = check.check(craft, cannonCount); - - if (result.isPresent()) { - String error = result.get(); - e.setCancelled(true); - e.setFailMessage("Detection Failed! " + error); - return; - } - } + checkMass(e, cannons, type); + } + private static void checkMass(CraftDetectEvent e, Set cannons, CraftType type) { int cannonsMassCount = 0; + Set exclude = CannonProperties.EXCLUDE_FROM_MASS.get(type); for (Cannon cannon : cannons) { - cannonsMassCount += cannon.getCannonDesign().getMassOfCannon(); + CannonDesign design = cannon.getCannonDesign(); + + if (!exclude.contains(design.getDesignID())) { + cannonsMassCount += design.getMassOfCannon(); + } } + cannon.logDebug("MassCount " + cannonsMassCount); Integer maxMass = CannonProperties.MAX_MASS.get(type); if (maxMass != null && maxMass < cannonsMassCount) { e.setCancelled(true); @@ -84,9 +75,32 @@ public void onCraftDetect(CraftDetectEvent e) { } } - private void printCannonCount(Map cannonCount) { + private boolean checkMaxMin(CraftDetectEvent e, CraftType type, Set cannons, Craft craft) { + Set cannonMaxMinChecks = CannonProperties.MAX_CANNONS.get(type); + cannonMaxMinChecks.addAll(CannonProperties.MIN_CANNONS.get(type)); + + if (cannonMaxMinChecks.isEmpty()) return false; + + Map cannonCount = new HashMap<>(); + for (Cannon cannon : cannons) { + String design = cannon.getCannonDesign().getDesignID(); + cannonCount.compute(design, (key, value) -> (value == null) ? 1 : value + 1); + } + for (var entry : cannonCount.entrySet()) { cannon.logDebug("Cannon found: " + entry.getKey() + " | " + entry.getValue()); } + + for (CannonCheck check : cannonMaxMinChecks) { + Optional result = check.check(craft, cannonCount); + + if (result.isEmpty()) continue; + + String error = result.get(); + e.setCancelled(true); + e.setFailMessage("Detection Failed! " + error); + return true; + } + return false; } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java index 40b406e6..a71fb714 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java @@ -7,4 +7,5 @@ public class CraftKeys { public static NamespacedKey MIN_CANNONS = new NamespacedKey("cannons-revamped", "min_cannons"); public static NamespacedKey MAX_MASS = new NamespacedKey("cannons-revamped", "max_mass"); public static NamespacedKey MIN_MASS = new NamespacedKey("cannons-revamped", "min_mass"); + public static NamespacedKey EXCLUDE_FROM_MASS = new NamespacedKey("cannons-revamped", "exclude_from_mass"); } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java index 95824d0a..553cf6d7 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java @@ -7,6 +7,7 @@ import net.countercraft.movecraft.craft.type.TypeData; import java.util.HashSet; +import java.util.List; import java.util.Set; public class CannonProperties { @@ -25,6 +26,12 @@ public class CannonProperties { public static final PropertyWrapperInt MAX_MASS = new PropertyWrapperInt(CraftKeys.MAX_MASS, (type) -> null); public static final PropertyWrapperInt MIN_MASS = new PropertyWrapperInt(CraftKeys.MIN_MASS, (type) -> null); + public static final PropertyWrapper EXCLUDE_FROM_MASS = new PropertyWrapper<>( + CraftKeys.EXCLUDE_FROM_MASS, + Set.class, + Set::of + ); + public static void register() { MAX_CANNONS.register((data, type, fileKey, namespacedKey) -> { var map = data.getData(fileKey).getBackingData(); @@ -62,5 +69,13 @@ public static void register() { MAX_MASS.register(); MIN_MASS.register(); + + EXCLUDE_FROM_MASS.register((data, type, fileKey, namespacedKey) -> { + List list = data.getStringList(fileKey); + if (list.isEmpty()) + throw new TypeData.InvalidValueException("Value for " + fileKey + " must not be an empty list"); + + return new HashSet<>(list); + }, (type -> new HashSet<>())); } } From 5e9cb6a4793b17165baf973171e08aaeb56d22c2 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 13 Jan 2026 11:16:34 +0100 Subject: [PATCH 12/17] Add useShipAngles --- .../hooks/movecraft/MovecraftHook.java | 2 + .../listener/CraftDetectListener.java | 16 ++++-- .../movecraft/listener/ReleaseListener.java | 15 ++++++ .../movecraft/listener/SinkListener.java | 3 +- .../hooks/movecraft/type/CraftKeys.java | 14 ++++-- .../type/properties/CannonProperties.java | 6 ++- .../properties/PropertyWrapperBoolean.java | 50 +++++++++++++++++++ 7 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java create mode 100644 cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java index ad7ba0cc..03d0d061 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftHook.java @@ -3,6 +3,7 @@ import at.pavlov.cannons.Cannons; import at.pavlov.cannons.hooks.BukkitHook; import at.pavlov.cannons.hooks.movecraft.listener.CraftDetectListener; +import at.pavlov.cannons.hooks.movecraft.listener.ReleaseListener; import at.pavlov.cannons.hooks.movecraft.listener.RotationListener; import at.pavlov.cannons.hooks.movecraft.listener.SinkListener; import at.pavlov.cannons.hooks.movecraft.listener.TranslationListener; @@ -41,6 +42,7 @@ public void onEnable() { pluginManager.registerEvents(new TranslationListener(), plugin); pluginManager.registerEvents(new RotationListener(), plugin); pluginManager.registerEvents(new SinkListener(), plugin); + pluginManager.registerEvents(new ReleaseListener(), plugin); plugin.logInfo(ChatColor.GREEN + enabledMessage()); } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index 2ee936a6..ad7b1077 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -22,7 +22,7 @@ public class CraftDetectListener implements Listener { private static final Cannons cannon = Cannons.getPlugin(); - @EventHandler + @EventHandler(ignoreCancelled = true) public void onCraftDetect(CraftDetectEvent e) { Craft craft = e.getCraft(); CraftType type = craft.getType(); @@ -38,10 +38,15 @@ public void onCraftDetect(CraftDetectEvent e) { if (checkMaxMin(e, type, cannons, craft)) return; - checkMass(e, cannons, type); + if (checkMass(e, cannons, type)) return; + + boolean useShip = CannonProperties.USE_SHIP_ANGLES.get(type) == Boolean.TRUE; + if (useShip) { + cannons.forEach(it -> it.setOnShip(true)); + } } - private static void checkMass(CraftDetectEvent e, Set cannons, CraftType type) { + private boolean checkMass(CraftDetectEvent e, Set cannons, CraftType type) { int cannonsMassCount = 0; Set exclude = CannonProperties.EXCLUDE_FROM_MASS.get(type); for (Cannon cannon : cannons) { @@ -61,7 +66,7 @@ private static void checkMass(CraftDetectEvent e, Set cannons, CraftType "Detection Failed! Too much cannon mass on board! %d > %d", cannonsMassCount, maxMass ) ); - return; + return true; } Integer minMass = CannonProperties.MIN_MASS.get(type); @@ -72,7 +77,10 @@ private static void checkMass(CraftDetectEvent e, Set cannons, CraftType "Detection Failed! Not enough cannon mass on board! %d < %d", cannonsMassCount, minMass ) ); + return true; } + + return false; } private boolean checkMaxMin(CraftDetectEvent e, CraftType type, Set cannons, Craft craft) { diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java new file mode 100644 index 00000000..db82cea6 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java @@ -0,0 +1,15 @@ +package at.pavlov.cannons.hooks.movecraft.listener; + +import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; +import net.countercraft.movecraft.events.CraftReleaseEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +public class ReleaseListener implements Listener { + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onCraftSink(CraftReleaseEvent event) { + MovecraftUtils.getCannons(event.getCraft()).forEach(it -> it.setOnShip(false)); + } +} diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/SinkListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/SinkListener.java index d57fd694..30a4d96b 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/SinkListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/SinkListener.java @@ -6,13 +6,14 @@ import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; import net.countercraft.movecraft.events.CraftSinkEvent; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import java.util.Set; public class SinkListener implements Listener { - @EventHandler + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onCraftSink(CraftSinkEvent event) { Set cannons = MovecraftUtils.getCannons(event.getCraft()); cannons.forEach(cannon -> CannonManager.getInstance().removeCannon(cannon.getUID(), false, true, BreakCause.Explosion)); diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java index a71fb714..dac1d636 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java @@ -2,10 +2,14 @@ import org.bukkit.NamespacedKey; +@SuppressWarnings("all") public class CraftKeys { - public static NamespacedKey MAX_CANNONS = new NamespacedKey("cannons-revamped", "max_cannons"); - public static NamespacedKey MIN_CANNONS = new NamespacedKey("cannons-revamped", "min_cannons"); - public static NamespacedKey MAX_MASS = new NamespacedKey("cannons-revamped", "max_mass"); - public static NamespacedKey MIN_MASS = new NamespacedKey("cannons-revamped", "min_mass"); - public static NamespacedKey EXCLUDE_FROM_MASS = new NamespacedKey("cannons-revamped", "exclude_from_mass"); + public static final NamespacedKey MAX_CANNONS = new NamespacedKey("cannons_revamped", "max_cannons"); + public static final NamespacedKey MIN_CANNONS = new NamespacedKey("cannons_revamped", "min_cannons"); + + public static final NamespacedKey MAX_MASS = new NamespacedKey("cannons_revamped", "max_mass"); + public static final NamespacedKey MIN_MASS = new NamespacedKey("cannons_revamped", "min_mass"); + public static final NamespacedKey EXCLUDE_FROM_MASS = new NamespacedKey("cannons_revamped", "exclude_from_mass"); + + public static final NamespacedKey USE_SHIP_ANGLES = new NamespacedKey("cannons_revamped", "use_ship_angles"); } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java index 553cf6d7..4697a10f 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java @@ -25,13 +25,14 @@ public class CannonProperties { public static final PropertyWrapperInt MAX_MASS = new PropertyWrapperInt(CraftKeys.MAX_MASS, (type) -> null); public static final PropertyWrapperInt MIN_MASS = new PropertyWrapperInt(CraftKeys.MIN_MASS, (type) -> null); - public static final PropertyWrapper EXCLUDE_FROM_MASS = new PropertyWrapper<>( CraftKeys.EXCLUDE_FROM_MASS, Set.class, Set::of ); + public static final PropertyWrapperBoolean USE_SHIP_ANGLES = new PropertyWrapperBoolean(CraftKeys.USE_SHIP_ANGLES, (type) -> false); + public static void register() { MAX_CANNONS.register((data, type, fileKey, namespacedKey) -> { var map = data.getData(fileKey).getBackingData(); @@ -69,7 +70,6 @@ public static void register() { MAX_MASS.register(); MIN_MASS.register(); - EXCLUDE_FROM_MASS.register((data, type, fileKey, namespacedKey) -> { List list = data.getStringList(fileKey); if (list.isEmpty()) @@ -77,5 +77,7 @@ public static void register() { return new HashSet<>(list); }, (type -> new HashSet<>())); + + USE_SHIP_ANGLES.register(); } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java new file mode 100644 index 00000000..eba267a3 --- /dev/null +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java @@ -0,0 +1,50 @@ +package at.pavlov.cannons.hooks.movecraft.type.properties; + +import at.pavlov.cannons.Cannons; +import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.craft.type.property.BooleanProperty; +import org.bukkit.NamespacedKey; + +import java.util.function.Function; + +public class PropertyWrapperBoolean { + + public final NamespacedKey key; + public final Function defaultProvider; + public final String fileEntryName; + + public PropertyWrapperBoolean(NamespacedKey key, Function defaultProvider) { + this.key = key; + this.defaultProvider = defaultProvider; + this.fileEntryName = PropertyUtils.snakeToCamel(key.getKey()); + } + + public void register() { + String configName = PropertyUtils.snakeToCamel(key.getKey()); + if (defaultProvider == null) { + CraftType.registerProperty(new BooleanProperty(configName, key)); + } else { + CraftType.registerProperty(new BooleanProperty(configName, key, defaultProvider)); + } + } + + public Boolean get(CraftType type) { + try { + // movecraft treats it as Integer but returns a not null int primitive... whatever + return type.getBoolProperty(key); + } catch (Exception exception) { + if (defaultProvider != null) return defaultProvider.apply(type); + + PropertyWrapper.notifyError.add(type); + Cannons.getPlugin().logSevere( + "Failed to get " + fileEntryName + " property from craft " + + type.getStringProperty(CraftType.NAME) + + " so it won't be applied. - Cause: " + + exception.getMessage() + ); + + return null; + } + } +} + From 21adc612861350e02f6d5539eba10b2476f8ccfc Mon Sep 17 00:00:00 2001 From: Intybyte Date: Wed, 14 Jan 2026 12:42:43 +0100 Subject: [PATCH 13/17] Add wiki links --- FEATURES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index e1f2bc4c..84fa3cae 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -18,8 +18,8 @@ Fixes: Hooks: --------------- - Vault hook to buy cannons (was there even before fork) -- Movecraft-Cannons support is now integrated (Movecraft Combat hook + Movecraft hook) -- [PlaceholderAPI](./PLACEHOLDERS.md) hook +- [Movecraft](https://github.com/Intybyte/Cannons/wiki/Hooks-&-Integrations#movecraft): Movecraft-Cannons support is now integrated (Movecraft Combat hook + Movecraft hook) +- [PlaceholderAPI](https://github.com/Intybyte/Cannons/wiki/Hooks-&-Integrations#placeholder-api) hook Optimizations: --------------- From 8522aee78e1f7a9a4c7b6afbf1bfc77e229c0250 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 18 Jan 2026 11:11:11 +0100 Subject: [PATCH 14/17] Add notify error for each property --- .../hooks/movecraft/listener/CraftDetectListener.java | 2 -- .../hooks/movecraft/type/properties/PropertyWrapper.java | 4 +++- .../movecraft/type/properties/PropertyWrapperBoolean.java | 8 ++++++-- .../movecraft/type/properties/PropertyWrapperInt.java | 7 ++++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index ad7b1077..6a71382b 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -27,8 +27,6 @@ public void onCraftDetect(CraftDetectEvent e) { Craft craft = e.getCraft(); CraftType type = craft.getType(); - if (PropertyWrapper.notifyError.contains(type)) return; - if (!(craft instanceof PlayerCraft)) return; // Sum up counts of each cannon design diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java index b8acaf23..7444fd41 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapper.java @@ -15,7 +15,7 @@ import java.util.function.Supplier; public class PropertyWrapper { - public static final Set notifyError = new HashSet<>(); + public final Set notifyError = new HashSet<>(); public final NamespacedKey key; public final Class clazz; @@ -42,6 +42,8 @@ public void register(@NotNull QuadFunction notifyError = new HashSet<>(); public final NamespacedKey key; public final Function defaultProvider; @@ -29,13 +32,14 @@ public void register() { } public Boolean get(CraftType type) { + if (notifyError.contains(type)) return null; + try { - // movecraft treats it as Integer but returns a not null int primitive... whatever return type.getBoolProperty(key); } catch (Exception exception) { if (defaultProvider != null) return defaultProvider.apply(type); - PropertyWrapper.notifyError.add(type); + notifyError.add(type); Cannons.getPlugin().logSevere( "Failed to get " + fileEntryName + " property from craft " + type.getStringProperty(CraftType.NAME) + diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java index 9f9e73c5..a21575d7 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperInt.java @@ -5,9 +5,12 @@ import net.countercraft.movecraft.craft.type.property.IntegerProperty; import org.bukkit.NamespacedKey; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; public class PropertyWrapperInt { + public final Set notifyError = new HashSet<>(); public final NamespacedKey key; public final Function defaultProvider; @@ -29,13 +32,15 @@ public void register() { } public Integer get(CraftType type) { + if (notifyError.contains(type)) return null; + try { // movecraft treats it as Integer but returns a not null int primitive... whatever return type.getIntProperty(key); } catch (Exception exception) { if (defaultProvider != null) return defaultProvider.apply(type); - PropertyWrapper.notifyError.add(type); + notifyError.add(type); Cannons.getPlugin().logSevere( "Failed to get " + fileEntryName + " property from craft " + type.getStringProperty(CraftType.NAME) + From 1cb21aea90b3a304847e2e16d55acfce2efa3d71 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 18 Jan 2026 13:12:24 +0100 Subject: [PATCH 15/17] Address sonar cloud review --- .../hooks/movecraft/listener/CraftDetectListener.java | 7 +++---- .../hooks/movecraft/type/properties/CannonProperties.java | 3 +++ .../movecraft/type/properties/PropertyWrapperBoolean.java | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java index 6a71382b..800d3643 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/CraftDetectListener.java @@ -6,7 +6,6 @@ import at.pavlov.cannons.hooks.movecraft.MovecraftUtils; import at.pavlov.cannons.hooks.movecraft.type.CannonCheck; import at.pavlov.cannons.hooks.movecraft.type.properties.CannonProperties; -import at.pavlov.cannons.hooks.movecraft.type.properties.PropertyWrapper; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PlayerCraft; import net.countercraft.movecraft.craft.type.CraftType; @@ -20,7 +19,7 @@ import java.util.Set; public class CraftDetectListener implements Listener { - private static final Cannons cannon = Cannons.getPlugin(); + private static final Cannons cannonPlugin = Cannons.getPlugin(); @EventHandler(ignoreCancelled = true) public void onCraftDetect(CraftDetectEvent e) { @@ -55,7 +54,7 @@ private boolean checkMass(CraftDetectEvent e, Set cannons, CraftType typ } } - cannon.logDebug("MassCount " + cannonsMassCount); + cannonPlugin.logDebug("MassCount " + cannonsMassCount); Integer maxMass = CannonProperties.MAX_MASS.get(type); if (maxMass != null && maxMass < cannonsMassCount) { e.setCancelled(true); @@ -94,7 +93,7 @@ private boolean checkMaxMin(CraftDetectEvent e, CraftType type, Set cann } for (var entry : cannonCount.entrySet()) { - cannon.logDebug("Cannon found: " + entry.getKey() + " | " + entry.getValue()); + cannonPlugin.logDebug("Cannon found: " + entry.getKey() + " | " + entry.getValue()); } for (CannonCheck check : cannonMaxMinChecks) { diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java index 4697a10f..ea11522a 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/CannonProperties.java @@ -4,12 +4,15 @@ import at.pavlov.cannons.hooks.movecraft.type.CraftKeys; import at.pavlov.cannons.hooks.movecraft.type.MaxCannonsEntry; import at.pavlov.cannons.hooks.movecraft.type.MinCannonsEntry; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import net.countercraft.movecraft.craft.type.TypeData; import java.util.HashSet; import java.util.List; import java.util.Set; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class CannonProperties { public static final PropertyWrapper MAX_CANNONS = new PropertyWrapper<>( CraftKeys.MAX_CANNONS, diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java index 89ce779c..9a7c5d5e 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/properties/PropertyWrapperBoolean.java @@ -4,6 +4,7 @@ import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.craft.type.property.BooleanProperty; import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.Nullable; import java.util.HashSet; import java.util.Set; @@ -31,7 +32,7 @@ public void register() { } } - public Boolean get(CraftType type) { + public @Nullable Boolean get(CraftType type) { if (notifyError.contains(type)) return null; try { From 80ce6c165d25744e7d23462aa081dbfddc8b8984 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 18 Jan 2026 13:15:43 +0100 Subject: [PATCH 16/17] Rename handler --- .../cannons/hooks/movecraft/listener/ReleaseListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java index db82cea6..9b1bbfae 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/listener/ReleaseListener.java @@ -9,7 +9,7 @@ public class ReleaseListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onCraftSink(CraftReleaseEvent event) { + public void onRelease(CraftReleaseEvent event) { MovecraftUtils.getCannons(event.getCraft()).forEach(it -> it.setOnShip(false)); } } From 9c93e27b4ba6c17bcb871954c78e0d9f3d6f6191 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 18 Jan 2026 13:21:03 +0100 Subject: [PATCH 17/17] prob final cleanup --- .../main/java/at/pavlov/cannons/Cannons.java | 4 ++++ .../cannons/hooks/movecraft/MovecraftUtils.java | 3 +++ .../cannons/hooks/movecraft/type/CraftKeys.java | 17 ++++++++++------- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java index 7f0ffacd..10f80179 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/Cannons.java @@ -476,4 +476,8 @@ public boolean isDebugMode() { public void setDebugMode(boolean debugMode) { this.debugMode = debugMode; } + + public static NamespacedKey nsKey(String key) { + return new NamespacedKey(getPlugin(), key); + } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java index 88ed09ab..990b4969 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/MovecraftUtils.java @@ -2,6 +2,8 @@ import at.pavlov.cannons.cannon.Cannon; import at.pavlov.cannons.cannon.CannonManager; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PilotedCraft; @@ -17,6 +19,7 @@ import static net.countercraft.movecraft.craft.type.TypeData.NUMERIC_PREFIX; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class MovecraftUtils { public static Set getCannons(Craft craft) { diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java index dac1d636..692eb143 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/hooks/movecraft/type/CraftKeys.java @@ -1,15 +1,18 @@ package at.pavlov.cannons.hooks.movecraft.type; +import at.pavlov.cannons.Cannons; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.bukkit.NamespacedKey; -@SuppressWarnings("all") +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class CraftKeys { - public static final NamespacedKey MAX_CANNONS = new NamespacedKey("cannons_revamped", "max_cannons"); - public static final NamespacedKey MIN_CANNONS = new NamespacedKey("cannons_revamped", "min_cannons"); + public static final NamespacedKey MAX_CANNONS = Cannons.nsKey("max_cannons"); + public static final NamespacedKey MIN_CANNONS = Cannons.nsKey("min_cannons"); - public static final NamespacedKey MAX_MASS = new NamespacedKey("cannons_revamped", "max_mass"); - public static final NamespacedKey MIN_MASS = new NamespacedKey("cannons_revamped", "min_mass"); - public static final NamespacedKey EXCLUDE_FROM_MASS = new NamespacedKey("cannons_revamped", "exclude_from_mass"); + public static final NamespacedKey MAX_MASS = Cannons.nsKey("max_mass"); + public static final NamespacedKey MIN_MASS = Cannons.nsKey("min_mass"); + public static final NamespacedKey EXCLUDE_FROM_MASS = Cannons.nsKey("exclude_from_mass"); - public static final NamespacedKey USE_SHIP_ANGLES = new NamespacedKey("cannons_revamped", "use_ship_angles"); + public static final NamespacedKey USE_SHIP_ANGLES = Cannons.nsKey("use_ship_angles"); }