From 9c6e1effb8f10d52faff69baea7c32d6a4c9b266 Mon Sep 17 00:00:00 2001 From: XxFran10xX Date: Mon, 26 Oct 2026 10:59:43 +0100 Subject: [PATCH 1/3] fix: a held realm keeps its nodes A realm whose last member died is held by SimpleFactions for a revive or its will heir, but Dowsing released its nodes four times a second because the guild had no members, so anyone could claim them. Held guilds now keep their nodes; a closed realm's guild is gone, which still frees them. SimpleFactions is bumped to 4.7.0 for Guild.isHeld; an older one falls back to the member rule. Co-Authored-By: Claude Opus 5.5 --- pom.xml | 2 +- .../net/tfminecraft/dowsing/objects/Node.java | 14 +++++++++++ .../dowsing/objects/NodeBehaviorTest.java | 24 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0851776..d9fce59 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 0.3.0 0.2.0 - 4.0.0 + 4.7.0 2.4.0 diff --git a/src/main/java/net/tfminecraft/dowsing/objects/Node.java b/src/main/java/net/tfminecraft/dowsing/objects/Node.java index a4b3aaf..2b5824b 100644 --- a/src/main/java/net/tfminecraft/dowsing/objects/Node.java +++ b/src/main/java/net/tfminecraft/dowsing/objects/Node.java @@ -279,6 +279,7 @@ Guild resolveGuild() { public boolean canHold(Guild g){ if(g == null) return false; + if(held(g)) return true; Player p = Members.player(g.getLeader()); if(p != null && p.isOnline() && Permissions.isAdmin(p)) return true; if(g.getMembers().size() < Cache.minMembersForNode) return false; @@ -286,6 +287,19 @@ public boolean canHold(Guild g){ return true; } + /** + * A realm SimpleFactions holds after its last member died keeps its nodes, so they wait for its + * revived leader or will heir instead of going to whoever claims them first. A closed realm's + * guild is gone, which frees them. SimpleFactions before 4.7.0 has no held realms. + */ + static boolean held(Guild g) { + try { + return g.isHeld(); + } catch (LinkageError olderSimpleFactions) { + return false; + } + } + public boolean canClaim(Player p, Guild g){ if(Permissions.isAdmin(p)) return true; if(!isClaimable()) return false; diff --git a/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java b/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java index e7dd114..61891ff 100644 --- a/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java +++ b/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java @@ -141,6 +141,30 @@ void invalidOwnershipNotifiesTheLeaderAndReleasesTheNode() { node.setGuild(f.guild); node.check(); assertTrue(node.hasGuild()); } + @Test + void aHeldRealmKeepsItsNodeWithNoMembersAndLosesItOnceNoLongerHeld() { + Node node = f.owned(); + when(f.guild.getMembers()).thenReturn(List.of()); + when(f.guild.getLeader()).thenReturn(""); + when(f.guild.isHeld()).thenReturn(true); + assertTrue(node.canHold(f.guild)); + node.check(); + assertSame(f.guild, node.getGuild()); assertFalse(node.isClaimable()); + when(f.guild.isHeld()).thenReturn(false); + node.check(); + assertTrue(node.isClaimable()); + } + + @Test + void simpleFactionsWithoutHeldRealmsFallsBackToTheMemberRule() { + when(f.guild.isHeld()).thenThrow(new NoSuchMethodError("isHeld")); + assertFalse(Node.held(f.guild)); + Node node = f.owned(); + assertTrue(node.canHold(f.guild)); + when(f.guild.getMembers()).thenReturn(List.of()); + assertFalse(node.canHold(f.guild)); + } + @Test void guildNoticesReachOnlyOnlineMembersOfTheOwningGuild() { var leader = f.server.addPlayer("Leader"); From eb9bcba15b9f6a0e70537ecde58978a5406a04da Mon Sep 17 00:00:00 2001 From: XxFran10xX Date: Mon, 26 Oct 2026 11:16:47 +0100 Subject: [PATCH 2/3] fix: a held realm's nodes pause instead of producing and charging upkeep SimpleFactions pauses a held realm's other upkeep, but node upkeep comes from Dowsing's total, so a kept node would have charged its treasury every day of the hold and produced for nobody. Co-Authored-By: Claude Opus 5.5 --- .../tfminecraft/dowsing/managers/NodeManager.java | 4 +++- .../net/tfminecraft/dowsing/objects/Node.java | 10 +++++----- .../dowsing/managers/NodeManagerCyclesTest.java | 15 +++++++++++++++ .../dowsing/objects/NodeBehaviorTest.java | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java b/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java index d306a58..17d5502 100644 --- a/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java +++ b/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java @@ -103,7 +103,7 @@ public static boolean canPurchaseCapacity(Guild g) { } public static double getTotalUpkeep(Guild g) { - if (g == null) return 0; + if (g == null || Node.held(g)) return 0; double total = 0; for (Node n : nodes) { if (!n.getIsActive() || !n.hasGuild()) continue; @@ -167,6 +167,8 @@ public void run() try { if(!n.getIsActive()) continue; if(!n.hasGuild()) continue; + // A held realm's nodes pause until it is taken on. + if(Node.held(n.getGuild())) continue; if(n.getLoc().getChunk().isForceLoaded() == false) { n.getLoc().getChunk().setForceLoaded(true); } diff --git a/src/main/java/net/tfminecraft/dowsing/objects/Node.java b/src/main/java/net/tfminecraft/dowsing/objects/Node.java index 2b5824b..a1cc3be 100644 --- a/src/main/java/net/tfminecraft/dowsing/objects/Node.java +++ b/src/main/java/net/tfminecraft/dowsing/objects/Node.java @@ -288,13 +288,13 @@ public boolean canHold(Guild g){ } /** - * A realm SimpleFactions holds after its last member died keeps its nodes, so they wait for its - * revived leader or will heir instead of going to whoever claims them first. A closed realm's - * guild is gone, which frees them. SimpleFactions before 4.7.0 has no held realms. + * A realm SimpleFactions holds after its last member died keeps its nodes, paused like its other + * upkeep, so they wait for its revived leader or will heir instead of going to whoever claims them + * first. A closed realm's guild is gone, which frees them. SimpleFactions before 4.7.0 has no held realms. */ - static boolean held(Guild g) { + public static boolean held(Guild g) { try { - return g.isHeld(); + return g != null && g.isHeld(); } catch (LinkageError olderSimpleFactions) { return false; } diff --git a/src/test/java/net/tfminecraft/dowsing/managers/NodeManagerCyclesTest.java b/src/test/java/net/tfminecraft/dowsing/managers/NodeManagerCyclesTest.java index dda1009..a8b9322 100644 --- a/src/test/java/net/tfminecraft/dowsing/managers/NodeManagerCyclesTest.java +++ b/src/test/java/net/tfminecraft/dowsing/managers/NodeManagerCyclesTest.java @@ -86,6 +86,21 @@ void countsCapacityAndUpkeepRespectOwnershipSpecialNodesAndPurchasedLimits() { NodeManager.extraCapacityByGuild.put("guild", 2); assertEquals(4, NodeManager.getNodeCapacity(f.guild)); assertTrue(NodeManager.canPurchaseCapacity(f.guild)); NodeManager.extraCapacityByGuild.put("guild", 3); assertFalse(NodeManager.canPurchaseCapacity(f.guild)); + when(f.guild.isHeld()).thenReturn(true); + assertEquals(0, NodeManager.getTotalUpkeep(f.guild)); + } + + @Test + void aHeldRealmsNodesPauseInsteadOfProducing() { + Node held = f.nodeAt(f.location); + when(held.getIsActive()).thenReturn(true); when(held.getGuild()).thenReturn(f.guild); + when(f.guild.isHeld()).thenReturn(true); + NodeManager.nodes.add(held); + try (Cycles cycles = new Cycles(); var droppers = mockConstruction(ItemDropper.class)) { + f.manager.start(); cycles.tasks.get(1200L).run(); + verify(held, never()).tickCycle(); verify(held, never()).tick(); assertTrue(droppers.constructed().isEmpty()); + verify(f.chunk, never()).setForceLoaded(true); + } } @Test diff --git a/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java b/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java index 61891ff..b6354cc 100644 --- a/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java +++ b/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java @@ -158,7 +158,7 @@ void aHeldRealmKeepsItsNodeWithNoMembersAndLosesItOnceNoLongerHeld() { @Test void simpleFactionsWithoutHeldRealmsFallsBackToTheMemberRule() { when(f.guild.isHeld()).thenThrow(new NoSuchMethodError("isHeld")); - assertFalse(Node.held(f.guild)); + assertFalse(Node.held(f.guild)); assertFalse(Node.held(null)); Node node = f.owned(); assertTrue(node.canHold(f.guild)); when(f.guild.getMembers()).thenReturn(List.of()); From 49de9dd7050a1c66df029c492579ee796f4b54ed Mon Sep 17 00:00:00 2001 From: XxFran10xX Date: Mon, 26 Oct 2026 11:28:16 +0100 Subject: [PATCH 3/3] fix: a held realm's nodes keep their efficiency Review: the hourly efficiency growth caps a node at its guild's member count, which is zero while the realm is held, so a kept node fell to 0% an hour after the death and the heir got it back 4x slower. Co-Authored-By: Claude Opus 5.5 --- src/main/java/net/tfminecraft/dowsing/objects/Node.java | 3 ++- .../java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/tfminecraft/dowsing/objects/Node.java b/src/main/java/net/tfminecraft/dowsing/objects/Node.java index a1cc3be..94ba5c8 100644 --- a/src/main/java/net/tfminecraft/dowsing/objects/Node.java +++ b/src/main/java/net/tfminecraft/dowsing/objects/Node.java @@ -556,7 +556,8 @@ public void deActivate() { } public void growEfficiency() { - if(resolveGuild() == null) return; + // A held realm has no members, which would cap its nodes' efficiency at 0 until it is taken on. + if(resolveGuild() == null || held(guild)) return; int members = guild.getMembers().size(); double growth = Math.min(1.0, members*Cache.efficiencyGrowthPerMember); updateEfficiency(growth); diff --git a/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java b/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java index b6354cc..9818a51 100644 --- a/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java +++ b/src/test/java/net/tfminecraft/dowsing/objects/NodeBehaviorTest.java @@ -150,6 +150,9 @@ void aHeldRealmKeepsItsNodeWithNoMembersAndLosesItOnceNoLongerHeld() { assertTrue(node.canHold(f.guild)); node.check(); assertSame(f.guild, node.getGuild()); assertFalse(node.isClaimable()); + Node efficient = new Node(UUID.randomUUID(), f.block, f.location, f.guild, true, 1, 0, f.type, 60, 0, 25); + efficient.growEfficiency(); + assertEquals(25, efficient.getEfficiency()); when(f.guild.isHeld()).thenReturn(false); node.check(); assertTrue(node.isClaimable());