From c690d33ad6d3154f0962543d5b1b713d84a8fe67 Mon Sep 17 00:00:00 2001 From: Olof Larsson Date: Mon, 13 Oct 2014 11:42:40 +0200 Subject: [PATCH] Better claiming. Step 1. --- .../factions/cmd/CmdFactionsAutoClaim.java | 5 +- .../factions/cmd/CmdFactionsClaim.java | 76 +++--- .../factions/cmd/CmdFactionsUnclaim.java | 4 +- .../factions/cmd/CmdFactionsUnclaimall.java | 11 +- .../factions/engine/EngineEcon.java | 31 ++- .../factions/engine/EngineMain.java | 167 +++++++++++++- .../massivecraft/factions/entity/Board.java | 20 ++ .../factions/entity/BoardColl.java | 61 ++++- .../factions/entity/BoardInterface.java | 2 + .../massivecraft/factions/entity/MConf.java | 3 - .../massivecraft/factions/entity/MPlayer.java | 210 ++++++----------- .../event/EventFactionsChunkChange.java | 59 ----- .../event/EventFactionsChunkChangeType.java | 16 ++ .../event/EventFactionsChunksChange.java | 78 +++++++ .../factions/integration/lwc/EngineLwc.java | 31 ++- .../factions/task/SpiralTask.java | 216 ------------------ 16 files changed, 506 insertions(+), 484 deletions(-) delete mode 100644 src/main/java/com/massivecraft/factions/event/EventFactionsChunkChange.java create mode 100644 src/main/java/com/massivecraft/factions/event/EventFactionsChunksChange.java delete mode 100644 src/main/java/com/massivecraft/factions/task/SpiralTask.java diff --git a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsAutoClaim.java b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsAutoClaim.java index 5eb276b3..55375bc5 100644 --- a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsAutoClaim.java +++ b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsAutoClaim.java @@ -1,5 +1,7 @@ package com.massivecraft.factions.cmd; +import java.util.Collections; + import com.massivecraft.factions.Perm; import com.massivecraft.factions.cmd.arg.ARFaction; import com.massivecraft.factions.entity.Faction; @@ -50,7 +52,8 @@ public class CmdFactionsAutoClaim extends FactionsCommand msender.setAutoClaimFaction(forFaction); msg("Now auto-claiming land for %s.", forFaction.describeTo(msender)); - msender.tryClaim(forFaction, PS.valueOf(me), true, true); + + msender.tryClaim(forFaction, Collections.singletonList(PS.valueOf(me).getChunk(true))); } } \ No newline at end of file diff --git a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsClaim.java b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsClaim.java index d81a13fa..4376ec67 100644 --- a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsClaim.java +++ b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsClaim.java @@ -1,11 +1,13 @@ package com.massivecraft.factions.cmd; +import java.util.LinkedHashSet; +import java.util.Set; + import com.massivecraft.factions.Perm; import com.massivecraft.factions.cmd.arg.ARFaction; import com.massivecraft.factions.entity.Faction; import com.massivecraft.factions.entity.MConf; import com.massivecraft.factions.entity.MPerm; -import com.massivecraft.factions.task.SpiralTask; import com.massivecraft.massivecore.cmd.arg.ARInteger; import com.massivecraft.massivecore.cmd.req.ReqHasPerm; import com.massivecraft.massivecore.cmd.req.ReqIsPlayer; @@ -43,68 +45,52 @@ public class CmdFactionsClaim extends FactionsCommand Integer radius = this.arg(0, ARInteger.get(), 1); if (radius == null) return; - final Faction forFaction = this.arg(1, ARFaction.get(), msenderFaction); - if (forFaction == null) return; + final Faction newFaction = this.arg(1, ARFaction.get(), msenderFaction); + if (newFaction == null) return; // MPerm - if (forFaction.isNormal() && !MPerm.getPermTerritory().has(msender, forFaction, true)) return; + if (newFaction.isNormal() && ! MPerm.getPermTerritory().has(msender, newFaction, true)) return; - // Validate + // Radius Claim Min if (radius < 1) { msg("If you specify a radius, it must be at least 1."); return; } - if (radius > MConf.get().radiusClaimRadiusLimit && !msender.isUsingAdminMode()) + // Radius Claim Perm + if (radius > 1 && ! Perm.CLAIM_RADIUS.has(sender, false)) + { + msg("You do not have permission to claim in a radius."); + return; + } + + // Radius Claim Max + if (radius > MConf.get().radiusClaimRadiusLimit && ! msender.isUsingAdminMode()) { msg("The maximum radius allowed is %s.", MConf.get().radiusClaimRadiusLimit); return; } - // Apply - - // single chunk - if (radius < 2) + // Get Chunks + final int radiusZero = radius -1; + final PS chunk = PS.valueOf(me).getChunk(true); + final int xmin = chunk.getChunkX() - radiusZero; + final int xmax = chunk.getChunkX() + radiusZero; + final int zmin = chunk.getChunkZ() - radiusZero; + final int zmax = chunk.getChunkZ() + radiusZero; + Set chunks = new LinkedHashSet(); + chunks.add(chunk); // The center should come first for pretty messages + for (int x = xmin; x <= xmax; x++) { - msender.tryClaim(forFaction, PS.valueOf(me), true, true); - return; - } - - // radius claim - if (!Perm.CLAIM_RADIUS.has(sender, false)) - { - msg("You do not have permission to claim in a radius."); - return; - } - - // TODO: There must be a better way than using a spiral task. - // TODO: Do some research to allow for claming sets of chunks in a batch with atomicity. - // This will probably result in an alteration to the owner change event. - // It would possibly contain a set of chunks instead of a single chunk. - - new SpiralTask(PS.valueOf(me), radius) - { - private int failCount = 0; - private final int limit = MConf.get().radiusClaimFailureLimit - 1; - - @Override - public boolean work() + for (int z = zmin; z <= zmax; z++) { - boolean success = msender.tryClaim(forFaction, PS.valueOf(this.currentLocation()), true, true); - if (success) - { - this.failCount = 0; - } - else if (this.failCount++ >= this.limit) - { - this.stop(); - return false; - } - return true; + chunks.add(chunk.withChunkX(x).withChunkZ(z)); } - }; + } + // Apply / Inform + msender.tryClaim(newFaction, chunks); } } diff --git a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaim.java b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaim.java index 3e1097ac..38e3d073 100644 --- a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaim.java +++ b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaim.java @@ -1,5 +1,7 @@ package com.massivecraft.factions.cmd; +import java.util.Collections; + import com.massivecraft.factions.cmd.req.ReqHasFaction; import com.massivecraft.factions.entity.Faction; import com.massivecraft.factions.entity.FactionColl; @@ -37,7 +39,7 @@ public class CmdFactionsUnclaim extends FactionsCommand Faction newFaction = FactionColl.get().getNone(); // Apply - if (msender.tryClaim(newFaction, chunk, true, true)) return; + if (msender.tryClaim(newFaction, Collections.singletonList(chunk))) return; } } diff --git a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaimall.java b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaimall.java index d8b0d0e2..1eae1db3 100644 --- a/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaimall.java +++ b/src/main/java/com/massivecraft/factions/cmd/CmdFactionsUnclaimall.java @@ -2,7 +2,6 @@ package com.massivecraft.factions.cmd; import java.util.Set; -import com.massivecraft.factions.Factions; import com.massivecraft.factions.Perm; import com.massivecraft.factions.Rel; import com.massivecraft.factions.cmd.req.ReqHasFaction; @@ -10,9 +9,7 @@ import com.massivecraft.factions.cmd.req.ReqRoleIsAtLeast; import com.massivecraft.factions.entity.BoardColl; import com.massivecraft.factions.entity.Faction; import com.massivecraft.factions.entity.FactionColl; -import com.massivecraft.factions.entity.MConf; import com.massivecraft.factions.entity.MPerm; -import com.massivecraft.factions.event.EventFactionsChunkChange; import com.massivecraft.massivecore.cmd.req.ReqHasPerm; import com.massivecraft.massivecore.ps.PS; @@ -49,12 +46,15 @@ public class CmdFactionsUnclaimall extends FactionsCommand // Apply Set chunks = BoardColl.get().getChunks(faction); + msender.tryClaim(newFaction, chunks); + + /* int countTotal = chunks.size(); int countSuccess = 0; int countFail = 0; for (PS chunk : chunks) { - EventFactionsChunkChange event = new EventFactionsChunkChange(sender, chunk, newFaction); + EventFactionsChunksChange event = new EventFactionsChunksChange(sender, chunk, newFaction); event.run(); if (event.isCancelled()) { @@ -74,7 +74,8 @@ public class CmdFactionsUnclaimall extends FactionsCommand if (MConf.get().logLandUnclaims) { Factions.get().log(msender.getName()+" unclaimed everything for the faction: "+msenderFaction.getName()); - } + }*/ + } } diff --git a/src/main/java/com/massivecraft/factions/engine/EngineEcon.java b/src/main/java/com/massivecraft/factions/engine/EngineEcon.java index f8f082d5..2c603c41 100644 --- a/src/main/java/com/massivecraft/factions/engine/EngineEcon.java +++ b/src/main/java/com/massivecraft/factions/engine/EngineEcon.java @@ -1,5 +1,10 @@ package com.massivecraft.factions.engine; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.plugin.Plugin; @@ -9,7 +14,7 @@ import com.massivecraft.factions.entity.Faction; import com.massivecraft.factions.entity.MConf; import com.massivecraft.factions.entity.MPlayer; import com.massivecraft.factions.event.EventFactionsAbstractSender; -import com.massivecraft.factions.event.EventFactionsChunkChange; +import com.massivecraft.factions.event.EventFactionsChunksChange; import com.massivecraft.factions.event.EventFactionsChunkChangeType; import com.massivecraft.factions.event.EventFactionsCreate; import com.massivecraft.factions.event.EventFactionsDescriptionChange; @@ -26,6 +31,8 @@ import com.massivecraft.factions.event.EventFactionsTitleChange; import com.massivecraft.factions.integration.Econ; import com.massivecraft.massivecore.EngineAbstract; import com.massivecraft.massivecore.money.Money; +import com.massivecraft.massivecore.ps.PS; +import com.massivecraft.massivecore.util.Txt; public class EngineEcon extends EngineAbstract { @@ -116,13 +123,27 @@ public class EngineEcon extends EngineAbstract } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void payForAction(EventFactionsChunkChange event) + public void payForAction(EventFactionsChunksChange event) { - EventFactionsChunkChangeType type = event.getType(); - Double cost = MConf.get().econChunkCost.get(type); + double cost = 0; + List typeNames = new ArrayList(); - String desc = type.toString().toLowerCase() + " this land"; + for (Entry> typeChunks : event.getTypeChunks().entrySet()) + { + final EventFactionsChunkChangeType type = typeChunks.getKey(); + final Set chunks = typeChunks.getValue(); + + Double typeCost = MConf.get().econChunkCost.get(type); + if (typeCost == null) continue; + if (typeCost == 0) continue; + + typeCost *= chunks.size(); + cost += typeCost; + + typeNames.add(type.now); + } + String desc = Txt.implodeCommaAnd(typeNames) + " this land"; payForAction(event, cost, desc); } diff --git a/src/main/java/com/massivecraft/factions/engine/EngineMain.java b/src/main/java/com/massivecraft/factions/engine/EngineMain.java index 4b48857c..668eaedf 100644 --- a/src/main/java/com/massivecraft/factions/engine/EngineMain.java +++ b/src/main/java/com/massivecraft/factions/engine/EngineMain.java @@ -2,10 +2,12 @@ package com.massivecraft.factions.engine; import java.text.MessageFormat; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -62,12 +64,14 @@ import com.massivecraft.factions.Factions; import com.massivecraft.factions.Rel; import com.massivecraft.factions.TerritoryAccess; import com.massivecraft.factions.entity.BoardColl; +import com.massivecraft.factions.entity.FactionColl; import com.massivecraft.factions.entity.MFlag; import com.massivecraft.factions.entity.MPerm; import com.massivecraft.factions.entity.MPlayer; import com.massivecraft.factions.entity.Faction; import com.massivecraft.factions.entity.MConf; import com.massivecraft.factions.entity.MPlayerColl; +import com.massivecraft.factions.event.EventFactionsChunksChange; import com.massivecraft.factions.event.EventFactionsPvpDisallowed; import com.massivecraft.factions.event.EventFactionsPowerChange; import com.massivecraft.factions.event.EventFactionsPowerChange.PowerChangeReason; @@ -230,6 +234,167 @@ public class EngineMain extends EngineAbstract // CHUNK CHANGE: DETECT // -------------------------------------------- // + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onChunksChange(EventFactionsChunksChange event) + { + // Args + final MPlayer msender = event.getMSender(); + final Faction newFaction = event.getNewFaction(); + final Map> currentFactionChunks = event.getOldFactionChunks(); + final Set currentFactions = currentFactionChunks.keySet(); + final Set chunks = event.getChunks(); + + // Admin Mode? Sure! + if (msender.isUsingAdminMode()) return; + + // CALC: Is there at least one normal faction among the current ones? + boolean currentFactionsContainsAtLeastOneNormal = false; + for (Faction currentFaction : currentFactions) + { + if (currentFaction.isNormal()) + { + currentFactionsContainsAtLeastOneNormal = true; + break; + } + } + + // If the new faction is normal (not wilderness/none), meaning if we are claiming for a faction ... + if (newFaction.isNormal()) + { + // ... ensure claiming is enabled for the worlds of all chunks ... + for (PS chunk : chunks) + { + String worldId = chunk.getWorld(); + if ( ! MConf.get().worldsClaimingEnabled.contains(worldId)) + { + String worldName = Mixin.getWorldDisplayName(worldId); + msender.msg("Land claiming is disabled in %s.", worldName); + event.setCancelled(true); + return; + } + } + + // ... ensure we have permission to alter the territory of the new faction ... + if ( ! MPerm.getPermTerritory().has(msender, newFaction, true)) + { + // NOTE: No need to send a message. We send message from the permission check itself. + event.setCancelled(true); + return; + } + + // ... ensure the new faction has enough players to claim ... + if (newFaction.getMPlayers().size() < MConf.get().claimsRequireMinFactionMembers) + { + msender.msg("Factions must have at least %s members to claim land.", MConf.get().claimsRequireMinFactionMembers); + event.setCancelled(true); + return; + } + + // ... ensure the claim would not bypass the global max limit ... + int ownedLand = newFaction.getLandCount(); + if (MConf.get().claimedLandsMax != 0 && ownedLand + chunks.size() > MConf.get().claimedLandsMax && ! newFaction.getFlag(MFlag.getFlagInfpower())) + { + msender.msg("Limit reached. You can't claim more land."); + event.setCancelled(true); + return; + } + + // ... ensure the claim would not bypass the faction power ... + if (ownedLand + chunks.size() > newFaction.getPowerRounded()) + { + msender.msg("You don't have enough power to claim that land."); + event.setCancelled(true); + return; + } + + // ... ensure the claim would not violate distance to neighbors ... + // HOW: Calculate the factions nearby, excluding the chunks themselves, the faction itself and the wilderness faction. + // HOW: The chunks themselves will be handled in the "if (oldFaction.isNormal())" section below. + Set nearbyChunks = BoardColl.getNearbyChunks(chunks, MConf.get().claimMinimumChunksDistanceToOthers); + nearbyChunks.removeAll(chunks); + Set nearbyFactions = BoardColl.getDistinctFactions(nearbyChunks); + nearbyFactions.remove(FactionColl.get().getNone()); + nearbyFactions.remove(newFaction); + // HOW: Next we check if the new faction has permission to claim nearby the nearby factions. + MPerm claimnear = MPerm.getPermClaimnear(); + for (Faction nearbyFaction : nearbyFactions) + { + if (claimnear.has(newFaction, nearbyFaction)) continue; + msender.sendMessage(claimnear.createDeniedMessage(msender, nearbyFaction)); + event.setCancelled(true); + return; + } + + // ... ensure claims are properly connected ... + if + ( + // If claims must be connected ... + MConf.get().claimsMustBeConnected + // ... and this faction already has claimed something on this map (meaning it's not their first claim) ... + && + newFaction.getLandCountInWorld(chunks.iterator().next().getWorld()) > 0 + // ... and none of the chunks are connected to an already claimed chunk for the faction ... + && + ! BoardColl.get().isAnyConnectedPs(chunks, newFaction) + // ... and either claims must always be connected or there is at least one normal faction among the old factions ... + && + ( ! MConf.get().claimsCanBeUnconnectedIfOwnedByOtherFaction || currentFactionsContainsAtLeastOneNormal) + ) + { + if (MConf.get().claimsCanBeUnconnectedIfOwnedByOtherFaction) + { + msender.msg("You can only claim additional land which is connected to your first claim or controlled by another faction!"); + } + else + { + msender.msg("You can only claim additional land which is connected to your first claim!"); + } + event.setCancelled(true); + return; + } + } + + // For each of the old factions ... + for (Faction oldFaction : currentFactions) + { + // ... that is an actual faction ... + if (oldFaction.isNone()) continue; + + // ... for which the msender lacks permission ... + if (MPerm.getPermTerritory().has(msender, oldFaction, false)) continue; + + // ... print the error message of choice ... + if (msender.hasFaction() && msender.getFaction() == oldFaction) + { + msender.sendMessage(MPerm.getPermTerritory().createDeniedMessage(msender, oldFaction)); + } + else if ( ! MConf.get().claimingFromOthersAllowed) + { + msender.msg("You may not claim land from others."); + } + else if (oldFaction.getRelationTo(newFaction).isAtLeast(Rel.TRUCE)) + { + msender.msg("You can't claim this land due to your relation with the current owner."); + } + else if ( ! oldFaction.hasLandInflation()) + { + msender.msg("%s owns this land and is strong enough to keep it.", oldFaction.getName(msender)); + } + else if ( ! BoardColl.get().isAnyBorderPs(chunks)) + { + msender.msg("You must start claiming land at the border of the territory."); + } + + // ... and cancel. + event.setCancelled(true); + return; + } + } + + // -------------------------------------------- // + // CHUNK CHANGE: DETECT + // -------------------------------------------- // + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void chunkChangeDetect(PlayerMoveEvent event) { @@ -307,7 +472,7 @@ public class EngineMain extends EngineAbstract if (autoClaimFaction == null) return; // ... try claim. - mplayer.tryClaim(autoClaimFaction, chunkTo, true, true); + mplayer.tryClaim(autoClaimFaction, Collections.singletonList(chunkTo)); } // -------------------------------------------- // diff --git a/src/main/java/com/massivecraft/factions/entity/Board.java b/src/main/java/com/massivecraft/factions/entity/Board.java index 9ef0868a..ccc46cb2 100644 --- a/src/main/java/com/massivecraft/factions/entity/Board.java +++ b/src/main/java/com/massivecraft/factions/entity/Board.java @@ -242,6 +242,16 @@ public class Board extends Entity implements BoardInterface return false; } + + @Override + public boolean isAnyBorderPs(Set pss) + { + for (PS ps : pss) + { + if (this.isBorderPs(ps)) return true; + } + return false; + } // Is this coord connected to any coord claimed by the specified faction? @Override @@ -266,6 +276,16 @@ public class Board extends Entity implements BoardInterface return false; } + @Override + public boolean isAnyConnectedPs(Set pss, Faction faction) + { + for (PS ps : pss) + { + if (this.isConnectedPs(ps, faction)) return true; + } + return false; + } + // MAP GENERATION @Override diff --git a/src/main/java/com/massivecraft/factions/entity/BoardColl.java b/src/main/java/com/massivecraft/factions/entity/BoardColl.java index 4a1001b4..7dbefcea 100644 --- a/src/main/java/com/massivecraft/factions/entity/BoardColl.java +++ b/src/main/java/com/massivecraft/factions/entity/BoardColl.java @@ -3,7 +3,9 @@ package com.massivecraft.factions.entity; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import com.massivecraft.factions.Const; @@ -150,6 +152,16 @@ public class BoardColl extends Coll implements BoardInterface return board.isBorderPs(ps); } + @Override + public boolean isAnyBorderPs(Set pss) + { + for (PS ps : pss) + { + if (this.isBorderPs(ps)) return true; + } + return false; + } + @Override public boolean isConnectedPs(PS ps, Faction faction) { @@ -159,6 +171,16 @@ public class BoardColl extends Coll implements BoardInterface return board.isConnectedPs(ps, faction); } + @Override + public boolean isAnyConnectedPs(Set pss, Faction faction) + { + for (PS ps : pss) + { + if (this.isConnectedPs(ps, faction)) return true; + } + return false; + } + // MAP GENERATION @Override @@ -177,7 +199,7 @@ public class BoardColl extends Coll implements BoardInterface // Distance -1 returns 0 chunks always. // Distance 0 returns 1 chunk only (the one supplied). // Distance 1 returns 3x3 = 9 chunks. - public static Set getNearbyChunks(PS chunk, int distance, boolean includeSelf) + public static Set getNearbyChunks(PS chunk, int distance) { // Fix Args if (chunk == null) throw new NullPointerException("chunk"); @@ -187,7 +209,6 @@ public class BoardColl extends Coll implements BoardInterface Set ret = new LinkedHashSet(); if (distance < 0) return ret; - // if (distance == 0 && ! includeSelf) return ret; // This will be done by the code below. // Main int xmin = chunk.getChunkX() - distance; @@ -200,7 +221,6 @@ public class BoardColl extends Coll implements BoardInterface { for (int z = zmin; z <= zmax; z++) { - if ( ! includeSelf && x == chunk.getChunkX() && z == chunk.getChunkZ()) continue; ret.add(chunk.withChunkX(x).withChunkZ(z)); } } @@ -209,6 +229,26 @@ public class BoardColl extends Coll implements BoardInterface return ret; } + public static Set getNearbyChunks(Collection chunks, int distance) + { + // Fix Args + if (chunks == null) throw new NullPointerException("chunks"); + + // Create Ret + Set ret = new LinkedHashSet(); + + if (distance < 0) return ret; + + // Main + for (PS chunk : chunks) + { + ret.addAll(getNearbyChunks(chunk, distance)); + } + + // Return Ret + return ret; + } + public static Set getDistinctFactions(Collection chunks) { // Fix Args @@ -229,4 +269,19 @@ public class BoardColl extends Coll implements BoardInterface return ret; } + public static Map getChunkFaction(Collection chunks) + { + Map ret = new LinkedHashMap(); + + for (PS chunk : chunks) + { + chunk = chunk.getChunk(true); + Faction faction = get().getFactionAt(chunk); + if (faction == null) faction = FactionColl.get().getNone(); + ret.put(chunk, faction); + } + + return ret; + } + } diff --git a/src/main/java/com/massivecraft/factions/entity/BoardInterface.java b/src/main/java/com/massivecraft/factions/entity/BoardInterface.java index 0e701a62..1a891422 100644 --- a/src/main/java/com/massivecraft/factions/entity/BoardInterface.java +++ b/src/main/java/com/massivecraft/factions/entity/BoardInterface.java @@ -30,7 +30,9 @@ public interface BoardInterface // NEARBY DETECTION public boolean isBorderPs(PS ps); + public boolean isAnyBorderPs(Set pss); public boolean isConnectedPs(PS ps, Faction faction); + public boolean isAnyConnectedPs(Set pss, Faction faction); // MAP // TODO: Could the degrees be embedded in centerPs yaw instead? diff --git a/src/main/java/com/massivecraft/factions/entity/MConf.java b/src/main/java/com/massivecraft/factions/entity/MConf.java index e0f48cd9..e2294783 100644 --- a/src/main/java/com/massivecraft/factions/entity/MConf.java +++ b/src/main/java/com/massivecraft/factions/entity/MConf.java @@ -151,9 +151,6 @@ public class MConf extends Entity // CLAIM LIMITS // -------------------------------------------- // - // if someone is doing a radius claim and the process fails to claim land this many times in a row, it will exit - public int radiusClaimFailureLimit = 9; - // the maximum radius allowed when using the claim command. public int radiusClaimRadiusLimit = 5; diff --git a/src/main/java/com/massivecraft/factions/entity/MPlayer.java b/src/main/java/com/massivecraft/factions/entity/MPlayer.java index 10f473cb..3de6844e 100644 --- a/src/main/java/com/massivecraft/factions/entity/MPlayer.java +++ b/src/main/java/com/massivecraft/factions/entity/MPlayer.java @@ -1,6 +1,9 @@ package com.massivecraft.factions.entity; +import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; import java.util.Set; import org.bukkit.ChatColor; @@ -12,7 +15,8 @@ import com.massivecraft.factions.Lang; import com.massivecraft.factions.Perm; import com.massivecraft.factions.Rel; import com.massivecraft.factions.RelationParticipator; -import com.massivecraft.factions.event.EventFactionsChunkChange; +import com.massivecraft.factions.event.EventFactionsChunkChangeType; +import com.massivecraft.factions.event.EventFactionsChunksChange; import com.massivecraft.factions.event.EventFactionsMembershipChange; import com.massivecraft.factions.event.EventFactionsRemovePlayerMillis; import com.massivecraft.factions.event.EventFactionsMembershipChange.MembershipChangeReason; @@ -793,163 +797,91 @@ public class MPlayer extends SenderEntity implements EconomyParticipato } } - public boolean tryClaim(Faction newFaction, PS ps, boolean verbooseChange, boolean verbooseSame) + // NEW + public boolean tryClaim(Faction newFaction, Collection pss) { - PS chunk = ps.getChunk(true); - Faction oldFaction = BoardColl.get().getFactionAt(chunk); + // Args + if (newFaction == null) throw new NullPointerException("newFaction"); - MConf mconf = MConf.get(); + if (pss == null) throw new NullPointerException("pss"); + final Set chunks = PS.getDistinctChunks(pss); // NoChange - if (newFaction == oldFaction) + // We clean the chunks further by removing what does not change. + // This is also very suggested cleaning of EventFactionsChunksChange input. + Iterator iter = chunks.iterator(); + while (iter.hasNext()) + { + PS chunk = iter.next(); + Faction oldFaction = BoardColl.get().getFactionAt(chunk); + if (newFaction == oldFaction) iter.remove(); + } + if (chunks.isEmpty()) { msg("%s already owns this land.", newFaction.describeTo(this, true)); return true; } - if ( ! this.isUsingAdminMode()) - { - if (newFaction.isNormal()) - { - if ( ! mconf.worldsClaimingEnabled.contains(ps.getWorld())) - { - msg("Sorry, this world has land claiming disabled."); - return false; - } - - if ( ! MPerm.getPermTerritory().has(this, newFaction, true)) - { - return false; - } - - if (newFaction.getMPlayers().size() < mconf.claimsRequireMinFactionMembers) - { - msg("Factions must have at least %s members to claim land.", mconf.claimsRequireMinFactionMembers); - return false; - } - - int ownedLand = newFaction.getLandCount(); - - if (mconf.claimedLandsMax != 0 && ownedLand >= mconf.claimedLandsMax && ! newFaction.getFlag(MFlag.getFlagInfpower())) - { - msg("Limit reached. You can't claim more land."); - return false; - } - - if (ownedLand >= newFaction.getPowerRounded()) - { - msg("You can't claim more land. You need more power."); - return false; - } - - // Calculate the factions nearby, excluding the chunk itself, the faction itself and the wilderness faction. - // The chunk itself is handled in the "if (oldFaction.isNormal())" section below. - Set nearbyChunks = BoardColl.getNearbyChunks(chunk, MConf.get().claimMinimumChunksDistanceToOthers, false); - Set nearbyFactions = BoardColl.getDistinctFactions(nearbyChunks); - nearbyFactions.remove(FactionColl.get().getNone()); - nearbyFactions.remove(newFaction); - // Next we check if the new faction has permission to claim nearby the nearby factions. - MPerm claimnear = MPerm.getPermClaimnear(); - for (Faction nearbyFaction : nearbyFactions) - { - if (claimnear.has(newFaction, nearbyFaction)) continue; - sendMessage(claimnear.createDeniedMessage(this, nearbyFaction)); - return false; - } - - if - ( - mconf.claimsMustBeConnected - && - newFaction.getLandCountInWorld(ps.getWorld()) > 0 - && - ! BoardColl.get().isConnectedPs(chunk, newFaction) - && - ( ! mconf.claimsCanBeUnconnectedIfOwnedByOtherFaction || oldFaction.isNone()) - ) - { - if (mconf.claimsCanBeUnconnectedIfOwnedByOtherFaction) - { - msg("You can only claim additional land which is connected to your first claim or controlled by another faction!"); - } - else - { - msg("You can only claim additional land which is connected to your first claim!"); - } - return false; - } - } - - if (oldFaction.isNormal()) - { - if ( ! MPerm.getPermTerritory().has(this, oldFaction, false)) - { - if (this.hasFaction() && this.getFaction() == oldFaction) - { - sendMessage(MPerm.getPermTerritory().createDeniedMessage(this, oldFaction)); - return false; - } - - if ( ! mconf.claimingFromOthersAllowed) - { - msg("You may not claim land from others."); - return false; - } - - if (oldFaction.getRelationTo(newFaction).isAtLeast(Rel.TRUCE)) - { - msg("You can't claim this land due to your relation with the current owner."); - return false; - } - - if ( ! oldFaction.hasLandInflation()) - { - msg("%s owns this land and is strong enough to keep it.", oldFaction.getName(this)); - return false; - } - - if ( ! BoardColl.get().isBorderPs(chunk)) - { - msg("You must start claiming land at the border of the territory."); - return false; - } - } - } - - } - // Event - EventFactionsChunkChange event = new EventFactionsChunkChange(this.getSender(), chunk, newFaction); + // NOTE: We listen to this event ourselves at LOW. + // NOTE: That is where we apply the standard checks. + EventFactionsChunksChange event = new EventFactionsChunksChange(this.getSender(), chunks, newFaction); event.run(); if (event.isCancelled()) return false; - + // Apply - BoardColl.get().setFactionAt(chunk, newFaction); + for (PS chunk : chunks) + { + BoardColl.get().setFactionAt(chunk, newFaction); + } // Inform - Set informees = new HashSet(); - informees.add(this); - if (newFaction.isNormal()) + for (Entry> entry : event.getOldFactionChunks().entrySet()) { - informees.addAll(newFaction.getMPlayers()); - } - if (oldFaction.isNormal()) - { - informees.addAll(oldFaction.getMPlayers()); - } - if (MConf.get().logLandClaims) - { - informees.add(MPlayer.get(IdUtil.getConsole())); + final Faction oldFaction = entry.getKey(); + final Set oldChunks = entry.getValue(); + final PS oldChunk = oldChunks.iterator().next(); + final Set informees = getClaimInformees(this, oldFaction, newFaction); + final EventFactionsChunkChangeType type = EventFactionsChunkChangeType.get(oldFaction, newFaction, this.getFaction()); + + String chunkString = oldChunk.toString(PSFormatHumanSpace.get()); + String typeString = type.past; + + for (MPlayer informee : informees) + { + informee.msg("%s %s %d " + (oldChunks.size() == 1 ? "chunk" : "chunks") + " near %s", this.describeTo(informee, true), typeString, oldChunks.size(), chunkString); + informee.msg(" %s --> %s", oldFaction.describeTo(informee, true), newFaction.describeTo(informee, true)); + } } - String chunkString = chunk.toString(PSFormatHumanSpace.get()); - String typeString = event.getType().past; - for (MPlayer informee : informees) - { - informee.msg("%s %s %s | %s --> %s", this.describeTo(informee, true), typeString, chunkString, oldFaction.describeTo(informee, true), newFaction.describeTo(informee, true)); - } - + // Success return true; } + // -------------------------------------------- // + // UTIL + // -------------------------------------------- // + + public static Set getClaimInformees(MPlayer msender, Faction... factions) + { + Set ret = new HashSet(); + + ret.add(msender); + + for (Faction faction : factions) + { + if (faction.isNormal()) + { + ret.addAll(faction.getMPlayers()); + } + } + + if (MConf.get().logLandClaims) + { + ret.add(MPlayer.get(IdUtil.getConsole())); + } + + return ret; + } + } diff --git a/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChange.java b/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChange.java deleted file mode 100644 index dc5f9397..00000000 --- a/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChange.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.massivecraft.factions.event; - -import org.bukkit.command.CommandSender; -import org.bukkit.event.HandlerList; - -import com.massivecraft.factions.entity.BoardColl; -import com.massivecraft.factions.entity.Faction; -import com.massivecraft.factions.entity.MPlayer; -import com.massivecraft.massivecore.ps.PS; - -public class EventFactionsChunkChange extends EventFactionsAbstractSender -{ - // -------------------------------------------- // - // REQUIRED EVENT CODE - // -------------------------------------------- // - - private static final HandlerList handlers = new HandlerList(); - @Override public HandlerList getHandlers() { return handlers; } - public static HandlerList getHandlerList() { return handlers; } - - // -------------------------------------------- // - // FIELDS - // -------------------------------------------- // - - private final PS chunk; - public PS getChunk() { return this.chunk; } - - private final Faction currentFaction; - private final Faction newFaction; - public Faction getNewFaction() { return this.newFaction; } - - // -------------------------------------------- // - // CONSTRUCT - // -------------------------------------------- // - - public EventFactionsChunkChange(CommandSender sender, PS chunk, Faction newFaction) - { - super(sender); - this.chunk = chunk.getChunk(true); - this.currentFaction = BoardColl.get().getFactionAt(chunk); - this.newFaction = newFaction; - } - - // -------------------------------------------- // - // UTIL - // -------------------------------------------- // - - public EventFactionsChunkChangeType getType() - { - if (currentFaction.isNone()) return EventFactionsChunkChangeType.BUY; - if (newFaction.isNormal()) return EventFactionsChunkChangeType.CONQUER; - - MPlayer usender = this.getMSender(); - if (usender != null && usender.getFaction() == currentFaction) return EventFactionsChunkChangeType.SELL; - - return EventFactionsChunkChangeType.PILLAGE; - } - -} diff --git a/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChangeType.java b/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChangeType.java index 2666cf5e..15e7e1b0 100644 --- a/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChangeType.java +++ b/src/main/java/com/massivecraft/factions/event/EventFactionsChunkChangeType.java @@ -1,11 +1,14 @@ package com.massivecraft.factions.event; +import com.massivecraft.factions.entity.Faction; + public enum EventFactionsChunkChangeType { // -------------------------------------------- // // ENUM // -------------------------------------------- // + NONE("none", "none"), BUY("buy", "bought"), SELL("sell", "sold"), CONQUER("conquer", "conquered"), @@ -31,4 +34,17 @@ public enum EventFactionsChunkChangeType this.past = past; } + // -------------------------------------------- // + // UTIL + // -------------------------------------------- // + + public static EventFactionsChunkChangeType get(Faction oldFaction, Faction newFaction, Faction self) + { + if (newFaction == oldFaction) return NONE; + if (oldFaction.isNone()) return BUY; + if (newFaction.isNormal()) return CONQUER; + if (oldFaction == self) return SELL; + return PILLAGE; + } + } diff --git a/src/main/java/com/massivecraft/factions/event/EventFactionsChunksChange.java b/src/main/java/com/massivecraft/factions/event/EventFactionsChunksChange.java new file mode 100644 index 00000000..29033dce --- /dev/null +++ b/src/main/java/com/massivecraft/factions/event/EventFactionsChunksChange.java @@ -0,0 +1,78 @@ +package com.massivecraft.factions.event; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; + +import com.massivecraft.factions.entity.BoardColl; +import com.massivecraft.factions.entity.Faction; +import com.massivecraft.factions.entity.MPlayer; +import com.massivecraft.massivecore.ps.PS; +import com.massivecraft.massivecore.util.MUtil; + +public class EventFactionsChunksChange extends EventFactionsAbstractSender +{ + // -------------------------------------------- // + // REQUIRED EVENT CODE + // -------------------------------------------- // + + private static final HandlerList handlers = new HandlerList(); + @Override public HandlerList getHandlers() { return handlers; } + public static HandlerList getHandlerList() { return handlers; } + + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + private final Set chunks; + public Set getChunks() { return this.chunks; } + + private final Faction newFaction; + public Faction getNewFaction() { return this.newFaction; } + + private final Map oldChunkFaction; + public Map getOldChunkFaction() { return this.oldChunkFaction; } + + private final Map> oldFactionChunks; + public Map> getOldFactionChunks() { return this.oldFactionChunks; } + + private final Map chunkType; + public Map getChunkType() { return this.chunkType; } + + private final Map> typeChunks; + public Map> getTypeChunks() { return this.typeChunks; } + + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + public EventFactionsChunksChange(CommandSender sender, Set chunks, Faction newFaction) + { + super(sender); + chunks = PS.getDistinctChunks(chunks); + this.chunks = Collections.unmodifiableSet(chunks); + this.newFaction = newFaction; + this.oldChunkFaction = Collections.unmodifiableMap(BoardColl.getChunkFaction(chunks)); + this.oldFactionChunks = Collections.unmodifiableMap(MUtil.reverseIndex(this.oldChunkFaction)); + + MPlayer msender = this.getMSender(); + Faction self = null; + if (msender != null) self = msender.getFaction(); + Map currentChunkType = new LinkedHashMap(); + for (Entry entry : this.oldChunkFaction.entrySet()) + { + PS chunk = entry.getKey(); + Faction from = entry.getValue(); + currentChunkType.put(chunk, EventFactionsChunkChangeType.get(from, newFaction, self)); + } + + this.chunkType = Collections.unmodifiableMap(currentChunkType); + this.typeChunks = Collections.unmodifiableMap(MUtil.reverseIndex(this.chunkType)); + } + +} diff --git a/src/main/java/com/massivecraft/factions/integration/lwc/EngineLwc.java b/src/main/java/com/massivecraft/factions/integration/lwc/EngineLwc.java index b0c96e19..868f9e8c 100644 --- a/src/main/java/com/massivecraft/factions/integration/lwc/EngineLwc.java +++ b/src/main/java/com/massivecraft/factions/integration/lwc/EngineLwc.java @@ -1,6 +1,9 @@ package com.massivecraft.factions.integration.lwc; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; @@ -14,7 +17,7 @@ import com.massivecraft.factions.Factions; import com.massivecraft.factions.entity.Faction; import com.massivecraft.factions.entity.MConf; import com.massivecraft.factions.entity.MPlayer; -import com.massivecraft.factions.event.EventFactionsChunkChange; +import com.massivecraft.factions.event.EventFactionsChunksChange; import com.massivecraft.factions.event.EventFactionsChunkChangeType; import com.massivecraft.massivecore.EngineAbstract; import com.massivecraft.massivecore.ps.PS; @@ -59,12 +62,9 @@ public class EngineLwc extends EngineAbstract // LISTENER // -------------------------------------------- // - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void removeProtectionsOnChunkChange(EventFactionsChunkChange event) + public void removeProtectionsOnChunkChange(Faction newFaction, EventFactionsChunkChangeType type, Set chunks) { // If we are supposed to clear at this chunk change type ... - Faction newFaction = event.getNewFaction(); - EventFactionsChunkChangeType type = event.getType(); Boolean remove = MConf.get().lwcRemoveOnChange.get(type); if (remove == null) return; if (remove == false) return; @@ -72,7 +72,26 @@ public class EngineLwc extends EngineAbstract // ... then remove for all other factions than the new one. // First we wait one tick to make sure the chunk ownership changes have been applied. // Then we remove the protections but we do it asynchronously to not lock the main thread. - removeAlienProtectionsAsyncNextTick(event.getChunk(), newFaction); + for (PS chunk : chunks) + { + removeAlienProtectionsAsyncNextTick(chunk, newFaction); + } + } + + public void removeProtectionsOnChunkChange(Faction newFaction, Map> typeChunks) + { + for (Entry> typeChunk : typeChunks.entrySet()) + { + final EventFactionsChunkChangeType type = typeChunk.getKey(); + final Set chunks = typeChunk.getValue(); + removeProtectionsOnChunkChange(newFaction, type, chunks); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void removeProtectionsOnChunkChange(EventFactionsChunksChange event) + { + removeProtectionsOnChunkChange(event.getNewFaction(), event.getTypeChunks()); } // -------------------------------------------- // diff --git a/src/main/java/com/massivecraft/factions/task/SpiralTask.java b/src/main/java/com/massivecraft/factions/task/SpiralTask.java deleted file mode 100644 index da23f79b..00000000 --- a/src/main/java/com/massivecraft/factions/task/SpiralTask.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.massivecraft.factions.task; - -import java.util.logging.Level; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; - -import com.massivecraft.factions.Factions; -import com.massivecraft.massivecore.ps.PS; - - -/* - * reference diagram, task should move in this pattern out from chunk 0 in the center. - * 8 [>][>][>][>][>] etc. - * [^][6][>][>][>][>][>][6] - * [^][^][4][>][>][>][4][v] - * [^][^][^][2][>][2][v][v] - * [^][^][^][^][0][v][v][v] - * [^][^][^][1][1][v][v][v] - * [^][^][3][<][<][3][v][v] - * [^][5][<][<][<][<][5][v] - * [7][<][<][<][<][<][<][7] - */ - -public abstract class SpiralTask implements Runnable -{ - // general task-related reference data - private transient World world = null; - private transient boolean readyToGo = false; - private transient int taskID = -1; - private transient int limit = 0; - - // values for the spiral pattern routine - private transient int x = 0; - private transient int z = 0; - private transient boolean isZLeg = false; - private transient boolean isNeg = false; - private transient int length = -1; - private transient int current = 0; - - // @SuppressWarnings("LeakingThisInConstructor") This actually triggers a warning in Eclipse xD Could we find another way to suppress the error please? :) - public SpiralTask(PS chunk, int radius) - { - chunk = chunk.getChunk(true); - - // limit is determined based on spiral leg length for given radius; see insideRadius() - this.limit = (radius - 1) * 2; - - this.world = Bukkit.getWorld(chunk.getWorld()); - if (this.world == null) - { - Factions.get().log(Level.WARNING, "[SpiralTask] A valid world must be specified!"); - this.stop(); - return; - } - - this.x = (int)chunk.getChunkX(); - this.z = (int)chunk.getChunkZ(); - - this.readyToGo = true; - - // get this party started - this.setTaskID(Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(Factions.get(), this, 2, 2)); - } - -/* - * This is where the necessary work is done; you'll need to override this method with whatever you want - * done at each chunk in the spiral pattern. - * Return false if the entire task needs to be aborted, otherwise return true to continue. - */ - public abstract boolean work(); - -/* - * Returns a PS pointing at the current chunk X and Z values. - */ - public final PS currentChunk() - { - return PS.valueOf(this.world.getName(), null, null, null, null, null, null, this.x, this.z, null, null, null, null, null); - } -/* - * Returns a Location pointing at the current chunk X and Z values. - * note that the Location is at the corner of the chunk, not the center. - */ - public final Location currentLocation() - { - - return new Location(world, this.x * 16, 65.0, this.z * 16); - } -/* - * Returns current chunk X and Z values. - */ - public final int getX() - { - return x; - } - public final int getZ() - { - return z; - } - - - -/* - * Below are the guts of the class, which you normally wouldn't need to mess with. - */ - - public final void setTaskID(int ID) - { - if (ID == -1) - this.stop(); - taskID = ID; - } - - public final void run() - { - if (!this.valid() || !readyToGo) return; - - // this is set so it only does one iteration at a time, no matter how frequently the timer fires - readyToGo = false; - - // make sure we're still inside the specified radius - if ( ! this.insideRadius()) return; - - // track this to keep one iteration from dragging on too long and possibly choking the system - long loopStartTime = now(); - - // keep going until the task has been running for 20ms or more, then stop to take a breather - while (now() < loopStartTime + 20) - { - // run the primary task on the current X/Z coordinates - if ( ! this.work()) - { - this.finish(); - return; - } - - // move on to next chunk in spiral - if ( ! this.moveToNext()) - return; - } - - // ready for the next iteration to run - readyToGo = true; - } - - // step through chunks in spiral pattern from center; returns false if we're done, otherwise returns true - public final boolean moveToNext() - { - if ( ! this.valid()) return false; - - // make sure we don't need to turn down the next leg of the spiral - if (current < length) - { - current++; - - // if we're outside the radius, we're done - if ( ! this.insideRadius()) return false; - } - else - { // one leg/side of the spiral down... - current = 0; - isZLeg ^= true; - // every second leg (between X and Z legs, negative or positive), length increases - if (isZLeg) - { - isNeg ^= true; - length++; - } - } - - // move one chunk further in the appropriate direction - if (isZLeg) - z += (isNeg) ? -1 : 1; - else - x += (isNeg) ? -1 : 1; - - return true; - } - - public final boolean insideRadius() - { - boolean inside = current < limit; - if (!inside) - this.finish(); - return inside; - } - - // for successful completion - public void finish() - { -// P.p.log("SpiralTask successfully completed!"); - this.stop(); - } - - // we're done, whether finished or cancelled - public final void stop() - { - if (!this.valid()) return; - - readyToGo = false; - Bukkit.getServer().getScheduler().cancelTask(taskID); - taskID = -1; - } - - // is this task still valid/workable? - public final boolean valid() - { - return taskID != -1; - } - - private static long now() - { - return System.currentTimeMillis(); - } -}