Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.managers;

import net.tfminecraft.simplefactions.map.AdminLandService;
import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.economy.TreasuryActions;

Expand Down Expand Up @@ -531,6 +532,21 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("forceremovecapital") && args.length == 2) {
if(!Cache.requireProvinces(p)) {
return true;
}
if(!Permissions.isAdmin(sender)) {
p.sendMessage("§a[SimpleFactions]§c You do not have access to this command");
return true;
}
Guild g = FactionManager.getGuildByString(args[1]);
if(g == null) {
p.sendMessage("§cNo guild by the id "+args[1]);
return true;
}
AdminLandService.forceRemoveCapital(g).forEach(p::sendMessage);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("forceleader") && args.length >= 3) {
if(!Permissions.isAdmin(sender)) {
p.sendMessage("§a[SimpleFactions]§c You do not have access to this command");
Expand Down Expand Up @@ -819,6 +835,26 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
FactionManager.getMap().unclaim(p, f, claim);
}

return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forceclaim") && args.length == 2) {
if(!Cache.requireProvinces(p)) {
return true;
}
if(!Permissions.isAdmin(sender)) {
p.sendMessage("§a[SimpleFactions]§c You do not have access to this command");
return true;
}
Faction f = FactionManager.getByString(args[1]);
if(f == null) {
p.sendMessage("§cNo faction by the id "+args[1]);
return true;
}
int claim = RestServer.getProvince(p);
if(claim == -2) {
p.sendMessage("§a[SimpleFactions] §cError! could not resolve province");
return true;
}
AdminLandService.forceClaim(f, claim).forEach(p::sendMessage);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("accept") && args.length == 1) {
if(!RequestManager.hasRequest(p)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package net.tfminecraft.simplefactions.map;

import java.util.ArrayList;
import java.util.List;

import net.tfminecraft.simplefactions.SimpleFactions;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.installation.InstallationTransferService;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.map.provinces.Province;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.war.civilwar.CivilWarCopy;
import net.tfminecraft.simplefactions.war.civilwar.wartime.CivilWarBorderLock;

/**
* Staff overrides for land: hand a province to a realm, or unseat a guild capital.
* Both skip prestige, caps and border rules, but still leave every realm's claims valid.
*/
public final class AdminLandService {
private AdminLandService() {
}

/** Gives {@code province} to {@code to}, taking it from its current owner if needed. */
public static List<String> forceClaim(Faction to, int province) {
List<String> lines = new ArrayList<>();
if (province <= 0) {
lines.add("§cThis location has no province!");
return lines;
}
Province site = SimpleFactions.getInstance().getProvinceManager().get(province);
if (site == null || !site.isValid() || site.isSea()) {
lines.add("§cThis location has no province!");
return lines;
}
if (to.ownsProvince(province)) {
lines.add("§c" + to.getName() + " §calready owns province " + province);
return lines;
}
if (CivilWarBorderLock.isLocked(to)) {
lines.add(CivilWarCopy.CANNOT_CLAIM);
return lines;
}
Faction owner = FactionManager.getByProvince(province);
if (owner != null) {
if (owner.getCapital() == province) {
lines.add("§cProvince " + province + " §cis the capital of " + owner.getName()
+ "§c. Move their capital first.");
return lines;
}
if (CivilWarBorderLock.isLocked(owner)) {
lines.add(CivilWarCopy.CANNOT_STEAL);
return lines;
}
InstallationTransferService.transfer(owner, to, province);
FactionManager.getMap().unclaim(null, owner, province);
lines.add("§aTook province " + province + " §afrom " + owner.getName());
}
to.addProvince(province);
lines.add("§aGave province " + province + " §ato " + to.getName());
if (!to.getProvinceHandler().previewLegalProvinces(to.getProvinces()).contains(province)) {
lines.add("§eWarning: this province is not connected to " + to.getName()
+ "§e's realm, so it will be dropped the next time their claims are checked.");
}
return lines;
}

/** Unseats a guild's capital and drops any land that only that capital was holding. */
public static List<String> forceRemoveCapital(Guild guild) {
List<String> lines = new ArrayList<>();
Faction faction = guild.getFaction();
if (!guild.hasCapital()) {
lines.add("§c" + guild.getName() + " §chas no capital");
return lines;
}
int capital = guild.getCapital();
if (guild.isBase() || capital == faction.getCapital()) {
lines.add("§cProvince " + capital + " §cis the faction capital of " + faction.getName()
+ "§c. Move it with §e/faction setcapital §cinstead.");
return lines;
}
if (CivilWarBorderLock.isLocked(faction)) {
lines.add(CivilWarCopy.CANNOT_UNCLAIM);
return lines;
}
List<Integer> before = new ArrayList<>(faction.getProvinces());
guild.setCapital(-1);
faction.getProvinceHandler().revalidateClaims();
before.removeAll(faction.getProvinces());
lines.add("§aRemoved the capital of " + guild.getName() + " §a(province " + capital + ")");
if (!before.isEmpty()) {
lines.add("§e" + faction.getName() + " §elost provinces cut off from their realm: " + before);
}
return lines;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ public boolean canClaim(int provinceId, boolean sea) {
}
}

// 2️⃣ Sea adjacency mode (existing flood logic)
// 2️⃣ Sea adjacency mode, sailing only from land that traces back to the capital
if (sea) {
return isSeaAdjacent(pm, target);
return isSeaAdjacentFromSet(pm, target, seaClaimSources(pm));
}

return false;
Expand Down Expand Up @@ -308,35 +308,6 @@ private boolean isSeaAdjacentFromSet(ProvinceManager pm, Province target, Set<In
return false;
}

private boolean isLandConnected(ProvinceManager pm, int startId, int targetId) {
Set<Integer> visited = new HashSet<>();
ArrayDeque<Integer> queue = new ArrayDeque<>();

queue.add(startId);
visited.add(startId);

while (!queue.isEmpty()) {
int currentId = queue.poll();
if (currentId == targetId) return true;

Province current = pm.get(currentId);
if (current == null) continue;

for (int nId : current.getNeighbours()) {
if (visited.contains(nId)) continue;
if (!provinces.contains(nId)) continue;

Province n = pm.get(nId);
if (n == null || n.isSea()) continue;

visited.add(nId);
queue.add(nId);
}
}

return false;
}

private boolean isSeaAdjacent(ProvinceManager pm, Province target) {
Set<Integer> visited = new HashSet<>();
ArrayDeque<Integer> queue = new ArrayDeque<>();
Expand Down Expand Up @@ -385,11 +356,11 @@ private boolean isSeaAdjacent(ProvinceManager pm, Province target) {
public void revalidateClaims() {
ProvinceManager pm = SimpleFactions.getInstance().getProvinceManager();

Set<Integer> legalCapitals = legalGuildCapitals(pm, capital, guildCapitals());
for (Guild g : f.getGuildHandler().getGuilds()) {
if (!g.hasCapital()) continue;

int cap = g.getCapital();
if (!isCapitalStillLegal(pm, cap, capital)) {
if (!legalCapitals.contains(g.getCapital())) {
g.setCapital(-1);
}
}
Expand Down Expand Up @@ -419,25 +390,83 @@ public List<Integer> previewProvincesLostIfCapitalMoved(int newCapital) {
return lost;
}

private List<Integer> guildCapitals() {
if (f.getGuildHandler() == null) return List.of();
return f.getGuildHandler().getGuilds().stream()
.filter(Guild::hasCapital).map(Guild::getCapital).toList();
}

private Set<Integer> computeLegalProvinces(ProvinceManager pm, int factionCapital) {
return computeLegalProvinces(pm, factionCapital, f.getGuildHandler().getGuilds().stream()
.filter(Guild::hasCapital).map(Guild::getCapital).toList());
return computeLegalProvinces(pm, factionCapital, guildCapitals());
}

private Set<Integer> computeLegalProvinces(ProvinceManager pm, int factionCapital, java.util.Collection<Integer> capitals) {
Set<Integer> legal = new HashSet<>();
Set<Integer> capitalBlob = capitalLandBlob(pm, factionCapital);
Set<Integer> seeds = new HashSet<>(legalGuildCapitals(pm, factionCapital, capitals, capitalBlob));
if (provinces.contains(factionCapital)) {
seeds.add(factionCapital);
}
return expandLegal(pm, seeds, capitalBlob);
}

for (int cap : capitals) {
if (!isCapitalStillLegal(pm, cap, factionCapital)) {
continue;
}
legal.add(cap);
/**
* Where a sea-mode claim may set sail from: land that traces back to the faction capital.
* A realm without a seated capital keeps sailing from anything it owns.
*/
private Set<Integer> seaClaimSources(ProvinceManager pm) {
if (!provinces.contains(capital)) {
return new HashSet<>(provinces);
}
return computeLegalProvinces(pm, capital);
}

if (provinces.contains(factionCapital)) {
legal.add(factionCapital);
private Set<Integer> legalGuildCapitals(ProvinceManager pm, int factionCapital, java.util.Collection<Integer> candidates) {
return legalGuildCapitals(pm, factionCapital, candidates, capitalLandBlob(pm, factionCapital));
}

/**
* Guild capitals that trace back to the faction capital: over owned land, or by water from
* land already traced, so one colony can anchor the next. An enclave cut off from the realm
* cannot vouch for itself by touching its own lake.
*/
private Set<Integer> legalGuildCapitals(ProvinceManager pm, int factionCapital,
java.util.Collection<Integer> candidates, Set<Integer> capitalBlob) {
Set<Integer> legal = new HashSet<>();
List<Integer> pending = new ArrayList<>();
for (int cap : candidates) {
Province site = pm.get(cap);
if (provinces.contains(cap) && site != null && site.isValid() && !site.isSea()) {
pending.add(cap);
}
}
if (!provinces.contains(factionCapital)) {
// Without a seated faction capital there is nothing to trace back to.
for (int cap : pending) {
if (isSeaAdjacent(pm, pm.get(cap))) legal.add(cap);
}
return legal;
}

Set<Integer> seeds = new HashSet<>(Set.of(factionCapital));
boolean changed = true;
while (changed) {
changed = false;
Set<Integer> reached = expandLegal(pm, seeds, capitalBlob);
for (java.util.Iterator<Integer> it = pending.iterator(); it.hasNext();) {
int cap = it.next();
if (capitalBlob.contains(cap) || isSeaAdjacentFromSet(pm, pm.get(cap), reached)) {
legal.add(cap);
seeds.add(cap);
it.remove();
changed = true;
}
}
}
return legal;
}

private Set<Integer> expandLegal(ProvinceManager pm, Set<Integer> seeds, Set<Integer> capitalBlob) {
Set<Integer> legal = new HashSet<>(seeds);

// Follow the same edges that allowed these claims: owned land, WATER
// bridges, and one SEA tile from the proposed capital's land blob.
Expand Down Expand Up @@ -469,7 +498,9 @@ public TopologyClaims previewTopology(int proposedCapital, List<Integer> owned,
copy.provinces = new ArrayList<>(owned);
ProvinceManager pm = SimpleFactions.getInstance().getProvinceManager();
java.util.Map<String, Integer> after = new java.util.LinkedHashMap<>(capitals);
after.replaceAll((id, cap) -> cap != -1 && !copy.isCapitalStillLegal(pm, cap, proposedCapital) ? -1 : cap);
Set<Integer> legalCapitals = copy.legalGuildCapitals(pm, proposedCapital,
after.values().stream().filter(cap -> cap != -1).toList());
after.replaceAll((id, cap) -> cap != -1 && !legalCapitals.contains(cap) ? -1 : cap);
int effectiveCapital = after.getOrDefault(f.getId(), proposedCapital);
copy.capital = effectiveCapital;
Set<Integer> legal = copy.computeLegalProvinces(pm, effectiveCapital,
Expand All @@ -487,10 +518,11 @@ public Set<Integer> previewLegalProvinces(java.util.Collection<Integer> owned) {
copy.capital = capital;
copy.provinces = new ArrayList<>(owned);
ProvinceManager pm = SimpleFactions.getInstance().getProvinceManager();
Set<Integer> legal = copy.computeLegalProvinces(pm, capital);
for (Guild g : f.getGuildHandler().getGuilds()) {
if (g.hasCapital() && !copy.isCapitalStillLegal(pm, g.getCapital(), capital)) {
legal.remove(g.getCapital());
Set<Integer> legal = copy.computeLegalProvinces(pm, capital, guildCapitals());
Set<Integer> legalCapitals = copy.legalGuildCapitals(pm, capital, guildCapitals());
for (int cap : guildCapitals()) {
if (!legalCapitals.contains(cap)) {
legal.remove(cap);
}
}
return legal;
Expand All @@ -503,19 +535,6 @@ public void restoreTopology(int restoredCapital, List<Integer> restoredProvinces
provinces.addAll(restoredProvinces);
}

private boolean isCapitalStillLegal(ProvinceManager pm, int guildCapitalId, int factionCapitalId) {
Province guildCapital = pm.get(guildCapitalId);
if (!provinces.contains(guildCapitalId) || guildCapital == null || !guildCapital.isValid()
|| guildCapital.isSea()) {
return false;
}
if (factionCapitalId != -1 && isLandConnected(pm, factionCapitalId, guildCapitalId)) {
return true;
}

return isSeaAdjacent(pm, guildCapital);
}

private void floodLand(ProvinceManager pm, int start, Set<Integer> out) {
ArrayDeque<Integer> queue = new ArrayDeque<>();
Set<Integer> visited = new HashSet<>();
Expand Down Expand Up @@ -571,7 +590,7 @@ public String getClaimDeniedReason(int provinceId, boolean sea) {
: "§cThis province does not border your current realm.";
}

if (adjacent || isSeaAdjacent(pm, target)) {
if (adjacent || isSeaAdjacentFromSet(pm, target, seaClaimSources(pm))) {
return "Success";
}

Expand Down
Loading
Loading