From a36ff51bf88a84072e5bb7ed2e75f8072077185e Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:01:09 +0000
Subject: [PATCH 01/35] wip: member keys core
---
pom.xml | 2 +-
.../simplefactions/database/Database.java | 1 +
.../simplefactions/database/GuildData.java | 2 +
.../espionage/EspionageAccess.java | 4 +-
.../espionage/EspionageCommands.java | 8 +-
.../espionage/EspionageService.java | 14 +-
.../simplefactions/government/Government.java | 6 +-
.../government/election/Election.java | 10 +-
.../government/movement/Movement.java | 12 +-
.../simplefactions/guild/Guild.java | 34 +-
.../simplefactions/identity/Members.java | 303 ++++++++++++++++++
.../InstallationProtectionListener.java | 2 +-
.../chat/FactionChatRecipientResolver.java | 4 +-
.../chat/GuildChatRecipientResolver.java | 4 +-
.../managers/CapitalMovePrompt.java | 4 +-
.../managers/CommandManager.java | 146 ++++-----
.../managers/FactionManager.java | 27 +-
.../managers/InventoryManager.java | 32 +-
.../managers/InviteCommands.java | 28 +-
.../managers/MercenaryCommandManager.java | 10 +-
.../managers/PlayerManager.java | 16 +-
.../managers/RelationManager.java | 20 +-
.../managers/SessionManager.java | 6 +-
.../simplefactions/managers/TitleManager.java | 4 +-
.../simplefactions/managers/WarManager.java | 10 +-
.../CampaignInstallationPickView.java | 8 +-
.../inventory/CampaignRaidLaunchView.java | 8 +-
.../managers/inventory/CampaignView.java | 46 +--
.../managers/inventory/CompanyView.java | 10 +-
.../managers/inventory/ContractView.java | 12 +-
.../managers/inventory/DeclareWarView.java | 4 +-
.../managers/inventory/ElectionCreator.java | 4 +-
.../managers/inventory/ElectionView.java | 6 +-
.../managers/inventory/EspionageView.java | 10 +-
.../managers/inventory/FactionCreator.java | 10 +-
.../managers/inventory/FactionView.java | 20 +-
.../managers/inventory/GovernmentCreator.java | 26 +-
.../managers/inventory/GovernmentView.java | 34 +-
.../managers/inventory/GuildView.java | 8 +-
.../managers/inventory/InstallationView.java | 12 +-
.../managers/inventory/InventoryUpdater.java | 6 +-
.../managers/inventory/MilitaryView.java | 8 +-
.../managers/inventory/MovementCreator.java | 24 +-
.../managers/inventory/MovementView.java | 44 +--
.../managers/inventory/RelationView.java | 30 +-
.../managers/inventory/TierTitleView.java | 20 +-
.../managers/inventory/WarCreator.java | 6 +-
.../managers/inventory/WarView.java | 10 +-
.../simplefactions/map/MapSystem.java | 4 +-
.../mercenary/company/MercenaryInvites.java | 4 +-
.../mercenary/contract/ContractHandler.java | 4 +-
.../simplefactions/objects/Faction.java | 17 +-
.../simplefactions/utils/EconomicImpact.java | 4 +-
.../simplefactions/utils/TabCompletion.java | 32 +-
.../vehicles/VehicleFactionCommands.java | 10 +-
.../BattleVehicleEligibilityService.java | 6 +-
.../berth/FactionVehicleReleaseListener.java | 4 +-
.../berth/VehicleTransferConsentService.java | 4 +-
.../berth/VehicleTransferListener.java | 4 +-
.../handover/VehicleHandoverService.java | 4 +-
.../war/battle/ui/BattleCommandManager.java | 6 +-
.../war/battle/warband/WarbandManager.java | 4 +-
.../warband/WarbandMembershipService.java | 4 +-
.../raid/CampaignRaidWarbandService.java | 10 +-
.../war/campaign/raid/RaidCommandManager.java | 6 +-
.../war/campaign/raid/RaidTabCompletion.java | 6 +-
.../runtime/BattleAutoresolveService.java | 6 +-
.../runtime/BattleScheduleLookups.java | 4 +-
.../CharacterNameResolutionTest.java | 2 +-
.../espionage/EspionageBypassTest.java | 4 +-
.../espionage/EspionagePermissionsTest.java | 12 +-
.../espionage/EspionageTestFixtures.java | 2 +-
.../espionage/IntelligenceBrowsingTest.java | 6 +-
.../espionage/IntelligencePolicyTest.java | 2 +-
.../espionage/OfficeAppointmentsTest.java | 2 +-
.../espionage/OfficePersistenceTest.java | 4 +-
.../espionage/OfficeVacancyTest.java | 4 +-
.../espionage/UnguardedFactionTest.java | 10 +-
.../espionage/VassalIntelligenceTest.java | 8 +-
.../managers/CommandManagerCoverageTest.java | 4 +-
.../managers/FactionManagerCoverageTest.java | 2 +-
.../contract/MercenaryMarketTest.java | 2 +-
...mainConfigurationBoundaryCoverageTest.java | 4 +-
.../objects/FactionCoverageTest.java | 4 +-
.../testsupport/NameKeyedMembers.java | 84 +++++
.../CampaignRaidIntruderServiceTest.java | 4 +-
.../org.junit.jupiter.api.extension.Extension | 1 +
src/test/resources/junit-platform.properties | 1 +
88 files changed, 955 insertions(+), 425 deletions(-)
create mode 100644 src/main/java/net/tfminecraft/simplefactions/identity/Members.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/testsupport/NameKeyedMembers.java
create mode 100644 src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
create mode 100644 src/test/resources/junit-platform.properties
diff --git a/pom.xml b/pom.xml
index 6905dd15..8d25d52a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
full
0.2.4
2.16.0
- 2.3.0
+ 2.4.0
2.10.0
2.1.0
diff --git a/src/main/java/net/tfminecraft/simplefactions/database/Database.java b/src/main/java/net/tfminecraft/simplefactions/database/Database.java
index 07144755..a045a9f1 100644
--- a/src/main/java/net/tfminecraft/simplefactions/database/Database.java
+++ b/src/main/java/net/tfminecraft/simplefactions/database/Database.java
@@ -411,6 +411,7 @@ public boolean saveFactionChecked(Faction f) {
GuildData gd = new GuildData();
gd.id = g.getId();
+ gd.incarnation = g.getIncarnation();
gd.name = g.getOwnName();
gd.leader = g.getLeader();
gd.leaderCharacter = g.getLeaderCharacter();
diff --git a/src/main/java/net/tfminecraft/simplefactions/database/GuildData.java b/src/main/java/net/tfminecraft/simplefactions/database/GuildData.java
index 37655c61..ce0f22e7 100644
--- a/src/main/java/net/tfminecraft/simplefactions/database/GuildData.java
+++ b/src/main/java/net/tfminecraft/simplefactions/database/GuildData.java
@@ -8,6 +8,8 @@
public class GuildData {
public String id;
+ /** Immutable identity of this guild, kept across renames and never reused. */
+ public String incarnation;
public String name;
public String leader;
diff --git a/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageAccess.java b/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageAccess.java
index f02c4517..12c4d61a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageAccess.java
+++ b/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageAccess.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.espionage;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.EnumSet;
import java.util.Set;
@@ -63,6 +65,6 @@ public static boolean denied(Player player, SFInventoryHolder holder) {
var spymaster = EspionageService.spymaster(faction);
return spymaster == null || !spymaster.isHolder(player.getUniqueId());
}
- return holder.getType() == SFGUI.SPYMASTER_SELECT && !faction.isLeader(player.getName());
+ return holder.getType() == SFGUI.SPYMASTER_SELECT && !faction.isLeader(Members.key(player));
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageCommands.java b/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageCommands.java
index 643a7531..1ddd054a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageCommands.java
+++ b/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageCommands.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.espionage;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -40,7 +42,7 @@ public static boolean matches(String subcommand) {
}
public static boolean handle(Player player, String[] args) {
- var faction = FactionManager.getByMember(player.getName());
+ var faction = FactionManager.getByMember(Members.key(player));
if (faction == null) {
player.sendMessage("§cYou need to belong to a faction.");
return true;
@@ -88,12 +90,12 @@ private static void usage(Player player) {
public static List complete(Player player, String[] args) {
List choices = new ArrayList<>();
- var faction = FactionManager.getByMember(player.getName());
+ var faction = FactionManager.getByMember(Members.key(player));
if (faction == null || !args[0].equalsIgnoreCase("spymaster")) return choices;
var holder = EspionageService.spymaster(faction);
boolean isHolder = holder != null && holder.isHolder(player.getUniqueId());
if (args.length == 2) {
- if (faction.isLeader(player.getName())) {
+ if (faction.isLeader(Members.key(player))) {
choices.add("remove");
for (String name : faction.getMembers())
if (Bukkit.getPlayerExact(name) != null && EspionageService.eligible(faction, name)) choices.add(name);
diff --git a/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageService.java b/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageService.java
index 4bf5ce9d..f434081c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/espionage/EspionageService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.espionage;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.LinkedHashMap;
@@ -43,7 +45,7 @@ public static void loadAptitudes(java.nio.file.Path file) throws java.io.IOExcep
}
public static boolean eligible(Faction faction, String playerName) {
- return faction.isMemberIgnoreCase(playerName) || faction.isLeader(playerName);
+ return faction.isMember(playerName) || faction.isLeader(playerName);
}
/** The leader's default holding follows the leadership; deliberate appointments stay. */
@@ -164,7 +166,7 @@ public static String duration(long millis) {
public static boolean isOwn(Player viewer, Faction target) {
return viewer != null && target != null
- && (target.isMemberIgnoreCase(viewer.getName()) || target.isLeader(viewer.getName()));
+ && (target.isMember(Members.key(viewer)) || target.isLeader(Members.key(viewer)));
}
public static boolean bypasses(Player viewer) {
@@ -270,7 +272,7 @@ public static java.util.List dirty = new java.util.LinkedHashSet<>();
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/Government.java b/src/main/java/net/tfminecraft/simplefactions/government/Government.java
index 574e1fba..a5d11a1c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/Government.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/Government.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.government;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
@@ -358,7 +360,7 @@ private void replaceLeader() {
}
public boolean isCouncilMember(Player p) {
- return p != null && isCouncilMember(p.getName());
+ return p != null && isCouncilMember(Members.key(p));
}
/** Name-based so offline checks (mercenary loyalty, contract signing) can use it too. */
@@ -430,7 +432,7 @@ public boolean canProposePolitical(Player p, Action action) {
case GUILD_MEMBER:
if(action == Action.NATIONHOOD) return false; //only leaders can demand that
case GUILD_LEADER:
- if(action == Action.NATIONHOOD && !FactionManager.getGuildByMember(p.getName()).canBeElevated(null)) return false;
+ if(action == Action.NATIONHOOD && !FactionManager.getGuildByMember(Members.key(p)).canBeElevated(null)) return false;
if(!PoliticalActionLoader.getByAction(action).allowGuilds()) return false;
break;
case VASSAL_LEADER:
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java b/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java
index 4346e629..1810b933 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.government.election;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -206,11 +208,11 @@ public void applyReasons(List lore, Candidate type, Player p) {
lore.add(StringFormatter.formatHex("§7- #ba7872Election is in the voting phase"));
}
if(type == Candidate.LEADER) {
- if(!gov.getFaction().isMember(p.getName())) {
+ if(!gov.getFaction().isMember(Members.key(p))) {
lore.add(StringFormatter.formatHex("§7- #ba7872You must be a member of the faction to be Leader"));
}
- Guild g = gov.getFaction().getGuild(p.getName());
- if (g != null && !g.isBase() && g.isLeader(p.getName())) {
+ Guild g = gov.getFaction().getGuild(Members.key(p));
+ if (g != null && !g.isBase() && g.isLeader(Members.key(p))) {
lore.add(StringFormatter.formatHex(
"§7- #ba7872Guild leaders cannot be faction leaders"
));
@@ -262,7 +264,7 @@ public void tick() {
if(active) {
if(notify == 300) {
for(Player p : Bukkit.getOnlinePlayers()) {
- if(canVote(p.getName()) && gov.getVotingBooths().size() > 0) {
+ if(canVote(Members.key(p)) && gov.getVotingBooths().size() > 0) {
p.sendMessage("§e=================================");
p.sendMessage("§a§lActive Election! §7Head to a §e§lVoting Booth §7to vote!");
p.sendMessage("§eBooths:");
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java
index 10317d80..fbc9f3fc 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.government.movement;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -586,11 +588,11 @@ private double getTotalTradePower() {
}
public Object getJoiningAs(Player p) {
- Member relation = f.getRelationToFaction(p.getName());
+ Member relation = f.getRelationToFaction(Members.key(p));
if (relation == Member.GUILD_LEADER || relation == Member.GUILD_MEMBER) {
- return FactionManager.getGuildByMember(p.getName());
+ return FactionManager.getGuildByMember(Members.key(p));
} else if (relation == Member.VASSAL_LEADER) {
- return FactionManager.getByMember(p.getName());
+ return FactionManager.getByMember(Members.key(p));
} else if (relation == Member.MEMBER) {
return p.getName();
}
@@ -598,8 +600,8 @@ public Object getJoiningAs(Player p) {
}
public boolean quickJoinCheck(Player p) {
- if(isMember(p.getName())) return false;
- Member relation = f.getRelationToFaction(p.getName());
+ if(isMember(Members.key(p))) return false;
+ Member relation = f.getRelationToFaction(Members.key(p));
switch(relation) {
case FOREIGNER:
case GUILD_MEMBER:
diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java b/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
index aa2a3819..7d934ced 100644
--- a/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
+++ b/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.guild;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -104,6 +106,8 @@ public class Guild {
private double donationAmount = 0.0;
private List dividendEligible = new ArrayList<>();
private MercenaryCompany company;
+ /** Fixed for this guild's lifetime; a later guild with the same name gets a new one. */
+ private String incarnation = java.util.UUID.randomUUID().toString();
public Guild(Faction f) {
host = f;
@@ -139,7 +143,7 @@ public Guild(String id, Player p, Faction f, int province) {
host = f;
this.id = format.formatId(id);
this.name = StringFormatter.formatHex(format.formatName(id));
- this.leader = p.getName();
+ this.leader = Members.key(p);
rgb = RandomRGB.random();
stance = Stance.NEUTRAL;
while(!RandomRGB.isFree(rgb)) {
@@ -159,7 +163,7 @@ public Guild(String id, Player p, Faction f, int province) {
for(Upgrade u : UpgradeLoader.getList()) {
upgrades.put(u.getId(), new Upgrade(u, 0));
}
- f.getOrCreateMainGuild().kick(p.getName()); //remove from main guild
+ f.getOrCreateMainGuild().kick(leader); //remove from main guild
this.ledger = new Ledger(this);
this.loanHandler = new LoanHandler(this);
this.dividendEligible = new ArrayList<>(members);
@@ -179,6 +183,7 @@ public Guild(GuildData data, Faction host) {
this.id = data.id;
this.name = data.name;
this.leader = data.leader;
+ if (data.incarnation != null && !data.incarnation.isBlank()) this.incarnation = data.incarnation;
this.rgb = data.rgb;
this.capital = data.capital;
this.stance = data.stance != null ? Stance.valueOf(data.stance) : Stance.NEUTRAL;
@@ -413,20 +418,22 @@ public void setHost(Faction host) {
}
public List getInvites() { return invites; }
public boolean isInvited(String p) {
- return findIgnoreCase(invites, p) != null;
+ return find(invites, p) != null;
}
public void invite(String p) {
if(p == null || isInvited(p)) return;
invites.add(p);
}
public boolean consumeInvite(String p) {
- String stored = findIgnoreCase(invites, p);
+ String stored = find(invites, p);
if(stored == null) return false;
invites.remove(stored);
return true;
}
public String getId() { return id; }
+ public String getIncarnation() { return incarnation; }
+
/** A removed branch still saved above level 0. The level is not refunded. */
static void warnRetiredBranch(Faction host, String guildName, String branchId, int level) {
if (level <= 0 || branchId == null) {
@@ -447,8 +454,8 @@ static void warnRetiredBranch(Faction host, String guildName, String branchId, i
/** Backing name, even while this guild is a faction base (display name follows the host). */
public String getOwnName() { return name; }
public List getMembers() { return members; }
- public boolean isMember(String p) { return findIgnoreCase(members, p) != null; }
- public boolean isMember(Player p) { return isMember(p.getName()); }
+ public boolean isMember(String p) { return find(members, p) != null; }
+ public boolean isMember(Player p) { return isMember(Members.key(p)); }
public void addMember(String p) {
if(isMember(p)) return;
if(!isBase() && host.getOrCreateMainGuild().isMember(p)) {
@@ -458,7 +465,7 @@ public void addMember(String p) {
members.add(p);
}
public void kick(String member) {
- String stored = findIgnoreCase(members, member);
+ String stored = find(members, member);
if(stored != null) members.remove(stored);
}
public String getLeader() { return isBase() ? host.getLeader() : leader; }
@@ -489,9 +496,9 @@ public void setLeader(String leader) {
}
public boolean isLeader(String p) {
String currentLeader = getLeader();
- return currentLeader != null && currentLeader.equalsIgnoreCase(p);
+ return currentLeader != null && currentLeader.equals(p);
}
- public boolean isLeader(Player p) { return isLeader(p.getName()); }
+ public boolean isLeader(Player p) { return isLeader(Members.key(p)); }
public Map getBranches() { return branches; }
public Branch getBranch(int i) {
return branches.getOrDefault(i, null);
@@ -1121,10 +1128,11 @@ private void clearFavoursAndRepressions() {
repressed = false;
}
- public static String findIgnoreCase(List names, String name) {
- if(name == null || names == null) return null;
- for(String stored : names) {
- if(stored != null && stored.equalsIgnoreCase(name)) return stored;
+ /** The stored member key equal to {@code key}, or null; keys match exactly. */
+ public static String find(List keys, String key) {
+ if(key == null || keys == null) return null;
+ for(String stored : keys) {
+ if(key.equals(stored)) return stored;
}
return null;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/identity/Members.java b/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
new file mode 100644
index 00000000..1368b18c
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
@@ -0,0 +1,303 @@
+package net.tfminecraft.simplefactions.identity;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
+import net.tfminecraft.tlibs.character.Characters;
+
+/**
+ * Member keys: every stored member string is the lower-case ID of a character.
+ * A player acts for the character of their current session; with no session
+ * they are a stranger to every guild.
+ */
+public final class Members {
+
+ /** Prefix of a leader seat whose original holder is waiting in quarantine. */
+ public static final String QUARANTINED = "quarantine:";
+
+ /** Answers the identity questions; production uses the shared character provider. */
+ public interface Resolver {
+ Optional session(Player player);
+
+ boolean isCurrent(CharacterSession session);
+
+ String key(Player player);
+
+ Optional info(String key);
+
+ Optional online(String key);
+ }
+
+ private static final Resolver SHARED = new SharedResolver();
+ private static volatile Resolver resolver = SHARED;
+
+ private Members() {}
+
+ public static void setResolver(Resolver next) {
+ resolver = next == null ? SHARED : next;
+ }
+
+ public static void useSharedIdentity() {
+ resolver = SHARED;
+ }
+
+ /** The current character's key, or null for a player without a ready session. */
+ public static String key(Player player) {
+ return player == null ? null : resolver.key(player);
+ }
+
+ /** Key form of a character ID. */
+ public static String key(UUID characterId) {
+ return characterId == null ? null : characterId.toString().toLowerCase(Locale.ROOT);
+ }
+
+ public static String key(CharacterRef ref) {
+ return ref == null ? null : key(ref.characterId());
+ }
+
+ public static Optional session(Player player) {
+ return player == null ? Optional.empty() : resolver.session(player);
+ }
+
+ public static boolean isCurrent(CharacterSession session) {
+ return session != null && resolver.isCurrent(session);
+ }
+
+ /** The character name for a key; offline characters are read from the provider. */
+ public static String display(String key) {
+ if (key == null || key.isBlank()) return "Unknown";
+ if (isQuarantined(key)) return "Unresolved member";
+ if (characterId(key) == null) return key;
+ return resolver.info(key).map(CharacterInfo::name).map(Members::plain).orElse("Unknown");
+ }
+
+ /** The name of the character the player is playing, for messages to others. */
+ public static String name(Player player) {
+ if (player == null) return "Unknown";
+ String key = key(player);
+ return key == null ? player.getName() : display(key);
+ }
+
+ /**
+ * The character name, with the account name after it for staff who
+ * bypass espionage, so they can tell who plays it.
+ */
+ public static String display(Player viewer, String key) {
+ String name = display(key);
+ if (!net.tfminecraft.simplefactions.espionage.EspionageService.bypasses(viewer)) return name;
+ String account = accountName(key);
+ return account == null ? name : name + " \u00a77(" + account + ")";
+ }
+
+ /** The account that plays the character, or empty if unknown. */
+ public static Optional accountId(String key) {
+ return ref(key).map(CharacterRef::ownerId);
+ }
+
+ /** The Minecraft name of the account that plays the character, or null. */
+ public static String accountName(String key) {
+ UUID owner = accountId(key).orElse(null);
+ if (owner == null) return null;
+ try {
+ return Bukkit.getServer() == null ? null : Bukkit.getOfflinePlayer(owner).getName();
+ } catch (RuntimeException ignored) {
+ return null;
+ }
+ }
+
+ /** Display names for several keys, in order. */
+ public static List display(Collection keys) {
+ List names = new ArrayList<>();
+ if (keys != null) for (String key : keys) names.add(display(key));
+ return names;
+ }
+
+ /** The online player currently playing that character. */
+ public static Optional online(String key) {
+ if (key == null || key.isBlank()) return Optional.empty();
+ return resolver.online(key);
+ }
+
+ /** Nullable form of {@link #online(String)} for older call sites. */
+ public static Player player(String key) {
+ return online(key).orElse(null);
+ }
+
+ public static boolean isOnline(String key) {
+ return online(key).isPresent();
+ }
+
+ public static Optional ref(String key) {
+ if (key == null || characterId(key) == null) return Optional.empty();
+ return resolver.info(key).map(CharacterInfo::ref);
+ }
+
+ public static Optional info(String key) {
+ if (key == null || characterId(key) == null) return Optional.empty();
+ return resolver.info(key);
+ }
+
+ /** Whether the player is currently playing the character with that key. */
+ public static boolean is(Player player, String key) {
+ String own = key(player);
+ return own != null && own.equals(key);
+ }
+
+ public static boolean isQuarantined(String key) {
+ return key != null && key.startsWith(QUARANTINED);
+ }
+
+ /**
+ * Finds a member among {@code keys} by what a player typed: an online
+ * player's Minecraft name or a character name. Null when nothing or more
+ * than one member matches.
+ */
+ public static String find(Collection keys, String input) {
+ if (keys == null || input == null) return null;
+ String query = unquote(input);
+ Player online = onlineByName(query);
+ String onlineKey = key(online);
+ if (onlineKey != null && keys.contains(onlineKey)) return onlineKey;
+ String match = null;
+ for (String key : keys) {
+ if (key == null || !display(key).equalsIgnoreCase(query)) continue;
+ if (match != null && !match.equals(key)) return null;
+ match = key;
+ }
+ return match;
+ }
+
+ /** The key of an online player named by Minecraft or character name, or null. */
+ public static String findOnline(String input) {
+ if (input == null) return null;
+ String query = unquote(input);
+ String direct = key(onlineByName(query));
+ if (direct != null) return direct;
+ String match = null;
+ for (Player player : onlinePlayers()) {
+ String key = key(player);
+ if (key == null || !display(key).equalsIgnoreCase(query)) continue;
+ if (match != null) return null;
+ match = key;
+ }
+ return match;
+ }
+
+ /**
+ * An online player named by Minecraft name, else by character name. Ambiguous
+ * character names never pick a player; the sender is told why nothing matched.
+ */
+ public static Player resolveOnline(Player sender, String input) {
+ String query = unquote(input);
+ Player exact = onlineByName(query);
+ if (exact != null) return exact;
+ List matches = new ArrayList<>();
+ for (Player player : onlinePlayers()) {
+ if (player != null && name(player).equalsIgnoreCase(query)) matches.add(player);
+ }
+ if (matches.size() == 1) return matches.getFirst();
+ if (sender != null) {
+ sender.sendMessage(matches.isEmpty()
+ ? "\u00a7cNo online player found by that character or Minecraft name."
+ : "\u00a7cSeveral characters share that name. Use their Minecraft name.");
+ }
+ return null;
+ }
+
+ /** Bukkit replaces only the current argument, so return the untyped suffix of a full name. */
+ public static List completions(Collection names, String[] args) {
+ if (args.length < 2) return List.of();
+ int skip = args.length - 2;
+ String prefix = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
+ return names.stream().filter(java.util.Objects::nonNull).map(ChatColor::stripColor).distinct()
+ .filter(name -> name.regionMatches(true, 0, prefix, 0, prefix.length()))
+ .map(name -> name.split(" "))
+ .filter(words -> words.length > skip)
+ .map(words -> String.join(" ", java.util.Arrays.copyOfRange(words, skip, words.length)))
+ .toList();
+ }
+
+ /** The parsed character ID of a key, or null if it is not one. */
+ public static UUID characterId(String key) {
+ if (key == null) return null;
+ try {
+ UUID id = UUID.fromString(key);
+ return id.toString().equalsIgnoreCase(key) ? id : null;
+ } catch (IllegalArgumentException ignored) {
+ return null;
+ }
+ }
+
+ private static Player onlineByName(String name) {
+ try {
+ return Bukkit.getServer() == null ? null : Bukkit.getPlayerExact(name);
+ } catch (RuntimeException ignored) {
+ return null;
+ }
+ }
+
+ private static Collection extends Player> onlinePlayers() {
+ try {
+ return Bukkit.getServer() == null ? List.of() : Bukkit.getOnlinePlayers();
+ } catch (RuntimeException ignored) {
+ return List.of();
+ }
+ }
+
+ private static String unquote(String input) {
+ String query = input.strip();
+ if (query.length() >= 2 && query.startsWith("\"") && query.endsWith("\""))
+ query = query.substring(1, query.length() - 1).strip();
+ return query;
+ }
+
+ static String plain(String name) {
+ String stripped = ChatColor.stripColor(name);
+ return stripped == null || stripped.isBlank() ? "Unknown" : stripped.strip();
+ }
+
+ /** Reads everything from the shared character provider on every call. */
+ static final class SharedResolver implements Resolver {
+ @Override
+ public Optional session(Player player) {
+ if (!Characters.available()) return Optional.empty();
+ return Characters.currentSession(player)
+ .filter(session -> session.ref().ownerId().equals(player.getUniqueId()))
+ .filter(Characters::isCurrent);
+ }
+
+ @Override
+ public boolean isCurrent(CharacterSession session) {
+ return Characters.available() && Characters.isCurrent(session);
+ }
+
+ @Override
+ public String key(Player player) {
+ return session(player).map(session -> Members.key(session.ref())).orElse(null);
+ }
+
+ @Override
+ public Optional info(String key) {
+ UUID id = characterId(key);
+ if (id == null || !Characters.available()) return Optional.empty();
+ return Characters.info(id).filter(info -> id.equals(info.ref().characterId()));
+ }
+
+ @Override
+ public Optional online(String key) {
+ return info(key).map(info -> Bukkit.getPlayer(info.ref().ownerId()))
+ .filter(player -> key.equals(key(player)));
+ }
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/installation/InstallationProtectionListener.java b/src/main/java/net/tfminecraft/simplefactions/installation/InstallationProtectionListener.java
index 9439ca89..91305c0d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/installation/InstallationProtectionListener.java
+++ b/src/main/java/net/tfminecraft/simplefactions/installation/InstallationProtectionListener.java
@@ -64,7 +64,7 @@ static boolean isOwnerMember(Player player, Installation installation) {
return false;
}
String name = player.getName();
- return owner.isMemberIgnoreCase(name) || name.equalsIgnoreCase(owner.getLeader());
+ return owner.isMember(name) || name.equalsIgnoreCase(owner.getLeader());
}
private static String blockedMessage(Installation installation) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/FactionChatRecipientResolver.java b/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/FactionChatRecipientResolver.java
index 1cbb7095..a95637f0 100644
--- a/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/FactionChatRecipientResolver.java
+++ b/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/FactionChatRecipientResolver.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.integration.rpcharacters.chat;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.Collections;
import java.util.Set;
@@ -19,7 +21,7 @@ public Set resolve(Player sender, ChatChannel channel) {
return Collections.emptySet();
}
- Faction faction = FactionManager.getByMember(sender.getName());
+ Faction faction = FactionManager.getByMember(Members.key(sender));
if (faction == null) {
return Collections.emptySet();
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/GuildChatRecipientResolver.java b/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/GuildChatRecipientResolver.java
index 7ad6b868..3e72fbe5 100644
--- a/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/GuildChatRecipientResolver.java
+++ b/src/main/java/net/tfminecraft/simplefactions/integration/rpcharacters/chat/GuildChatRecipientResolver.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.integration.rpcharacters.chat;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.Collections;
import java.util.Set;
@@ -19,7 +21,7 @@ public Set resolve(Player sender, ChatChannel channel) {
return Collections.emptySet();
}
- Guild guild = FactionManager.getGuildByMember(sender.getName());
+ Guild guild = FactionManager.getGuildByMember(Members.key(sender));
if (guild == null) {
return Collections.emptySet();
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/CapitalMovePrompt.java b/src/main/java/net/tfminecraft/simplefactions/managers/CapitalMovePrompt.java
index 177f8474..12c914e3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/CapitalMovePrompt.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/CapitalMovePrompt.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.HashMap;
import java.util.Map;
@@ -93,7 +95,7 @@ static Outcome confirm(Player player, CapitalMovePending state, boolean confirme
}
Faction faction = state.faction();
// The leader or the capital may have changed while the menu was open.
- if (FactionManager.getByLeader(player.getName()) != faction
+ if (FactionManager.getByLeader(Members.key(player)) != faction
|| faction.getCapital() != state.fromCapital()) {
player.sendMessage("§cThe capital changed while you were deciding. Nothing was charged.");
return Outcome.REFUSED;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
index 44749ab2..c9b1fd27 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageCommands;
import net.tfminecraft.simplefactions.utils.BankAmount;
import net.tfminecraft.simplefactions.vehicles.berth.VehicleFindMessages;
@@ -95,7 +97,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("create") && args.length == 2) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to be in a faction to make a guild");
return true;
@@ -104,7 +106,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cYou are already in a guild");
return true;
}
- if(f.getLeader().equalsIgnoreCase(p.getName())) {
+ if(Members.is(p, f.getLeader())) {
p.sendMessage("§cYou are the leader of the faction");
return true;
}
@@ -128,7 +130,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cNo guild by the id "+args[1]);
return true;
}
- if(!guild.getLeader().equalsIgnoreCase(p.getName()) && !Permissions.isAdmin(sender)) {
+ if(!Members.is(p, guild.getLeader()) && !Permissions.isAdmin(sender)) {
p.sendMessage("§cYou are not the leader of this guild");
return true;
}
@@ -164,7 +166,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cGuild "+guild.getName()+" §cdeleted!");
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("dummyLeader") && args.length == 1) {
- Guild guild = FactionManager.getGuildByMember(p.getName());
+ Guild guild = FactionManager.getGuildByMember(Members.key(p));
if(!Permissions.isAdmin(sender)) {
p.sendMessage("§cYou do not have access to this command");
return true;
@@ -176,7 +178,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
guild.dummyLeader(p);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("dummify") && args.length == 1) {
- Guild guild = FactionManager.getGuildByMember(p.getName());
+ Guild guild = FactionManager.getGuildByMember(Members.key(p));
if(!Permissions.isAdmin(sender)) {
p.sendMessage("§cYou do not have access to this command");
return true;
@@ -188,7 +190,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
guild.dummify(p);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("menu") && args.length == 1) {
- Guild guild = FactionManager.getGuildByMember(p.getName());
+ Guild guild = FactionManager.getGuildByMember(Members.key(p));
if(guild == null) {
p.sendMessage("§cYou are not in a guild");
return true;
@@ -201,7 +203,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
i.guildList(p);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("invite") && args.length >= 2) {
- Guild guild = FactionManager.getGuildByLeader(p.getName());
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));
if(guild == null) {
p.sendMessage("§cYou are not the leader of a guild");
return true;
@@ -245,9 +247,9 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
}
- boolean inThisFaction = guild.getFaction().isMemberIgnoreCase(name)
+ boolean inThisFaction = guild.getFaction().isMember(name)
|| (otherGuild != null && otherGuild.getFaction().equals(guild.getFaction()));
- if(!inThisFaction && guild.getFaction().hasFactionRule(Rules.CLOSED_BORDERS) && !guild.getFaction().isLeader(p.getName())) {
+ if(!inThisFaction && guild.getFaction().hasFactionRule(Rules.CLOSED_BORDERS) && !guild.getFaction().isLeader(Members.key(p))) {
p.sendMessage("§cThe faction has closed borders, cannot invite members from outside the faction");
return true;
}
@@ -257,21 +259,21 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
invited.sendMessage(InviteCommands.buttons(cmd2, guild.getId(), guild.getName()));
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("leave") && args.length == 1) {
- if(FactionManager.getGuildByMember(p.getName()) == null) {
+ if(FactionManager.getGuildByMember(Members.key(p)) == null) {
p.sendMessage("§cYou are not in a guild");
return true;
}
- if(FactionManager.getGuildByLeader(p.getName()) != null) {
+ if(FactionManager.getGuildByLeader(Members.key(p)) != null) {
p.sendMessage("§cA leader cannot abandon their guild.");
p.sendMessage("§cUse /guild delete first");
return true;
}
- Guild g = FactionManager.getGuildByMember(p.getName());
- g.kick(p.getName());
+ Guild g = FactionManager.getGuildByMember(Members.key(p));
+ g.kick(Members.key(p));
p.sendMessage("§aLeft "+g.getName());
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("kick") && args.length == 2) {
- Guild guild = FactionManager.getGuildByLeader(p.getName());
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));
if(guild == null) {
p.sendMessage("§cYou are not the leader of a guild");
return true;
@@ -297,8 +299,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("setbank") && args.length == 1) {
- if(FactionManager.getGuildByLeader(p.getName()) != null) {
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ if(FactionManager.getGuildByLeader(Members.key(p)) != null) {
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
String bankBlockReason = BankPlacementValidator.failureReasonForGuild(g, p.getLocation());
if(bankBlockReason != null) {
p.sendMessage(bankBlockReason);
@@ -318,8 +320,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("deposit") && args.length == 2) {
- if(FactionManager.getGuildByMember(p.getName()) != null) {
- Guild g = FactionManager.getGuildByMember(p.getName());
+ if(FactionManager.getGuildByMember(Members.key(p)) != null) {
+ Guild g = FactionManager.getGuildByMember(Members.key(p));
Bank b = g.getBank();
if(b == null) {
p.sendMessage("§cYour guild has not established a bank");
@@ -356,8 +358,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("withdraw") && args.length == 2) {
// Only the guild's leader may take money out; members of the main guild would
// otherwise be able to empty the faction treasury.
- if(FactionManager.getGuildByLeader(p.getName()) != null) {
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ if(FactionManager.getGuildByLeader(Members.key(p)) != null) {
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
Bank b = g.getBank();
if(b == null) {
p.sendMessage("§cYour guild has not established a bank");
@@ -394,7 +396,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
if(!Cache.requireProvinces(p)) {
return true;
}
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
if(g == null) {
p.sendMessage("§cYou must be the leader of a guild to set the capital");
return true;
@@ -426,7 +428,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("setbanner") && args.length == 1) {
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
if(g == null) {
p.sendMessage("§cYou must be the leader of a guild to change the banner!");
return true;
@@ -446,7 +448,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§aGuild banner changed!");
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("setcolour") && args.length == 2) {
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
if(g == null) {
p.sendMessage("§cYou must be the leader of a guild to change the colour!");
return true;
@@ -472,12 +474,12 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("setleader") && args.length == 2) {
- Guild g = FactionManager.getGuildByMember(p.getName());
+ Guild g = FactionManager.getGuildByMember(Members.key(p));
if(g == null) {
p.sendMessage("§cYou need to have a guild to change leader");
return true;
}
- if(!p.getName().equalsIgnoreCase(g.getLeader())) {
+ if(!Members.is(p, g.getLeader())) {
p.sendMessage("§cOnly the leader can set a new leader!");
return true;
}
@@ -491,7 +493,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
g.setLeader(args[1]);
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(g.isMember(pl.getName())) {
+ if(g.isMember(Members.key(pl))) {
pl.sendMessage("§a"+args[1]+ " is the new guild leader!");
}
}
@@ -510,7 +512,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cThis is the base guild, use /faction forceleader instead");
return true;
}
- String member = Guild.findIgnoreCase(g.getMembers(), args[2]);
+ String member = Members.find(g.getMembers(), args[2]);
if(member == null) {
p.sendMessage("§cPlayer is not in the guild");
return true;
@@ -522,13 +524,13 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
g.setLeader(member);
p.sendMessage("§a"+member+" now leads "+g.getName());
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(!pl.equals(p) && g.isMember(pl.getName())) {
+ if(!pl.equals(p) && g.isMember(Members.key(pl))) {
pl.sendMessage("§a"+member+ " is the new guild leader!");
}
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("rename") && args.length == 2) {
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
if(g == null) {
p.sendMessage("§a[SimpleFactions]§c Error! You must be the leader of a guild to rename one!");
return true;
@@ -543,7 +545,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("create") && args.length == 2) {
- if(FactionManager.getByMember(p.getName()) != null) {
+ if(FactionManager.getByMember(Members.key(p)) != null) {
p.sendMessage("§cYou already have a faction!");
return true;
}
@@ -566,8 +568,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
if(!Cache.requireProvinces(p)) {
return true;
}
- if(FactionManager.getByLeader(p.getName()) != null) {
- Faction f = FactionManager.getByMember(p.getName());
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f.getProvinces().isEmpty()) {
p.sendMessage("§cUse §e/faction setcapital §cto claim your first province and found your capital city.");
return true;
@@ -586,7 +588,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
if(!Cache.requireProvinces(p)) {
return true;
}
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to be a faction leader to construct installations");
return true;
@@ -629,7 +631,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
if(!Cache.requireProvinces(p)) {
return true;
}
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to be a faction leader to deconstruct installations");
return true;
@@ -659,7 +661,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
if (!Cache.requireProvinces(p)) {
return true;
}
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if (f == null) {
p.sendMessage("§cYou need to be a faction leader to upgrade installations");
return true;
@@ -717,7 +719,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p, transferId == null || transferId.isBlank() ? null : transferId);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("findvehicles")) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage(net.tfminecraft.simplefactions.vehicles.berth.VehicleFindMessages.notLeader());
return true;
@@ -743,8 +745,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
Faction f = null;
if(args.length == 1) {
- if(FactionManager.getByLeader(p.getName()) != null) {
- f = FactionManager.getByMember(p.getName());
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
+ f = FactionManager.getByMember(Members.key(p));
} else {
p.sendMessage("§cYou need to be a faction leader to unclaim land");
return true;
@@ -783,12 +785,12 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
RequestManager.decline(p);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setcolour") && args.length == 2) {
- if(FactionManager.getByLeader(p.getName()) != null) {
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
String rgb = args[1];
int result = FactionManager.validateRGB(rgb);
if(result == 0) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
FactionManager.getMap().enqueue("nation", f.getRGB());
f.setRGB(rgb);
FactionManager.getMap().enqueue("nation", f.getRGB());
@@ -812,7 +814,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
if(!Permissions.isAdmin(sender)) {
- if(!f.getMembers().contains(p.getName())) {
+ if(!f.getMembers().contains(Members.key(p))) {
p.sendMessage("§cCannot delete a faction you are not part of!");
return true;
}
@@ -824,7 +826,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cCannot delete a faction with active loans");
return true;
}
- if(!p.getName().equalsIgnoreCase(f.getLeader())) {
+ if(!Members.is(p, f.getLeader())) {
p.sendMessage("§cOnly the faction leader can delete the faction!");
return true;
}
@@ -851,7 +853,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
i.factionList(p);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("menu") && args.length == 1) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) {
p.sendMessage("§cYou are not in a faction");
return true;
@@ -860,12 +862,12 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
i.factionView(p, f);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("kick") && args.length == 2) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to have a faction to kick someone");
return true;
}
- if(!p.getName().equalsIgnoreCase(f.getLeader())) {
+ if(!Members.is(p, f.getLeader())) {
p.sendMessage("§cOnly the leader can dismiss members!");
return true;
}
@@ -890,12 +892,12 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setleader") && args.length == 2) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to have a faction to change leader");
return true;
}
- if(!p.getName().equalsIgnoreCase(f.getLeader())) {
+ if(!Members.is(p, f.getLeader())) {
p.sendMessage("§cOnly the leader can set a new leader!");
return true;
}
@@ -917,18 +919,18 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
f.setLeader(args[1]);
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(f.getMembers().contains(pl.getName())) {
+ if(f.getMembers().contains(Members.key(pl))) {
pl.sendMessage("§a"+args[1]+ " is the new faction leader!");
}
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("invite") && args.length >= 2) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to have a faction to invite someone");
return true;
}
- if(!p.getName().equalsIgnoreCase(f.getLeader())) {
+ if(!Members.is(p, f.getLeader())) {
p.sendMessage("§cOnly the leader can invite new members!");
return true;
}
@@ -938,7 +940,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
String name = invited.getName();
- if(f.isMemberIgnoreCase(name)) {
+ if(f.isMember(name)) {
p.sendMessage("§cPlayer is already a member");
return true;
}
@@ -956,26 +958,26 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
invited.sendMessage(InviteCommands.buttons(cmd1, f.getId(), f.getName()));
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("leave") && args.length == 1) {
- if(FactionManager.getByMember(p.getName()) == null) {
+ if(FactionManager.getByMember(Members.key(p)) == null) {
p.sendMessage("§cYou are not in a faction");
return true;
}
- if(FactionManager.getByLeader(p.getName()) != null) {
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
p.sendMessage("§cA leader cannot abandon their faction.");
p.sendMessage("§cUse /faction delete first");
return true;
}
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f.isInGuild(p.getName())) {
p.sendMessage("§cYou are in a guild, use /guild leave instead");
return true;
}
- f.forceRemoveMember(p.getName());
+ f.forceRemoveMember(Members.key(p));
p.sendMessage("§aLeft "+f.getName());
f.updatePrestige();
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("rename") && args.length == 2) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§a[SimpleFactions]§c Error! You must be the leader of a faction to rename one!");
return true;
@@ -990,7 +992,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§aFaction renamed to "+f.getName());
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setrulertitle") && args.length == 2) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou must be the leader of a faction to change ruler title");
return true;
@@ -1003,7 +1005,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
if(!Cache.requireProvinces(p)) {
return true;
}
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou must be the leader of a faction to set the capital");
return true;
@@ -1057,7 +1059,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
CapitalMovePrompt.applyFactionCapitalMove(p, f, claim, name);
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setrulingsystem") && args.length == 2) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou must be the leader of a faction to change ruling system!");
return true;
@@ -1067,7 +1069,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§aFaction ruling system changed to "+f.getGovernmentString());
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setculture") && args.length == 2) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou must be the leader of a faction to change culture!");
return true;
@@ -1077,7 +1079,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§aFaction culture changed to "+f.getCulture());
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setreligion") && args.length == 2) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou must be the leader of a faction to change religion!");
return true;
@@ -1087,7 +1089,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§aFaction religion changed to "+f.getReligion());
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setbanner") && args.length == 1) {
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if(f == null) {
p.sendMessage("§cYou must be the leader of a faction to change the banner!");
return true;
@@ -1102,8 +1104,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§aFaction banner changed!");
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setbank") && args.length == 1) {
- if(FactionManager.getByLeader(p.getName()) != null) {
- Faction f = FactionManager.getByMember(p.getName());
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
+ Faction f = FactionManager.getByMember(Members.key(p));
String bankBlockReason = BankPlacementValidator.failureReasonForFaction(f, p.getLocation());
if(bankBlockReason != null) {
p.sendMessage(bankBlockReason);
@@ -1123,8 +1125,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("deposit") && args.length == 2) {
- if(FactionManager.getByMember(p.getName()) != null) {
- Faction f = FactionManager.getByMember(p.getName());
+ if(FactionManager.getByMember(Members.key(p)) != null) {
+ Faction f = FactionManager.getByMember(Members.key(p));
Bank b = f.getBank();
if(b == null) {
p.sendMessage("§cYour faction has not established a bank");
@@ -1159,8 +1161,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("withdraw") && args.length == 2) {
- if(FactionManager.getByLeader(p.getName()) != null) {
- Faction f = FactionManager.getByMember(p.getName());
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
+ Faction f = FactionManager.getByMember(Members.key(p));
Bank b = f.getBank();
if(b == null) {
p.sendMessage("§cYour faction has not established a bank");
@@ -1418,7 +1420,7 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
}
f.setLeader(args[2]);
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(f.getMembers().contains(pl.getName())) {
+ if(f.getMembers().contains(Members.key(pl))) {
pl.sendMessage("§a"+args[2]+ " is the new faction leader!");
}
}
@@ -1443,7 +1445,7 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
}
f.addMember(args[2]);;
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(f.getMembers().contains(pl.getName())) {
+ if(f.getMembers().contains(Members.key(pl))) {
pl.sendMessage("§a"+args[2]+ " joined the faction!");
}
}
@@ -1535,7 +1537,7 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
f.getGovernment().getElection().start();
p.sendMessage("§aElection started for " + f.getName() + "!");
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(f.getMembers().contains(pl.getName())) {
+ if(f.getMembers().contains(Members.key(pl))) {
pl.sendMessage("§a§lElection Started! §7Vote at voting booths!");
pl.playSound(pl.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
}
@@ -1558,7 +1560,7 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
f.getGovernment().getElection().end();
p.sendMessage("§aElection ended for " + f.getName() + "!");
for(Player pl : Bukkit.getOnlinePlayers()) {
- if(f.getMembers().contains(pl.getName())) {
+ if(f.getMembers().contains(Members.key(pl))) {
pl.sendMessage("§e§lElection Ended! §7Results have been applied!");
pl.playSound(pl.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1.5f);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java
index 81ecc581..bec38b6c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -685,9 +686,11 @@ public static boolean guildExists(String id) {
}
public static boolean canJoinGuild(Player p) {
- Guild guild = getGuildByMember(p.getName());
+ String key = Members.key(p);
+ if(key == null) return false;
+ Guild guild = getGuildByMember(key);
if(guild == null) return true;
- if(guild.getLeader().equalsIgnoreCase(p.getName())) return false;
+ if(key.equals(guild.getLeader())) return false;
return guild.getType().isBase();
}
@@ -695,13 +698,14 @@ public static Guild getGuildByMember(String player) {
if(player == null) return null;
for(Faction f : factions) {
for(Guild guild : f.getGuildHandler().getGuilds()) {
- if(Guild.findIgnoreCase(guild.getMembers(), player) != null) return guild;
+ if(Guild.find(guild.getMembers(), player) != null) return guild;
}
}
return null;
}
public static Guild getGuildByLeader(String leader) {
+ if(leader == null) return null;
for(Faction f : factions) {
for(Guild guild : f.getGuildHandler().getGuilds()) {
if(guild.isLeader(leader)) return guild;
@@ -915,16 +919,17 @@ public static List getCopy(){
return c;
}
public static Faction getByLeader(String name) {
+ if(name == null) return null;
for(Faction f : factions) {
- if(f.getLeader().equalsIgnoreCase(name)) return f;
+ if(name.equals(f.getLeader())) return f;
}
return null;
}
public static Faction getByMember(String name) {
if(name == null) return null;
for(Faction f : factions) {
- if(f.isMemberIgnoreCase(name)) return f;
- if(f.getLeader().equalsIgnoreCase(name)) {
+ if(f.isMember(name)) return f;
+ if(name.equals(f.getLeader())) {
f.addMember(name);
return f;
}
@@ -1014,7 +1019,7 @@ public static void requestRelocation(Player sender, Guild g, Faction target, int
public static void acceptRelocateRequest(Player p) {
RelocateRequest req = (RelocateRequest) RequestManager.getRequest(p);
- Faction reciever = FactionManager.getByLeader(p.getName());
+ Faction reciever = FactionManager.getByLeader(Members.key(p));
if(reciever == null) {
p.sendMessage("§cYou do not have a faction");
return;
@@ -1073,7 +1078,7 @@ public static void requestElevation(Player sender, Guild target) {
public static void acceptElevationRequest(Player p) {
if (!(RequestManager.getRequest(p) instanceof ElevateRequest req)) return;
- Guild guild = FactionManager.getGuildByLeader(p.getName());
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));
if(guild == null) {
p.sendMessage("§cYou are not the leader of a guild");
return;
@@ -1270,7 +1275,7 @@ boolean presentBooth(Player p, Block b) {
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return true;
}
- if(!f.canVote(p.getName())) {
+ if(!f.canVote(Members.key(p))) {
p.sendMessage("§cYou have no voting rights in this faction");
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return true;
@@ -1289,7 +1294,7 @@ public void placeVotingBooth(BlockPlaceEvent e) {
}
void registerVotingBooth(Player p, Block b) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) return;
if(!mayManageVotingBooths(p, f)) {
p.sendMessage("§cOnly the faction leader can set up a voting booth");
@@ -1323,7 +1328,7 @@ boolean mayRemoveVotingBooth(Player p, Block b) {
}
private static boolean mayManageVotingBooths(Player p, Faction f) {
- return p.getName().equalsIgnoreCase(f.getLeader()) || Permissions.isAdmin(p);
+ return Members.is(p, f.getLeader()) || Permissions.isAdmin(p);
}
void unregisterVotingBooth(Player p, Block b) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java
index d87ed35d..23412cc5 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageAccess;
import net.tfminecraft.simplefactions.managers.inventory.EspionageView;
@@ -431,7 +433,7 @@ public boolean isPayingLoan(Player p) {
}
public void setPayingLoan(Player p, Loan loan) {
- Guild guild = FactionManager.getGuildByLeader(p.getName());;
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));;
if(guild == null) return;
loanPayments.put(p, new LoanPayment(guild, loan));
p.closeInventory();
@@ -540,7 +542,7 @@ public void taxChat(Player p, AsyncPlayerChatEvent e) {
*/
private void submitRateProposal(Player p, Faction f, Proposal proposal) {
Government gov = f.getGovernment();
- if(!gov.hasCouncil() && f.isLeader(p.getName())) {
+ if(!gov.hasCouncil() && f.isLeader(Members.key(p))) {
p.sendMessage("§aChange applied!");
proposal.apply(null);
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
@@ -556,7 +558,7 @@ private void submitRateProposal(Player p, Faction f, Proposal proposal) {
p.sendTitle("", "§aProposal Added", 20, 80, 20);
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
} else if(gov.canProposeOrStartMovement(p)) {
- Movement movement = gov.getMovementByMember(p.getName());
+ Movement movement = gov.getMovementByMember(Members.key(p));
if(movement != null) {
movement.createCause(p.getName(), proposal);
p.sendMessage("§aProposal added to your movement! Rally support for your proposal by sharing it with your faction and allies!");
@@ -628,7 +630,7 @@ public void feeChat(Player p, AsyncPlayerChatEvent e) {
@SuppressWarnings("deprecation")
public void loanPaymentChat(Player p, AsyncPlayerChatEvent e) {
Guild g = loanPayments.get(p).getGuild();
- Guild compare = FactionManager.getGuildByLeader(p.getName());
+ Guild compare = FactionManager.getGuildByLeader(Members.key(p));
if(compare == null) {
loanPayments.remove(p); //player left guild
return;
@@ -815,7 +817,7 @@ public void slotChat(Player p, AsyncPlayerChatEvent e) {
contractView.detailView(p, guild, contract.getId());
return;
}
- if (!company.isLeader(p.getName())) {
+ if (!company.isLeader(Members.key(p))) {
slotChanges.remove(p);
p.sendMessage("§cOnly the guild leader can change a contract's slots.");
return;
@@ -902,7 +904,7 @@ private boolean executeQueueCancel(Player player, QueueCancelPayload.Parsed pars
return switch (parsed.type()) {
case MILITARY -> {
Faction f = FactionManager.getByString(parsed.ownerId());
- yield f != null && player.getName().equalsIgnoreCase(f.getLeader())
+ yield f != null && Members.is(player, f.getLeader())
&& f.getMilitary().cancelQueue(parsed.index());
}
case GUILD_UPGRADE -> {
@@ -911,21 +913,21 @@ private boolean executeQueueCancel(Player player, QueueCancelPayload.Parsed pars
}
case INSTALLATION -> {
Faction f = FactionManager.getByString(parsed.ownerId());
- yield f != null && player.getName().equalsIgnoreCase(f.getLeader())
+ yield f != null && Members.is(player, f.getLeader())
&& f.getInstallationHandler().cancelPending(parsed.detail());
}
case COMPANY_SLOT -> {
Guild guild = FactionManager.getGuildByString(parsed.ownerId());
yield guild != null
&& guild.getCompany() != null
- && guild.getCompany().isLeader(player.getName())
+ && guild.getCompany().isLeader(Members.key(player))
&& guild.getCompany().cancelSlotQueue(parsed.index());
}
case COMPANY_UPGRADE -> {
Guild guild = FactionManager.getGuildByString(parsed.ownerId());
yield guild != null
&& guild.getCompany() != null
- && guild.getCompany().isLeader(player.getName())
+ && guild.getCompany().isLeader(Members.key(player))
&& guild.getCompany().cancelUpgradeQueue(parsed.index());
}
};
@@ -1159,7 +1161,7 @@ public void clickButton(InventoryClickEvent e) {
factionView(p, f);
break;
case DIPLOMACY_VIEW:
- Faction viewer = FactionManager.getByMember(p.getName());
+ Faction viewer = FactionManager.getByMember(Members.key(p));
if (viewer != null) {
diplomacyListView(null, p, viewer, true);
} else {
@@ -1490,7 +1492,7 @@ public void clickButton(InventoryClickEvent e) {
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
return;
}
- if(f == null || !f.getLeader().equalsIgnoreCase(p.getName())) return;
+ if(f == null || !Members.is(p, f.getLeader())) return;
Regiment r = f.getMilitary().getRegiment(data);
if(r == null) return;
r.sizeDecrease();
@@ -1503,7 +1505,7 @@ public void clickButton(InventoryClickEvent e) {
data = m.getPersistentDataContainer().get(key, PersistentDataType.STRING);
if(data != null) {
Faction f = confirming.get(p);
- if (f == null || !p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (f == null || !Members.is(p, f.getLeader())) {
confirming.remove(p);
p.closeInventory();
return;
@@ -1529,7 +1531,7 @@ public void clickButton(InventoryClickEvent e) {
Faction f = confirming.get(p);
boolean fromCommand = installationConfirmFromCommand.getOrDefault(p, false);
installationConfirmFromCommand.remove(p);
- if (f == null || !p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (f == null || !Members.is(p, f.getLeader())) {
confirming.remove(p);
p.closeInventory();
return;
@@ -1557,7 +1559,7 @@ public void clickButton(InventoryClickEvent e) {
data = m.getPersistentDataContainer().get(key, PersistentDataType.STRING);
if(data != null) {
Faction f = confirming.get(p);
- if (f == null || !p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (f == null || !Members.is(p, f.getLeader())) {
confirming.remove(p);
p.closeInventory();
return;
@@ -1582,7 +1584,7 @@ public void clickButton(InventoryClickEvent e) {
data = m.getPersistentDataContainer().get(key, PersistentDataType.STRING);
if (data != null) {
Faction f = confirming.get(p);
- if (f == null || !p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (f == null || !Members.is(p, f.getLeader())) {
confirming.remove(p);
p.closeInventory();
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java b/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java
index 7b08b6c6..7bd87862 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -43,11 +45,11 @@ public static boolean handle(Player p, String command, String[] args) {
if (guild) joinGuild(p, args[1]);
else joinFaction(p, args[1]);
} else if (guild) {
- List invites = guildInvites(p.getName());
+ List invites = guildInvites(Members.key(p));
joinOnlyInvite(p, GUILD, invites.stream().map(Guild::getId).toList(),
invites.stream().map(Guild::getName).toList());
} else {
- List invites = factionInvites(p.getName());
+ List invites = factionInvites(Members.key(p));
joinOnlyInvite(p, FACTION, invites.stream().map(Faction::getId).toList(),
invites.stream().map(Faction::getName).toList());
}
@@ -119,7 +121,7 @@ static List factionInvites(String player) {
}
private static void joinGuild(Player p, String id) {
- if (FactionManager.getGuildByLeader(p.getName()) != null) {
+ if (FactionManager.getGuildByLeader(Members.key(p)) != null) {
p.sendMessage("§cYou are the leader of a guild");
return;
}
@@ -132,22 +134,22 @@ private static void joinGuild(Player p, String id) {
p.sendMessage("§cNo guild by the id " + id);
return;
}
- if (!g.consumeInvite(p.getName())) {
+ if (!g.consumeInvite(Members.key(p))) {
p.sendMessage("§cYou need to be invited to this guild by the leader first!");
return;
}
- Guild previous = FactionManager.getGuildByMember(p.getName());
+ Guild previous = FactionManager.getGuildByMember(Members.key(p));
if (previous != null) {
- previous.kick(p.getName());
+ previous.kick(Members.key(p));
}
- g.addMember(p.getName());
+ g.addMember(Members.key(p));
p.sendMessage("§aJoined " + g.getName());
g.getFaction().updatePrestige();
tellOthers(g.getFaction(), p, "§a" + p.getName() + " joined the guild " + g.getName() + "§a!");
}
private static void joinFaction(Player p, String id) {
- if (FactionManager.getByMember(p.getName()) != null) {
+ if (FactionManager.getByMember(Members.key(p)) != null) {
p.sendMessage("§cAlready in a faction, leave your current faction first!");
return;
}
@@ -156,11 +158,11 @@ private static void joinFaction(Player p, String id) {
p.sendMessage("§cNo faction with that name exists");
return;
}
- if (!f.consumeInvite(p.getName())) {
+ if (!f.consumeInvite(Members.key(p))) {
p.sendMessage("§cYou need to be invited to this faction by the leader first!");
return;
}
- f.addMember(p.getName());
+ f.addMember(Members.key(p));
p.sendMessage("§aJoined " + f.getName());
f.updatePrestige();
tellOthers(f, p, "§a" + p.getName() + " joined the faction!");
@@ -168,7 +170,7 @@ private static void joinFaction(Player p, String id) {
private static void declineGuild(Player p, String id) {
Guild g = FactionManager.getGuildByString(id);
- if (g == null || !g.consumeInvite(p.getName())) {
+ if (g == null || !g.consumeInvite(Members.key(p))) {
p.sendMessage("§cYou have no invite from that guild");
return;
}
@@ -178,7 +180,7 @@ private static void declineGuild(Player p, String id) {
private static void declineFaction(Player p, String id) {
Faction f = FactionManager.getByString(id);
- if (f == null || !f.consumeInvite(p.getName())) {
+ if (f == null || !f.consumeInvite(Members.key(p))) {
p.sendMessage("§cYou have no invite from that faction");
return;
}
@@ -188,7 +190,7 @@ private static void declineFaction(Player p, String id) {
private static void tellOthers(Faction f, Player joiner, String message) {
for (Player pl : Bukkit.getOnlinePlayers()) {
- if (!pl.equals(joiner) && f.getMembers().contains(pl.getName())) {
+ if (!pl.equals(joiner) && f.getMembers().contains(Members.key(pl))) {
pl.sendMessage(message);
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/MercenaryCommandManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/MercenaryCommandManager.java
index ee7d2885..854a19c3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/MercenaryCommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/MercenaryCommandManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.List;
import org.bukkit.command.Command;
@@ -136,7 +138,7 @@ private boolean market(Player p, String[] args) {
/** Hands out stage 1 of the negotiation book. */
private void draft(Player p) {
MercenaryCompany company = company(p);
- if (company == null || !company.isLeader(p.getName())) {
+ if (company == null || !company.isLeader(Members.key(p))) {
p.sendMessage("§cOnly a company leader may draft a contract.");
return;
}
@@ -153,7 +155,7 @@ private void draft(Player p) {
*/
private void offer(Player p, String factionName) {
MercenaryCompany company = company(p);
- if (company == null || !company.isLeader(p.getName())) {
+ if (company == null || !company.isLeader(Members.key(p))) {
p.sendMessage("§cOnly a company leader may offer a contract.");
return;
}
@@ -264,8 +266,8 @@ private static MercenaryCompany company(Player p) {
}
private static Guild leaderGuild(Player p) {
- Guild guild = FactionManager.getGuildByLeader(p.getName());
- return guild != null ? guild : FactionManager.getGuildByMember(p.getName());
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));
+ return guild != null ? guild : FactionManager.getGuildByMember(Members.key(p));
}
private static void report(Player p, MercenaryResult result) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java
index 7481f4f5..7961f6cb 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.function.Consumer;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
@@ -112,7 +114,7 @@ public void run() {
}.runTaskLater(SimpleFactions.plugin, 1L);
} else if(stage == 3) {
e.setCancelled(true);
- Guild borrowerGuild = FactionManager.getGuildByLeader(p.getName());
+ Guild borrowerGuild = FactionManager.getGuildByLeader(Members.key(p));
String offerId = LoanBook.offerId(newMeta);
Loan loan = offerId == null ? null : LoanBook.createLoanFromBook(newMeta, borrowerGuild, offerId);
String validate = newMeta.getPersistentDataContainer().get(Keys.SECONDARY_STRING_KEY, PersistentDataType.STRING);
@@ -221,7 +223,7 @@ private void signContractBook(
return;
}
if(stage == ContractBook.STAGE_DRAFT) {
- if(!company.isLeader(p.getName())) {
+ if(!company.isLeader(Members.key(p))) {
p.sendMessage("§cOnly the company leader may draft a contract.");
return;
}
@@ -235,7 +237,7 @@ private void signContractBook(
return;
}
if(stage == ContractBook.STAGE_REVIEW) {
- if(!company.isLeader(p.getName())) {
+ if(!company.isLeader(Members.key(p))) {
p.sendMessage("§cOnly the company leader may publish an offer.");
return;
}
@@ -360,8 +362,8 @@ public void bankPulse(PlayerBankPulseEvent e) {
}
private boolean isInBankChunk(Player p) {
- Faction f = FactionManager.getByMember(p.getName());
- Guild g = FactionManager.getGuildByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
+ Guild g = FactionManager.getGuildByMember(Members.key(p));
if (f == null && g == null) {
p.sendMessage("§cYou must belong to a guild or faction to bank here.");
@@ -377,8 +379,8 @@ private boolean isInBankChunk(Player p) {
}
private boolean inFactionOrGuildBankChunk(Player p) {
- Faction f = FactionManager.getByMember(p.getName());
- Guild g = FactionManager.getGuildByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
+ Guild g = FactionManager.getGuildByMember(Members.key(p));
Chunk playerChunk = p.getLocation().getChunk();
if (f != null && f.getBank() != null && f.getBank().getChunk() != null
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java
index 3fc9f446..b54222ec 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -626,15 +628,15 @@ private static void sendRequest(Player sender, Faction f, RelationType type) {
return;
}
sender.sendMessage("§aSent a request to "+f.getName()+" §afor them to become your "+type.getName());
- p.sendMessage(FactionManager.getByLeader(sender.getName()).getName()+" §7is requesting that you become their "+type.getName());
+ p.sendMessage(FactionManager.getByLeader(Members.key(sender)).getName()+" §7is requesting that you become their "+type.getName());
p.sendMessage("§7Type §a/faction accept §7to accept");
p.sendMessage("§7Request will time out in 60 seconds");
- RequestManager.addRequest(sender, p, new RelationRequest(FactionManager.getByLeader(sender.getName()).getOrCreateMainGuild(), type, false));
+ RequestManager.addRequest(sender, p, new RelationRequest(FactionManager.getByLeader(Members.key(sender)).getOrCreateMainGuild(), type, false));
}
public static void acceptRequest(Player p) {
RelationRequest req = (RelationRequest) RequestManager.getRequest(p);
- Faction reciever = FactionManager.getByLeader(p.getName());
+ Faction reciever = FactionManager.getByLeader(Members.key(p));
if(reciever == null) {
p.sendMessage("§cYou do not have a faction");
return;
@@ -664,15 +666,15 @@ private static void sendTradeRequest(Player sender, Faction f, RelationType type
return;
}
sender.sendMessage("§aSent a request to "+f.getName()+" §ato set trade to "+type.getName());
- p.sendMessage(FactionManager.getByLeader(sender.getName()).getName()+" §7is requesting that you set trade to "+type.getName());
+ p.sendMessage(FactionManager.getByLeader(Members.key(sender)).getName()+" §7is requesting that you set trade to "+type.getName());
p.sendMessage("§7Type §a/faction accept §7to accept");
p.sendMessage("§7Request will time out in 60 seconds");
- RequestManager.addRequest(sender, p, new RelationRequest(FactionManager.getByLeader(sender.getName()).getOrCreateMainGuild(), type, true));
+ RequestManager.addRequest(sender, p, new RelationRequest(FactionManager.getByLeader(Members.key(sender)).getOrCreateMainGuild(), type, true));
}
public static void acceptTradeRequest(Player p) {
RelationRequest req = (RelationRequest) RequestManager.getRequest(p);
- Faction reciever = FactionManager.getByLeader(p.getName());
+ Faction reciever = FactionManager.getByLeader(Members.key(p));
if(reciever == null) {
p.sendMessage("§cYou do not have a faction");
return;
@@ -695,15 +697,15 @@ private static void sendTreatyRequest(Player sender, Faction f, RelationType typ
return;
}
sender.sendMessage("§aSent a request to "+f.getName()+" §ato set treaty to "+type.getName());
- p.sendMessage(FactionManager.getByLeader(sender.getName()).getName()+" §7is requesting that you set treaty to "+type.getName());
+ p.sendMessage(FactionManager.getByLeader(Members.key(sender)).getName()+" §7is requesting that you set treaty to "+type.getName());
p.sendMessage("§7Type §a/faction accept §7to accept");
p.sendMessage("§7Request will time out in 60 seconds");
- RequestManager.addRequest(sender, p, new RelationRequest(FactionManager.getByLeader(sender.getName()).getOrCreateMainGuild(), type, false, true));
+ RequestManager.addRequest(sender, p, new RelationRequest(FactionManager.getByLeader(Members.key(sender)).getOrCreateMainGuild(), type, false, true));
}
public static void acceptTreatyRequest(Player p) {
RelationRequest req = (RelationRequest) RequestManager.getRequest(p);
- Faction reciever = FactionManager.getByLeader(p.getName());
+ Faction reciever = FactionManager.getByLeader(Members.key(p));
if(reciever == null) {
p.sendMessage("§cYou do not have a faction");
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java
index 7723a06f..9f1c0818 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
@@ -78,12 +80,12 @@ public void onPlayerInteract(PlayerInteractEvent event) {
if (clickedBlock == null) return;
if (!clickedBlock.getType().equals(Material.LANTERN)) return;
- Faction faction = FactionManager.getByMember(player.getName());
+ Faction faction = FactionManager.getByMember(Members.key(player));
if (faction == null) return;
Council council = faction.getGovernment().getCouncil();
Session session = getSession(council);
- if (session != null && (faction.getLeader().equals(player.getName()) || faction.getGovernment().isCouncilMember(player))) {
+ if (session != null && (Members.is(player, faction.getLeader()) || faction.getGovernment().isCouncilMember(player))) {
if(session.isStarted() && clickedBlock.equals(session.getLantern())) {
event.setCancelled(true);
Proposal currentProposal = session.getCurrentProposal();
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/TitleManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/TitleManager.java
index 3cfb094b..627a1e62 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/TitleManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/TitleManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -49,7 +51,7 @@ public void run() {
private void completeTitle(Player p, Tier pending, String message) {
if (!p.isOnline()) return;
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
Tier tier = TierLoader.getByString(pending.getId());
if (f == null || tier != pending || !tier.canForm()
|| !f.getGovernment().stateReport().status.canFormTitles()) return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java
index 1e5b6791..20fe37c0 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashSet;
@@ -493,18 +495,18 @@ public static void sendRequest(Player sender, Faction origin, Faction target, Wa
return;
}
sender.sendMessage("§aSent a call to arms to "+target.getName());
- p.sendMessage(FactionManager.getByLeader(sender.getName()).getName()+" §7is requesting that you aid them in their war against "+w.getEnemy(origin).getName());
+ p.sendMessage(FactionManager.getByLeader(Members.key(sender)).getName()+" §7is requesting that you aid them in their war against "+w.getEnemy(origin).getName());
p.sendMessage("§7Type §a/faction accept §7to accept");
p.sendMessage("§7Type §c/faction decline §7to decline");
p.sendMessage("§7Request will time out in 60 seconds");
- RequestManager.addRequest(sender, p, new WarRequest(FactionManager.getByLeader(sender.getName()).getOrCreateMainGuild(), w, target.getId()));
+ RequestManager.addRequest(sender, p, new WarRequest(FactionManager.getByLeader(Members.key(sender)).getOrCreateMainGuild(), w, target.getId()));
}
public static void acceptRequest(Player p) {
if (!(RequestManager.getRequest(p) instanceof WarRequest req)) {
return;
}
- Faction reciever = FactionManager.getByLeader(p.getName());
+ Faction reciever = FactionManager.getByLeader(Members.key(p));
if(reciever == null) {
p.sendMessage("§cYou do not have a faction");
return;
@@ -545,7 +547,7 @@ public static void declineCallToArms(Player called, WarRequest req, boolean expl
// A stored target that no longer exists penalises nobody.
Faction receiver = req.getTargetFactionId() != null
? FactionManager.getByString(req.getTargetFactionId())
- : called != null ? FactionManager.getByLeader(called.getName()) : null;
+ : called != null ? FactionManager.getByLeader(Members.key(called)) : null;
if (receiver != null && receiver.getGovernment() != null) {
receiver.getGovernment().addStabilityModifier(new StabilityModifier(
"Declined Call to Arms",
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java
index 980fcadb..a4b5611b 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.war.campaign.runtime.pick.BattleInstallationPickService.InstallationPickResults;
import java.time.Instant;
@@ -166,7 +168,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
return;
}
- if (!viewerFaction.isLeader(player.getName())) {
+ if (!viewerFaction.isLeader(Members.key(player))) {
player.sendMessage("§cOnly your faction leader can select installations for this battle.");
return;
}
@@ -200,11 +202,11 @@ static String toggleMessage(InstallationPickToggleResult result, String installa
}
private Faction resolveViewerFaction(Player player, War war) {
- Faction leaderFaction = FactionManager.getByLeader(player.getName());
+ Faction leaderFaction = FactionManager.getByLeader(Members.key(player));
if (leaderFaction != null && war.isParticipating(leaderFaction)) {
return leaderFaction;
}
- Faction memberFaction = FactionManager.getByMember(player.getName());
+ Faction memberFaction = FactionManager.getByMember(Members.key(player));
if (memberFaction != null && war.isParticipating(memberFaction)) {
return memberFaction;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java
index b6795cd4..86730dbc 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidResults;
import java.time.Instant;
@@ -196,7 +198,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
return;
}
- if (!viewerFaction.isLeader(player.getName())) {
+ if (!viewerFaction.isLeader(Members.key(player))) {
player.sendMessage(CampaignRaidMessages.NOT_LEADER);
return;
}
@@ -287,11 +289,11 @@ private boolean validateParticipation(Player player, War war, Faction viewerFact
}
private Faction resolveViewerFaction(Player player, War war) {
- Faction leaderFaction = FactionManager.getByLeader(player.getName());
+ Faction leaderFaction = FactionManager.getByLeader(Members.key(player));
if (leaderFaction != null && war.isParticipating(leaderFaction)) {
return leaderFaction;
}
- Faction memberFaction = FactionManager.getByMember(player.getName());
+ Faction memberFaction = FactionManager.getByMember(Members.key(player));
if (memberFaction != null && war.isParticipating(memberFaction)) {
return memberFaction;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignView.java
index ec36b45d..bffe0f18 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidResults;
import java.time.Instant;
@@ -95,9 +97,9 @@ public void campaignView(Player player, War war, boolean open) {
return;
}
- Faction viewerFaction = FactionManager.getByLeader(player.getName());
+ Faction viewerFaction = FactionManager.getByLeader(Members.key(player));
if (viewerFaction == null) {
- viewerFaction = FactionManager.getByMember(player.getName());
+ viewerFaction = FactionManager.getByMember(Members.key(player));
}
if (viewerFaction == null) {
if (open) {
@@ -221,7 +223,7 @@ private void populateHourToggles(Inventory inventory, War war, Faction viewerFac
}
private void populatePostBattleChoiceButtons(Inventory inventory, War war, Player player) {
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
for (int slot : List.of(40, 41, 42, 43)) {
inventory.setItem(slot, inv.getFiller(Material.GRAY_STAINED_GLASS_PANE));
}
@@ -306,9 +308,9 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
CampaignCreator.installationsEntryWarKey(),
PersistentDataType.INTEGER);
if (installationsEntryWarId != null && installationsEntryWarId == war.getId()) {
- Faction viewerFaction = FactionManager.getByLeader(player.getName());
+ Faction viewerFaction = FactionManager.getByLeader(Members.key(player));
if (viewerFaction == null) {
- viewerFaction = FactionManager.getByMember(player.getName());
+ viewerFaction = FactionManager.getByMember(Members.key(player));
}
if (viewerFaction == null || !war.isParticipating(viewerFaction)) {
player.sendMessage("§cYou are not a belligerent in this war.");
@@ -323,15 +325,15 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
CampaignCreator.raidEntryWarKey(),
PersistentDataType.INTEGER);
if (raidEntryWarId != null && raidEntryWarId == war.getId()) {
- Faction viewerFaction = FactionManager.getByLeader(player.getName());
+ Faction viewerFaction = FactionManager.getByLeader(Members.key(player));
if (viewerFaction == null) {
- viewerFaction = FactionManager.getByMember(player.getName());
+ viewerFaction = FactionManager.getByMember(Members.key(player));
}
if (viewerFaction == null || !war.isParticipating(viewerFaction)) {
player.sendMessage(CampaignRaidMessages.NOT_PARTICIPANT);
return;
}
- if (!viewerFaction.isLeader(player.getName())) {
+ if (!viewerFaction.isLeader(Members.key(player))) {
player.sendMessage(CampaignRaidMessages.NOT_LEADER);
return;
}
@@ -370,7 +372,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot retreat right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_retreat", String.valueOf(war.getId()));
@@ -386,7 +388,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot surrender right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_surrender", String.valueOf(war.getId()));
@@ -402,7 +404,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot accept white peace right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_accept_peace", String.valueOf(war.getId()));
@@ -418,7 +420,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot push right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_push", String.valueOf(war.getId()));
@@ -434,7 +436,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot hold right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_hold", String.valueOf(war.getId()));
@@ -450,7 +452,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot attack right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_attack", String.valueOf(war.getId()));
@@ -466,7 +468,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
player.sendMessage("§cYou cannot accept peace right now.");
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
inv.confirming.put(player, leader);
inv.campaignConfirmWar.put(player, war.getId());
inv.confirmView(player, leader, "campaign_loser_peace", String.valueOf(war.getId()));
@@ -483,7 +485,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
}
private void handleHourToggleClick(Player player, War war, int hour) {
- Faction faction = FactionManager.getByMember(player.getName());
+ Faction faction = FactionManager.getByMember(Members.key(player));
if (faction == null || !BattleVoterEligibility.canToggleVote(war, faction, CampaignClock.now())) {
player.sendMessage("§cYou cannot vote for battle hours right now.");
return;
@@ -546,7 +548,7 @@ public void handleConfirm(Player player, String key, String data, boolean confir
return;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
if (leader == null) {
return;
}
@@ -649,7 +651,7 @@ public void handleConfirm(Player player, String key, String data, boolean confir
}
public boolean canRetreat(Player player, War war) {
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
if (leader == null) {
return false;
}
@@ -664,7 +666,7 @@ public boolean canSurrender(Player player, War war) {
}
public boolean canAcceptWhitePeace(Player player, War war) {
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
if (leader == null) {
return false;
}
@@ -672,17 +674,17 @@ public boolean canAcceptWhitePeace(Player player, War war) {
}
public boolean canMakePostBattleChoice(Player player, War war) {
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
return leader != null && CampaignPostBattleChoiceService.isChoiceLeader(war, leader);
}
public boolean isDefenderLeader(Player player, War war) {
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
return leader != null && leader.getId().equalsIgnoreCase(war.getDefenderLeaderId());
}
public boolean isAttackerLeader(Player player, War war) {
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
return leader != null && leader.getId().equalsIgnoreCase(war.getAttackerLeaderId());
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java
index e1113459..2fb06c8d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
@@ -92,7 +94,7 @@ public void slotsView(Player player, Guild guild, Inventory i) {
for (int slot = 0; slot < visibleSlots; slot++) {
i.setItem(FIRST_SLOT_BUTTON + slot, creator.createSlotItem(company, firstVisibleSlot + slot));
}
- if (company.isLeader(player.getName())) {
+ if (company.isLeader(Members.key(player))) {
i.setItem(EXPAND_BUTTON, creator.createExpandButton(company));
}
for (int x = 0; x < company.getSlotQueue().size() && x < 3; x++) {
@@ -119,7 +121,7 @@ public void rosterView(Player player, Guild guild, Inventory i) {
i.clear();
MercenaryCompany company = guild.getCompany();
if (company == null) return;
- boolean leader = company.isLeader(player.getName());
+ boolean leader = company.isLeader(Members.key(player));
for (int x = 0; x < company.getEnlisted().size() && x < 36; x++) {
i.setItem(9 + x, creator.createRosterItem(company, company.getEnlisted().get(x), leader));
}
@@ -223,7 +225,7 @@ private void clickSlots(
}
String queuePayload = queueCancelPayload(e.getCurrentItem());
if (queuePayload != null) {
- if (!company.isLeader(p.getName())) return;
+ if (!company.isLeader(Members.key(p))) return;
inv.openQueueCancelConfirm(p, guild.getFaction(), queuePayload, "§eCancel queued slot expansion?");
chirp(p);
}
@@ -241,7 +243,7 @@ private void clickUpgrade(
InventoryClickEvent e, Inventory inventory, Player p, Guild guild, MercenaryCompany company) {
String queuePayload = queueCancelPayload(e.getCurrentItem());
if (queuePayload != null) {
- if (!company.isLeader(p.getName())) return;
+ if (!company.isLeader(Members.key(p))) return;
inv.openQueueCancelConfirm(p, guild.getFaction(), queuePayload, "§eCancel queued company upgrade?");
chirp(p);
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java
index 9085b86b..b3aef9db 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
@@ -67,7 +69,7 @@ public void listView(Player player, Guild guild, Inventory i) {
i.setItem(9 + index, creator.createContractItem(contract));
index++;
}
- if (company.isLeader(player.getName())) {
+ if (company.isLeader(Members.key(player))) {
i.setItem(DRAFT_BUTTON, creator.createDraftButton(company));
}
i.setItem(53, inv.createBackButton(SFGUI.CONTRACT_LIST_VIEW));
@@ -101,7 +103,7 @@ public void detailView(Player player, Guild guild, Inventory i, String contractI
if (contract.isActive()) {
long now = System.currentTimeMillis();
boolean pending = contract.hasPendingSlots(now);
- if (company.isLeader(player.getName())) {
+ if (company.isLeader(Members.key(player))) {
i.setItem(CHANGE_SLOTS_BUTTON, pending
? creator.createWithdrawSlotsButton()
: creator.createChangeSlotsButton());
@@ -118,7 +120,7 @@ public void detailView(Player player, Guild guild, Inventory i, String contractI
public static boolean canSign(Player player, MercenaryContract contract) {
Faction hirer = contract.getHirer();
if (hirer == null || hirer.getGovernment() == null) return false;
- return hirer.getGovernment().isCouncilMember(player.getName());
+ return hirer.getGovernment().isCouncilMember(Members.key(player));
}
/* =====================================================
@@ -142,7 +144,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
private void clickList(InventoryClickEvent e, Player p, Guild guild, MercenaryCompany company) {
if (e.getSlot() == DRAFT_BUTTON) {
- if (!company.isLeader(p.getName())) return;
+ if (!company.isLeader(Members.key(p))) return;
for (ItemStack leftover : p.getInventory().addItem(ContractBook.draftBook(company)).values()) {
p.getWorld().dropItemNaturally(p.getLocation(), leftover);
}
@@ -175,7 +177,7 @@ private void clickDetail(
}
if (!contract.isActive()) return;
long now = System.currentTimeMillis();
- if (e.getSlot() == CHANGE_SLOTS_BUTTON && company.isLeader(p.getName())) {
+ if (e.getSlot() == CHANGE_SLOTS_BUTTON && company.isLeader(Members.key(p))) {
if (contract.hasPendingSlots(now)) {
report(p, company.getContractHandler().withdrawSlots(contractId, p.getName(), now));
detailView(p, guild, inventory, contractId);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/DeclareWarView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/DeclareWarView.java
index ec9d3688..689c3b3e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/DeclareWarView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/DeclareWarView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.List;
import org.bukkit.Bukkit;
@@ -325,7 +327,7 @@ public void handleConfirm(Player player, WarDeclareRequest request, boolean conf
Faction defender = request.getDefender();
if (FactionManager.getByString(attacker.getId()) != attacker
|| FactionManager.getByString(defender.getId()) != defender
- || !attacker.isLeader(player.getName())) {
+ || !attacker.isLeader(Members.key(player))) {
WarDeclareCodeService.clearSession(player);
player.sendMessage("§cThis war declaration is no longer available.");
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java
index 4cf3cc66..ca316a72 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -54,7 +56,7 @@ public ItemStack createCandidateTypeItem(Player p, Faction f, Candidate c) {
} else {
lore.add(StringFormatter.formatHex("#28ed70Click to sign up"));
}
- if(gov.hasElection() && e.canVote(p.getName())){
+ if(gov.hasElection() && e.canVote(Members.key(p))){
if(!e.hasVoted(c, p.getName())) {
lore.add(StringFormatter.formatHex("#28ed70Click to view candidates"));
} else {
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java
index 078bdad7..855bb9fa 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.List;
import org.bukkit.Sound;
@@ -103,7 +105,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
Candidate c = Candidate.valueOf(id);
Election election = f.getGovernment().getElection();
if(election.isActive()) {
- if(election.canVote(p.getName()) && !election.hasVoted(c, p.getName())) {
+ if(election.canVote(Members.key(p)) && !election.hasVoted(c, p.getName())) {
votingView(p, f, c);
}
} else {
@@ -129,7 +131,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(cid == null) return;
try {
Candidate type = Candidate.valueOf(cid);
- if(election.canVote(p.getName()) && election.isActive()) {
+ if(election.canVote(Members.key(p)) && election.isActive()) {
if(!election.hasVoted(type, p.getName()) &&
election.getCandidates(type).contains(candidateName)) {
election.addVote(type, p.getName(), candidateName);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java
index 8640afa5..29194aa2 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import net.tfminecraft.simplefactions.espionage.CharacterNames;
import net.tfminecraft.simplefactions.espionage.IntelligenceLedger;
@@ -70,7 +72,7 @@ public static void foreign(Inventory inventory, Player viewer, Faction target, I
}
static ItemStack reportHeader(Player viewer, IntelligenceReport report) {
- var observer = FactionManager.getByMember(viewer.getName());
+ var observer = FactionManager.getByMember(Members.key(viewer));
var spy = observer == null ? null : observer.getEspionage().getSpymaster();
boolean missingSpymaster = !EspionageService.hasSpymaster(observer);
String last = missingSpymaster ? "\u00a77The Spymaster's office stands vacant; no findings reach your court."
@@ -288,7 +290,7 @@ public static void spymasterOffice(Player viewer, Faction faction, InventoryMana
head.setItemMeta(meta);
}
inventory.setItem(13, head);
- if (faction.isLeader(viewer.getName())) {
+ if (faction.isLeader(Members.key(viewer))) {
long next = EspionageService.nextAppointmentAt(faction);
inventory.setItem(11, item(Material.NAME_TAG, "Appoint Spymaster", "§7Choose a member of your faction.",
buildUpTerms(),
@@ -374,7 +376,7 @@ private static ItemStack conduct(boolean offense, int reduction) {
}
private static void candidates(Player viewer, Faction faction, InventoryManager manager, int page) {
- if (!EspionageService.isOwn(viewer, faction) || !faction.isLeader(viewer.getName())) return;
+ if (!EspionageService.isOwn(viewer, faction) || !faction.isLeader(Members.key(viewer))) return;
List members = faction.getMembers().stream().distinct()
.filter(name -> EspionageService.eligible(faction, name)).sorted(String.CASE_INSENSITIVE_ORDER).toList();
int safePage = Math.max(0, Math.min(page, Math.max(0, (members.size() - 1) / 45)));
@@ -460,7 +462,7 @@ else if (slot == 11 || slot == 15) {
settings(viewer, faction, manager);
}
} else {
- if (!faction.isLeader(viewer.getName())) { viewer.closeInventory(); return; }
+ if (!faction.isLeader(Members.key(viewer))) { viewer.closeInventory(); return; }
if (slot == 53) spymasterOffice(viewer, faction, manager);
else if (slot == 45 || slot == 52) candidates(viewer, faction, manager, menu.getPage() + (slot == 45 ? -1 : 1));
else if (event.getCurrentItem() != null && event.getCurrentItem().hasItemMeta()) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java
index 4cb45c40..6d0dd38c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import net.tfminecraft.simplefactions.espionage.CharacterNames;
import net.tfminecraft.simplefactions.util.LegacyModelData;
@@ -112,7 +114,7 @@ public List listLore(Player p, Faction f) {
if (f.getProsperity() > 0) lore.add(StringFormatter.formatHex("#4bb244Prosperity: #4fd945" + f.getProsperity()));
} else lore.add(StringFormatter.formatHex("#4bb244Prosperity: #4fd945" + intelligence(report, "Prosperity", "")));
if(p != null && !EspionageService.isOwn(p, f)) {
- Faction origin = FactionManager.getByMember(p.getName());
+ Faction origin = FactionManager.getByMember(Members.key(p));
if(origin != null) {
Relation r = origin.getRelation(f.getId());
Relation ofR = f.getRelation(origin.getId());
@@ -497,7 +499,7 @@ public ItemStack createMenuItem(Player p, Faction f, MenuItemType t) {
} else if(t.equals(MenuItemType.TIER)) {
ItemMeta m = i.getItemMeta();
m.setDisplayName(StringFormatter.formatHex("#c45749§lTier: "+f.getTier().getFormattedName()));
- if(f.getLeader().equalsIgnoreCase(p.getName())) {
+ if(Members.is(p, f.getLeader())) {
List lore = new ArrayList();
lore.add("§7Click to edit Tier");
m.setLore(lore);
@@ -509,10 +511,10 @@ public ItemStack createMenuItem(Player p, Faction f, MenuItemType t) {
ItemMeta m = i.getItemMeta();
m.setDisplayName(StringFormatter.formatHex("#8acc6eTitles"));
List lore = new ArrayList();
- if(f.getLeader().equalsIgnoreCase(p.getName())) {
+ if(Members.is(p, f.getLeader())) {
lore.add("§7Click to view Titles");
} else {
- Faction pf = FactionManager.getByLeader(p.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(p));
String overlord = RelationManager.getOverlord(f);
if(pf != null && overlord != null && overlord.equalsIgnoreCase(pf.getId())) {
lore.add("§7Click to view Titles to grant to "+f.getName());
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionView.java
index 494b9b08..3cb35886 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,9 +125,9 @@ public void factionView(Player player, Faction f, Inventory i) {
}
i.clear();
i.setItem(20, EspionageService.isOwn(player, f) ? EspionageView.positionButton() : EspionageView.foreignPositionsItem(player, f));
- if(f.getMembers().contains(player.getName())) i.setItem(1, creator.createMenuItem(player, f, MenuItemType.BANNER_GET));
+ if(f.getMembers().contains(Members.key(player))) i.setItem(1, creator.createMenuItem(player, f, MenuItemType.BANNER_GET));
i.setItem(10, creator.createMenuItem(player, f, MenuItemType.BANNER));
- if(f.getLeader().equalsIgnoreCase(player.getName())) i.setItem(19, creator.createMenuItem(player, f, MenuItemType.BANNER_RANDOM));
+ if(Members.is(player, f.getLeader())) i.setItem(19, creator.createMenuItem(player, f, MenuItemType.BANNER_RANDOM));
i.setItem(11, creator.createMenuItem(player, f, MenuItemType.GOVERNMENT));
i.setItem(12, creator.createMenuItem(player, f, MenuItemType.WEALTH));
i.setItem(13, creator.createMenuItem(player, f, MenuItemType.PRESTIGE));
@@ -136,7 +138,7 @@ public void factionView(Player player, Faction f, Inventory i) {
i.setItem(25, creator.createMenuItem(player, f, MenuItemType.TAX));
i.setItem(28, creator.createMenuItem(player, f, MenuItemType.LAWS));
i.setItem(29, creator.createMenuItem(player, f, MenuItemType.MILITARY));
- if(f.canDissolve() && f.isLeader(player.getName())) i.setItem(30, creator.createDissolveItem(f));
+ if(f.canDissolve() && f.isLeader(Members.key(player))) i.setItem(30, creator.createDissolveItem(f));
i.setItem(31, creator.createMenuItem(player, f, MenuItemType.DIPLOMACY));
i.setItem(32, creator.createMenuItem(player, f, MenuItemType.INSTALLATIONS));
i.setItem(33, creator.createMenuItem(player, f, MenuItemType.TIER));
@@ -361,7 +363,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
String factionId = m.getPersistentDataContainer().get(id, PersistentDataType.STRING);
if(factionId == null) return;
Faction f = FactionManager.getByString(factionId);
- if(f != null && f.getLeader().equalsIgnoreCase(p.getName())) {
+ if(f != null && Members.is(p, f.getLeader())) {
inv.tierView(null, p, f, true);
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
}
@@ -373,10 +375,10 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(factionId == null) return;
Faction f = FactionManager.getByString(factionId);
if(f != null) {
- Faction pf = FactionManager.getByLeader(p.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(p));
if(pf == null) return;
String overlord = RelationManager.getOverlord(f);
- if(f.getLeader().equalsIgnoreCase(p.getName()) || (overlord != null && overlord.equalsIgnoreCase(pf.getId()))) {
+ if(Members.is(p, f.getLeader()) || (overlord != null && overlord.equalsIgnoreCase(pf.getId()))) {
inv.titleView(null, p, f, true);
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
}
@@ -385,9 +387,9 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(!(inventory.getHolder() instanceof SFInventoryHolder)) return;
SFInventoryHolder h = (SFInventoryHolder) inventory.getHolder();
Faction f = FactionManager.getByString(h.getId());
- if(!f.isLeader(p.getName())) return;
+ if(!f.isLeader(Members.key(p))) return;
BannerFetcher.fetch("faction:" + f.getId(), patterns -> {
- if (FactionManager.getByString(f.getId()) != f || !f.isLeader(p.getName())) return;
+ if (FactionManager.getByString(f.getId()) != f || !f.isLeader(Members.key(p))) return;
if (patterns == null) {
if (p.isOnline()) p.sendMessage("§cCould not generate a banner right now. Try again later.");
return;
@@ -401,7 +403,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(!(inventory.getHolder() instanceof SFInventoryHolder)) return;
SFInventoryHolder h = (SFInventoryHolder) inventory.getHolder();
Faction f = FactionManager.getByString(h.getId());
- if (f == null || !f.isMemberIgnoreCase(p.getName())) return;
+ if (f == null || !f.isMember(Members.key(p))) return;
ItemStack i = new ItemStack(f.getBanner());
p.getInventory().addItem(i);
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java
index 677ca7e6..9ee81d0b 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -424,7 +426,7 @@ public ItemStack createStanceItem(Faction f, Guild guild, boolean canChange) {
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@SuppressWarnings("deprecation")
public ItemStack createToggleRefuseButton(Player p, Faction f) {
- boolean refuse = f.getGovernment().getCouncil().refuses(p.getName());
+ boolean refuse = f.getGovernment().getCouncil().refuses(Members.key(p));
ItemStack item = TLibs.getItemAPI().getCreator().getItemsAdderItem(refuse ? "mcicons:icon_cancel" : "mcicons:icon_confirm");
ItemMeta m = item.getItemMeta();
m.setDisplayName(StringFormatter.formatHex("#a27cbfToggle Council Stance"));
@@ -534,7 +536,7 @@ public ItemStack createProposalItem(Player p, Faction f) {
if(gov.isCouncilMember(p)) {
lore.add(StringFormatter.formatHex("#b8ae61Create a new proposal to change"));
lore.add(StringFormatter.formatHex("#b8ae61your faction's laws or taxes."));
- lore.add(StringFormatter.formatHex("#525d5dCurrently Active Proposals: #e3d5a1"+gov.getCouncil().getCurrentProposals(p.getName())+"/2"));
+ lore.add(StringFormatter.formatHex("#525d5dCurrently Active Proposals: #e3d5a1"+gov.getCouncil().getCurrentProposals(Members.key(p))+"/2"));
} else {
lore.add(StringFormatter.formatHex("#b8ae61Create a new movement to demand"));
lore.add(StringFormatter.formatHex("#b8ae61changes to your faction's laws or taxes."));
@@ -542,7 +544,7 @@ public ItemStack createProposalItem(Player p, Faction f) {
lore.add(StringFormatter.formatHex("#b8ae61ultimatum to the council."));
lore.add(StringFormatter.formatHex("§7(#812222Potential Civil War§7)"));
}
- if(gov.canProposeOrStartMovement(p) && gov.getMovementByMember(p.getName()) == null) lore.add(StringFormatter.formatHex("#28ed70Click to start"));
+ if(gov.canProposeOrStartMovement(p) && gov.getMovementByMember(Members.key(p)) == null) lore.add(StringFormatter.formatHex("#28ed70Click to start"));
else lore.add(StringFormatter.formatHex("#89504eYou cannot start a new proposal/movement at this time."));
m.setLore(lore);
item.setItemMeta(m);
@@ -594,7 +596,7 @@ public ItemStack createCouncilMemberItem(Player player, Faction f, int slot) {
String memberName = slot < members.size() ? members.get(slot) : null;
boolean isEmpty = memberName == null;
- boolean isLeader = player.getName().equalsIgnoreCase(f.getLeader());
+ boolean isLeader = Members.is(player, f.getLeader());
boolean canModify = isLeader && (
council.getType().equals(Rules.APPOINTED_COUNCIL) ||
council.getType().equals(Rules.WEALTH_BASED_COUNCIL) ||
@@ -914,14 +916,14 @@ public ItemStack createFavourRepressGuildItem(Player p, Faction f, Guild guild,
if(gov.canFavour(guild)) {
lore.add(StringFormatter.formatHex("#eddda8If Toggled:"));
EconomicImpact.applyFavourRepressChange(lore, p, f, guild, isFavourMode, false, m, false);
- if(f.isLeader(p.getName())) {
+ if(f.isLeader(Members.key(p))) {
if(!guild.isFavoured()) {
lore.add(StringFormatter.formatHex("#eddda8Upkeep: §e" + guild.getRepressFavourCost()+" Administrative Power"));
}
lore.add("");
lore.add(StringFormatter.formatHex("#28ed70Click to toggle"));
}
- } else if(f.isLeader(p.getName())) {
+ } else if(f.isLeader(Members.key(p))) {
lore.add("");
if(guild.isRepressed()) {
lore.add(StringFormatter.formatHex("#89504eCannot favour: Guild is repressed"));
@@ -939,14 +941,14 @@ public ItemStack createFavourRepressGuildItem(Player p, Faction f, Guild guild,
if(gov.canRepress(guild)) {
lore.add(StringFormatter.formatHex("#eddda8If Toggled:"));
EconomicImpact.applyFavourRepressChange(lore, p, f, guild, isFavourMode, false, m, false);
- if(f.isLeader(p.getName())) {
+ if(f.isLeader(Members.key(p))) {
if(!guild.isRepressed()) {
lore.add(StringFormatter.formatHex("#eddda8Upkeep: §e" + guild.getRepressFavourCost()+" Administrative Power"));
}
lore.add("");
lore.add(StringFormatter.formatHex("#28ed70Click to toggle"));
}
- } else if(f.isLeader(p.getName())) {
+ } else if(f.isLeader(Members.key(p))) {
lore.add("");
if(guild.isFavoured()) {
lore.add(StringFormatter.formatHex("#89504eCannot repress: Guild is favoured"));
@@ -983,14 +985,14 @@ public ItemStack createFavourRepressVassalItem(Player p, Faction f, Faction vass
if(gov.canFavour(mainGuild)) {
lore.add(StringFormatter.formatHex("#eddda8If Toggled:"));
EconomicImpact.applyFavourRepressChange(lore, p, f, mainGuild, isFavourMode, false, m, false);
- if(f.isLeader(p.getName())) {
+ if(f.isLeader(Members.key(p))) {
if(!mainGuild.isFavoured()) {
lore.add(StringFormatter.formatHex("#eddda8Upkeep: §e" + mainGuild.getRepressFavourCost()+" Administrative Power"));
}
lore.add("");
lore.add(StringFormatter.formatHex("#28ed70Click to toggle"));
}
- } else if(f.isLeader(p.getName())) {
+ } else if(f.isLeader(Members.key(p))) {
lore.add("");
if(mainGuild.isRepressed()) {
lore.add(StringFormatter.formatHex("#89504eCannot favour: Vassal is repressed"));
@@ -1008,14 +1010,14 @@ public ItemStack createFavourRepressVassalItem(Player p, Faction f, Faction vass
if(gov.canRepress(mainGuild)) {
lore.add(StringFormatter.formatHex("#eddda8If Toggled:"));
EconomicImpact.applyFavourRepressChange(lore, p, f, mainGuild, isFavourMode, false, m, false);
- if(f.isLeader(p.getName())) {
+ if(f.isLeader(Members.key(p))) {
lore.add("");
if(!mainGuild.isRepressed()) {
lore.add(StringFormatter.formatHex("#eddda8Upkeep: §e" + mainGuild.getRepressFavourCost()+" Administrative Power"));
}
lore.add(StringFormatter.formatHex("#28ed70Click to toggle"));
}
- } else if(f.isLeader(p.getName())) {
+ } else if(f.isLeader(Members.key(p))) {
lore.add("");
if(mainGuild.isFavoured()) {
lore.add(StringFormatter.formatHex("#89504eCannot repress: Vassal is favoured"));
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java
index 40020c80..1e554b17 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import java.util.List;
@@ -77,10 +79,10 @@ public void governmentView(Player player, Faction f, Inventory i) {
Government gov = f.getGovernment();
if(gov.getCouncil().canBeMember(player.getName(), true, true)) i.setItem(21, creator.createToggleRefuseButton(player, f));
if(gov.hasElections()) i.setItem(14, creator.createElectionItem(player, f));
- if(gov.getCouncil().canHostSession() && f.getLeader().equalsIgnoreCase(player.getName())) {
+ if(gov.getCouncil().canHostSession() && Members.is(player, f.getLeader())) {
i.setItem(23, creator.createStartCouncilButton(player, f));
}
- Guild viewer = FactionManager.getGuildByMember(player.getName());
+ Guild viewer = FactionManager.getGuildByMember(Members.key(player));
if(viewer != null && gov.canAffectStability(viewer)) {
i.setItem(28, creator.createStanceItem(f, viewer, viewer.isLeader(player)));
}
@@ -359,7 +361,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
}
} else if(slot == 15) {
Government gov = f.getGovernment();
- if(!gov.canProposeOrStartMovement(p) || gov.getMovementByMember(p.getName()) != null) {
+ if(!gov.canProposeOrStartMovement(p) || gov.getMovementByMember(Members.key(p)) != null) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
@@ -370,7 +372,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
// Foreign viewers see this menu too; only those offered the button may use it.
if(!EspionageService.canViewExact(p, f)
|| !gov.getCouncil().canBeMember(p.getName(), true, true)) return;
- gov.getCouncil().toggleRefuse(p.getName());
+ gov.getCouncil().toggleRefuse(Members.key(p));
governmentView(p, f, inventory);
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
} else if(slot == 24) {
@@ -381,7 +383,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
} else if(slot == 23) {
if(!f.getGovernment().hasCouncil()) return;
- if(!f.isLeader(p.getName()) || !f.getGovernment().getCouncil().canHostSession()) {
+ if(!f.isLeader(Members.key(p)) || !f.getGovernment().getCouncil().canHostSession()) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
@@ -518,7 +520,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
double cost = law.getCost()*law.getCompatibility(f.getLawHandler().getGroup(group).getCurrent().getId());
Proposal proposal = new Proposal(p.getName(), gov);
proposal.setLawProposal(law);
- if(!gov.hasCouncil() && f.isLeader(p.getName())) {
+ if(!gov.hasCouncil() && f.isLeader(Members.key(p))) {
if(gov.getPower() < cost) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
p.sendMessage("§cThe government does not have enough power to propose this! §7(Need §e"+cost+"§7, has §e"+gov.getPower()+"§7)");
@@ -546,7 +548,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
} else if(gov.canProposeOrStartMovement(p)) {
- Movement movement = gov.getMovementByMember(p.getName());
+ Movement movement = gov.getMovementByMember(Members.key(p));
if(movement != null) {
if(!gov.canBeProposed(proposal)) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
@@ -598,7 +600,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
proposal.setPoliticalActionProposal(politicalAction);
if(gov.canProposeOrStartMovement(p)) {
- Movement movement = gov.getMovementByMember(p.getName());
+ Movement movement = gov.getMovementByMember(Members.key(p));
if(movement != null) {
if(!gov.canBeProposed(proposal)) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
@@ -661,7 +663,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(item == null) return;
Faction f = FactionManager.getByString(((SFInventoryHolder)e.getInventory().getHolder()).getId());
if(f == null) return;
- boolean isLeader = p.getName().equalsIgnoreCase(f.getLeader());
+ boolean isLeader = Members.is(p, f.getLeader());
if(!isLeader) return;
// Handle council member click for replace/appoint
ItemMeta meta = item.getItemMeta();
@@ -688,7 +690,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(item == null) return;
Faction f = FactionManager.getByString(((SFInventoryHolder)e.getInventory().getHolder()).getId());
if(f == null) return;
- boolean isLeader = p.getName().equalsIgnoreCase(f.getLeader());
+ boolean isLeader = Members.is(p, f.getLeader());
if(!isLeader) return;
if(e.getSlot() == 45 || e.getSlot() == 52) {
int seat = Integer.parseInt(h.getSecondaryId());
@@ -760,7 +762,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
Faction f = FactionManager.getByString(h.getId());
if(f == null) return;
- if(!f.isLeader(p.getName())) return;
+ if(!f.isLeader(Members.key(p))) return;
String id = meta.getPersistentDataContainer().get(Keys.STRING_KEY, PersistentDataType.STRING);
if(id == null) return;
@@ -822,7 +824,7 @@ private static final class WarPeaceHolder extends SFInventoryHolder {
WarPeaceHolder(Player player, Faction faction, Action action, boolean fromCause, int index) {
super(faction.getId(), SFGUI.WAR_PEACE_SELECT, index, fromCause, action.name());
- movement = fromCause ? faction.getGovernment().getMovementByMember(player.getName()) : null;
+ movement = fromCause ? faction.getGovernment().getMovementByMember(Members.key(player)) : null;
cause = movement != null && index >= 0 && index < movement.getCauses().size()
? movement.getCauses().get(index) : null;
}
@@ -830,7 +832,7 @@ private static final class WarPeaceHolder extends SFInventoryHolder {
public Cause displayedWarPeaceCause(Player player, Faction faction, Inventory inventory) {
if (!(inventory.getHolder() instanceof WarPeaceHolder holder) || holder.cause == null
- || faction.getGovernment().getMovementByMember(player.getName()) != holder.movement
+ || faction.getGovernment().getMovementByMember(Members.key(player)) != holder.movement
|| !holder.movement.getCauses().contains(holder.cause)) return null;
return holder.cause;
}
@@ -853,7 +855,7 @@ private void submitWarEnd(Player p, Faction f, Action action, String warId, bool
return;
}
Movement movement = cause.getMovement();
- if (!cause.hasLeader() || !cause.getLeader().equals(p.getName())) {
+ if (!cause.hasLeader() || !Members.is(p, cause.getLeader())) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
@@ -866,7 +868,7 @@ private void submitWarEnd(Player p, Faction f, Action action, String warId, bool
PoliticalAction politicalAction = PoliticalActionLoader.getByAction(action);
proposal.setPoliticalActionProposal(politicalAction);
proposal.setTarget(warId);
- if (!gov.hasCouncil() && f.isLeader(p.getName())) {
+ if (!gov.hasCouncil() && f.isLeader(Members.key(p))) {
proposal.apply(null);
p.sendMessage("§aChange applied!");
governmentView(p, f, null);
@@ -884,7 +886,7 @@ private void submitWarEnd(Player p, Faction f, Action action, String warId, bool
return;
}
if (gov.canProposeOrStartMovement(p)) {
- Movement movement = gov.getMovementByMember(p.getName());
+ Movement movement = gov.getMovementByMember(Members.key(p));
if (movement != null) {
if (!gov.canBeProposed(proposal)) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildView.java
index 38535d89..8486c6e6 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import java.util.ArrayList;
import java.util.Collections;
@@ -194,7 +196,7 @@ public void guildView(Player player, Guild guild, Inventory i) {
if(target != null && Cache.provincesEnabled) {
i.setItem(34, creator.createRelocateItem(player, target, guild));
}
- if(guild.getFaction().isLeader(player.getName()) && !guild.isBase()) {
+ if(guild.getFaction().isLeader(Members.key(player)) && !guild.isBase()) {
if(guild.hasCapital()) {
i.setItem(43, creator.createElevationItem(player, guild));
}
@@ -408,7 +410,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
}
guildView(p, guild, inventory);
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
- } else if(guild.getFaction().isLeader(p.getName())) {
+ } else if(guild.getFaction().isLeader(Members.key(p))) {
if(!guild.canBeEvicted(p)) {
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f);
return;
@@ -429,7 +431,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
}
return;
} else if(e.getSlot() == 43) {
- if(!guild.getFaction().isLeader(p.getName())) return;
+ if(!guild.getFaction().isLeader(Members.key(p))) return;
if(!guild.canBeElevated(p)) {
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f);
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InstallationView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InstallationView.java
index 9f2d70e1..bd44f892 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InstallationView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InstallationView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import java.util.ArrayList;
import java.util.Comparator;
@@ -131,7 +133,7 @@ public void installationDetailView(Player player, Faction f, String installation
}
inventory.clear();
- boolean leader = f.getLeader().equalsIgnoreCase(player.getName());
+ boolean leader = Members.is(player, f.getLeader());
if (isPending) {
inventory.setItem(
49,
@@ -207,7 +209,7 @@ public void click(InventoryClickEvent event, Inventory inventory, Player player)
ItemMeta meta = item.getItemMeta();
String queuePayload = meta.getPersistentDataContainer().get(Keys.QUEUE_CANCEL, PersistentDataType.STRING);
if (queuePayload != null) {
- if (!f.getLeader().equalsIgnoreCase(player.getName())) {
+ if (!Members.is(player, f.getLeader())) {
return;
}
inv.openQueueCancelConfirm(player, f, queuePayload, "§eCancel construction?");
@@ -230,7 +232,7 @@ public void click(InventoryClickEvent event, Inventory inventory, Player player)
ItemStack upgradeItem = event.getCurrentItem();
if (upgradeItem == null
|| !upgradeItem.hasItemMeta()
- || !f.getLeader().equalsIgnoreCase(player.getName())) {
+ || !Members.is(player, f.getLeader())) {
return;
}
String id = upgradeItem.getItemMeta()
@@ -256,7 +258,7 @@ public void click(InventoryClickEvent event, Inventory inventory, Player player)
handleBerthedVehicleClick(event, inventory, player, f);
return;
}
- if (slot != 11 || !f.getLeader().equalsIgnoreCase(player.getName())) {
+ if (slot != 11 || !Members.is(player, f.getLeader())) {
return;
}
ItemStack item = event.getCurrentItem();
@@ -278,7 +280,7 @@ public void click(InventoryClickEvent event, Inventory inventory, Player player)
private void handleBerthedVehicleClick(
InventoryClickEvent event, Inventory inventory, Player player, Faction faction) {
- if (!faction.getLeader().equalsIgnoreCase(player.getName())) {
+ if (!Members.is(player, faction.getLeader())) {
return;
}
ItemStack item = event.getCurrentItem();
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InventoryUpdater.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InventoryUpdater.java
index 39d891b9..19b5f8e1 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InventoryUpdater.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/InventoryUpdater.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageAccess;
import java.util.EnumSet;
import java.util.Set;
@@ -321,7 +323,7 @@ private void refreshCampaignPick(Player p, Inventory i, CampaignInventoryHolder
if (h.getType() != SFGUI.CAMPAIGN_INSTALLATION_PICK_VIEW) return;
War war = WarManager.getById(h.getWarId());
if (war == null || !war.isActive()) return;
- Faction viewerFaction = FactionManager.getByMember(p.getName());
+ Faction viewerFaction = FactionManager.getByMember(Members.key(p));
if (viewerFaction == null) return;
if (!war.isParticipating(viewerFaction)) return;
inv.campaignInstallationPickView.open(p, war, viewerFaction, false, i);
@@ -330,7 +332,7 @@ private void refreshCampaignPick(Player p, Inventory i, CampaignInventoryHolder
private void refreshCampaignRaid(Player p, Inventory i, CampaignRaidLaunchHolder h) {
War war = WarManager.getById(h.getWarId());
if (war == null || !war.isActive()) return;
- Faction viewerFaction = FactionManager.getByMember(p.getName());
+ Faction viewerFaction = FactionManager.getByMember(Members.key(p));
if (viewerFaction == null) return;
if (h.isSourcePage()) {
inv.campaignRaidLaunchView.openSourcePage(p, war, viewerFaction, false, i);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MilitaryView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MilitaryView.java
index 9ca7f800..8f8b8d59 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MilitaryView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MilitaryView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageService;
import org.bukkit.Material;
import org.bukkit.Sound;
@@ -50,7 +52,7 @@ public void militaryView(Inventory i, Player player, Faction f, boolean open) {
}
var m = f.getMilitary();
i.setItem(10, creator.createMilitarySummary(f));
- boolean leader = f.getLeader().equalsIgnoreCase(player.getName());
+ boolean leader = Members.is(player, f.getLeader());
for(int x = 0; x= movement.getPhase().getMaxOrganization() && movement.isLeader(p.getName())) {
+ } else if(phase.getIndex() < 4 && phase.getIndex() == movement.getPhase().getIndex()+1 && movement.getOrganization() >= movement.getPhase().getMaxOrganization() && movement.isLeader(Members.key(p))) {
item = new ItemStack(Material.YELLOW_CONCRETE);
}
ItemMeta m = item.getItemMeta();
@@ -347,7 +349,7 @@ public ItemStack createPhaseItem(Player p, Phase phase, Movement movement) {
lore.add(StringFormatter.formatHex("#7a7a7aThe current phase of the movement"));
lore.add(StringFormatter.formatHex("#7a7a7awhich determines its max organization level"));
lore.add("");
- if(movement.isLeader(p.getName())) {
+ if(movement.isLeader(Members.key(p))) {
if(item.getType().equals(Material.RED_CONCRETE)) {
lore.add(StringFormatter.formatHex("#c74d32Unavailable"));
if(phase.getIndex() > 0 && movement.getPhase().getIndex() == phase.getIndex()-1) {
@@ -634,7 +636,7 @@ public ItemStack createTargetButton(Player p, Cause cause) {
lore.add(StringFormatter.formatHex("#7a7a7athe new faction leader."));
}
- if (cause.hasLeader() && cause.getLeader().equals(p.getName())) {
+ if (cause.hasLeader() && Members.is(p, cause.getLeader())) {
lore.add(" ");
lore.add(StringFormatter.formatHex("#50e846Click to select target"));
} else {
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java
index 05e28d9b..57808f74 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -97,11 +99,11 @@ public void movementView(Player player, Faction f, Movement movement, Inventory
// Join as foreign backer button
i.setItem(16, creator.createJoinAsForeignBackerButton(player, movement));
- if(movement.isLeader(player.getName())) {
+ if(movement.isLeader(Members.key(player))) {
i.setItem(19, creator.createSendDemandsItem(movement));
i.setItem(34, creator.createEndMovmentItem(movement));
}
- if(player.getName().equalsIgnoreCase(f.getLeader())) {
+ if(Members.is(player, f.getLeader())) {
i.setItem(25, creator.createDemandDisbandItem(f, movement));
}
@@ -376,8 +378,8 @@ private void handleMovementViewClick(InventoryClickEvent e, Movement movement, P
int slot = e.getSlot();
//leader
if (slot == 10) {
- if(!movement.hasLeader() && movement.canBeLeader(p.getName())) {
- movement.setLeader(p.getName());
+ if(!movement.hasLeader() && movement.canBeLeader(Members.key(p))) {
+ movement.setLeader(Members.key(p));
movementView(p, f, movement, inventory);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, 1, 1);
}
@@ -393,7 +395,7 @@ else if (slot == 15) {
Object supporter = getSupporterObject(p, movement);
if (supporter != null) {
movement.leave(supporter, null);
- if (movement.isLeader(p.getName())) {
+ if (movement.isLeader(Members.key(p))) {
movement.setLeader(null);
}
movementView(p, f, movement, inventory);
@@ -415,7 +417,7 @@ else if (slot == 15) {
}
// Join as foreign backer button
else if (slot == 16) {
- Faction playerFaction = FactionManager.getByMember(p.getName());
+ Faction playerFaction = FactionManager.getByMember(Members.key(p));
if (playerFaction != null && !playerFaction.getId().equals(f.getId())) {
if (movement.getForeignBackers().contains(playerFaction)) {
if (getSupporterObject(p, movement) != null) {
@@ -439,7 +441,7 @@ else if (slot == 16) {
}
// Send demands button
else if (slot == 19) {
- if (movement.isLeader(p.getName()) && movement.getOrganization() >= 100) {
+ if (movement.isLeader(Members.key(p)) && movement.getOrganization() >= 100) {
for(Cause cause : movement.getCauses()) {
if(cause.getProposal().isPoliticalActionProposal() && cause.getProposal().needsTarget()) {
if(!cause.getProposal().hasTarget()) {
@@ -466,7 +468,7 @@ else if (slot == 19) {
}
}
else if (slot == 25) {
- if (!p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (!Members.is(p, f.getLeader())) {
return;
}
if (!MovementCrackdownQueries.canCrush(f, movement)) {
@@ -497,7 +499,7 @@ else if (slot == 25) {
// Phase buttons (slots 28-31)
else if (slot >= 28 && slot <= 31) {
ItemStack item = e.getCurrentItem();
- if (item != null && item.getType() == Material.YELLOW_CONCRETE && movement.isLeader(p.getName())) {
+ if (item != null && item.getType() == Material.YELLOW_CONCRETE && movement.isLeader(Members.key(p))) {
if (meta.getPersistentDataContainer().has(Keys.SECONDARY_STRING_KEY, PersistentDataType.STRING)) {
String phaseName = meta.getPersistentDataContainer().get(Keys.SECONDARY_STRING_KEY, PersistentDataType.STRING);
try {
@@ -515,7 +517,7 @@ else if (slot >= 28 && slot <= 31) {
}
}
else if (slot == 34) {
- if(!movement.isLeader(p.getName())) {
+ if(!movement.isLeader(Members.key(p))) {
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1);
return;
}
@@ -562,8 +564,8 @@ private void handleCauseViewClick(InventoryClickEvent e, Movement movement, Play
return;
}
if(slot == 10) {
- if(!cause.hasLeader() && cause.canBeLeader(p.getName())) {
- cause.setLeader(p.getName());
+ if(!cause.hasLeader() && cause.canBeLeader(Members.key(p))) {
+ cause.setLeader(Members.key(p));
causeView(p, f, movement, cause, inventory);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, 1, 1);
}
@@ -572,12 +574,12 @@ private void handleCauseViewClick(InventoryClickEvent e, Movement movement, Play
else if (slot == 14) {
Object joiningAs = movement.getJoiningAs(p);
if(joiningAs == null) return;
- if (cause.getFullMemberList().contains(p.getName())) {
+ if (cause.getFullMemberList().contains(Members.key(p))) {
movement.leave(joiningAs, cause);
- if (cause.hasLeader() && cause.getLeader().equals(p.getName())) {
+ if (cause.hasLeader() && Members.is(p, cause.getLeader())) {
cause.setLeader(null);
}
- if (movement.isLeader(p.getName())) {
+ if (movement.isLeader(Members.key(p))) {
movement.setLeader(null);
}
causeView(p, f, movement, cause, inventory);
@@ -597,7 +599,7 @@ else if (slot == 14) {
}
// Target selection button
else if (slot == 28) {
- if (cause.getProposal().needsTarget() && cause.hasLeader() && cause.getLeader().equals(p.getName())) {
+ if (cause.getProposal().needsTarget() && cause.hasLeader() && Members.is(p, cause.getLeader())) {
if (CouncilPeaceQueries.isWarEndAction(cause.getProposal().getPoliticalAction().getAction())) {
inv.governmentView.warPeaceSelectView(
p, f, cause.getProposal().getPoliticalAction().getAction(), true, cause.getIndex(), null);
@@ -622,7 +624,7 @@ private void handleTargetSelectClick(InventoryClickEvent e, Movement movement, P
Cause cause = causeAt(inventory, movement, causeIndex);
if (cause == null) return;
- if (!cause.hasLeader() || !cause.getLeader().equals(p.getName())) return;
+ if (!cause.hasLeader() || !Members.is(p, cause.getLeader())) return;
// Validate target can become leader
if (!f.canBecomeLeader(targetName)) {
@@ -641,7 +643,7 @@ private void handleDemandsViewClick(InventoryClickEvent e, Movement movement, Pl
// Accept button (green concrete)
if (slot == 29) {
- if (p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (Members.is(p, f.getLeader())) {
p.sendMessage(StringFormatter.formatHex("§aYou have accepted the movement's demands."));
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 2f);
LogManager.movement(
@@ -664,7 +666,7 @@ private void handleDemandsViewClick(InventoryClickEvent e, Movement movement, Pl
}
// Decline button (red concrete)
else if (slot == 33) {
- if (p.getName().equalsIgnoreCase(f.getLeader())) {
+ if (Members.is(p, f.getLeader())) {
LogManager.movement(
"DEMANDS_REJECT movementId=%s faction=%s power=%.1f",
movement.getId(),
@@ -696,7 +698,7 @@ else if (slot == 33) {
private void handleCrackdownViewClick(InventoryClickEvent e, Movement movement, Player p, Faction f, Inventory inventory, ItemMeta meta) {
int slot = e.getSlot();
- if (!movement.isLeader(p.getName())) {
+ if (!movement.isLeader(Members.key(p))) {
return;
}
if (slot == 29) {
@@ -794,7 +796,7 @@ public void handleEndConfirm(Player player, String movementId, boolean confirmed
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, 1, 1);
return;
}
- if (!movement.isLeader(player.getName())) {
+ if (!movement.isLeader(Members.key(player))) {
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1);
movementView(player, f, movement, null);
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/RelationView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/RelationView.java
index 1bfcaed8..74d41f0f 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/RelationView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/RelationView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.List;
import org.bukkit.Material;
@@ -71,7 +73,7 @@ public void diplomacyListView(Inventory i, Player player, Faction f, boolean ope
private void fillDiplomacyList(Inventory i, Player player, Faction f, int page) {
i.clear();
- boolean own = f.getMembers() != null && f.getMembers().contains(player.getName());
+ boolean own = f.getMembers() != null && f.getMembers().contains(Members.key(player));
List entries = own
? DiplomacyQueries.ownDirectory(f)
: DiplomacyQueries.foreignList(f);
@@ -105,7 +107,7 @@ private void handleDiplomacyListClick(InventoryClickEvent e, Inventory inventory
return;
}
int page = Math.max(0, holder.getPage());
- boolean own = viewed.getMembers() != null && viewed.getMembers().contains(p.getName());
+ boolean own = viewed.getMembers() != null && viewed.getMembers().contains(Members.key(p));
int slot = e.getSlot();
if (slot == LIST_PREV_SLOT && page > 0) {
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
@@ -145,8 +147,8 @@ public void diplomacyView(Inventory i, Player player, Faction f, boolean open) {
if(open) {
i = SimpleFactions.plugin.getServer().createInventory(new SFInventoryHolder(f.getId(), SFGUI.DIPLOMACY_VIEW), 54, "§7Diplomacy View");
}
- if(!f.getMembers().contains(player.getName()) && FactionManager.getByMember(player.getName()) != null) {
- Faction pf = FactionManager.getByMember(player.getName());
+ if(!f.getMembers().contains(Members.key(player)) && FactionManager.getByMember(Members.key(player)) != null) {
+ Faction pf = FactionManager.getByMember(Members.key(player));
Relation r = pf.getRelation(f.getId());
i.setItem(21, creator.createRelationItem(f, pf));
RelationType trade = pf.getDiplomacyHandler().getTradeRelation(f.getId());
@@ -176,7 +178,7 @@ public void attitudeView(Inventory i, Player player, Faction f, boolean open) {
if(open) {
i = SimpleFactions.plugin.getServer().createInventory(new SFInventoryHolder(f.getId(), SFGUI.ATTITUDE_VIEW), 27, "§7Change Attitude");
}
- Faction origin = FactionManager.getByMember(player.getName());
+ Faction origin = FactionManager.getByMember(Members.key(player));
for(int x = 0; x 0) {
@@ -100,13 +102,13 @@ public void titleTypeView(Inventory i, Player player, Faction f, Tier tier, bool
} else if (i != null && i.getHolder() instanceof SFInventoryHolder holder) {
holder.setPage(page);
}
- Faction pf = FactionManager.getByLeader(player.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(player));
String overlord = RelationManager.getOverlord(f);
int slot = 0;
for (int x = 0; x < 53; x++) {
i.setItem(x, null);
}
- if(f.getLeader().equalsIgnoreCase(player.getName())) {
+ if(Members.is(player, f.getLeader())) {
int maxPerPage = 45;
List items = creator.getSortedTitleItems(player, f, TitleLoader.getByTier(tier));
int start = page * maxPerPage;
@@ -145,7 +147,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if(!(inventory.getHolder() instanceof SFInventoryHolder)) return;
SFInventoryHolder h = (SFInventoryHolder) inventory.getHolder();
Faction f = FactionManager.getByString(h.getId());
- if (f == null || !f.isLeader(p.getName())) return;
+ if (f == null || !f.isLeader(Members.key(p))) return;
NamespacedKey key = new NamespacedKey(SimpleFactions.plugin, "index");
Integer index = clicked.getItemMeta().getPersistentDataContainer().get(key, PersistentDataType.INTEGER);
String tierId = clicked.getItemMeta().getPersistentDataContainer().get(
@@ -178,7 +180,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
Faction f = FactionManager.getByString(h.getId());
if (f == null) return;
if(type.equals(Material.WRITABLE_BOOK)) {
- if (!f.isLeader(p.getName())) return;
+ if (!f.isLeader(Members.key(p))) return;
if (!f.getGovernment().stateReport().status.canFormTitles()) {
p.sendMessage("§cA " + f.getGovernment().stateReport().status.getLabel() + " cannot form titles.");
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
@@ -232,7 +234,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
if("claim".equalsIgnoreCase(action)) {
if(!type.equals(Material.YELLOW_CONCRETE)) return;
- if (!f.isLeader(p.getName()) || FactionManager.getTitleOwner(t) != null
+ if (!f.isLeader(Members.key(p)) || FactionManager.getTitleOwner(t) != null
|| !t.canBeCreatedBy(f, TitleManager.getProvinces(f), TitleManager.getTitles(f),
f.getGovernment().deJureScore())) return;
if (f.getGovernment() != null && !f.getGovernment().stateReport().status.canFormTitles()) {
@@ -258,7 +260,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
} else if("grant".equalsIgnoreCase(action)) {
if(!type.equals(Material.GREEN_CONCRETE)) return;
- Faction pf = FactionManager.getByLeader(p.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(p));
if(pf == null || !pf.getId().equalsIgnoreCase(RelationManager.getOverlord(f))
|| !TitleManager.getGrantableTitles(pf, f, t.getTier()).contains(t)) return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java
index 39270b50..6184952d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -130,7 +132,7 @@ public ItemStack createSecondaryItem(
boolean subject,
boolean called,
boolean backer) {
- Faction pf = FactionManager.getByLeader(p.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(p));
ItemStack i = new ItemStack(f.getBanner());
ItemMeta m = i.getItemMeta();
m.setDisplayName(f.getName());
@@ -183,7 +185,7 @@ public ItemStack createSecondaryItem(
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@SuppressWarnings("deprecation")
public ItemStack createParticipantItem(Player p, Participant par, String type, War w, boolean full, boolean warGoal) {
- Faction pf = FactionManager.getByLeader(p.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(p));
Faction f = par.getLeader();
ItemStack i = new ItemStack(f.getBanner());
ItemMeta m = i.getItemMeta();
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java
index e5caa3e9..d90e77e9 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -163,7 +165,7 @@ private boolean canEditCounterGoal(War war, Player player) {
|| war.getWarType() == WarType.RAID || CivilWarBorderLock.isCivilWar(war)) {
return false;
}
- Faction leader = FactionManager.getByLeader(player.getName());
+ Faction leader = FactionManager.getByLeader(Members.key(player));
return leader != null && war.getDefenderLeaderId() != null
&& war.getDefenderLeaderId().equalsIgnoreCase(leader.getId());
}
@@ -479,7 +481,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
warView(null, p, w, true);
return;
}
- Faction pf = FactionManager.getByLeader(p.getName());
+ Faction pf = FactionManager.getByLeader(Members.key(p));
if(pf == null) return;
Participant par = w.getParticipant(pf);
if(par == null) return;
@@ -505,9 +507,9 @@ private boolean canShowCampaignView(War w, Player player) {
if (axis == null || axis.isEmpty()) {
return false;
}
- Faction faction = FactionManager.getByLeader(player.getName());
+ Faction faction = FactionManager.getByLeader(Members.key(player));
if (faction == null) {
- faction = FactionManager.getByMember(player.getName());
+ faction = FactionManager.getByMember(Members.key(player));
}
return faction != null && w.getParticipant(faction) != null;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/map/MapSystem.java b/src/main/java/net/tfminecraft/simplefactions/map/MapSystem.java
index 94918d67..c3d23dc4 100644
--- a/src/main/java/net/tfminecraft/simplefactions/map/MapSystem.java
+++ b/src/main/java/net/tfminecraft/simplefactions/map/MapSystem.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.map;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -286,7 +288,7 @@ private void claimProvince(
}
public Faction getRelocationTarget(Player p) {
- Guild guild = FactionManager.getGuildByLeader(p.getName());
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));
if(guild == null) return null;
if(guild.isBase()) return null;
if(!guild.hasCapital()) return null;
diff --git a/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryInvites.java b/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryInvites.java
index 6df0e846..470617fb 100644
--- a/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryInvites.java
+++ b/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryInvites.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.mercenary.company;
+import net.tfminecraft.simplefactions.identity.Members;
+
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -16,7 +18,7 @@ private MercenaryInvites() {
public static void invite(Player leader, String targetName) {
Guild guild = net.tfminecraft.simplefactions.managers.FactionManager
- .getGuildByLeader(leader.getName());
+ .getGuildByLeader(Members.key(leader));
MercenaryResult check = MercenaryCompanyService
.canInvite(guild, leader.getName(), targetName);
if (!check.ok()) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/mercenary/contract/ContractHandler.java b/src/main/java/net/tfminecraft/simplefactions/mercenary/contract/ContractHandler.java
index 27a08629..3b22f76c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/mercenary/contract/ContractHandler.java
+++ b/src/main/java/net/tfminecraft/simplefactions/mercenary/contract/ContractHandler.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.mercenary.contract;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -399,7 +401,7 @@ private static void tell(String player, String message) {
private static void tellGovernment(Faction hirer, String message) {
if (hirer == null || hirer.getGovernment() == null || SimpleFactions.plugin == null) return;
for (Player online : Bukkit.getOnlinePlayers()) {
- if (online != null && hirer.getGovernment().isCouncilMember(online.getName())) {
+ if (online != null && hirer.getGovernment().isCouncilMember(Members.key(online))) {
online.sendMessage(message);
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java b/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java
index 5ac3e5e8..3dbec89e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java
+++ b/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java
@@ -519,21 +519,18 @@ public void setInvited(List invited) {
this.invited = invited;
}
public boolean isInvited(String name) {
- return Guild.findIgnoreCase(invited, name) != null;
+ return Guild.find(invited, name) != null;
}
public void invite(String name) {
if(name == null || isInvited(name)) return;
invited.add(name);
}
public boolean consumeInvite(String name) {
- String stored = Guild.findIgnoreCase(invited, name);
+ String stored = Guild.find(invited, name);
if(stored == null) return false;
invited.remove(stored);
return true;
}
- public boolean isMemberIgnoreCase(String name) {
- return Guild.findIgnoreCase(getMembers(), name) != null;
- }
public List getWealthModifiers() {
List list = new ArrayList<>(getOrCreateMainGuild().getWealthModifiers());
for(Guild guild : guildHandler.getGuilds()) {
@@ -694,6 +691,14 @@ public boolean isMember(String member) {
public boolean isLeader(String member) {
return getOrCreateMainGuild().isLeader(member);
}
+ /** Whether the player's current character leads this realm. */
+ public boolean isLeader(org.bukkit.entity.Player player) {
+ return isLeader(net.tfminecraft.simplefactions.identity.Members.key(player));
+ }
+ /** Whether the player's current character belongs to this realm. */
+ public boolean isMember(org.bukkit.entity.Player player) {
+ return isMember(net.tfminecraft.simplefactions.identity.Members.key(player));
+ }
public boolean canBecomeLeader(String member) {
if(isLeader(member)) return false;
if(!isMember(member)) return false;
@@ -708,7 +713,7 @@ public boolean canRemainLeader(String name) {
}
public boolean canBeCleanKicked(String p) {
- if(leader.equalsIgnoreCase(p)) return false;
+ if(leader != null && leader.equals(p)) return false;
return !guildHandler.isGuildLeader(p);
}
public Double getWealth() {
diff --git a/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java b/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java
index 0352fe38..de60b32b 100644
--- a/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java
+++ b/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.utils;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.List;
import java.util.Map;
@@ -133,7 +135,7 @@ private static Guild viewer(Player player) {
if (player == null) {
return null;
}
- return FactionManager.getGuildByMember(player.getName());
+ return FactionManager.getGuildByMember(Members.key(player));
}
private static boolean defer(
diff --git a/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java b/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java
index 6ee514ec..ceeaab8a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java
+++ b/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.utils;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageCommands;
import net.tfminecraft.simplefactions.vehicles.VehicleFactionCommands.VehicleTabCompletions;
@@ -41,7 +43,7 @@ private boolean eitherCommand(Command cmd) {
private List completeCompany(Player p, String[] args) {
List completions = new ArrayList<>();
- Guild guild = FactionManager.getGuildByLeader(p.getName());
+ Guild guild = FactionManager.getGuildByLeader(Members.key(p));
if(args.length <= 1) {
if(guild != null && guild.getCompany() == null) completions.add("found");
if(guild != null && guild.hasCompany()) {
@@ -150,7 +152,7 @@ private List completeTransferVehicleIds(Player p, String[] args) {
private List completeInstallationIds(Player p, String prefix, boolean includePendingConstruction) {
List completions = new ArrayList<>();
- Faction f = FactionManager.getByLeader(p.getName());
+ Faction f = FactionManager.getByLeader(Members.key(p));
if (f == null) {
return completions;
}
@@ -211,7 +213,7 @@ else if(cmd.getName().equalsIgnoreCase("guild") && args.length >= 0 && args.leng
if(Cache.provincesEnabled) {
completions.add("setcapital");
}
- if(FactionManager.getGuildByLeader(p.getName()) != null) {
+ if(FactionManager.getGuildByLeader(Members.key(p)) != null) {
completions.add("invite");
completions.add("kick");
completions.add("setleader");
@@ -236,7 +238,7 @@ else if(cmd.getName().equalsIgnoreCase("faction")
}
if(sender instanceof Player) {
Player p = (Player) sender;
- if(FactionManager.getByLeader(p.getName()) == null) {
+ if(FactionManager.getByLeader(Members.key(p)) == null) {
return new ArrayList<>();
}
return completeConstructKinds(args);
@@ -248,7 +250,7 @@ else if (cmd.getName().equalsIgnoreCase("faction")
&& args[0].equalsIgnoreCase("upgrade")) {
if (!Cache.provincesEnabled
|| !(sender instanceof Player p)
- || FactionManager.getByLeader(p.getName()) == null) {
+ || FactionManager.getByLeader(Members.key(p)) == null) {
return new ArrayList<>();
}
return completeInstallationIds(p, args.length >= 2 ? args[1] : "", false);
@@ -320,8 +322,8 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
Player p = (Player) sender;
List completions = new ArrayList<>();
completions.add("list");
- if(FactionManager.getByMember(p.getName()) != null) completions.add("menu");
- if(FactionManager.getByMember(p.getName()) != null) completions.addAll(List.of("positions", "spymaster", "espionage"));
+ if(FactionManager.getByMember(Members.key(p)) != null) completions.add("menu");
+ if(FactionManager.getByMember(Members.key(p)) != null) completions.addAll(List.of("positions", "spymaster", "espionage"));
if (p.hasPermission(net.tfminecraft.simplefactions.espionage.EspionageConfig.reloadPermission())) completions.add("reloadespionage");
completions.add("create");
completions.add("accept");
@@ -329,7 +331,7 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
completions.add("join");
completions.add("vehicle");
- if(FactionManager.getByLeader(p.getName()) != null) {
+ if(FactionManager.getByLeader(Members.key(p)) != null) {
if(Cache.provincesEnabled) {
completions.add("claim");
completions.add("construct");
@@ -402,12 +404,12 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
Player p = (Player) sender;
Faction f = null;
if(cmd.getName().equalsIgnoreCase("guild")) {
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
if(g != null) {
f = g.getFaction();
}
} else {
- f = FactionManager.getByLeader(p.getName());
+ f = FactionManager.getByLeader(Members.key(p));
}
if(f == null) {
return new ArrayList<>();
@@ -421,7 +423,7 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
} else if(cmd.getName().equalsIgnoreCase("faction") && args.length == 2 && args[0].equalsIgnoreCase("delete")){
if(sender instanceof Player){
Player p = (Player) sender;
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
List completions = new ArrayList();
if(f != null) {
completions.add(f.getId());
@@ -431,7 +433,7 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
} else if(cmd.getName().equalsIgnoreCase("guild") && args.length == 2 && args[0].equalsIgnoreCase("delete")){
if(sender instanceof Player){
Player p = (Player) sender;
- Guild g = FactionManager.getGuildByMember(p.getName());
+ Guild g = FactionManager.getGuildByMember(Members.key(p));
List completions = new ArrayList();
if(g != null && !g.isBase()) {
completions.add(g.getId());
@@ -459,7 +461,7 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
} else if(cmd.getName().equalsIgnoreCase("guild") && args.length == 2 && args[0].equalsIgnoreCase("kick")){
if(sender instanceof Player){
Player p = (Player) sender;
- Guild g = FactionManager.getGuildByLeader(p.getName());
+ Guild g = FactionManager.getGuildByLeader(Members.key(p));
List completions = new ArrayList();
if(g != null && !g.isBase()) {
for(String member : g.getMembers()) {
@@ -471,7 +473,7 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
} else if(cmd.getName().equalsIgnoreCase("faction") && args.length == 2 && args[0].equalsIgnoreCase("kick")){
if(sender instanceof Player){
Player p = (Player) sender;
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
List completions = new ArrayList();
if(f != null) {
completions.addAll(f.getMembers());
@@ -482,7 +484,7 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
} else if(cmd.getName().equalsIgnoreCase("faction") && args.length == 2 && args[0].equalsIgnoreCase("setleader")){
if(sender instanceof Player){
Player p = (Player) sender;
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
List completions = new ArrayList();
if(f != null) {
for(String member : f.getMembers()) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/VehicleFactionCommands.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/VehicleFactionCommands.java
index 518dbdbb..31f8a389 100644
--- a/src/main/java/net/tfminecraft/simplefactions/vehicles/VehicleFactionCommands.java
+++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/VehicleFactionCommands.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.vehicles;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -32,7 +34,7 @@ public final class VehicleFactionCommands {
private VehicleFactionCommands() {}
public static void armTransfer(Player player, String installationId) {
- Faction faction = FactionManager.getByLeader(player.getName());
+ Faction faction = FactionManager.getByLeader(Members.key(player));
if (faction == null) {
player.sendMessage(VehicleTransferMessages.notLeader());
return;
@@ -68,7 +70,7 @@ public static void armTransfer(Player player, String installationId) {
}
public static void armTake(Player player) {
- Faction faction = FactionManager.getByLeader(player.getName());
+ Faction faction = FactionManager.getByLeader(Members.key(player));
if (faction == null) {
player.sendMessage(FactionVehicleReleaseMessages.notLeader());
return;
@@ -85,7 +87,7 @@ public static void armTake(Player player) {
}
public static void armGive(Player player, String targetName) {
- Faction faction = FactionManager.getByLeader(player.getName());
+ Faction faction = FactionManager.getByLeader(Members.key(player));
if (faction == null) {
player.sendMessage(FactionVehicleReleaseMessages.notLeader());
return;
@@ -167,7 +169,7 @@ public static void armMaintenancePay(Player player) {
}
public static void armMaintenancePay(Player player, PaymentSource source) {
- if (source == PaymentSource.POUCH && FactionManager.getByLeader(player.getName()) == null) {
+ if (source == PaymentSource.POUCH && FactionManager.getByLeader(Members.key(player)) == null) {
player.sendMessage(VehicleMaintenanceMessages.notLeader());
return;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/battle/BattleVehicleEligibilityService.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/battle/BattleVehicleEligibilityService.java
index 8b1930c1..596587e7 100644
--- a/src/main/java/net/tfminecraft/simplefactions/vehicles/battle/BattleVehicleEligibilityService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/battle/BattleVehicleEligibilityService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.vehicles.battle;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRegistry;
import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRecord;
@@ -106,9 +108,9 @@ public static BattleVehicleEligibilityResult check(
return BattleVehicleEligibilityResult.NOT_CAMPAIGN_BATTLE;
}
- Faction faction = FactionManager.getByMember(player.getName());
+ Faction faction = FactionManager.getByMember(Members.key(player));
if (faction == null) {
- faction = FactionManager.getByLeader(player.getName());
+ faction = FactionManager.getByLeader(Members.key(player));
}
if (faction == null || !war.isParticipating(faction)) {
return BattleVehicleEligibilityResult.NOT_CAMPAIGN_BATTLE;
diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/FactionVehicleReleaseListener.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/FactionVehicleReleaseListener.java
index 2a13563e..a10b4c52 100644
--- a/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/FactionVehicleReleaseListener.java
+++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/FactionVehicleReleaseListener.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.vehicles.berth;
+import net.tfminecraft.simplefactions.identity.Members;
+
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -40,7 +42,7 @@ public void onVehiclePreInteract(VehiclePreInteractEvent event) {
return;
}
- Faction faction = FactionManager.getByLeader(leader.getName());
+ Faction faction = FactionManager.getByLeader(Members.key(leader));
if (faction == null) {
sessionManager.clear(leader.getUniqueId());
leader.sendMessage(FactionVehicleReleaseMessages.notLeader());
diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferConsentService.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferConsentService.java
index 5747fc66..a11b887a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferConsentService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferConsentService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.vehicles.berth;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.vehicles.berth.InstallationVehicleOwnerSync;
@@ -268,7 +270,7 @@ ActiveVehicle resolveVehicle(String vehicleUuid) {
private static Faction resolveProposerFaction(VehicleTransferConsentRequest req) {
Player proposer = Bukkit.getPlayer(req.getProposerLeaderUuid());
Faction faction = FactionManager.getByString(req.getDestinationFactionId());
- if (proposer != null && faction != null && proposer.getName().equalsIgnoreCase(faction.getLeader())) {
+ if (proposer != null && faction != null && Members.is(proposer, faction.getLeader())) {
return faction;
}
return null;
diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferListener.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferListener.java
index 5e009906..52f60cd5 100644
--- a/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferListener.java
+++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/berth/VehicleTransferListener.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.vehicles.berth;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.vehicles.berth.InstallationVehicleService.CanRegisterResult;
@@ -51,7 +53,7 @@ public void onVehiclePreInteract(VehiclePreInteractEvent event) {
return;
}
- Faction faction = FactionManager.getByLeader(leader.getName());
+ Faction faction = FactionManager.getByLeader(Members.key(leader));
if (faction == null) {
sessionManager.clear(leader.getUniqueId());
leader.sendMessage(VehicleTransferMessages.notLeader());
diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/handover/VehicleHandoverService.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/handover/VehicleHandoverService.java
index a1e43349..2cb46d5d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/vehicles/handover/VehicleHandoverService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/handover/VehicleHandoverService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.vehicles.handover;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.Objects;
import java.util.UUID;
import java.util.function.BiPredicate;
@@ -94,7 +96,7 @@ private String personalVehicleType(String ownerName, String vehicleUuid) {
}
public void offer(Player owner, Player recipient, String vehicleUuid, String vehicleTypeId) {
- Guild guild = FactionManager.getGuildByMember(owner.getName());
+ Guild guild = FactionManager.getGuildByMember(Members.key(owner));
VehicleHandoverRequest request = new VehicleHandoverRequest(
guild,
vehicleUuid,
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java
index 02bafc4c..5cfde1da 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.battle.ui;
+import net.tfminecraft.simplefactions.identity.Members;
+
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
@@ -245,7 +247,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
return true;
}
}
- Faction playerFaction = FactionManager.getByMember(p.getName());
+ Faction playerFaction = FactionManager.getByMember(Members.key(p));
if (w.isFaction()) {
if (playerFaction == null) {
p.sendMessage("§cThis is a faction warband, you need a faction to join");
@@ -286,7 +288,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
Warband warband = WarbandManager.getByLeader(p);
CampaignBattleJoinService.CampaignBattleContext ctx =
CampaignBattleJoinService.findCampaignBattleForWarband(warband);
- Faction faction = FactionManager.getByMember(p.getName());
+ Faction faction = FactionManager.getByMember(Members.key(p));
FactionManager.inv.confirming.put(p, faction);
FactionManager.inv.confirmBattleRetreatView(p, ctx.battle().getId());
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandManager.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandManager.java
index a6dc107e..2aa2da20 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.battle.warband;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -160,7 +162,7 @@ public void invenClick(InventoryClickEvent e) {
refreshOpenWarbandLists();
return;
} else if (w.isFaction()) {
- Faction f = FactionManager.getByMember(p.getName());
+ Faction f = FactionManager.getByMember(Members.key(p));
if (f == null) {
p.sendMessage("§cThis is a faction warband, you need a faction to join");
refreshOpenWarbandLists();
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java
index 093fa035..273cc0a6 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.battle.warband;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -130,7 +132,7 @@ private Faction resolveFactionForWarband(Warband warband, UUID playerId) {
if (player == null) {
return null;
}
- return FactionManager.getByMember(player.getName());
+ return FactionManager.getByMember(Members.key(player));
}
private void detachFromBattle(UUID playerId, Warband warband) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java
index 8bd1df46..339a680b 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -46,9 +48,9 @@ public static boolean isRaidWarbandHiddenFromPlayer(Warband warband, org.bukkit.
return false;
}
net.tfminecraft.simplefactions.objects.Faction faction =
- net.tfminecraft.simplefactions.managers.FactionManager.getByMember(player.getName());
+ net.tfminecraft.simplefactions.managers.FactionManager.getByMember(Members.key(player));
if (faction == null) {
- faction = net.tfminecraft.simplefactions.managers.FactionManager.getByLeader(player.getName());
+ faction = net.tfminecraft.simplefactions.managers.FactionManager.getByLeader(Members.key(player));
}
for (War war : WarManager.getActive()) {
CampaignRaid raid = CampaignRaidService.getActive(war);
@@ -167,9 +169,9 @@ public static void tryEnrollDefenderOnLogin(Player player) {
if (player == null) {
return;
}
- Faction faction = FactionManager.getByMember(player.getName());
+ Faction faction = FactionManager.getByMember(Members.key(player));
if (faction == null) {
- faction = FactionManager.getByLeader(player.getName());
+ faction = FactionManager.getByLeader(Members.key(player));
}
if (faction == null || WarbandManager.getByMemberId(player.getUniqueId()) != null) {
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java
index cb2753d0..7807643e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid;
+import net.tfminecraft.simplefactions.identity.Members;
+
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -29,9 +31,9 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
return true;
}
- Faction faction = FactionManager.getByMember(player.getName());
+ Faction faction = FactionManager.getByMember(Members.key(player));
if (faction == null) {
- faction = FactionManager.getByLeader(player.getName());
+ faction = FactionManager.getByLeader(Members.key(player));
}
if (faction == null) {
player.sendMessage(CampaignRaidMessages.NOT_PARTICIPANT);
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidTabCompletion.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidTabCompletion.java
index 41c82b46..af550d15 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidTabCompletion.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidTabCompletion.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -26,9 +28,9 @@ public List onTabComplete(CommandSender sender, Command cmd, String labe
return completions;
}
if (args.length == 2 && args[0].equalsIgnoreCase("join")) {
- Faction faction = FactionManager.getByMember(player.getName());
+ Faction faction = FactionManager.getByMember(Members.key(player));
if (faction == null) {
- faction = FactionManager.getByLeader(player.getName());
+ faction = FactionManager.getByLeader(Members.key(player));
}
if (faction == null) {
return List.of();
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java
index 5f087419..8da5b940 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.runtime;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.time.Instant;
import java.util.Map;
import java.util.Random;
@@ -77,7 +79,7 @@ public static SendResult sendProposeRequest(Player proposer, War war, Belligeren
return SendResult.NOT_ALLOWED;
}
- Faction proposerFaction = FactionManager.getByLeader(proposer.getName());
+ Faction proposerFaction = FactionManager.getByLeader(Members.key(proposer));
if (proposerFaction == null || !isWarLeader(proposerFaction, war, side)) {
return SendResult.NOT_ALLOWED;
}
@@ -114,7 +116,7 @@ public static void acceptRequest(Player acceptor) {
BelligerentRole opposingSide = req.getProposerSide() == BelligerentRole.ATTACKER
? BelligerentRole.DEFENDER
: BelligerentRole.ATTACKER;
- Faction acceptorFaction = FactionManager.getByLeader(acceptor.getName());
+ Faction acceptorFaction = FactionManager.getByLeader(Members.key(acceptor));
if (acceptorFaction == null || !isWarLeader(acceptorFaction, war, opposingSide)) {
acceptor.sendMessage("§cYou cannot accept this request.");
return;
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java
index e20675a6..3442f2d7 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.runtime;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Function;
@@ -25,7 +27,7 @@ public static Function uuidToFaction() {
if (online == null) {
return null;
}
- return FactionManager.getByMember(online.getName());
+ return FactionManager.getByMember(Members.key(online));
};
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/CharacterNameResolutionTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/CharacterNameResolutionTest.java
index 4ed6657a..f0a0645f 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/CharacterNameResolutionTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/CharacterNameResolutionTest.java
@@ -28,7 +28,7 @@ class CharacterNameResolutionTest {
assertEquals("Unknown", CharacterNames.forForeign(account));
assertEquals("Unknown", CharacterNames.display(viewer, account, faction));
assertEquals(account, CharacterNames.of(account), "Invitations keep account fallback");
- when(faction.isMemberIgnoreCase("ForeignViewer")).thenReturn(true);
+ when(faction.isMember("ForeignViewer")).thenReturn(true);
assertEquals(account, CharacterNames.display(viewer, account, faction));
when(viewer.hasPermission(EspionageService.BYPASS_PERMISSION)).thenReturn(true);
assertEquals(account + " \u00a77(" + account + ")", CharacterNames.display(viewer, account, faction));
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageBypassTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageBypassTest.java
index bdc1be96..3a76afb5 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageBypassTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageBypassTest.java
@@ -25,7 +25,7 @@ private void protect(Faction faction) {
holder.playerName = "Spy";
state.appoint(holder, 50);
when(faction.getEspionage()).thenReturn(state);
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
}
@Test
@@ -35,7 +35,7 @@ void configuredPermissionChangesOnReloadAndOwnViewsRemainExact() {
when(viewer.hasPermission(EspionageService.BYPASS_PERMISSION)).thenReturn(true);
Faction own = mock(Faction.class), foreign = mock(Faction.class);
protect(foreign);
- when(own.isMemberIgnoreCase("Viewer")).thenReturn(true);
+ when(own.isMember("Viewer")).thenReturn(true);
var config = new org.bukkit.configuration.file.YamlConfiguration();
config.set(net.tfminecraft.simplefactions.testsupport.EspionageModes.MILITARY_ONLY, false);
try {
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/EspionagePermissionsTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/EspionagePermissionsTest.java
index 29aef22f..274540b5 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/EspionagePermissionsTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/EspionagePermissionsTest.java
@@ -68,7 +68,7 @@ void sabotageRequiresTheOfficeHoldersUuidEvenForLeadersAndMembers() {
assignment.characterId = "character";
state.appoint(assignment, () -> 60);
when(faction.getEspionage()).thenReturn(state);
- when(faction.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(faction.isMember(anyString())).thenReturn(true);
when(faction.isLeader("Leader")).thenReturn(true);
when(leader.getName()).thenReturn("Leader");
when(leader.getUniqueId()).thenReturn(UUID.randomUUID());
@@ -98,7 +98,7 @@ void onlyFactionLeaderCanAppointOrRemove() {
Player candidate = mock(Player.class);
Faction faction = mock(Faction.class);
when(member.getName()).thenReturn("Member");
- when(faction.isMemberIgnoreCase("Member")).thenReturn(true);
+ when(faction.isMember("Member")).thenReturn(true);
try (var databases = mockConstruction(Database.class)) {
assertFalse(EspionageService.appoint(member, faction, candidate));
assertFalse(EspionageService.remove(member, faction));
@@ -116,7 +116,7 @@ void unaffiliatedAndOwnMembersDoNotConsumeForeignRolls() {
try (var factions = mockStatic(FactionManager.class)) {
assertNull(EspionageService.report(viewer, target));
assertNull(target.getEspionage().cachedReport("unused", 0, 0));
- when(target.isMemberIgnoreCase("Visitor")).thenReturn(true);
+ when(target.isMember("Visitor")).thenReturn(true);
assertNull(EspionageService.report(viewer, target));
factions.verify(() -> FactionManager.getByMember("Visitor"), times(1));
}
@@ -132,7 +132,7 @@ void expiredMembershipAndReplacedOfficeCloseExistingPrivateScreens() {
try (var factions = mockStatic(FactionManager.class)) {
factions.when(() -> FactionManager.getByString("faction")).thenReturn(faction);
assertTrue(EspionageAccess.denied(member, new SFInventoryHolder("faction", SFGUI.MILITARY_VIEW)));
- when(faction.isMemberIgnoreCase("FormerSpy")).thenReturn(true);
+ when(faction.isMember("FormerSpy")).thenReturn(true);
assertFalse(EspionageAccess.denied(member, new SFInventoryHolder("faction", SFGUI.MILITARY_VIEW)));
assertTrue(EspionageAccess.denied(member, new SFInventoryHolder("faction", SFGUI.SPYMASTER_SETTINGS)));
assertFalse(EspionageAccess.denied(member, new SFInventoryHolder("faction", SFGUI.SPECIAL_POSITIONS)));
@@ -164,7 +164,7 @@ void departedSpymasterLosesOfficeAndCannotSabotage() {
void leaderIsAlwaysEligibleAndOneOfficeKeepsFullAptitude() {
Faction faction = mock(Faction.class);
when(faction.isLeader("Leader")).thenReturn(true);
- when(faction.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(faction.isMember(anyString())).thenReturn(true);
when(faction.getMembers()).thenReturn(java.util.List.of("Leader", "Spy"));
assertTrue(EspionageService.eligible(faction, "Leader"));
assertTrue(EspionageService.eligible(faction, "Spy"));
@@ -224,7 +224,7 @@ void defaultHoldingFollowsANewLeaderButAppointmentsStay() {
Faction faction = mock(Faction.class);
when(faction.getLeader()).thenReturn("NewLeader");
when(faction.isLeader("NewLeader")).thenReturn(true);
- when(faction.isMemberIgnoreCase("OldLeader")).thenReturn(true);
+ when(faction.isMember("OldLeader")).thenReturn(true);
var state = new EspionageState();
when(faction.getEspionage()).thenReturn(state);
var appointed = new SpecialPositionAssignment();
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageTestFixtures.java b/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageTestFixtures.java
index 9a576e59..bc2beac9 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageTestFixtures.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/EspionageTestFixtures.java
@@ -12,6 +12,6 @@ static void protect(Faction faction) {
holder.playerName = "Spy";
state.appoint(holder, 50);
when(faction.getEspionage()).thenReturn(state);
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
}
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligenceBrowsingTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligenceBrowsingTest.java
index fe77ab73..73c265a4 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligenceBrowsingTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligenceBrowsingTest.java
@@ -39,7 +39,7 @@ private Faction faction(String id) {
var holder = new SpecialPositionAssignment();
holder.playerName = "Spy";
faction.getEspionage().appoint(holder, 50);
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
when(faction.getMembers()).thenReturn(List.of());
when(faction.getGuildHandler().getGuilds()).thenReturn(List.of());
when(faction.getMilitary().getRegiments()).thenReturn(List.of());
@@ -53,7 +53,7 @@ void browsingOnlyReadsCacheAndMenuRefreshIsSharedOncePerDay() {
when(first.getName()).thenReturn("First");
when(second.getName()).thenReturn("Second");
Faction observer = faction("observer"), target = faction("target");
- when(observer.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(observer.isMember(anyString())).thenReturn(true);
try (var factions = mockStatic(FactionManager.class); var databases = mockConstruction(Database.class)) {
factions.when(() -> FactionManager.getByMember("First")).thenReturn(observer);
factions.when(() -> FactionManager.getByMember("Second")).thenReturn(observer);
@@ -98,7 +98,7 @@ void prestigeIsPublicAndWealthRankingUsesOnlyKnownMidpoints() {
Player viewer = mock(Player.class);
when(viewer.getName()).thenReturn("Viewer");
Faction observer = faction("own"), estimated = faction("estimated"), hidden = faction("hidden");
- when(observer.isMemberIgnoreCase("Viewer")).thenReturn(true);
+ when(observer.isMember("Viewer")).thenReturn(true);
when(observer.getWealth()).thenReturn(100.0);
when(observer.getPrestige()).thenReturn(1.0);
when(estimated.getPrestige()).thenReturn(5.0);
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligencePolicyTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligencePolicyTest.java
index 3e8e80bf..f9ed481c 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligencePolicyTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/IntelligencePolicyTest.java
@@ -59,7 +59,7 @@ class IntelligencePolicyTest {
var target = mock(Faction.class);
var state = new EspionageState();
when(target.getEspionage()).thenReturn(state);
- when(target.isMemberIgnoreCase("Account")).thenReturn(true);
+ when(target.isMember("Account")).thenReturn(true);
when(target.getMembers()).thenReturn(List.of("Account"));
var holder = new SpecialPositionAssignment();
holder.playerName = "Account";
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeAppointmentsTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeAppointmentsTest.java
index 402f3feb..9b6944a0 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeAppointmentsTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeAppointmentsTest.java
@@ -133,7 +133,7 @@ void anotherAppointmentWaitsForTheCooldownEvenAfterRemoval() {
void newHolderBuildsUpToFullAptitudeOverTheConfiguredDays() {
long day = 86_400_000L;
var faction = mock(Faction.class);
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
when(faction.getMembers()).thenReturn(java.util.List.of("Leader", "Spy"));
var holder = assignment("Spy");
new EspionageState().appoint(holder, 80, 10 * day);
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/OfficePersistenceTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/OfficePersistenceTest.java
index 30924aee..9206d899 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/OfficePersistenceTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/OfficePersistenceTest.java
@@ -35,7 +35,7 @@ class OfficePersistenceTest {
when(faction.getEspionage()).thenReturn(state);
when(actor.getName()).thenReturn("Leader");
when(faction.isLeader("Leader")).thenReturn(true);
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
try (var databases = mockConstruction(Database.class)) {
assertFalse(EspionageService.remove(actor, faction));
assertSame(holder, state.getSpymaster());
@@ -103,7 +103,7 @@ class OfficePersistenceTest {
when(faction.getEspionage()).thenReturn(state);
when(actor.getName()).thenReturn("Spy");
when(actor.getUniqueId()).thenReturn(holder.playerId);
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
try (var databases = mockConstruction(Database.class)) {
assertFalse(EspionageService.setSabotage(actor, faction, true, 100));
assertFalse(EspionageService.setSabotage(actor, faction, false, 75));
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeVacancyTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeVacancyTest.java
index e2779e80..5e380685 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeVacancyTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/OfficeVacancyTest.java
@@ -44,7 +44,7 @@ class OfficeVacancyTest {
assertEquals("Vacant Spymaster", penalties.getFirst().getName());
}
var holder = new SpecialPositionAssignment(); holder.playerName = "Spy";
- when(faction.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(faction.isMember("Spy")).thenReturn(true);
state.appoint(holder, 80);
assertTrue(EspionageService.stabilityModifiers(faction, 0).isEmpty());
state.removeSpymaster();
@@ -67,7 +67,7 @@ class OfficeVacancyTest {
state.addUnrest(SpecialPosition.SPYMASTER, 10, 7, 1000);
assertEquals(2, EspionageService.stabilityModifiers(faction, 1000).size());
assertEquals(1, EspionageService.stabilityModifiers(faction, 1000 + 8L * 86_400_000).size());
- when(faction.isMemberIgnoreCase("FormerSpy")).thenReturn(true);
+ when(faction.isMember("FormerSpy")).thenReturn(true);
assertTrue(EspionageService.stabilityModifiers(faction, 1000 + 8L * 86_400_000).isEmpty());
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/UnguardedFactionTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/UnguardedFactionTest.java
index 50a8fa16..5c085bbf 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/UnguardedFactionTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/UnguardedFactionTest.java
@@ -39,7 +39,7 @@ private SpecialPositionAssignment appoint(Faction faction, String name, int apti
holder.playerName = name;
holder.playerId = UUID.randomUUID();
holder.characterId = "character";
- when(faction.isMemberIgnoreCase(name)).thenReturn(true);
+ when(faction.isMember(name)).thenReturn(true);
faction.getEspionage().appoint(holder, aptitude);
return holder;
}
@@ -88,9 +88,9 @@ private SpecialPositionAssignment appoint(Faction faction, String name, int apti
var target = faction("target");
var holder = appoint(target, "Spy", 50);
try (var characters = mockStatic(OfficeCharacters.class); var databases = mockConstruction(Database.class)) {
- when(target.isMemberIgnoreCase("Spy")).thenReturn(false);
+ when(target.isMember("Spy")).thenReturn(false);
assertTrue(EspionageService.canViewExact(viewer, target));
- when(target.isMemberIgnoreCase("Spy")).thenReturn(true);
+ when(target.isMember("Spy")).thenReturn(true);
characters.when(() -> OfficeCharacters.isDead(holder.playerId, holder.characterId)).thenReturn(true);
assertTrue(EspionageService.canViewExact(viewer, target));
assertSame(holder, target.getEspionage().getSpymaster());
@@ -159,9 +159,9 @@ private SpecialPositionAssignment appoint(Faction faction, String name, int apti
factions.when(() -> FactionManager.getByMember("ForeignViewer")).thenReturn(observer);
factions.when(FactionManager::getCopy).thenAnswer(ignored -> new ArrayList<>(List.of(observer, target)));
assertSame(cached, EspionageService.report(viewer, target));
- when(observer.isMemberIgnoreCase("ObserverSpy")).thenReturn(false);
+ when(observer.isMember("ObserverSpy")).thenReturn(false);
assertNull(EspionageService.report(viewer, target), "Ineligible observers cannot read cached findings");
- when(observer.isMemberIgnoreCase("ObserverSpy")).thenReturn(true);
+ when(observer.isMember("ObserverSpy")).thenReturn(true);
try (var characters = mockStatic(OfficeCharacters.class)) {
characters.when(() -> OfficeCharacters.isDead(holder.playerId, holder.characterId)).thenReturn(true);
assertNull(EspionageService.report(viewer, target), "Dead observers cannot read cached findings");
diff --git a/src/test/java/net/tfminecraft/simplefactions/espionage/VassalIntelligenceTest.java b/src/test/java/net/tfminecraft/simplefactions/espionage/VassalIntelligenceTest.java
index c53658f7..4dd2069a 100644
--- a/src/test/java/net/tfminecraft/simplefactions/espionage/VassalIntelligenceTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/espionage/VassalIntelligenceTest.java
@@ -97,7 +97,7 @@ class VassalIntelligenceTest {
holder.playerName = "Account1";
state.appoint(holder, 70);
when(target.getEspionage()).thenReturn(state);
- when(target.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(target.isMember(anyString())).thenReturn(true);
var report = EspionageService.createReport(Map.of(), -50, IntelligenceTier.DETAILED, new Random(2));
try (var names = mockStatic(CharacterNames.class); var represents = mockStatic(Represents.class)) {
names.when(() -> CharacterNames.forForeign(anyString())).thenAnswer(call -> call.getArgument(0));
@@ -196,7 +196,7 @@ class VassalIntelligenceTest {
state.appoint(holder, 60);
when(faction.getId()).thenReturn("faction");
when(faction.getEspionage()).thenReturn(state);
- when(faction.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(faction.isMember(anyString())).thenReturn(true);
when(faction.getOverlord()).thenReturn(overlord);
when(faction.getVassals()).thenReturn(List.of(vassal));
when(spy.getName()).thenReturn("Spy");
@@ -238,7 +238,7 @@ class VassalIntelligenceTest {
state.appoint(holder, 60);
state.share(SharingPartner.VASSALS, IntelligenceTier.RUMOURS);
when(faction.getEspionage()).thenReturn(state);
- when(faction.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(faction.isMember(anyString())).thenReturn(true);
when(spy.getName()).thenReturn("Spy");
when(spy.getUniqueId()).thenReturn(holder.playerId);
try (var databases = mockConstruction(Database.class)) {
@@ -257,7 +257,7 @@ class VassalIntelligenceTest {
state.appoint(holder, 60);
when(faction.getId()).thenReturn("faction");
when(faction.getEspionage()).thenReturn(state);
- when(faction.isMemberIgnoreCase(anyString())).thenReturn(true);
+ when(faction.isMember(anyString())).thenReturn(true);
when(faction.getVassals()).thenReturn(List.of(vassal));
var vassalState = new EspionageState();
vassalState.report("faction", 0, 1, IntelligenceReport::new);
diff --git a/src/test/java/net/tfminecraft/simplefactions/managers/CommandManagerCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/managers/CommandManagerCoverageTest.java
index 296b332b..501eef5e 100644
--- a/src/test/java/net/tfminecraft/simplefactions/managers/CommandManagerCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/managers/CommandManagerCoverageTest.java
@@ -1805,10 +1805,10 @@ void factionInvitationsRequireLeadershipCapacityAndAnUnalignedInvitee() {
verify(faction, never()).invite(anyString());
Player alice = gui.player("Alice");
characters.when(() -> CharacterNames.resolveOnline(player, "Lady Alice")).thenReturn(alice);
- when(faction.isMemberIgnoreCase("Alice")).thenReturn(true);
+ when(faction.isMember("Alice")).thenReturn(true);
assertTrue(run("faction", "invite", "Lady", "Alice"));
verify(player).sendMessage(contains("already a member"));
- when(faction.isMemberIgnoreCase("Alice")).thenReturn(false);
+ when(faction.isMember("Alice")).thenReturn(false);
Faction other = mock(Faction.class);
factions.when(() -> FactionManager.getByMember("Alice")).thenReturn(other);
assertTrue(run("faction", "invite", "Lady", "Alice"));
diff --git a/src/test/java/net/tfminecraft/simplefactions/managers/FactionManagerCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/managers/FactionManagerCoverageTest.java
index e38c0dde..a995472f 100644
--- a/src/test/java/net/tfminecraft/simplefactions/managers/FactionManagerCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/managers/FactionManagerCoverageTest.java
@@ -127,7 +127,7 @@ private Faction faction(String id, String leader) {
when(faction.getRGB()).thenReturn(id + "-rgb");
List members = new ArrayList<>(List.of(leader));
when(faction.getMembers()).thenReturn(members);
- when(faction.isMemberIgnoreCase(anyString()))
+ when(faction.isMember(anyString()))
.thenAnswer(call -> members.stream().anyMatch(name -> name.equalsIgnoreCase(call.getArgument(0))));
doAnswer(call -> members.add(call.getArgument(0))).when(faction).addMember(anyString());
when(faction.getProvinces()).thenReturn(new ArrayList<>());
diff --git a/src/test/java/net/tfminecraft/simplefactions/mercenary/contract/MercenaryMarketTest.java b/src/test/java/net/tfminecraft/simplefactions/mercenary/contract/MercenaryMarketTest.java
index 84964075..f7bf99c9 100644
--- a/src/test/java/net/tfminecraft/simplefactions/mercenary/contract/MercenaryMarketTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/mercenary/contract/MercenaryMarketTest.java
@@ -223,7 +223,7 @@ private void governedHirer(String... council) {
List members = new java.util.ArrayList<>(List.of(council));
members.add("Farmhand");
when(fixture.hirer.getMembers()).thenReturn(members);
- when(fixture.hirer.isMemberIgnoreCase(anyString())).thenAnswer(inv -> {
+ when(fixture.hirer.isMember(anyString())).thenAnswer(inv -> {
String name = inv.getArgument(0);
return members.stream().anyMatch(m -> m.equalsIgnoreCase(name));
});
diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java
index 1d779a5f..21a9e5a9 100644
--- a/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java
@@ -475,10 +475,10 @@ void guildMembershipQueriesAndForcedRemovalAgreeWithoutChangingLeaders() {
assertFalse(guilds.isGuildLeader("Traveler"));
guilds.forceKick("traveler");
assertNull(guilds.getGuildByMember("Traveler"));
- assertFalse(faction.isMemberIgnoreCase("Traveler"));
+ assertFalse(faction.isMember("Traveler"));
assertEquals(List.of("Bob"), traders.getMembers());
assertEquals("Bob", traders.getLeader());
- assertTrue(faction.isMemberIgnoreCase("Citizen"));
+ assertTrue(faction.isMember("Citizen"));
guilds.forceKick("already gone");
assertTrue(guilds.isGuildLeader("Alice"));
assertTrue(guilds.isGuildLeader("Bob"));
diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java
index 43d4e062..d9f20319 100644
--- a/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java
@@ -145,8 +145,8 @@ void factionInvitationsAreCaseInsensitiveAndCanBeConsumedExactlyOnce() {
faction.setInvited(new ArrayList<>(List.of("Cara")));
assertTrue(faction.isInvited("cara"));
faction.addMember("Bob");
- assertTrue(faction.isMemberIgnoreCase("bob"));
- assertFalse(faction.isMemberIgnoreCase("missing"));
+ assertTrue(faction.isMember("bob"));
+ assertFalse(faction.isMember("missing"));
}
@Test
diff --git a/src/test/java/net/tfminecraft/simplefactions/testsupport/NameKeyedMembers.java b/src/test/java/net/tfminecraft/simplefactions/testsupport/NameKeyedMembers.java
new file mode 100644
index 00000000..7628d8c4
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/testsupport/NameKeyedMembers.java
@@ -0,0 +1,84 @@
+package net.tfminecraft.simplefactions.testsupport;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
+
+/**
+ * Member keys are opaque strings, so most tests key each mocked player by its
+ * Minecraft name. Tests about real character keys install
+ * {@link Members#useSharedIdentity()} themselves.
+ */
+public final class NameKeyedMembers implements Members.Resolver, BeforeEachCallback, AfterEachCallback {
+ public static final NameKeyedMembers INSTANCE = new NameKeyedMembers();
+
+ public static void install() {
+ Members.setResolver(INSTANCE);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext context) {
+ install();
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) {
+ install();
+ }
+
+ @Override
+ public Optional session(Player player) {
+ String name = player.getName();
+ if (name == null) return Optional.empty();
+ UUID owner = player.getUniqueId() != null ? player.getUniqueId() : id("owner:" + name);
+ return Optional.of(new CharacterSession(new CharacterRef(owner, id(name)), 1L));
+ }
+
+ @Override
+ public boolean isCurrent(CharacterSession session) {
+ return true;
+ }
+
+ @Override
+ public String key(Player player) {
+ return player.getName();
+ }
+
+ @Override
+ public Optional info(String key) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional online(String key) {
+ Player player = null;
+ try {
+ player = Bukkit.getPlayerExact(key);
+ } catch (RuntimeException ignored) {
+ // No server in this test.
+ }
+ if (player == null) {
+ try {
+ player = Bukkit.getPlayer(key);
+ } catch (RuntimeException ignored) {
+ // No server in this test.
+ }
+ }
+ return Optional.ofNullable(player);
+ }
+
+ private static UUID id(String name) {
+ return UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderServiceTest.java
index 68824f7a..758652f9 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderServiceTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderServiceTest.java
@@ -102,8 +102,8 @@ void setUp() {
when(attacker.getMembers()).thenReturn(new ArrayList<>(List.of("Bob")));
when(defender.getMembers()).thenReturn(new ArrayList<>(List.of("Carol")));
- when(attacker.isMemberIgnoreCase("Bob")).thenReturn(true);
- when(defender.isMemberIgnoreCase("Carol")).thenReturn(true);
+ when(attacker.isMember("Bob")).thenReturn(true);
+ when(defender.isMember("Carol")).thenReturn(true);
Instant raidWindow = BattleWindowService.atScheduleHour(BATTLE_DAY, 19);
CampaignRaidService.beginMuster(war, attacker, "port-atk", "port-def", raidWindow);
diff --git a/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 00000000..3a242080
--- /dev/null
+++ b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+net.tfminecraft.simplefactions.testsupport.NameKeyedMembers
diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties
new file mode 100644
index 00000000..6efc0d5e
--- /dev/null
+++ b/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled=true
From c473b20c509931d1a67574fffff5f3c547402db5 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:07:12 +0000
Subject: [PATCH 02/35] wip: commands and identity on member keys
---
.../identity/LeaderCharacterListener.java | 12 +-
.../identity/LeaderCharacters.java | 35 ++---
.../simplefactions/identity/Members.java | 14 +-
.../managers/CommandManager.java | 131 ++++++++++--------
.../managers/FactionManager.java | 45 +++---
.../managers/InviteCommands.java | 16 ++-
.../managers/PlayerManager.java | 18 +--
7 files changed, 148 insertions(+), 123 deletions(-)
diff --git a/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacterListener.java b/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacterListener.java
index 333cb4d8..32636bc8 100644
--- a/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacterListener.java
+++ b/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacterListener.java
@@ -19,25 +19,25 @@ public void onCharacterStart(CharacterSessionStartEvent event) {
if (!player.isOnline() || Bukkit.getPlayer(player.getUniqueId()) != player
|| !event.getSession().ref().ownerId().equals(player.getUniqueId())
|| !Characters.isCurrent(event.getSession())) return;
- refresh(player.getName());
+ refresh(Members.key(event.getSession().ref()));
}
/**
- * Re-reads every realm and guild leader's character (or only `player`'s
- * when given) and, if any name changed, asks the map to ship nation.json
- * on its next cycle.
+ * Re-reads every realm and guild leader's character (or only the
+ * character with member key `player` when given) and, if any name
+ * changed, asks the map to ship nation.json on its next cycle.
*/
public static void refresh(String player) {
boolean changed = false;
for (Faction faction : FactionManager.factions) {
if (faction == null) continue;
- if (player == null || player.equalsIgnoreCase(faction.getLeader())) {
+ if (player == null || player.equals(faction.getLeader())) {
changed |= LeaderCharacters.refresh(faction);
}
// Guild leaders too; a realm's own guild follows the realm above.
for (Guild guild : faction.getGuildHandler().getGuilds()) {
if (guild == null || guild.isBase()) continue;
- if (player != null && !player.equalsIgnoreCase(guild.getLeader())) continue;
+ if (player != null && !player.equals(guild.getLeader())) continue;
changed |= LeaderCharacters.refresh(guild);
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacters.java b/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacters.java
index 40411fb5..2f522904 100644
--- a/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacters.java
+++ b/src/main/java/net/tfminecraft/simplefactions/identity/LeaderCharacters.java
@@ -5,12 +5,12 @@
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.objects.Faction;
-/** Character names remembered with their account owner for the public map. */
+/** Leader character names remembered with the leader's member key for the public map. */
public final class LeaderCharacters {
- /** Active character name of an online player, or null if unknown. */
+ /** Name of the character with a member key, or null if unknown. */
public interface Probe {
- String activeCharacterName(String player);
+ String characterName(String key);
/** False while RPCharacters is missing, so remembered names are not kept either. */
default boolean available() {
@@ -18,7 +18,7 @@ default boolean available() {
}
}
- /** A remembered character name and the player it belongs to. */
+ /** A remembered character name and the member key it belongs to. */
public record Remembered(String name, String player) {
public static final Remembered NONE = new Remembered(null, null);
}
@@ -33,21 +33,8 @@ public static void useSharedIdentity() {
@Override public boolean available() {
return net.tfminecraft.tlibs.character.Characters.available();
}
- @Override public String activeCharacterName(String account) {
- var online = org.bukkit.Bukkit.getPlayerExact(account);
- if (online != null) {
- var session = net.tfminecraft.tlibs.character.Characters.currentSession(online).orElse(null);
- if (session == null || !session.ref().ownerId().equals(online.getUniqueId())) return null;
- var info = net.tfminecraft.tlibs.character.Characters.info(session.ref().characterId()).orElse(null);
- return info != null && info.ref().equals(session.ref())
- && online.isOnline() && org.bukkit.Bukkit.getPlayer(online.getUniqueId()) == online
- && net.tfminecraft.tlibs.character.Characters.isCurrent(session) ? info.name() : null;
- }
- var cached = org.bukkit.Bukkit.getOfflinePlayerIfCached(account);
- if (cached == null) return null;
- return net.tfminecraft.tlibs.character.Characters.lastPlayed(cached.getUniqueId())
- .filter(info -> info.ref().ownerId().equals(cached.getUniqueId()))
- .map(net.tfminecraft.tlibs.character.CharacterInfo::name).orElse(null);
+ @Override public String characterName(String key) {
+ return Members.info(key).map(net.tfminecraft.tlibs.character.CharacterInfo::name).orElse(null);
}
});
}
@@ -61,17 +48,17 @@ public static void reset() {
}
/**
- * What to remember for `leader` now: their active character if they are
- * online with one, else what was remembered for this same leader, else
- * nothing. Without RPCharacters it is always nothing.
+ * What to remember for `leader` now: the leader character's name, else
+ * what was remembered for this same leader, else nothing. Without
+ * RPCharacters it is always nothing.
*/
public static Remembered resolve(String leader, String rememberedName, String rememberedFor) {
if (leader == null || leader.isBlank()) return Remembered.NONE;
Probe current = probe;
if (current == null || !current.available()) return Remembered.NONE;
- String active = clean(current.activeCharacterName(leader));
+ String active = clean(current.characterName(leader));
if (active != null) return new Remembered(active, leader);
- if (rememberedName != null && leader.equalsIgnoreCase(rememberedFor)) {
+ if (rememberedName != null && leader.equals(rememberedFor)) {
String kept = clean(rememberedName);
if (kept != null) return new Remembered(kept, leader);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/identity/Members.java b/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
index 1368b18c..13486ea1 100644
--- a/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
+++ b/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
@@ -74,6 +74,11 @@ public static boolean isCurrent(CharacterSession session) {
return session != null && resolver.isCurrent(session);
}
+ /** The current character key of the online account with that Minecraft name, or null. */
+ public static String ofAccountName(String accountName) {
+ return accountName == null ? null : key(onlineByName(accountName));
+ }
+
/** The character name for a key; offline characters are read from the provider. */
public static String display(String key) {
if (key == null || key.isBlank()) return "Unknown";
@@ -166,6 +171,8 @@ public static boolean isQuarantined(String key) {
public static String find(Collection keys, String input) {
if (keys == null || input == null) return null;
String query = unquote(input);
+ String typedKey = key(characterId(query));
+ if (typedKey != null && keys.contains(typedKey)) return typedKey;
Player online = onlineByName(query);
String onlineKey = key(online);
if (onlineKey != null && keys.contains(onlineKey)) return onlineKey;
@@ -178,10 +185,15 @@ public static String find(Collection keys, String input) {
return match;
}
- /** The key of an online player named by Minecraft or character name, or null. */
+ /**
+ * The key of an online player named by Minecraft or character name, or
+ * null. A typed character ID is taken as it is, for staff.
+ */
public static String findOnline(String input) {
if (input == null) return null;
String query = unquote(input);
+ String typedKey = key(characterId(query));
+ if (typedKey != null) return typedKey;
String direct = key(onlineByName(query));
if (direct != null) return direct;
String match = null;
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
index c9b1fd27..6f862ca6 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
@@ -61,6 +61,8 @@
public class CommandManager implements Listener, CommandExecutor{
private Formatter format = new Formatter();
+ /** Sent to a player acting without a ready character session. */
+ public static final String NO_CHARACTER = "§cYou need to be playing a character to do that.";
public String cmd1 = "faction";
public String cmd2 = "guild";
@@ -96,13 +98,18 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§a[SimpleFactions]§c Error with command format, use the gameplay guide for a list of commands");
return true;
}
+ // Without a character the player is a stranger; only staff commands remain.
+ if(Members.key(p) == null && !Permissions.isAdmin(sender)) {
+ p.sendMessage(NO_CHARACTER);
+ return true;
+ }
if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("create") && args.length == 2) {
Faction f = FactionManager.getByMember(Members.key(p));
if(f == null) {
p.sendMessage("§cYou need to be in a faction to make a guild");
return true;
}
- if(f.isInGuild(p.getName())) {
+ if(f.isInGuild(Members.key(p))) {
p.sendMessage("§cYou are already in a guild");
return true;
}
@@ -156,8 +163,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
guild.getFaction().getGuildHandler().removeGuild(guild.getId());
guild.getFaction().getProvinceHandler().revalidateClaims();
for(String member : guild.getMembers()) {
- Player pl = Bukkit.getPlayerExact(member);
- if(pl != null && !member.equalsIgnoreCase(guild.getLeader())) {
+ Player pl = Members.player(member);
+ if(pl != null && !member.equals(guild.getLeader())) {
pl.sendMessage("§cYour guild has been deleted!");
pl.sendMessage("§aJoined "+guild.getFaction().getName());
}
@@ -217,13 +224,17 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
String invitee = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
- Player invited = net.tfminecraft.simplefactions.espionage.CharacterNames.resolveOnline(p, invitee);
+ Player invited = Members.resolveOnline(p, invitee);
if(invited == null) return true;
if(invited.getUniqueId().equals(p.getUniqueId())) {
p.sendMessage("§cCannot invite yourself");
return true;
}
- String name = invited.getName();
+ String name = Members.key(invited);
+ if(name == null) {
+ p.sendMessage("§cThat player has not chosen a character yet");
+ return true;
+ }
if(guild.isInvited(name)) {
p.sendMessage("§cPlayer is already invited");
return true;
@@ -254,8 +265,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
guild.invite(name);
- p.sendMessage("§aInvited "+name);
- invited.sendMessage("§a"+p.getName()+" invited you to the guild "+guild.getName());
+ p.sendMessage("§aInvited "+Members.display(name));
+ invited.sendMessage("§a"+Members.name(p)+" invited you to the guild "+guild.getName());
invited.sendMessage(InviteCommands.buttons(cmd2, guild.getId(), guild.getName()));
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("leave") && args.length == 1) {
@@ -282,21 +293,18 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cThis is the base guild, use /faction kick instead");
return true;
}
- if(!guild.isMember(args[1])) {
+ String kicked = Members.find(guild.getMembers(), args[1]);
+ if(kicked == null) {
p.sendMessage("§cPlayer is not a member");
return true;
}
- if(args[1].equalsIgnoreCase(guild.getLeader())) {
+ if(kicked.equals(guild.getLeader())) {
p.sendMessage("§cYou cannot dismiss the leader!");
return true;
}
- guild.kick(args[1]);
- p.sendMessage("§aKicked "+args[1]);
- for(Player pl : Bukkit.getOnlinePlayers()) {
- if(pl.getName().equalsIgnoreCase(args[1])) {
- pl.sendMessage("§a"+p.getName()+ " kicked you from "+guild.getName());
- }
- }
+ guild.kick(kicked);
+ p.sendMessage("§aKicked "+Members.display(kicked));
+ Members.online(kicked).ifPresent(pl -> pl.sendMessage("§a"+Members.name(p)+ " kicked you from "+guild.getName()));
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd2) && args[0].equalsIgnoreCase("setbank") && args.length == 1) {
if(FactionManager.getGuildByLeader(Members.key(p)) != null) {
@@ -344,7 +352,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
pouch.change(amount*-1);
g.getBank().deposit(amount);
- g.getLedger().getHistory().addDeposit(p.getName(), amount);
+ g.getLedger().getHistory().addDeposit(Members.key(p), amount);
p.sendMessage("§e============§6[Bank Report]§e==============");
p.sendMessage(StringFormatter.formatHex("#6ab05aDeposited: #b39122"+amount+"#dbaf1dd"));
p.sendMessage(StringFormatter.formatHex("#3ce8c9New Guild Balance: #b39122"+b.getWealth()+"#dbaf1dd"));
@@ -483,18 +491,19 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cOnly the leader can set a new leader!");
return true;
}
- if(!g.isMember(args[1])) {
+ String heir = Members.find(g.getMembers(), args[1]);
+ if(heir == null) {
p.sendMessage("§cPlayer is not in the guild");
return true;
}
- if(args[1].equalsIgnoreCase(g.getLeader())) {
+ if(heir.equals(g.getLeader())) {
p.sendMessage("§cPlayer is already the leader");
return true;
}
- g.setLeader(args[1]);
+ g.setLeader(heir);
for(Player pl : Bukkit.getOnlinePlayers()) {
if(g.isMember(Members.key(pl))) {
- pl.sendMessage("§a"+args[1]+ " is the new guild leader!");
+ pl.sendMessage("§a"+Members.display(heir)+ " is the new guild leader!");
}
}
return true;
@@ -522,10 +531,10 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
g.setLeader(member);
- p.sendMessage("§a"+member+" now leads "+g.getName());
+ p.sendMessage("§a"+Members.display(member)+" now leads "+g.getName());
for(Player pl : Bukkit.getOnlinePlayers()) {
if(!pl.equals(p) && g.isMember(Members.key(pl))) {
- pl.sendMessage("§a"+member+ " is the new guild leader!");
+ pl.sendMessage("§a"+Members.display(member)+ " is the new guild leader!");
}
}
return true;
@@ -553,7 +562,11 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
== DisplayNameGate.Result.NEEDS_CONFIRM) {
return true;
}
- Faction f = new Faction(args[1], p.getName());
+ if(Members.key(p) == null) {
+ p.sendMessage(NO_CHARACTER);
+ return true;
+ }
+ Faction f = new Faction(args[1], Members.key(p));
FactionCreateEvent factionCreateEvent = new FactionCreateEvent(p, f);
Bukkit.getPluginManager().callEvent(factionCreateEvent);
if(!factionCreateEvent.isCancelled()) {
@@ -871,25 +884,22 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cOnly the leader can dismiss members!");
return true;
}
- if(args[1].equalsIgnoreCase(f.getLeader())) {
+ String kicked = Members.find(f.getMembers(), args[1]);
+ if(kicked != null && kicked.equals(f.getLeader())) {
p.sendMessage("§cYou cannot dismiss the leader!");
return true;
}
- if(!f.getMembers().contains(args[1])) {
+ if(kicked == null) {
p.sendMessage("§cPlayer is not a member");
return true;
}
- if(f.isInGuild(args[1])) {
+ if(f.isInGuild(kicked)) {
p.sendMessage("§cPlayer is a member of a guild");
return true;
}
- f.forceRemoveMember(args[1]);;
- p.sendMessage("§aKicked "+args[1]);
- for(Player pl : Bukkit.getOnlinePlayers()) {
- if(pl.getName().equalsIgnoreCase(args[1])) {
- pl.sendMessage("§a"+p.getName()+ " kicked you from "+f.getName());
- }
- }
+ f.forceRemoveMember(kicked);
+ p.sendMessage("§aKicked "+Members.display(kicked));
+ Members.online(kicked).ifPresent(pl -> pl.sendMessage("§a"+Members.name(p)+ " kicked you from "+f.getName()));
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setleader") && args.length == 2) {
Faction f = FactionManager.getByMember(Members.key(p));
@@ -905,22 +915,23 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cCannot set leader in a democracy!");
return true;
}
- if(!f.getMembers().contains(args[1])) {
+ String heir = Members.find(f.getMembers(), args[1]);
+ if(heir == null) {
p.sendMessage("§cPlayer is not in the faction");
return true;
}
- if(args[1].equalsIgnoreCase(f.getLeader())) {
+ if(heir.equals(f.getLeader())) {
p.sendMessage("§cPlayer is already the leader");
return true;
}
- if(!f.canBecomeLeader(args[1])) {
+ if(!f.canBecomeLeader(heir)) {
p.sendMessage("§cPlayer is not eligible to be leader (perhaps they are a guild leader?)");
return true;
}
- f.setLeader(args[1]);
+ f.setLeader(heir);
for(Player pl : Bukkit.getOnlinePlayers()) {
if(f.getMembers().contains(Members.key(pl))) {
- pl.sendMessage("§a"+args[1]+ " is the new faction leader!");
+ pl.sendMessage("§a"+Members.display(heir)+ " is the new faction leader!");
}
}
return true;
@@ -934,12 +945,16 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
p.sendMessage("§cOnly the leader can invite new members!");
return true;
}
- Player invited = net.tfminecraft.simplefactions.espionage.CharacterNames.resolveOnline(p,
+ Player invited = Members.resolveOnline(p,
String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length)));
if(invited == null) {
return true;
}
- String name = invited.getName();
+ String name = Members.key(invited);
+ if(name == null) {
+ p.sendMessage("§cThat player has not chosen a character yet");
+ return true;
+ }
if(f.isMember(name)) {
p.sendMessage("§cPlayer is already a member");
return true;
@@ -953,8 +968,8 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
f.invite(name);
- p.sendMessage("§aInvited "+name);
- invited.sendMessage("§a"+p.getName()+ " invited you to "+f.getName());
+ p.sendMessage("§aInvited "+Members.display(name));
+ invited.sendMessage("§a"+Members.name(p)+ " invited you to "+f.getName());
invited.sendMessage(InviteCommands.buttons(cmd1, f.getId(), f.getName()));
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("leave") && args.length == 1) {
@@ -968,7 +983,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return true;
}
Faction f = FactionManager.getByMember(Members.key(p));
- if(f.isInGuild(p.getName())) {
+ if(f.isInGuild(Members.key(p))) {
p.sendMessage("§cYou are in a guild, use /guild leave instead");
return true;
}
@@ -1149,7 +1164,7 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
}
pouch.change(amount*-1);
f.getBank().deposit(amount);
- f.getOrCreateMainGuild().getLedger().getHistory().addDeposit(p.getName(), amount);
+ f.getOrCreateMainGuild().getLedger().getHistory().addDeposit(Members.key(p), amount);
p.sendMessage("§e============§6[Bank Report]§e==============");
p.sendMessage(StringFormatter.formatHex("#6ab05aDeposited: #b39122"+amount+"#dbaf1dd"));
p.sendMessage(StringFormatter.formatHex("#3ce8c9New Faction Balance: #b39122"+b.getWealth()+"#dbaf1dd"));
@@ -1406,22 +1421,23 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
p.sendMessage("§a[SimpleFactions]§c Error! faction does not exist!");
return true;
}
- if(!f.getMembers().contains(args[2])) {
+ String heir = Members.find(f.getMembers(), args[2]);
+ if(heir == null) {
p.sendMessage("§cPlayer is not in the faction");
return true;
}
- if(args[2].equalsIgnoreCase(f.getLeader())) {
+ if(heir.equals(f.getLeader())) {
p.sendMessage("§cPlayer is already the leader");
return true;
}
- if(!f.canBecomeLeader(args[2])) {
+ if(!f.canBecomeLeader(heir)) {
p.sendMessage("§cPlayer is not eligible to be leader (perhaps they are a guild leader?)");
return true;
}
- f.setLeader(args[2]);
+ f.setLeader(heir);
for(Player pl : Bukkit.getOnlinePlayers()) {
if(f.getMembers().contains(Members.key(pl))) {
- pl.sendMessage("§a"+args[2]+ " is the new faction leader!");
+ pl.sendMessage("§a"+Members.display(heir)+ " is the new faction leader!");
}
}
return true;
@@ -1435,18 +1451,23 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
p.sendMessage("§a[SimpleFactions]§c Error! faction does not exist!");
return true;
}
- if(f.getMembers().contains(args[2])) {
+ String joiner = Members.findOnline(args[2]);
+ if(joiner == null) {
+ p.sendMessage("§cNo online character or character ID matches "+args[2]);
+ return true;
+ }
+ if(f.getMembers().contains(joiner)) {
p.sendMessage("§cPlayer is already in the faction");
return true;
}
- if(FactionManager.getByMember(args[2]) != null) {
+ if(FactionManager.getByMember(joiner) != null) {
p.sendMessage("§cPlayer is already in a faction");
return true;
}
- f.addMember(args[2]);;
+ f.addMember(joiner);
for(Player pl : Bukkit.getOnlinePlayers()) {
if(f.getMembers().contains(Members.key(pl))) {
- pl.sendMessage("§a"+args[2]+ " joined the faction!");
+ pl.sendMessage("§a"+Members.display(joiner)+ " joined the faction!");
}
}
return true;
@@ -1485,7 +1506,7 @@ else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("forced
}
p.sendMessage("§6"+amount+" §cwas withdrawn from the bank of "+f.getName());
for(String s : f.getMembers()) {
- Player member = Bukkit.getPlayer(s);
+ Player member = Members.player(s);
if(member != null && member.isOnline()) {
member.sendMessage("§6"+amount+" §cwas withdrawn from the faction by admins, §cmake a ticket if you believe this was a mistake");
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java
index bec38b6c..5fbc6733 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java
@@ -528,7 +528,7 @@ public void time() {
if(timer%300 == 0) {
for(Faction f : factions) {
if(f.getProvinces().size() == 0) continue;
- Player leader = Bukkit.getPlayerExact(f.getLeader());
+ Player leader = Members.player(f.getLeader());
if(leader == null) continue;
if(TitleManager.overProvinceCap(f)) {
leader.sendMessage("§cYou are over your province cap! Other nations can steal your provinces from you!");
@@ -1005,7 +1005,7 @@ public static Title usurp(Player p, Faction usurping, Faction losing) {
//Guild relocation
public static void requestRelocation(Player sender, Guild g, Faction target, int capital, String settlementName) {
- Player p = Bukkit.getPlayerExact(target.getLeader());
+ Player p = Members.player(target.getLeader());
if(p == null) {
sender.sendMessage("§cTarget faction leader is not online");
return;
@@ -1033,14 +1033,14 @@ public static void acceptRelocateRequest(Player p) {
if(reciever.getSettlementHandler().requiresFoundingName(capital)
&& (req.getSettlementName() == null || req.getSettlementName().isBlank())) {
p.sendMessage("§cA city name is required to relocate here");
- Player sp = Bukkit.getPlayerExact(sender.getLeader());
+ Player sp = Members.player(sender.getLeader());
if(sp != null && sp.isOnline()) {
sp.sendMessage("§cRelocation failed - a city name is required at the destination");
}
return;
}
double cost = sender.getRelocationCost(capital);
- Player sp = Bukkit.getPlayerExact(sender.getLeader());
+ Player sp = Members.player(sender.getLeader());
if(sender.getBank() == null || !Double.isFinite(cost) || cost < 0 || sender.getBank().getWealth() < cost) {
p.sendMessage("§c"+sender.getName()+" does not have enough funds to relocate (Cost: "+Formatter.formatDouble(cost)+"d)");
if(sp != null && sp.isOnline()) sp.sendMessage("§cYour guild does not have enough funds to relocate (Cost: "+Formatter.formatDouble(cost)+"d)");
@@ -1064,7 +1064,7 @@ public static void acceptRelocateRequest(Player p) {
}
public static void requestElevation(Player sender, Guild target) {
- Player p = Bukkit.getPlayerExact(target.getLeader());
+ Player p = Members.player(target.getLeader());
if(p == null) {
sender.sendMessage("§cTarget guild leader is not online");
return;
@@ -1105,22 +1105,22 @@ public static void acceptElevationRequest(Player p) {
}
public static void requestMovementJoin(Player sender, Movement movement, String type, Cause cause) {
- Player p = Bukkit.getPlayerExact(movement.getLeader());
+ Player p = Members.player(movement.getLeader());
if(p == null) {
sender.sendMessage("§cTarget movement leader is not online");
return;
}
- p.sendMessage(sender.getName()+" §7wants to join your movement as a "+type);
+ p.sendMessage(Members.name(sender)+" §7wants to join your movement as a "+type);
p.sendMessage("§7Type §a/faction accept §7to accept");
p.sendMessage("§7Request will time out in 60 seconds");
- sender.sendMessage("§aJoin request sent to "+movement.getLeader());
- RequestManager.addRequest(sender, p, new MovementJoinRequest(null, sender.getName(), type, movement, cause));
+ sender.sendMessage("§aJoin request sent to "+Members.display(movement.getLeader()));
+ RequestManager.addRequest(sender, p, new MovementJoinRequest(null, Members.key(sender), type, movement, cause));
}
public static void acceptMovementJoinRequest(Player p) {
if (!(RequestManager.getRequest(p) instanceof MovementJoinRequest req)) return;
String sender = req.getPlayer();
- Player sp = Bukkit.getPlayerExact(sender);
+ Player sp = Members.player(sender);
if(sp == null || !sp.isOnline()) {
p.sendMessage("§cRequest sender is not online");
return;
@@ -1130,7 +1130,7 @@ public static void acceptMovementJoinRequest(Player p) {
p.sendMessage("§cTarget faction no longer exists");
return;
}
- Movement target = f.getGovernment().getMovementByLeader(p.getName());
+ Movement target = f.getGovernment().getMovementByLeader(Members.key(p));
if(target == null) {
p.sendMessage("§cYou are not the leader of a movement");
return;
@@ -1158,7 +1158,7 @@ public static void acceptMovementJoinRequest(Player p) {
return;
}
target.joinAsForeignBacker(fac);
- p.sendMessage("§a"+sender+" has joined your movement as a "+req.getType()+"!");
+ p.sendMessage("§a"+Members.display(sender)+" has joined your movement as a "+req.getType()+"!");
return;
}
if(!target.canJoin(o, cause)) {
@@ -1175,33 +1175,34 @@ public static void acceptMovementJoinRequest(Player p) {
return;
}
target.join(o, cause);
- p.sendMessage("§a"+sender+" has joined your movement as a "+req.getType()+"!");
+ p.sendMessage("§a"+Members.display(sender)+" has joined your movement as a "+req.getType()+"!");
}
+ /** Asks the character with member key {@code targetName} to be the movement's wanted leader. */
public static void requestMovementLeaderTarget(Player sender, Movement movement, Cause cause, String targetName) {
if (sender == null || movement == null || cause == null || targetName == null) {
return;
}
- Player target = Bukkit.getPlayerExact(targetName);
+ Player target = Members.player(targetName);
if (target == null || !target.isOnline()) {
sender.sendMessage("§cThat player must be online to become the wanted leader.");
return;
}
Faction host = movement.getFaction();
if (host == null || !host.canBecomeLeader(targetName)) {
- sender.sendMessage("§c" + targetName + " cannot be the wanted leader.");
+ sender.sendMessage("§c" + Members.display(targetName) + " cannot be the wanted leader.");
return;
}
- target.sendMessage(sender.getName() + " §7wants you to be the wanted leader of their movement.");
+ target.sendMessage(Members.name(sender) + " §7wants you to be the wanted leader of their movement.");
target.sendMessage("§7Type §a/faction accept §7to accept");
target.sendMessage("§7Request will time out in 60 seconds");
- sender.sendMessage("§aLeader request sent to " + targetName);
+ sender.sendMessage("§aLeader request sent to " + Members.display(targetName));
RequestManager.addRequest(
sender,
target,
new MovementLeaderTargetRequest(
null,
- sender.getName(),
+ Members.key(sender),
movement,
cause,
targetName));
@@ -1209,7 +1210,7 @@ public static void requestMovementLeaderTarget(Player sender, Movement movement,
public static void acceptMovementLeaderTargetRequest(Player p) {
if (!(RequestManager.getRequest(p) instanceof MovementLeaderTargetRequest req)) return;
- if (!p.getName().equalsIgnoreCase(req.getProposedName())) {
+ if (!Members.is(p, req.getProposedName())) {
p.sendMessage("§cThat request is not for you.");
return;
}
@@ -1224,7 +1225,7 @@ public static void acceptMovementLeaderTargetRequest(Player p) {
p.sendMessage("§cSpecified cause no longer exists");
return;
}
- if (!cause.hasLeader() || !cause.getLeader().equalsIgnoreCase(req.getRequester())) {
+ if (!cause.hasLeader() || !cause.getLeader().equals(req.getRequester())) {
p.sendMessage("§cThe requester no longer leads that cause.");
return;
}
@@ -1236,9 +1237,9 @@ public static void acceptMovementLeaderTargetRequest(Player p) {
cause.getProposal().setTarget(req.getProposedName());
new Database().saveFaction(host);
p.sendMessage("§aYou are now the wanted leader of this movement.");
- Player requester = Bukkit.getPlayerExact(req.getRequester());
+ Player requester = Members.player(req.getRequester());
if (requester != null && requester.isOnline()) {
- requester.sendMessage("§a" + p.getName() + " accepted becoming the wanted leader.");
+ requester.sendMessage("§a" + Members.name(p) + " accepted becoming the wanted leader.");
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java b/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java
index 7bd87862..546767b3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/InviteCommands.java
@@ -38,6 +38,10 @@ public static boolean handles(String command, String[] args) {
public static boolean handle(Player p, String command, String[] args) {
boolean guild = command.equalsIgnoreCase(GUILD);
+ if (Members.key(p) == null) {
+ p.sendMessage(CommandManager.NO_CHARACTER);
+ return true;
+ }
if (args[0].equalsIgnoreCase("decline")) {
if (guild) declineGuild(p, args[1]);
else declineFaction(p, args[1]);
@@ -145,7 +149,7 @@ private static void joinGuild(Player p, String id) {
g.addMember(Members.key(p));
p.sendMessage("§aJoined " + g.getName());
g.getFaction().updatePrestige();
- tellOthers(g.getFaction(), p, "§a" + p.getName() + " joined the guild " + g.getName() + "§a!");
+ tellOthers(g.getFaction(), p, "§a" + Members.name(p) + " joined the guild " + g.getName() + "§a!");
}
private static void joinFaction(Player p, String id) {
@@ -165,7 +169,7 @@ private static void joinFaction(Player p, String id) {
f.addMember(Members.key(p));
p.sendMessage("§aJoined " + f.getName());
f.updatePrestige();
- tellOthers(f, p, "§a" + p.getName() + " joined the faction!");
+ tellOthers(f, p, "§a" + Members.name(p) + " joined the faction!");
}
private static void declineGuild(Player p, String id) {
@@ -175,7 +179,7 @@ private static void declineGuild(Player p, String id) {
return;
}
p.sendMessage("§aDeclined the invite to " + g.getName());
- tellLeader(g.getLeader(), "§c" + p.getName() + " declined your invite to " + g.getName());
+ tellLeader(g.getLeader(), "§c" + Members.name(p) + " declined your invite to " + g.getName());
}
private static void declineFaction(Player p, String id) {
@@ -185,7 +189,7 @@ private static void declineFaction(Player p, String id) {
return;
}
p.sendMessage("§aDeclined the invite to " + f.getName());
- tellLeader(f.getLeader(), "§c" + p.getName() + " declined your invite to " + f.getName());
+ tellLeader(f.getLeader(), "§c" + Members.name(p) + " declined your invite to " + f.getName());
}
private static void tellOthers(Faction f, Player joiner, String message) {
@@ -197,8 +201,6 @@ private static void tellOthers(Faction f, Player joiner, String message) {
}
private static void tellLeader(String leader, String message) {
- if (leader == null) return;
- Player online = Bukkit.getPlayerExact(leader);
- if (online != null) online.sendMessage(message);
+ Members.online(leader).ifPresent(online -> online.sendMessage(message));
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java
index 7961f6cb..753f9c7d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/PlayerManager.java
@@ -173,7 +173,7 @@ public void run() {
issuer.getLoanHandler().issueLoan(loan);
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
p.sendMessage("§aLoan taken!");
- Player lender = Bukkit.getPlayer(issuer.getLeader());
+ Player lender = Members.player(issuer.getLeader());
if(lender != null && lender.isOnline()) {
lender.sendMessage("§aYour loan has been accepted by "+borrowerGuild.getName()+"!");
lender.playSound(lender, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
@@ -190,7 +190,7 @@ public void run() {
m.setPages(completeMeta.getPages());
m.setDisplayName("§6Loan Agreement §7"+Cache.getFantasyDate(System.currentTimeMillis()));
m.setTitle("§6Loan Agreement §7"+Cache.getFantasyDate(System.currentTimeMillis()));
- m.setAuthor(issuer.getLeader()+" and "+p.getName());
+ m.setAuthor(Members.display(issuer.getLeader())+" and "+Members.name(p));
i.setItemMeta(m);
replacement.accept(i);
}
@@ -267,12 +267,12 @@ private void signContractBook(
p.sendMessage((result.ok() ? "§a" : "§c")+result.message());
if(!result.ok()) return;
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
- Player leader = Bukkit.getPlayer(company.getLeader());
+ Player leader = Members.player(company.getLeader());
if(leader != null && leader.isOnline()) {
leader.sendMessage("§a"+result.message());
leader.playSound(leader, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
}
- replaceHeldBook(replacement, signedAgreement(company, contract, p.getName()));
+ replaceHeldBook(replacement, signedAgreement(company, contract, Members.name(p)));
}
}
@@ -312,7 +312,7 @@ private ItemStack signedAgreement(
String title = "§6Mercenary Contract §7"+Cache.getFantasyDate(System.currentTimeMillis());
m.setDisplayName(title);
m.setTitle(title);
- m.setAuthor(company.getLeader()+" and "+signer);
+ m.setAuthor(Members.display(company.getLeader())+" and "+signer);
i.setItemMeta(m);
return i;
}
@@ -322,12 +322,14 @@ public void earnMoney(PlayerEarnMoneyEvent e) {
String playerName = e.getPlayer();
double paidTax = 0;
double gross = e.getAmount();
- if(FactionManager.getByMember(playerName) != null) {
- Faction f = FactionManager.getByMember(playerName);
+ // The earner's realm is the one of the character they are playing now.
+ String member = Members.ofAccountName(playerName);
+ if(FactionManager.getByMember(member) != null) {
+ Faction f = FactionManager.getByMember(member);
if(f.getTaxRate(TaxTarget.CITIZENS, null, true) > 0) {
if(f.getBank() != null) {
paidTax = f.getTaxRate(TaxTarget.CITIZENS, null, true)/100.0*gross;
- f.giveTax(playerName, paidTax);
+ f.giveTax(member, paidTax);
}
}
}
From 16290fef94494cf7b41988d8eb26460cbe7dea86 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:14:48 +0000
Subject: [PATCH 03/35] wip: migration, quarantine, waiting state
---
.../simplefactions/SimpleFactions.java | 118 ++++-
.../simplefactions/api/SimpleFactionsApi.java | 19 +
.../events/SimpleFactionsReadyEvent.java | 21 +
.../simplefactions/identity/Members.java | 18 +
.../managers/CommandManager.java | 3 +
.../migration/CharacterMigration.java | 281 ++++++++++++
.../migration/MigrationFiles.java | 56 +++
.../migration/MigrationManifest.java | 60 +++
.../simplefactions/migration/Quarantine.java | 112 +++++
.../migration/QuarantineCommands.java | 94 ++++
.../simplefactions/migration/StoreMapper.java | 420 ++++++++++++++++++
11 files changed, 1195 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/net/tfminecraft/simplefactions/api/SimpleFactionsApi.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/events/SimpleFactionsReadyEvent.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/migration/CharacterMigration.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/migration/MigrationFiles.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/migration/MigrationManifest.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/migration/Quarantine.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/migration/QuarantineCommands.java
create mode 100644 src/main/java/net/tfminecraft/simplefactions/migration/StoreMapper.java
diff --git a/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java b/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java
index 62c639a5..ce2ac6b4 100644
--- a/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java
+++ b/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java
@@ -14,6 +14,7 @@
import org.bukkit.scheduler.BukkitTask;
import net.tfminecraft.simplefactions.Cache;
+import net.tfminecraft.simplefactions.migration.CharacterMigration;
import net.tfminecraft.simplefactions.map.ProvinceGrid;
import net.tfminecraft.simplefactions.map.infra.TrackProvinceCache;
import net.tfminecraft.simplefactions.map.presence.ProvincePresenceListener;
@@ -255,18 +256,32 @@ public class SimpleFactions extends JavaPlugin{
new VehicleMaintenanceDecayTask();
private boolean startupComplete;
+ /** Set when loading began, so a failed start still releases what it took. */
+ private boolean servicesBegun;
+ /** True once the data is migrated and every service has started. */
+ private volatile boolean started;
+ private boolean startFailed;
+ private BukkitTask identityPoll;
+ private CharacterMigration.Heirs migrationHeirs = CharacterMigration.SHARED;
+ /** Every command in plugin.yml; each answers "starting up" until the plugin has started. */
+ static final java.util.List COMMANDS = java.util.List.of(
+ "faction", "guild", "warband", "battle", "raid", "war", "movement", "ledger", "company", "mercenaries");
+ private static final long IDENTITY_POLL_TICKS = 5L * 20L;
@Override
public void onEnable() {
try {
- net.tfminecraft.tlibs.character.Characters.available();
+ net.tfminecraft.tlibs.character.Characters.legacyHeirsReady();
} catch (LinkageError error) {
incompatibleCharacterApi = true;
- getLogger().severe("SimpleFactions requires TLibs 2.3.0 or newer; disabling.");
+ getLogger().severe("SimpleFactions requires TLibs 2.4.0 or newer; disabling.");
getServer().getPluginManager().disablePlugin(this);
return;
}
startupComplete = false;
+ started = false;
+ startFailed = false;
+ servicesBegun = false;
config = getConfig();
plugin = this;
FactionManager.inv = inventoryManager;
@@ -279,6 +294,85 @@ public void onEnable() {
getServer().getPluginManager().disablePlugin(this);
return;
}
+ awaitIdentity();
+ }
+
+ /**
+ * Faction data names characters, so nothing loads, ticks, listens or saves
+ * until the character provider can name every legacy account's heir.
+ * Commands answer that factions are starting up meanwhile.
+ */
+ private void awaitIdentity() {
+ for (String name : COMMANDS) {
+ org.bukkit.command.PluginCommand command = getCommand(name);
+ if (command == null) continue;
+ command.setExecutor((sender, cmd, label, args) -> {
+ sender.sendMessage("§eFactions are starting up; try again in a moment.");
+ return true;
+ });
+ command.setTabCompleter((sender, cmd, label, args) -> java.util.List.of());
+ }
+ if (identityReady()) {
+ start();
+ return;
+ }
+ getLogger().severe("Waiting for character identity (RPCharacters) before loading any faction data; "
+ + "factions stay offline until it is ready");
+ getServer().getPluginManager().registerEvents(new Listener() {
+ @EventHandler
+ public void onPluginEnable(PluginEnableEvent event) {
+ if ("RPCharacters".equalsIgnoreCase(event.getPlugin().getName()) && identityReady()) start();
+ }
+ }, this);
+ identityPoll = getServer().getScheduler().runTaskTimer(this, () -> {
+ if (identityReady()) start();
+ }, IDENTITY_POLL_TICKS, IDENTITY_POLL_TICKS);
+ }
+
+ static boolean identityReady() {
+ return net.tfminecraft.tlibs.character.Characters.available()
+ && net.tfminecraft.tlibs.character.Characters.legacyHeirsReady();
+ }
+
+ /** Whether the data is migrated and everything has started; see {@code SimpleFactionsApi.ready()}. */
+ public boolean isStarted() {
+ return started;
+ }
+
+ void useMigrationHeirs(CharacterMigration.Heirs heirs) {
+ migrationHeirs = heirs;
+ }
+
+ /** Migrates the stores if needed, then loads and starts everything. */
+ void start() {
+ if (started || startFailed) return;
+ if (identityPoll != null) {
+ identityPoll.cancel();
+ identityPoll = null;
+ }
+ try {
+ CharacterMigration.Result result =
+ new CharacterMigration(getDataFolder().toPath(), migrationHeirs, getLogger()).run();
+ if (result.migrated() > 0 || result.assignmentsApplied() > 0) {
+ getLogger().info("Character migration: " + result.migrated() + " stores migrated, "
+ + result.assignmentsApplied() + " quarantine assignments applied");
+ }
+ if (result.quarantined() > 0) {
+ getLogger().warning(result.quarantined() + " quarantined faction records need staff; see /f admin quarantine list");
+ }
+ } catch (java.io.IOException | RuntimeException error) {
+ startFailed = true;
+ getLogger().log(java.util.logging.Level.SEVERE,
+ "Cannot migrate faction data to characters; factions stay offline until this is fixed", error);
+ return;
+ }
+ startServices();
+ if (!started) return;
+ getServer().getPluginManager().callEvent(new net.tfminecraft.simplefactions.events.SimpleFactionsReadyEvent());
+ }
+
+ private void startServices() {
+ servicesBegun = true;
net.tfminecraft.simplefactions.identity.LeaderCharacters.useSharedIdentity();
net.tfminecraft.simplefactions.prestige.MemberPlaytime.useSharedIdentity();
net.tfminecraft.simplefactions.mercenary.company.MercenaryEligibility.useSharedIdentity();
@@ -388,11 +482,19 @@ public void onEnable() {
inventoryManager.start();
vehicleMaintenanceDecayTask.start();
startupComplete = true;
+ started = true;
net.tfminecraft.simplefactions.identity.LeaderCharacterListener.refresh(null);
}
@Override
public void onDisable() {
if (incompatibleCharacterApi) return;
+ if (identityPoll != null) identityPoll.cancel();
+ identityPoll = null;
+ started = false;
+ if (!servicesBegun) {
+ // Still waiting for identity: nothing was loaded, so nothing may be saved.
+ return;
+ }
if (trackRefreshTask != null) trackRefreshTask.cancel();
net.tfminecraft.simplefactions.integration.rpcharacters.chat.RpCharactersChatIntegration.unregister();
MercenaryStatService.clearAll();
@@ -686,9 +788,9 @@ public void saveVehicleFees() {
vehicleFeeStore.save();
}
- /** Remembers who a vehicle was just handed to, for the transfer fee on a later release. */
- public void recordVehicleOwner(String vehicleUuid, String playerName) {
- vehicleFeeStore.setLastOwner(vehicleUuid, playerName);
+ /** Remembers the member key of the character a vehicle was just handed to, for a later transfer fee. */
+ public void recordVehicleOwner(String vehicleUuid, String memberKey) {
+ vehicleFeeStore.setLastOwner(vehicleUuid, memberKey);
vehicleFeeStore.save();
}
@@ -703,8 +805,10 @@ public void recordVehicleOwners() {
try {
for (net.tfminecraft.vehicleframework.data.OwnedVehicleSummary vehicle
: net.tfminecraft.simplefactions.vehicles.registry.VehicleOwnershipQueries.allPersonalVehicles(vehicleRegistry)) {
- String owner = net.tfminecraft.simplefactions.vehicles.registry.VehicleOwnershipQueries
- .playerNameFromOwner(vehicle.getOwner());
+ // VehicleFramework still owns vehicles by account; the fee follows that account's character.
+ String owner = net.tfminecraft.simplefactions.identity.Members.ofAccountOrHeir(
+ net.tfminecraft.simplefactions.vehicles.registry.VehicleOwnershipQueries
+ .playerNameFromOwner(vehicle.getOwner()));
if (owner != null) {
vehicleFeeStore.setLastOwner(vehicle.getUuid(), owner);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/api/SimpleFactionsApi.java b/src/main/java/net/tfminecraft/simplefactions/api/SimpleFactionsApi.java
new file mode 100644
index 00000000..a4badfb8
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/api/SimpleFactionsApi.java
@@ -0,0 +1,19 @@
+package net.tfminecraft.simplefactions.api;
+
+import net.tfminecraft.simplefactions.SimpleFactions;
+
+/**
+ * Entry point for other plugins. Until {@link #ready()} is true, SimpleFactions
+ * is waiting for character identity or migrating, and its lookups
+ * ({@code FactionManager.getByMember} and friends) return null or empty: treat
+ * that as "unavailable", never as "no faction", and do not persist decisions
+ * based on it. {@code SimpleFactionsReadyEvent} fires when it becomes ready.
+ */
+public final class SimpleFactionsApi {
+ private SimpleFactionsApi() {}
+
+ public static boolean ready() {
+ SimpleFactions plugin = SimpleFactions.getInstance();
+ return plugin != null && plugin.isStarted();
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/events/SimpleFactionsReadyEvent.java b/src/main/java/net/tfminecraft/simplefactions/events/SimpleFactionsReadyEvent.java
new file mode 100644
index 00000000..321989b5
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/events/SimpleFactionsReadyEvent.java
@@ -0,0 +1,21 @@
+package net.tfminecraft.simplefactions.events;
+
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+/**
+ * Fired once SimpleFactions has migrated its data to characters and started.
+ * Before it, faction lookups answer nothing; see {@code SimpleFactionsApi.ready()}.
+ */
+public class SimpleFactionsReadyEvent extends Event {
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/identity/Members.java b/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
index 13486ea1..c709b7e9 100644
--- a/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
+++ b/src/main/java/net/tfminecraft/simplefactions/identity/Members.java
@@ -79,6 +79,24 @@ public static String ofAccountName(String accountName) {
return accountName == null ? null : key(onlineByName(accountName));
}
+ /**
+ * The member key standing for an account known only by name: its current
+ * character if online, else its reviewed legacy heir. Used where another
+ * plugin still records ownership by account (stage 6 moves those).
+ */
+ public static String ofAccountOrHeir(String accountName) {
+ String online = ofAccountName(accountName);
+ if (online != null || accountName == null) return online;
+ try {
+ if (Bukkit.getServer() == null) return null;
+ org.bukkit.OfflinePlayer cached = Bukkit.getOfflinePlayerIfCached(accountName);
+ if (cached == null || cached.getUniqueId() == null || !Characters.available()) return null;
+ return Characters.legacyHeir(cached.getUniqueId()).map(Members::key).orElse(null);
+ } catch (RuntimeException unavailable) {
+ return null;
+ }
+ }
+
/** The character name for a key; offline characters are read from the provider. */
public static String display(String key) {
if (key == null || key.isBlank()) return "Unknown";
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
index 6f862ca6..3713d645 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/CommandManager.java
@@ -78,6 +78,9 @@ private boolean dispatchCommand(CommandSender sender, Command cmd, String label,
return net.tfminecraft.simplefactions.guild.network.TradeGraphCommand.handle(sender, args);
if (cmd.getName().equalsIgnoreCase(cmd1) && args.length >= 1 && args[0].equalsIgnoreCase("reloadespionage"))
return EspionageCommands.reload(sender, args);
+ if(cmd.getName().equalsIgnoreCase(cmd1) && net.tfminecraft.simplefactions.migration.QuarantineCommands.matches(args)) {
+ return net.tfminecraft.simplefactions.migration.QuarantineCommands.handle(sender, args);
+ }
if(cmd.getName().equalsIgnoreCase(cmd1) && args.length >= 1 && args[0].equalsIgnoreCase("addguildbank")) {
return addGuildBank(sender, args);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/migration/CharacterMigration.java b/src/main/java/net/tfminecraft/simplefactions/migration/CharacterMigration.java
new file mode 100644
index 00000000..9c4cf1c7
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/migration/CharacterMigration.java
@@ -0,0 +1,281 @@
+package net.tfminecraft.simplefactions.migration;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.Characters;
+
+/**
+ * Turns the stored names of SimpleFactions 3 into member keys, once, store by
+ * store. Each store's original is copied to {@code migrations/backup/} with
+ * its hash and marked in progress before the new file replaces it; a store
+ * still in progress after a crash is restored from that copy and redone.
+ */
+public final class CharacterMigration {
+ public static final String MANIFEST = "migrations/character-v1.json";
+ public static final String QUARANTINE = "migrations/quarantine.json";
+ private static final String BACKUP = "migrations/backup/";
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+
+ /** Where names and accounts lead: the account behind a name and the account's reviewed heir. */
+ public interface Heirs {
+ Optional account(String name);
+
+ Optional heir(UUID account);
+ }
+
+ /** DenarEconomy's name index first, then the server's user cache; heirs from the identity provider. */
+ public static final Heirs SHARED = new Heirs() {
+ @Override
+ public Optional account(String name) {
+ try {
+ UUID id = net.tfminecraft.denareconomy.accounts.OfflineModifier.playerId(name);
+ if (id != null) return Optional.of(id);
+ } catch (RuntimeException | LinkageError unavailable) {
+ // Fall back to the user cache below.
+ }
+ OfflinePlayer cached = Bukkit.getOfflinePlayerIfCached(name);
+ return cached == null ? Optional.empty() : Optional.ofNullable(cached.getUniqueId());
+ }
+
+ @Override
+ public Optional heir(UUID account) {
+ return Characters.legacyHeir(account);
+ }
+ };
+
+ /** What a run did, for the log and for tests. */
+ public record Result(int migrated, int skipped, int quarantined, int assignmentsApplied) {}
+
+ private final Path root;
+ private final Heirs heirs;
+ private final Logger logger;
+
+ public CharacterMigration(Path root, Heirs heirs, Logger logger) {
+ this.root = root;
+ this.heirs = heirs;
+ this.logger = logger;
+ }
+
+ /** Whether every store has been migrated already. */
+ public boolean complete() throws IOException {
+ return MigrationManifest.load(root.resolve(MANIFEST)).complete;
+ }
+
+ public Result run() throws IOException {
+ Path manifestFile = root.resolve(MANIFEST);
+ MigrationManifest manifest = MigrationManifest.load(manifestFile);
+ Quarantine quarantine = Quarantine.load(root.resolve(QUARANTINE));
+ int migrated = 0;
+ int skipped = 0;
+ if (!manifest.complete) {
+ Map> names = new HashMap<>();
+ Map> accounts = new HashMap<>();
+ for (String store : stores()) {
+ MigrationManifest.Entry entry = manifest.entry(store);
+ if (entry != null && !MigrationManifest.IN_PROGRESS.equals(entry.state)) continue;
+ if (entry != null) restore(store, entry);
+ if (migrate(store, manifest, manifestFile, quarantine, names, accounts)) migrated++;
+ else skipped++;
+ }
+ manifest.complete = true;
+ manifest.save(manifestFile);
+ }
+ int applied = applyAssignments(quarantine);
+ int open = (int) quarantine.records().stream().filter(record -> record.assigned == null).count();
+ return new Result(migrated, skipped, open, applied);
+ }
+
+ /** Every store that may hold names or accounts, in a stable order. */
+ List stores() throws IOException {
+ List stores = new ArrayList<>();
+ for (String folder : List.of("Data", "Wars", "Warbands", "Battles")) {
+ Path directory = root.resolve(folder);
+ if (!Files.isDirectory(directory)) continue;
+ try (Stream files = Files.list(directory)) {
+ files.filter(file -> file.getFileName().toString().endsWith(".json") && Files.isRegularFile(file))
+ .map(file -> folder + "/" + file.getFileName()).sorted().forEach(stores::add);
+ }
+ }
+ for (String cache : List.of("Cache/logins.json", "Cache/vehicle_fees.json", "Cache/vehicles_registry.json")) {
+ if (Files.isRegularFile(root.resolve(cache))) stores.add(cache);
+ }
+ return stores;
+ }
+
+ private void restore(String store, MigrationManifest.Entry entry) throws IOException {
+ Path backup = root.resolve(entry.backup);
+ if (!Files.isRegularFile(backup) || !MigrationFiles.sha256(Files.readAllBytes(backup)).equals(entry.hash)) {
+ throw new IOException("Backup of " + store + " is missing or does not match its hash; restore it by hand");
+ }
+ MigrationFiles.write(root.resolve(store), Files.readAllBytes(backup));
+ logger.warning("Restored " + store + " from its backup to redo an interrupted migration");
+ }
+
+ private boolean migrate(String store, MigrationManifest manifest, Path manifestFile, Quarantine quarantine,
+ Map> names, Map> accounts) throws IOException {
+ Path file = root.resolve(store);
+ byte[] original = Files.readAllBytes(file);
+ String hash = MigrationFiles.sha256(original);
+ String backup = BACKUP + store;
+ MigrationFiles.write(root.resolve(backup), original);
+ manifest.mark(store, MigrationManifest.IN_PROGRESS, backup, hash);
+ manifest.save(manifestFile);
+
+ JsonElement tree;
+ try {
+ tree = JsonParser.parseString(new String(original, StandardCharsets.UTF_8));
+ } catch (JsonParseException malformed) {
+ logger.log(Level.SEVERE, "Cannot read " + store + "; it was left unchanged and is not migrated", malformed);
+ manifest.mark(store, MigrationManifest.UNREADABLE, backup, hash);
+ manifest.save(manifestFile);
+ return false;
+ }
+ StoreMapper mapper = new StoreMapper(store, heirs, names, accounts);
+ if (store.startsWith("Data/") && tree instanceof JsonObject faction) mapper.faction(faction, hash);
+ else if (store.startsWith("Wars/") && tree instanceof JsonObject war) mapper.war(war);
+ else if (store.equals("Cache/logins.json") && tree instanceof JsonObject logins) mapper.logins(logins);
+ else if (store.equals("Cache/vehicle_fees.json") && tree instanceof JsonObject fees) mapper.vehicleFees(fees);
+ else if (store.equals("Cache/vehicles_registry.json") && tree instanceof JsonArray rows) mapper.vehicleRegistry(rows);
+ // Warbands and battles hold accounts for live combat only, which stay as they are.
+
+ quarantine.merge(mapper.records());
+ quarantine.save();
+ for (String warning : mapper.warnings()) logger.warning(warning);
+ if (mapper.changed()) MigrationFiles.write(file, GSON.toJson(tree));
+ manifest.mark(store, MigrationManifest.DONE, backup, hash);
+ manifest.save(manifestFile);
+ return true;
+ }
+
+ /**
+ * Writes staff assignments into their stores: every place holding the
+ * record's token now holds the character's key. Runs before the stores
+ * are loaded, so it never races the plugin's own saves.
+ */
+ int applyAssignments(Quarantine quarantine) throws IOException {
+ int applied = 0;
+ for (Quarantine.Record record : quarantine.records()) {
+ if (record.assigned == null || record.applied) continue;
+ Path file = root.resolve(record.store);
+ if (Files.isRegularFile(file)) {
+ byte[] current = Files.readAllBytes(file);
+ JsonElement tree = JsonParser.parseString(new String(current, StandardCharsets.UTF_8));
+ if (replace(tree, record.token, record.assigned, "")) {
+ MigrationFiles.write(root.resolve(BACKUP + "assign-" + record.id + "/" + record.store), current);
+ MigrationFiles.write(file, GSON.toJson(tree));
+ }
+ }
+ record.applied = true;
+ quarantine.save();
+ applied++;
+ logger.info("Gave quarantine record " + record.id + " (" + record.original + ") to character " + record.assigned);
+ }
+ return applied;
+ }
+
+ /**
+ * Replaces every string and map key equal to {@code token}; merged entries
+ * follow the migration's rules. {@code path} is the chain of field names.
+ */
+ static boolean replace(JsonElement element, String token, String key, String path) {
+ boolean changed = false;
+ if (element instanceof JsonArray array) {
+ Set seen = new java.util.HashSet<>();
+ JsonArray rewritten = new JsonArray();
+ for (JsonElement child : array) {
+ String value = StoreMapper.string(child);
+ if (token.equals(value)) {
+ changed = true;
+ value = key;
+ child = new JsonPrimitive(key);
+ }
+ if (value != null && memberList(path) && !seen.add(value)) {
+ changed = true;
+ continue;
+ }
+ changed |= replace(child, token, key, path);
+ rewritten.add(child);
+ }
+ if (changed) {
+ while (!array.isEmpty()) array.remove(0);
+ array.addAll(rewritten);
+ }
+ } else if (element instanceof JsonObject object) {
+ Map rewritten = new LinkedHashMap<>();
+ for (Map.Entry entry : object.entrySet()) {
+ String name = entry.getKey();
+ JsonElement value = entry.getValue();
+ if (token.equals(StoreMapper.string(value))) {
+ value = new JsonPrimitive(key);
+ changed = true;
+ }
+ changed |= replace(value, token, key, path + "/" + name);
+ if (token.equals(name)) {
+ name = key;
+ changed = true;
+ }
+ JsonElement existing = rewritten.get(name);
+ rewritten.put(name, existing == null ? value : StoreMapper.merge(existing, value, mergeFor(path)));
+ }
+ if (changed) {
+ for (String old : new ArrayList<>(object.keySet())) object.remove(old);
+ rewritten.forEach(object::add);
+ }
+ }
+ return changed;
+ }
+
+ private static final Set LISTS_OF_MEMBERS = Set.of(
+ "members", "dividendEligible", "enlisted", "council members", "eligible voters", "citizens");
+ private static final Set SUMMED = Set.of(
+ "citizen taxes", "deposits today", "pendingWages", "CITIZENS", "DEPOSITS");
+
+ private static String last(String path) {
+ return path.substring(path.lastIndexOf('/') + 1);
+ }
+
+ static boolean memberList(String path) {
+ return LISTS_OF_MEMBERS.contains(last(path)) || path.contains("/election candidates/");
+ }
+
+ /** Logins keep the most recent; money and vote counts add up; anything else keeps the first. */
+ static StoreMapper.Merge mergeFor(String path) {
+ if (path.isEmpty()) return StoreMapper.Merge.MIN;
+ if (SUMMED.contains(last(path)) || path.contains("/previous votes/")) return StoreMapper.Merge.SUM;
+ return StoreMapper.Merge.FIRST;
+ }
+
+ /** The key a record would be assigned: a character ID, lower-cased. */
+ public static String characterKey(String input) {
+ UUID id = Members.characterId(input == null ? null : input.strip());
+ return id == null ? null : Members.key(id);
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/migration/MigrationFiles.java b/src/main/java/net/tfminecraft/simplefactions/migration/MigrationFiles.java
new file mode 100644
index 00000000..6e2a688c
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/migration/MigrationFiles.java
@@ -0,0 +1,56 @@
+package net.tfminecraft.simplefactions.migration;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HexFormat;
+import java.util.UUID;
+
+/** Durable file writes for the migration: synced temp file, then an atomic rename. */
+final class MigrationFiles {
+ private MigrationFiles() {}
+
+ static String sha256(byte[] bytes) {
+ try {
+ return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes));
+ } catch (NoSuchAlgorithmException impossible) {
+ throw new IllegalStateException(impossible);
+ }
+ }
+
+ static String sha256(String text) {
+ return sha256(text.getBytes(StandardCharsets.UTF_8));
+ }
+
+ static void write(Path file, String text) throws IOException {
+ write(file, text.getBytes(StandardCharsets.UTF_8));
+ }
+
+ static void write(Path file, byte[] bytes) throws IOException {
+ Path target = file.toAbsolutePath();
+ Files.createDirectories(target.getParent());
+ Path temp = target.resolveSibling(target.getFileName() + "." + UUID.randomUUID() + ".tmp");
+ try {
+ try (FileChannel channel = FileChannel.open(temp, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ while (buffer.hasRemaining()) channel.write(buffer);
+ channel.force(true);
+ }
+ try {
+ Files.move(temp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ } catch (AtomicMoveNotSupportedException unsupported) {
+ Files.move(temp, target, StandardCopyOption.REPLACE_EXISTING);
+ }
+ } finally {
+ Files.deleteIfExists(temp);
+ }
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/migration/MigrationManifest.java b/src/main/java/net/tfminecraft/simplefactions/migration/MigrationManifest.java
new file mode 100644
index 00000000..b216b632
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/migration/MigrationManifest.java
@@ -0,0 +1,60 @@
+package net.tfminecraft.simplefactions.migration;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+
+/**
+ * {@code migrations/character-v1.json}: per store, whether it has been
+ * migrated, with the backup of the original and that original's hash.
+ */
+final class MigrationManifest {
+ static final String IN_PROGRESS = "in-progress";
+ static final String DONE = "done";
+ static final String UNREADABLE = "unreadable";
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+
+ static final class Entry {
+ String state;
+ String backup;
+ String hash;
+ }
+
+ String migration = "character-v1";
+ boolean complete;
+ Map stores = new TreeMap<>();
+
+ static MigrationManifest load(Path file) throws IOException {
+ if (!Files.exists(file)) return new MigrationManifest();
+ try {
+ MigrationManifest manifest = GSON.fromJson(Files.readString(file, StandardCharsets.UTF_8), MigrationManifest.class);
+ if (manifest == null || manifest.stores == null) throw new JsonParseException("empty manifest");
+ return manifest;
+ } catch (JsonParseException malformed) {
+ throw new IOException("Unreadable migration manifest " + file, malformed);
+ }
+ }
+
+ void save(Path file) throws IOException {
+ MigrationFiles.write(file, GSON.toJson(this));
+ }
+
+ Entry entry(String store) {
+ return stores.get(store);
+ }
+
+ void mark(String store, String state, String backup, String hash) {
+ Entry entry = new Entry();
+ entry.state = state;
+ entry.backup = backup;
+ entry.hash = hash;
+ stores.put(store, entry);
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/migration/Quarantine.java b/src/main/java/net/tfminecraft/simplefactions/migration/Quarantine.java
new file mode 100644
index 00000000..94c9353f
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/migration/Quarantine.java
@@ -0,0 +1,112 @@
+package net.tfminecraft.simplefactions.migration;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * {@code migrations/quarantine.json}: everything the migration could not give
+ * to a character. A record keeps the original name (or account), every place
+ * it was found with the complete original JSON there, and the token that
+ * holds its seat in the migrated store until staff assign a character.
+ */
+public final class Quarantine {
+ /** A name that was never a known account, or an account with no reviewed heir. */
+ public static final String NAME = "name";
+ /** An account UUID with no reviewed heir; the store keeps the account UUID. */
+ public static final String ACCOUNT = "account";
+
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+
+ /** One place a quarantined name or account was found. */
+ public static final class Occurrence {
+ public String path;
+ public JsonElement fragment;
+
+ Occurrence(String path, JsonElement fragment) {
+ this.path = path;
+ this.fragment = fragment;
+ }
+ }
+
+ /** Everything one unmapped name or account held in one store. */
+ public static final class Record {
+ public String id;
+ public String store;
+ public String kind;
+ public String original;
+ public String token;
+ public List occurrences = new ArrayList<>();
+ /** Character ID chosen by staff, or null while unresolved. */
+ public String assigned;
+ /** True once the assignment has been written into the store. */
+ public boolean applied;
+
+ /** Whether it held a realm or guild leader's seat. */
+ public boolean holdsLeader() {
+ for (Occurrence occurrence : occurrences) {
+ if (occurrence.path.equals("$.leader") || occurrence.path.matches("\\$\\.guilds\\[[^]]*]\\.leader")) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final Path file;
+ private final Map records = new LinkedHashMap<>();
+
+ private Quarantine(Path file) {
+ this.file = file;
+ }
+
+ public static Quarantine load(Path file) throws IOException {
+ Quarantine quarantine = new Quarantine(file);
+ if (!Files.exists(file)) return quarantine;
+ try {
+ List loaded = GSON.fromJson(Files.readString(file, StandardCharsets.UTF_8),
+ new TypeToken>() {}.getType());
+ if (loaded == null) throw new JsonParseException("empty quarantine");
+ for (Record record : loaded) quarantine.records.put(record.id, record);
+ return quarantine;
+ } catch (JsonParseException malformed) {
+ throw new IOException("Unreadable quarantine " + file, malformed);
+ }
+ }
+
+ public void save() throws IOException {
+ MigrationFiles.write(file, GSON.toJson(new ArrayList<>(records.values())));
+ }
+
+ public Collection records() {
+ return records.values();
+ }
+
+ public Record get(String id) {
+ return id == null ? null : records.get(id);
+ }
+
+ /** Adds the records of one store; a redone store replaces its earlier records with the same IDs. */
+ void merge(Collection found) {
+ for (Record record : found) {
+ Record existing = records.get(record.id);
+ if (existing != null) {
+ record.assigned = existing.assigned;
+ record.applied = existing.applied;
+ }
+ records.put(record.id, record);
+ }
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/migration/QuarantineCommands.java b/src/main/java/net/tfminecraft/simplefactions/migration/QuarantineCommands.java
new file mode 100644
index 00000000..fdf9d308
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/migration/QuarantineCommands.java
@@ -0,0 +1,94 @@
+package net.tfminecraft.simplefactions.migration;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.logging.Level;
+
+import org.bukkit.command.CommandSender;
+
+import net.tfminecraft.simplefactions.SimpleFactions;
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.simplefactions.utils.Permissions;
+import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.tlibs.character.Characters;
+
+/** {@code /f admin quarantine list} and {@code /f admin quarantine assign }. */
+public final class QuarantineCommands {
+ private QuarantineCommands() {}
+
+ public static boolean matches(String[] args) {
+ return args.length >= 2 && args[0].equalsIgnoreCase("admin") && args[1].equalsIgnoreCase("quarantine");
+ }
+
+ public static boolean handle(CommandSender sender, String[] args) {
+ if (!Permissions.isAdmin(sender)) {
+ sender.sendMessage("§a[SimpleFactions]§c You do not have access to this command");
+ return true;
+ }
+ Path root = SimpleFactions.getInstance().getDataFolder().toPath();
+ Quarantine quarantine;
+ try {
+ quarantine = Quarantine.load(root.resolve(CharacterMigration.QUARANTINE));
+ } catch (IOException unreadable) {
+ sender.sendMessage("§cCannot read the quarantine: " + unreadable.getMessage());
+ return true;
+ }
+ if (args.length == 3 && args[2].equalsIgnoreCase("list")) {
+ list(sender, quarantine);
+ } else if (args.length == 5 && args[2].equalsIgnoreCase("assign")) {
+ assign(sender, quarantine, args[3], args[4]);
+ } else {
+ sender.sendMessage("§cUsage: /faction admin quarantine list | assign ");
+ }
+ return true;
+ }
+
+ private static void list(CommandSender sender, Quarantine quarantine) {
+ if (quarantine.records().isEmpty()) {
+ sender.sendMessage("§aNothing is quarantined.");
+ return;
+ }
+ sender.sendMessage("§eQuarantined faction records:");
+ for (Quarantine.Record record : quarantine.records()) {
+ String status = record.applied ? "§agiven to " + record.assigned
+ : record.assigned != null ? "§6assigned to " + record.assigned + ", applies at the next restart"
+ : "§cunresolved";
+ sender.sendMessage("§f" + record.id + " §7" + record.kind + " §f" + record.original + " §7in " + record.store
+ + " (" + record.occurrences.size() + " place" + (record.occurrences.size() == 1 ? "" : "s")
+ + (record.holdsLeader() ? ", holds a leader's seat" : "") + ") " + status);
+ }
+ }
+
+ private static void assign(CommandSender sender, Quarantine quarantine, String id, String character) {
+ Quarantine.Record record = quarantine.get(id);
+ if (record == null) {
+ sender.sendMessage("§cNo quarantine record " + id);
+ return;
+ }
+ if (record.applied) {
+ sender.sendMessage("§cRecord " + id + " was already given to " + record.assigned);
+ return;
+ }
+ String key = CharacterMigration.characterKey(character);
+ if (key == null) {
+ sender.sendMessage("§cThat is not a character ID");
+ return;
+ }
+ CharacterInfo info = Characters.info(Members.characterId(key)).orElse(null);
+ if (info == null) {
+ sender.sendMessage("§cNo character with ID " + key);
+ return;
+ }
+ record.assigned = key;
+ try {
+ quarantine.save();
+ } catch (IOException failure) {
+ SimpleFactions.getInstance().getLogger().log(Level.SEVERE, "Cannot save the quarantine", failure);
+ sender.sendMessage("§cCannot save the quarantine; nothing changed");
+ record.assigned = null;
+ return;
+ }
+ sender.sendMessage("§aRecord " + id + " (" + record.original + ") goes to " + Members.display(key)
+ + " at the next restart, before factions load.");
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/migration/StoreMapper.java b/src/main/java/net/tfminecraft/simplefactions/migration/StoreMapper.java
new file mode 100644
index 00000000..9fe6c111
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/migration/StoreMapper.java
@@ -0,0 +1,420 @@
+package net.tfminecraft.simplefactions.migration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterRef;
+
+/**
+ * Rewrites the name and account fields of one store into member keys. Names
+ * resolve to an account, then to that account's reviewed heir; a name or
+ * account that does not is replaced by a quarantine token and recorded.
+ */
+final class StoreMapper {
+ enum Merge { SUM, FIRST, MIN }
+
+ private final String store;
+ private final CharacterMigration.Heirs heirs;
+ private final Map> names;
+ private final Map> accounts;
+ private final Map records = new LinkedHashMap<>();
+ private final List warnings = new ArrayList<>();
+ private boolean changed;
+
+ StoreMapper(String store, CharacterMigration.Heirs heirs,
+ Map> names, Map> accounts) {
+ this.store = store;
+ this.heirs = heirs;
+ this.names = names;
+ this.accounts = accounts;
+ }
+
+ Collection records() {
+ return records.values();
+ }
+
+ List warnings() {
+ return warnings;
+ }
+
+ boolean changed() {
+ return changed;
+ }
+
+ /* ---------- stores ---------- */
+
+ /** {@code Data/.json}. */
+ void faction(JsonObject root, String originalHash) {
+ scalar(root, "leader", "$");
+ forgetLeaderCharacter(root);
+ String factionId = string(root, "id");
+ for (JsonObject guild : objects(root, "guilds")) {
+ String path = "$.guilds[" + string(guild, "id") + "]";
+ if (!guild.has("incarnation")) {
+ guild.addProperty("incarnation", UUID.nameUUIDFromBytes(("incarnation:" + store + ":"
+ + string(guild, "id") + ":" + originalHash).getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString());
+ changed = true;
+ }
+ scalar(guild, "leader", path);
+ forgetLeaderCharacter(guild);
+ list(guild, "members", path);
+ list(guild, "dividendEligible", path);
+ keys(guild, "citizen taxes", Merge.SUM, path);
+ keys(guild, "deposits today", Merge.SUM, path);
+ for (String ledger : List.of("ledger last day", "ledger lifetime")) {
+ JsonObject sources = object(guild, ledger);
+ if (sources == null) continue;
+ for (String source : List.of("CITIZENS", "DEPOSITS")) {
+ keys(sources, source, Merge.SUM, path + "." + ledger);
+ }
+ }
+ JsonObject company = object(guild, "company");
+ if (company != null) {
+ String companyPath = path + ".company";
+ list(company, "enlisted", companyPath);
+ keys(company, "activeWageOverrides", Merge.FIRST, companyPath);
+ keys(company, "peacetimeWageOverrides", Merge.FIRST, companyPath);
+ keys(company, "pendingWages", Merge.SUM, companyPath);
+ }
+ }
+ oneGuildPerMember(root, factionId);
+ JsonObject government = object(root, "governmentData");
+ if (government != null) government(government, "$.governmentData");
+ }
+
+ /** {@code Wars/war_.json}. */
+ void war(JsonObject root) {
+ scalar(root, "civilWarWantedLeaderName", "$");
+ int index = 0;
+ for (JsonObject move : objects(root, "civilWarMemberMoves")) {
+ scalar(move, "player", "$.civilWarMemberMoves[" + index++ + "]");
+ }
+ }
+
+ /** {@code Cache/logins.json}: days offline by name; merged names keep the most recent login. */
+ void logins(JsonObject root) {
+ rekey(root, Merge.MIN, "$");
+ }
+
+ /** {@code Cache/vehicle_fees.json}: last owners by name, paid builds by payer account. */
+ void vehicleFees(JsonObject root) {
+ JsonObject owners = object(root, "lastOwners");
+ if (owners != null) {
+ for (String vehicle : new ArrayList<>(owners.keySet())) {
+ scalar(owners, vehicle, "$.lastOwners");
+ }
+ }
+ int index = 0;
+ for (JsonObject build : objects(root, "paidBuilds")) {
+ accountField(build, "payerUuid", "$.paidBuilds[" + index++ + "]");
+ }
+ }
+
+ /** {@code Cache/vehicles_registry.json}: a list of rows with the owning account. */
+ void vehicleRegistry(JsonArray rows) {
+ for (int index = 0; index < rows.size(); index++) {
+ if (rows.get(index) instanceof JsonObject row) {
+ accountField(row, "playerUuid", "$[" + index + "]");
+ }
+ }
+ }
+
+ /* ---------- government ---------- */
+
+ private void government(JsonObject government, String path) {
+ list(government, "council members", path);
+ list(government, "eligible voters", path);
+ JsonObject candidates = object(government, "election candidates");
+ if (candidates != null) {
+ for (String type : candidates.keySet()) list(candidates, type, path + ".election candidates");
+ }
+ JsonObject votes = object(government, "election votes");
+ if (votes != null) {
+ for (String type : votes.keySet()) votes(votes, type, path + ".election votes");
+ }
+ JsonObject previous = object(government, "previous votes");
+ if (previous != null) {
+ for (String type : previous.keySet()) keys(previous, type, Merge.SUM, path + ".previous votes");
+ }
+ proposals(government, path);
+ for (JsonObject movement : objects(government, "movements")) {
+ String movementPath = path + ".movements[" + string(movement, "id") + "]";
+ scalar(movement, "leader", movementPath);
+ pool(object(movement, "supporters"), movementPath + ".supporters");
+ int index = 0;
+ for (JsonObject cause : objects(movement, "causes")) {
+ String causePath = movementPath + ".causes[" + index++ + "]";
+ scalar(cause, "leader", causePath);
+ pool(object(cause, "members"), causePath + ".members");
+ JsonObject proposal = object(cause, "proposal");
+ if (proposal == null) continue;
+ scalar(proposal, "proposer", causePath + ".proposal");
+ if ("CHANGE_LEADER".equals(string(proposal, "action key"))) {
+ scalar(proposal, "target", causePath + ".proposal");
+ }
+ }
+ }
+ }
+
+ private void pool(JsonObject pool, String path) {
+ if (pool != null) list(pool, "citizens", path);
+ }
+
+ /** Council proposals are {@code proposer:kind:...}; a leader change ends with the wanted leader. */
+ private void proposals(JsonObject government, String path) {
+ if (!(government.get("proposals") instanceof JsonArray proposals)) return;
+ JsonArray rewritten = new JsonArray();
+ for (int index = 0; index < proposals.size(); index++) {
+ JsonElement element = proposals.get(index);
+ String text = string(element);
+ int separator = text == null ? -1 : text.indexOf(':');
+ if (separator <= 0) {
+ rewritten.add(element);
+ continue;
+ }
+ String entryPath = path + ".proposals[" + index + "]";
+ String rest = text.substring(separator + 1);
+ String proposer = name(text.substring(0, separator), entryPath, element);
+ String changeLeader = "action:CHANGE_LEADER:";
+ if (rest.startsWith(changeLeader) && rest.length() > changeLeader.length()) {
+ rest = changeLeader + name(rest.substring(changeLeader.length()), entryPath, element);
+ }
+ rewritten.add(proposer + ":" + rest);
+ }
+ government.add("proposals", rewritten);
+ }
+
+ /** Ballots by voter: a merged voter keeps the first ballot; candidates are names too. */
+ private void votes(JsonObject parent, String field, String path) {
+ JsonObject ballots = object(parent, field);
+ if (ballots == null) return;
+ String ballotsPath = path + "." + field;
+ JsonObject rewritten = new JsonObject();
+ for (Map.Entry ballot : ballots.entrySet()) {
+ JsonObject fragment = new JsonObject();
+ fragment.add(ballot.getKey(), ballot.getValue());
+ String voter = name(ballot.getKey(), ballotsPath, fragment);
+ if (rewritten.has(voter)) continue;
+ String candidate = string(ballot.getValue());
+ rewritten.add(voter, candidate == null ? ballot.getValue()
+ : new JsonPrimitive(name(candidate, ballotsPath, fragment)));
+ }
+ parent.add(field, rewritten);
+ }
+
+ /* ---------- field helpers ---------- */
+
+ private void scalar(JsonObject object, String field, String path) {
+ String value = string(object.get(field));
+ if (value == null) return;
+ object.addProperty(field, name(value, path + "." + field, object.get(field)));
+ }
+
+ private void list(JsonObject object, String field, String path) {
+ if (!(object.get(field) instanceof JsonArray values)) return;
+ String listPath = path + "." + field;
+ Set seen = new LinkedHashSet<>();
+ JsonArray rewritten = new JsonArray();
+ for (int index = 0; index < values.size(); index++) {
+ JsonElement element = values.get(index);
+ String value = string(element);
+ if (value == null) {
+ rewritten.add(element);
+ continue;
+ }
+ String key = name(value, listPath + "[" + index + "]", element);
+ if (seen.add(key)) rewritten.add(key);
+ else changed = true;
+ }
+ object.add(field, rewritten);
+ }
+
+ private void keys(JsonObject parent, String field, Merge merge, String path) {
+ if (parent.get(field) instanceof JsonObject map) rekey(map, merge, path + "." + field);
+ }
+
+ private void rekey(JsonObject map, Merge merge, String path) {
+ Map rewritten = new LinkedHashMap<>();
+ for (Map.Entry entry : map.entrySet()) {
+ JsonObject fragment = new JsonObject();
+ fragment.add(entry.getKey(), entry.getValue());
+ String key = name(entry.getKey(), path + "[" + entry.getKey() + "]", fragment);
+ JsonElement existing = rewritten.get(key);
+ rewritten.put(key, existing == null ? entry.getValue() : merge(existing, entry.getValue(), merge));
+ }
+ for (String old : new ArrayList<>(map.keySet())) map.remove(old);
+ rewritten.forEach(map::add);
+ }
+
+ static JsonElement merge(JsonElement kept, JsonElement added, Merge merge) {
+ if (merge == Merge.FIRST || !isNumber(kept) || !isNumber(added)) return kept;
+ double a = kept.getAsDouble();
+ double b = added.getAsDouble();
+ if (merge == Merge.MIN) return new JsonPrimitive(Math.min(a, b));
+ boolean whole = kept.getAsString().matches("-?\\d+") && added.getAsString().matches("-?\\d+");
+ return whole ? new JsonPrimitive(kept.getAsLong() + added.getAsLong()) : new JsonPrimitive(a + b);
+ }
+
+ private static boolean isNumber(JsonElement element) {
+ return element instanceof JsonPrimitive primitive && primitive.isNumber();
+ }
+
+ private void accountField(JsonObject row, String field, String path) {
+ String value = string(row.get(field));
+ if (value == null) return;
+ row.addProperty(field, account(value, path + "." + field, row));
+ }
+
+ /**
+ * A realm's members belong to one guild. Merged names can leave a key in
+ * several guilds; it stays where it leads, else in the first ordinary
+ * guild, else in the realm's own guild.
+ */
+ private void oneGuildPerMember(JsonObject root, String factionId) {
+ List guilds = objects(root, "guilds");
+ JsonObject base = base(guilds, factionId);
+ List leadersFirst = new ArrayList<>();
+ if (base != null) leadersFirst.add(base);
+ for (JsonObject guild : guilds) if (guild != base) leadersFirst.add(guild);
+ Map home = new LinkedHashMap<>();
+ for (JsonObject guild : leadersFirst) {
+ String leader = string(guild, "leader");
+ if (leader != null) home.putIfAbsent(leader, guild);
+ }
+ for (JsonObject guild : guilds) {
+ if (guild == base) continue;
+ for (String member : strings(guild, "members")) home.putIfAbsent(member, guild);
+ }
+ for (JsonObject guild : guilds) {
+ if (!(guild.get("members") instanceof JsonArray members)) continue;
+ JsonArray kept = new JsonArray();
+ for (JsonElement element : members) {
+ String member = string(element);
+ JsonObject owner = member == null ? null : home.get(member);
+ if (owner == null || owner == guild) {
+ kept.add(element);
+ } else {
+ changed = true;
+ warnings.add(store + ": " + member + " was in several guilds; kept in "
+ + string(owner, "id") + ", removed from " + string(guild, "id"));
+ }
+ }
+ guild.add("members", kept);
+ }
+ }
+
+ private static JsonObject base(List guilds, String factionId) {
+ for (JsonObject guild : guilds) {
+ if (factionId != null && factionId.equals(string(guild, "id"))) return guild;
+ }
+ return null;
+ }
+
+ private void forgetLeaderCharacter(JsonObject object) {
+ // Leader names are re-read from the leader's character when the plugin starts.
+ if (object.remove("leader character") != null) changed = true;
+ if (object.remove("leader character of") != null) changed = true;
+ }
+
+ /* ---------- resolution ---------- */
+
+ /** The member key for a stored name, or the quarantine token holding its place. */
+ String name(String raw, String path, JsonElement fragment) {
+ if (Members.isQuarantined(raw)) return raw;
+ UUID asKey = Members.characterId(raw);
+ if (asKey != null) return Members.key(asKey);
+ Optional key = names.computeIfAbsent(raw.toLowerCase(Locale.ROOT), lower -> heirs.account(raw)
+ .flatMap(this::heirKey));
+ changed = true;
+ if (key.isPresent()) return key.get();
+ Quarantine.Record record = record(Quarantine.NAME, raw, null);
+ record.occurrences.add(new Quarantine.Occurrence(path, fragment.deepCopy()));
+ return record.token;
+ }
+
+ /** The heir character's ID for an account field; unmapped accounts stay and are recorded. */
+ String account(String raw, String path, JsonElement fragment) {
+ UUID account;
+ try {
+ account = UUID.fromString(raw);
+ } catch (IllegalArgumentException notAnAccount) {
+ return raw;
+ }
+ Optional key = heirKey(account);
+ if (key.isPresent()) {
+ changed = true;
+ return key.get();
+ }
+ Quarantine.Record record = record(Quarantine.ACCOUNT, raw.toLowerCase(Locale.ROOT), raw);
+ record.occurrences.add(new Quarantine.Occurrence(path, fragment.deepCopy()));
+ return raw;
+ }
+
+ private Optional heirKey(UUID account) {
+ return accounts.computeIfAbsent(account, id -> heirs.heir(id).map(CharacterRef::characterId).map(Members::key));
+ }
+
+ private Quarantine.Record record(String kind, String original, String token) {
+ String id = "q" + MigrationFiles.sha256(store + "\n" + kind + "\n" + original.toLowerCase(Locale.ROOT)).substring(0, 12);
+ return records.computeIfAbsent(id, ignored -> {
+ Quarantine.Record record = new Quarantine.Record();
+ record.id = id;
+ record.store = store;
+ record.kind = kind;
+ record.original = original;
+ record.token = token != null ? token : Members.QUARANTINED + id;
+ return record;
+ });
+ }
+
+ /* ---------- JSON helpers ---------- */
+
+ private static String string(JsonObject object, String field) {
+ return object == null ? null : string(object.get(field));
+ }
+
+ static String string(JsonElement element) {
+ return element instanceof JsonPrimitive primitive && primitive.isString() ? primitive.getAsString() : null;
+ }
+
+ private static JsonObject object(JsonObject parent, String field) {
+ return parent.get(field) instanceof JsonObject object ? object : null;
+ }
+
+ private static List objects(JsonObject parent, String field) {
+ List objects = new ArrayList<>();
+ if (parent.get(field) instanceof JsonArray array) {
+ array.forEach(element -> {
+ if (element instanceof JsonObject object) objects.add(object);
+ });
+ }
+ return objects;
+ }
+
+ private static List strings(JsonObject parent, String field) {
+ List values = new ArrayList<>();
+ if (parent.get(field) instanceof JsonArray array) {
+ Consumer add = element -> {
+ String value = string(element);
+ if (value != null) values.add(value);
+ };
+ array.forEach(add);
+ }
+ return values;
+ }
+}
From 700cbab068e3a412f78f651937f614c8037e9b35 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:22:33 +0000
Subject: [PATCH 04/35] refactor: key government, guilds and factions by
character
Council seats, refusals, election candidates and votes, session voters,
movement and cause leaders, pool citizens and proposal proposers and
targets are member keys: matched exactly, shown by character name and
turned into players through Members. Movement admin commands take
character names with spaces.
Co-Authored-By: Claude Opus 5.5
---
.../simplefactions/government/Council.java | 13 +-
.../simplefactions/government/Government.java | 24 +-
.../government/election/Election.java | 24 +-
.../government/movement/Movement.java | 34 +--
.../government/movement/MovementJoinCopy.java | 21 +-
.../government/movement/Pool.java | 3 +-
.../movement/admin/MovementAdminService.java | 35 ++-
.../admin/MovementCommandManager.java | 15 +-
.../movement/admin/MovementTabCompletion.java | 40 ++--
.../government/movement/cause/Cause.java | 16 +-
.../government/proposal/Proposal.java | 8 +-
.../government/session/Session.java | 19 +-
.../government/stability/StateStability.java | 2 +-
.../simplefactions/guild/Guild.java | 2 +-
.../simplefactions/guild/loans/LoanBook.java | 6 +-
.../managers/SessionManager.java | 6 +-
.../simplefactions/objects/Faction.java | 10 +-
.../CharacterKeyedPoliticsTest.java | 224 ++++++++++++++++++
.../government/GovernmentCoverageTest.java | 6 +-
.../GovernmentProcessesCoverageTest.java | 5 +-
.../movement/MovementCoverageTest.java | 3 +-
.../admin/MovementAdminServiceTest.java | 16 +-
.../guild/GuildCoverageTest.java | 26 +-
.../guild/GuildDeleteCommandTest.java | 16 +-
.../simplefactions/guild/GuildInviteTest.java | 21 +-
.../guild/SharedAccountRivalGuildsTest.java | 76 ++++++
.../simplefactions/guild/TestCharacters.java | 71 ++++++
...mainConfigurationBoundaryCoverageTest.java | 8 +-
.../objects/FactionCoverageTest.java | 17 +-
.../objects/FactionInviteTest.java | 10 +-
30 files changed, 613 insertions(+), 164 deletions(-)
create mode 100644 src/test/java/net/tfminecraft/simplefactions/government/CharacterKeyedPoliticsTest.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/guild/SharedAccountRivalGuildsTest.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/guild/TestCharacters.java
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/Council.java b/src/main/java/net/tfminecraft/simplefactions/government/Council.java
index 21bbc33c..c570fec3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/Council.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/Council.java
@@ -5,9 +5,7 @@
import java.util.List;
import java.util.Set;
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.utils.Wealth;
import net.tfminecraft.simplefactions.enums.Rules;
@@ -132,7 +130,7 @@ public boolean toggleRefuse(String name) {
public boolean canBeMember(String name, boolean ignoreSize, boolean ignoreRefuses) {
if(members.contains(name)) return false;
- if(f.getLeader().equalsIgnoreCase(name)) return false;
+ if(f.getLeader().equals(name)) return false;
if(!ignoreSize && getCurrentSize() >= getMaxSize()) return false;
if(!ignoreRefuses && refuses.contains(name)) return false;
if(f.getOrCreateMainGuild().isMember(name)) return true;
@@ -149,7 +147,7 @@ public boolean canRemainMember(String name) {
if (refuses.contains(name)) {
return false;
}
- if (f.getLeader().equalsIgnoreCase(name)) {
+ if (f.getLeader().equals(name)) {
return false;
}
if (f.getOrCreateMainGuild().isMember(name)) {
@@ -251,9 +249,8 @@ public boolean hasEnoughValidVoters() {
int total = getEligibleVoters().size();
int validCount = 0;
- for (String voterName : getEligibleVoters()) {
- Player voterPlayer = Bukkit.getPlayer(voterName);
- if ((voterPlayer != null && voterPlayer.isOnline()) || isDummyAccount(voterName)) {
+ for (String voter : getEligibleVoters()) {
+ if (Members.isOnline(voter) || isDummyAccount(voter)) {
validCount++;
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/Government.java b/src/main/java/net/tfminecraft/simplefactions/government/Government.java
index a5d11a1c..249e6e5f 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/Government.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/Government.java
@@ -179,7 +179,7 @@ public void endMovement(Movement movement) {
public Movement getMovementByLeader(String leader) {
for (Movement movement : movements) {
- if (movement.getLeader() != null && movement.getLeader().equalsIgnoreCase(leader)) {
+ if (movement.getLeader() != null && movement.getLeader().equals(leader)) {
return movement;
}
}
@@ -363,14 +363,14 @@ public boolean isCouncilMember(Player p) {
return p != null && isCouncilMember(Members.key(p));
}
- /** Name-based so offline checks (mercenary loyalty, contract signing) can use it too. */
- public boolean isCouncilMember(String name) {
- if(name == null) return false;
- return council.isMember(name) || f.getLeader().equalsIgnoreCase(name);
+ /** Key-based so offline checks (mercenary loyalty, contract signing) can use it too. */
+ public boolean isCouncilMember(String key) {
+ if(key == null) return false;
+ return council.isMember(key) || key.equals(f.getLeader());
}
public boolean canProposeOrStartMovement(Player p) {
- String name = p.getName();
+ String name = Members.key(p);
if(isCouncilMember(p)) {
return council.getProposalHandler().canPropose(name);
}
@@ -378,7 +378,7 @@ public boolean canProposeOrStartMovement(Player p) {
if(guild != null) {
if(guild.getFaction().getId().equalsIgnoreCase(f.getId())) {
if(guild.isBase()) return true; //Member of the base guild in the faction
- if(guild.getLeader().equalsIgnoreCase(name))return true; //Guild leader of a guild in the faction
+ if(guild.isLeader(name))return true; //Guild leader of a guild in the faction
}
}
Faction faction = FactionManager.getByLeader(name);
@@ -393,8 +393,8 @@ public Faction getFaction() {
}
public boolean canPropose(Player p) {
- String name = p.getName();
- if(name.equalsIgnoreCase(f.getLeader())) return true;
+ String name = Members.key(p);
+ if(Members.is(p, f.getLeader())) return true;
if(council.canPropose(name)) return true;
return false;
}
@@ -413,11 +413,11 @@ public boolean canProposePolitical(Player p, Action action) {
if(warEnd && !CouncilPeaceQueries.isParticipatingInAny(f)) {
return false;
}
- String name = p.getName();
+ String name = Members.key(p);
if(warEnd && canPropose(p)) {
return true;
}
- if(name.equalsIgnoreCase(f.getLeader())) return false;
+ if(Members.is(p, f.getLeader())) return false;
if(council.isMember(name)) return false;
if(action == Action.SNAP_ELECTIONS && !hasElections()) return false;
Member relation = f.getRelationToFaction(name);
@@ -432,7 +432,7 @@ public boolean canProposePolitical(Player p, Action action) {
case GUILD_MEMBER:
if(action == Action.NATIONHOOD) return false; //only leaders can demand that
case GUILD_LEADER:
- if(action == Action.NATIONHOOD && !FactionManager.getGuildByMember(Members.key(p)).canBeElevated(null)) return false;
+ if(action == Action.NATIONHOOD && !FactionManager.getGuildByMember(name).canBeElevated(null)) return false;
if(!PoliticalActionLoader.getByAction(action).allowGuilds()) return false;
break;
case VASSAL_LEADER:
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java b/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java
index 1810b933..1a99b50e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/election/Election.java
@@ -150,7 +150,7 @@ public void clearCandidates() {
public int getVotes(Candidate c, String player) {
int count = 0;
for(Map.Entry entry : votes.get(c).entrySet()) {
- if(entry.getValue().equalsIgnoreCase(player)) count++;
+ if(entry.getValue().equals(player)) count++;
}
return count;
}
@@ -182,7 +182,8 @@ public List getWinners(Candidate c) {
result.sort((a, b) -> {
int cmp = Integer.compare(voteCounts.get(b), voteCounts.get(a));
if (cmp != 0) return cmp;
- return a.compareToIgnoreCase(b);
+ cmp = Members.display(a).compareToIgnoreCase(Members.display(b));
+ return cmp != 0 ? cmp : a.compareTo(b);
});
return result;
@@ -201,27 +202,28 @@ public boolean isCandiate(Candidate c, String player) {
}
public void applyReasons(List lore, Candidate type, Player p) {
- if(isCandiate(type, p.getName())) return;
- if(!canBeCandidate(type, p.getName())) {
+ String key = Members.key(p);
+ if(isCandiate(type, key)) return;
+ if(!canBeCandidate(type, key)) {
lore.add(StringFormatter.formatHex("§cYou cannot apply for this position:"));
if(active) {
lore.add(StringFormatter.formatHex("§7- #ba7872Election is in the voting phase"));
}
if(type == Candidate.LEADER) {
- if(!gov.getFaction().isMember(Members.key(p))) {
+ if(!gov.getFaction().isMember(key)) {
lore.add(StringFormatter.formatHex("§7- #ba7872You must be a member of the faction to be Leader"));
}
- Guild g = gov.getFaction().getGuild(Members.key(p));
- if (g != null && !g.isBase() && g.isLeader(Members.key(p))) {
+ Guild g = gov.getFaction().getGuild(key);
+ if (g != null && !g.isBase() && g.isLeader(key)) {
lore.add(StringFormatter.formatHex(
"§7- #ba7872Guild leaders cannot be faction leaders"
));
}
- if(otherCandidateExists(Candidate.LEADER, p.getName())) {
+ if(otherCandidateExists(Candidate.LEADER, key)) {
lore.add(StringFormatter.formatHex("§7- #ba7872Your guild already has a Leader candidate"));
}
} else if(type == Candidate.COUNCIL) {
- if(otherCandidateExists(Candidate.COUNCIL, p.getName())) {
+ if(otherCandidateExists(Candidate.COUNCIL, key)) {
lore.add(StringFormatter.formatHex("§7- #ba7872Your guild already has a Council candidate"));
}
}
@@ -301,7 +303,7 @@ public void removeCandidate(Candidate c, String candidate) {
Map voteMap = votes.get(c);
voteMap.entrySet().removeIf(entry ->
- entry.getValue().equalsIgnoreCase(candidate)
+ entry.getValue().equals(candidate)
);
}
@@ -310,7 +312,7 @@ public boolean otherCandidateExists(Candidate type, String player) {
if(g == null) return false;
if(g.isBase()) return false;
for(String candidate : candidates.get(type)) {
- if(candidate.equalsIgnoreCase(player)) continue;
+ if(candidate.equals(player)) continue;
Guild cg = gov.getFaction().getGuildHandler().getGuildByMember(candidate);
if(cg == null) continue;
if(cg.isBase()) continue;
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java
index fbc9f3fc..ca5fcf5c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/Movement.java
@@ -232,19 +232,19 @@ public void checkSupporters() {
supporters.getFactions().removeIf(faction -> !canFactionBeSupporter(faction, false));
}
- public boolean canCitizenBeSupporter(String playerName, boolean feedback) {
- return citizenSupporterBlockReason(playerName, false) == null;
+ public boolean canCitizenBeSupporter(String member, boolean feedback) {
+ return citizenSupporterBlockReason(member, false) == null;
}
- public String citizenSupporterBlockReason(String playerName, boolean staff) {
- Member relation = f.getRelationToFaction(playerName);
+ public String citizenSupporterBlockReason(String member, boolean staff) {
+ Member relation = f.getRelationToFaction(member);
if (relation != Member.MEMBER) {
- return MovementJoinCopy.citizenMustBeMember(staff, playerName, f);
+ return MovementJoinCopy.citizenMustBeMember(staff, member, f);
}
for (Movement otherMovement : f.getGovernment().getMovements()) {
if (otherMovement.getId().equals(this.id)) continue;
- if (otherMovement.isMember(playerName)) {
- return MovementJoinCopy.citizenAlreadySupportingOther(staff, playerName, f);
+ if (otherMovement.isMember(member)) {
+ return MovementJoinCopy.citizenAlreadySupportingOther(staff, member, f);
}
}
return null;
@@ -400,12 +400,12 @@ public boolean hasLeader() {
}
public boolean isLeader(String player) {
- return leader != null && leader.equalsIgnoreCase(player);
+ return leader != null && leader.equals(player);
}
public boolean canBeLeader(String player) {
for(Cause cause : causes) {
- if(cause.getLeader() != null && cause.getLeader().equalsIgnoreCase(player)) {
+ if(cause.getLeader() != null && cause.getLeader().equals(player)) {
return true;
}
}
@@ -422,7 +422,7 @@ public List getCauses() {
public Cause getCauseByLeader(String leaderName) {
for (Cause cause : causes) {
- if (cause.getLeader() != null && cause.getLeader().equalsIgnoreCase(leaderName)) {
+ if (cause.getLeader() != null && cause.getLeader().equals(leaderName)) {
return cause;
}
}
@@ -588,20 +588,22 @@ private double getTotalTradePower() {
}
public Object getJoiningAs(Player p) {
- Member relation = f.getRelationToFaction(Members.key(p));
+ String key = Members.key(p);
+ Member relation = f.getRelationToFaction(key);
if (relation == Member.GUILD_LEADER || relation == Member.GUILD_MEMBER) {
- return FactionManager.getGuildByMember(Members.key(p));
+ return FactionManager.getGuildByMember(key);
} else if (relation == Member.VASSAL_LEADER) {
- return FactionManager.getByMember(Members.key(p));
+ return FactionManager.getByMember(key);
} else if (relation == Member.MEMBER) {
- return p.getName();
+ return key;
}
return null;
}
public boolean quickJoinCheck(Player p) {
- if(isMember(Members.key(p))) return false;
- Member relation = f.getRelationToFaction(Members.key(p));
+ String key = Members.key(p);
+ if(isMember(key)) return false;
+ Member relation = f.getRelationToFaction(key);
switch(relation) {
case FOREIGNER:
case GUILD_MEMBER:
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/MovementJoinCopy.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/MovementJoinCopy.java
index e7592145..773cac86 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/MovementJoinCopy.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/MovementJoinCopy.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.government.movement;
import net.tfminecraft.simplefactions.guild.Guild;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.war.civilwar.CivilWarCopy;
@@ -25,18 +26,18 @@ public static String guildId(Guild guild) {
return guild.getId();
}
- public static String citizenMustBeMember(boolean staff, String playerName, Faction host) {
+ public static String citizenMustBeMember(boolean staff, String member, Faction host) {
return pick(
staff,
"§cYou must be a member of the movement's faction to support it.",
- "§c" + playerName + " is not a citizen of " + factionId(host) + ".");
+ "§c" + Members.display(member) + " is not a citizen of " + factionId(host) + ".");
}
- public static String citizenAlreadySupportingOther(boolean staff, String playerName, Faction host) {
+ public static String citizenAlreadySupportingOther(boolean staff, String member, Faction host) {
return pick(
staff,
"§cYou are already supporting another movement in this faction.",
- "§c" + playerName + " is already supporting another movement in " + factionId(host) + ".");
+ "§c" + Members.display(member) + " is already supporting another movement in " + factionId(host) + ".");
}
public static String oneProvinceHostGuild(boolean staff, Guild guild) {
@@ -116,25 +117,25 @@ public static String backerAlreadyBackingOther(boolean staff, Faction joining, F
"§c" + factionId(joining) + " is already backing another movement in " + factionId(host) + ".");
}
- public static String causeCitizenMustBeMember(boolean staff, String playerName, Faction host) {
+ public static String causeCitizenMustBeMember(boolean staff, String member, Faction host) {
return pick(
staff,
"§cYou must be a member of the movement's faction to join it.",
- "§c" + playerName + " is not a citizen of " + factionId(host) + ".");
+ "§c" + Members.display(member) + " is not a citizen of " + factionId(host) + ".");
}
- public static String causeCitizensNotAllowed(boolean staff, String playerName) {
+ public static String causeCitizensNotAllowed(boolean staff, String member) {
return pick(
staff,
"§cCitizens are not allowed to join this movement as members.",
- "§cCitizens cannot join this cause (blocked for " + playerName + ").");
+ "§cCitizens cannot join this cause (blocked for " + Members.display(member) + ").");
}
- public static String alreadyCauseMember(boolean staff, String playerName) {
+ public static String alreadyCauseMember(boolean staff, String member) {
return pick(
staff,
"§cYou are already a member of this movement.",
- "§c" + playerName + " is already a member of this movement.");
+ "§c" + Members.display(member) + " is already a member of this movement.");
}
public static String causeGuildsNotAllowed(boolean staff, Guild guild) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/Pool.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/Pool.java
index 33674376..4b7d79e4 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/Pool.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/Pool.java
@@ -4,6 +4,7 @@
import java.util.List;
import net.tfminecraft.simplefactions.guild.Guild;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter;
@@ -70,7 +71,7 @@ public List getAllMembers() {
public List getFormattedList() {
List formattedList = new ArrayList<>();
for (String citizen : citizens) {
- formattedList.add(StringFormatter.formatHex(citizen + " #77d1a3(Citizen)"));
+ formattedList.add(StringFormatter.formatHex(Members.display(citizen) + " #77d1a3(Citizen)"));
}
for (Guild guild : guilds) {
formattedList.add(StringFormatter.formatHex(guild.getName() + " #d1b83b(Guild)"));
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminService.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminService.java
index ffe15a6d..5e06068c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminService.java
@@ -5,6 +5,7 @@
import net.tfminecraft.simplefactions.database.Database;
import net.tfminecraft.simplefactions.guild.Guild;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.managers.LogManager;
import net.tfminecraft.simplefactions.objects.Faction;
@@ -140,12 +141,14 @@ public static Result target(String movementId, String causeIndex, String player)
return new Result(false, TARGET_USAGE);
}
Faction host = movement.getFaction();
- if (host == null || !host.canBecomeLeader(player)) {
+ String key = host == null ? null : memberKey(host.getMembers(), player);
+ if (key == null || !host.canBecomeLeader(key)) {
return new Result(false, "§c" + player + " cannot be the wanted leader.");
}
- cause.getProposal().setTarget(player);
+ cause.getProposal().setTarget(key);
persist(movement);
- return new Result(true, "§aSet wanted leader of " + movement.getId() + " cause " + causeIndex + " to " + player + ".");
+ return new Result(true, "§aSet wanted leader of " + movement.getId() + " cause " + causeIndex + " to "
+ + Members.display(key) + ".");
}
private static String missingTargetReason(Movement movement) {
@@ -178,7 +181,7 @@ private static Result mutateMember(
return new Result(false, "§cUnknown cause index: " + causeIndex);
}
}
- Object obj = resolveMember(memberType, targetId);
+ Object obj = resolveMember(movement, cause, memberType, targetId);
if (obj == null) {
return new Result(false, "§cUnknown " + memberType + ": " + targetId);
}
@@ -272,18 +275,32 @@ private static Cause resolveCause(Movement movement, String indexArg) {
}
}
- private static Object resolveMember(String type, String target) {
+ private static Object resolveMember(Movement movement, Cause cause, String type, String target) {
if (type == null || target == null || target.isBlank()) {
return null;
}
return switch (type.toLowerCase(java.util.Locale.ROOT)) {
- case "citizen" -> target;
+ case "citizen" -> memberKey(citizenCandidates(movement, cause), target);
case "guild" -> FactionManager.getGuildByString(target);
case "vassal" -> FactionManager.getByString(target);
default -> null;
};
}
+ /** Current pool citizens plus the host's members, so staff can remove a citizen who has since left. */
+ private static List citizenCandidates(Movement movement, Cause cause) {
+ Pool pool = cause == null ? movement.getSupporters() : cause.getPool();
+ List keys = new ArrayList<>(pool.getCitizens());
+ if (movement.getFaction() != null) keys.addAll(movement.getFaction().getCompleteMemberList());
+ return keys;
+ }
+
+ /** A member key typed as-is, or found by Minecraft or character name. */
+ static String memberKey(List keys, String input) {
+ if (keys.contains(input)) return input;
+ return Members.find(keys, input);
+ }
+
private static boolean isPresent(Movement movement, Cause cause, Object obj) {
Pool pool = cause == null ? movement.getSupporters() : cause.getPool();
if (obj instanceof String citizen) {
@@ -296,7 +313,7 @@ private static boolean isPresent(Movement movement, Cause cause, Object obj) {
}
private static String formatListLine(Faction host, Movement movement) {
- String leader = movement.getLeader() == null ? "None" : movement.getLeader();
+ String leader = movement.getLeader() == null ? "None" : Members.display(movement.getLeader());
String phase = movement.getPhase() == null ? "-" : movement.getPhase().name();
int supporters = movement.getSupporters() == null ? 0 : movement.getSupporters().getAllMembers().size();
int backers = movement.getForeignBackers() == null ? 0 : movement.getForeignBackers().size();
@@ -348,7 +365,7 @@ public static List hostCitizenNames(Movement movement) {
if (host == null || host.getOrCreateMainGuild() == null || host.getOrCreateMainGuild().getMembers() == null) {
return List.of();
}
- return new ArrayList<>(host.getOrCreateMainGuild().getMembers());
+ return Members.display(host.getOrCreateMainGuild().getMembers());
}
public static List hostGuildIds(Movement movement) {
@@ -407,7 +424,7 @@ public static List wantedLeaderNames(Movement movement) {
}
for (String member : host.getMembers()) {
if (host.canBecomeLeader(member)) {
- names.add(member);
+ names.add(Members.display(member));
}
}
return names;
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementCommandManager.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementCommandManager.java
index 918684d2..0cc8ffc5 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementCommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementCommandManager.java
@@ -66,7 +66,7 @@ private boolean handleTarget(Player player, String[] args) {
player.sendMessage(MovementAdminService.TARGET_USAGE);
return true;
}
- MovementAdminService.Result result = MovementAdminService.target(args[2], args[3], args[4]);
+ MovementAdminService.Result result = MovementAdminService.target(args[2], args[3], tail(args, 4));
player.sendMessage(result.message());
return true;
}
@@ -94,16 +94,16 @@ private boolean handleMutate(Player player, boolean joining, String[] args) {
return true;
}
result = joining
- ? MovementAdminService.joinSupporter(movementId, args[4], args[5])
- : MovementAdminService.leaveSupporter(movementId, args[4], args[5]);
+ ? MovementAdminService.joinSupporter(movementId, args[4], tail(args, 5))
+ : MovementAdminService.leaveSupporter(movementId, args[4], tail(args, 5));
} else if (slot.equals("cause")) {
if (args.length < 7) {
player.sendMessage(usage);
return true;
}
result = joining
- ? MovementAdminService.joinCause(movementId, args[4], args[5], args[6])
- : MovementAdminService.leaveCause(movementId, args[4], args[5], args[6]);
+ ? MovementAdminService.joinCause(movementId, args[4], args[5], tail(args, 6))
+ : MovementAdminService.leaveCause(movementId, args[4], args[5], tail(args, 6));
} else {
player.sendMessage(usage);
return true;
@@ -111,4 +111,9 @@ private boolean handleMutate(Player player, boolean joining, String[] args) {
player.sendMessage(result.message());
return true;
}
+
+ /** The rest of the arguments, so character names may contain spaces. */
+ private static String tail(String[] args, int from) {
+ return String.join(" ", java.util.Arrays.copyOfRange(args, from, args.length));
+ }
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementTabCompletion.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementTabCompletion.java
index a1ba2ac0..28beb23c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementTabCompletion.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/admin/MovementTabCompletion.java
@@ -7,6 +7,7 @@
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.utils.Permissions;
import net.tfminecraft.simplefactions.government.movement.Movement;
@@ -42,10 +43,7 @@ public List onTabComplete(CommandSender sender, Command command, String
if (args.length == 4) {
return prefixList(args[3], MovementAdminService.causeIndices(movement));
}
- if (args.length == 5) {
- return prefixList(args[4], MovementAdminService.wantedLeaderNames(movement));
- }
- return List.of();
+ return names(args, 4, MovementAdminService.wantedLeaderNames(movement));
}
if (action.equals("demands")) {
if (args.length == 3) {
@@ -86,10 +84,7 @@ public List onTabComplete(CommandSender sender, Command command, String
if (args.length == 5) {
return prefix(args[4], "citizen", "guild", "vassal");
}
- if (args.length == 6) {
- return prefixList(args[5], targets(movement, args[4]));
- }
- return List.of();
+ return targets(movement, args, 5);
}
if (slot.equals("cause")) {
if (args.length == 5) {
@@ -98,22 +93,35 @@ public List onTabComplete(CommandSender sender, Command command, String
if (args.length == 6) {
return prefix(args[5], "citizen", "guild", "vassal");
}
- if (args.length == 7) {
- return prefixList(args[6], targets(movement, args[5]));
- }
+ return targets(movement, args, 6);
}
return List.of();
}
- private static List targets(Movement movement, String memberType) {
- return switch (memberType.toLowerCase(java.util.Locale.ROOT)) {
- case "citizen" -> MovementAdminService.hostCitizenNames(movement);
- case "guild" -> MovementAdminService.hostGuildIds(movement);
- case "vassal" -> MovementAdminService.hostSubjectIds(movement);
+ /** Completes the target at {@code from}; citizen names may span several words. */
+ private static List targets(Movement movement, String[] args, int from) {
+ String memberType = args[from - 1].toLowerCase(java.util.Locale.ROOT);
+ if (memberType.equals("citizen")) {
+ return names(args, from, MovementAdminService.hostCitizenNames(movement));
+ }
+ if (args.length != from + 1) {
+ return List.of();
+ }
+ return switch (memberType) {
+ case "guild" -> prefixList(args[from], MovementAdminService.hostGuildIds(movement));
+ case "vassal" -> prefixList(args[from], MovementAdminService.hostSubjectIds(movement));
default -> List.of();
};
}
+ /** Completes a character name that starts at {@code from} and may contain spaces. */
+ private static List names(String[] args, int from, List names) {
+ String[] tail = new String[args.length - from + 1];
+ tail[0] = "";
+ System.arraycopy(args, from, tail, 1, args.length - from);
+ return Members.completions(names, tail);
+ }
+
private static List prefix(String typed, String... candidates) {
List completions = new ArrayList<>();
String lower = typed == null ? "" : typed.toLowerCase(java.util.Locale.ROOT);
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/movement/cause/Cause.java b/src/main/java/net/tfminecraft/simplefactions/government/movement/cause/Cause.java
index 6fc239d1..00c16149 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/movement/cause/Cause.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/movement/cause/Cause.java
@@ -129,20 +129,20 @@ public void checkMembers(Pool pool) {
}
}
- public boolean canMemberJoin(String playerName, boolean checkExisting, boolean feedback) {
- return memberBlockReason(playerName, checkExisting, false) == null;
+ public boolean canMemberJoin(String member, boolean checkExisting, boolean feedback) {
+ return memberBlockReason(member, checkExisting, false) == null;
}
- public String memberBlockReason(String playerName, boolean checkExisting, boolean staff) {
- Member relation = movement.getFaction().getRelationToFaction(playerName);
+ public String memberBlockReason(String member, boolean checkExisting, boolean staff) {
+ Member relation = movement.getFaction().getRelationToFaction(member);
if (relation != Member.MEMBER) {
- return MovementJoinCopy.causeCitizenMustBeMember(staff, playerName, movement.getFaction());
+ return MovementJoinCopy.causeCitizenMustBeMember(staff, member, movement.getFaction());
}
if (!proposal.getPoliticalAction().allowCitizens()) {
- return MovementJoinCopy.causeCitizensNotAllowed(staff, playerName);
+ return MovementJoinCopy.causeCitizensNotAllowed(staff, member);
}
- if (checkExisting && movement.isMember(playerName)) {
- return MovementJoinCopy.alreadyCauseMember(staff, playerName);
+ if (checkExisting && movement.isMember(member)) {
+ return MovementJoinCopy.alreadyCauseMember(staff, member);
}
return null;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java b/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java
index 60c31fac..ebb41a6c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java
@@ -4,6 +4,8 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.entity.Player;
+
+import net.tfminecraft.simplefactions.identity.Members;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.Material;
import java.util.ArrayList;
@@ -171,7 +173,7 @@ public ItemStack getAsBook(Player p) {
: isFeeProposal() ? "Fee Proposal" : "Political Action Proposal";
if (title.length() > 32) title = title.substring(0, 32);
meta.setTitle(title);
- meta.setAuthor(proposer != null ? proposer : "Unknown");
+ meta.setAuthor(Members.display(proposer));
Faction f = gov != null ? gov.getFaction() : null;
@@ -179,7 +181,7 @@ public ItemStack getAsBook(Player p) {
List first = new ArrayList<>();
first.add(StringFormatter.formatHex("#93c9a7" + title));
first.add("");
- first.add(StringFormatter.formatHex("#85c265Proposed by: #c2bea7" + proposer));
+ first.add(StringFormatter.formatHex("#85c265Proposed by: #c2bea7" + Members.display(proposer)));
if (isLawProposal() && law != null) {
LawGroup group = f != null ? f.getLawHandler().getGroup(law.getGroup()) : null;
@@ -228,7 +230,7 @@ public ItemStack getAsBook(Player p) {
first.add(StringFormatter.formatHex("#b8ae61Action: #c2bea7"+action.getDisplay()));
switch(action) {
case CHANGE_LEADER:
- first.add(StringFormatter.formatHex("#d4c9aeTarget: "+ (hasTarget() ? "#51e0a2"+ target : "#a3462cNo target")));
+ first.add(StringFormatter.formatHex("#d4c9aeTarget: "+ (hasTarget() ? "#51e0a2"+ Members.display(target) : "#a3462cNo target")));
break;
case WHITE_PEACE:
case SURRENDER:
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/session/Session.java b/src/main/java/net/tfminecraft/simplefactions/government/session/Session.java
index 37011281..26a6bb20 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/session/Session.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/session/Session.java
@@ -6,7 +6,6 @@
import java.util.HashSet;
import java.util.Set;
-import org.bukkit.Bukkit;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.block.Block;
@@ -14,6 +13,7 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.government.Council;
import net.tfminecraft.simplefactions.government.proposal.Proposal;
@@ -32,7 +32,7 @@ public class Session {
private Map proposals = new LinkedHashMap<>();
private final Map decidedVoteCounts = new HashMap<>();
private SessionHologram hologram;
- private Map currentVotes = new HashMap<>(); // Player name -> Vote
+ private Map currentVotes = new HashMap<>(); // member key -> Vote
private Set eligibleVoters = new HashSet<>(); // Council members + leader
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@@ -80,7 +80,7 @@ private void initializeVoting() {
eligibleVoters.clear();
// Add leader
- eligibleVoters.add(leader.getName());
+ eligibleVoters.add(Members.key(leader));
// Add council members
for (String member : c.getMembers()) {
@@ -89,18 +89,17 @@ private void initializeVoting() {
// Auto-abstain offline members
for (String member : eligibleVoters) {
- Player memberPlayer = Bukkit.getPlayer(member);
- if (memberPlayer == null || !memberPlayer.isOnline()) {
+ if (!Members.isOnline(member)) {
currentVotes.put(member, Vote.ABSTAIN);
}
}
}
- public boolean recordVote(String playerName, Vote vote) {
- if (!started || terminated || getCurrentProposal() == null || !eligibleVoters.contains(playerName)) {
+ public boolean recordVote(String voter, Vote vote) {
+ if (!started || terminated || getCurrentProposal() == null || !eligibleVoters.contains(voter)) {
return false;
}
- currentVotes.put(playerName, vote);
+ currentVotes.put(voter, vote);
updateHologram();
// Check if all votes are cast
@@ -213,7 +212,7 @@ public void end() {
if (terminated) return;
endSession();
// Create session report
- SessionReport report = new SessionReport(leader.getName(), f);
+ SessionReport report = new SessionReport(Members.name(leader), f);
// Gather all results
for(Proposal p : proposals.keySet()) {
@@ -240,7 +239,7 @@ public void end() {
// Send title to all online eligible voters
for (String voterName : eligibleVoters) {
- org.bukkit.entity.Player voter = Bukkit.getPlayer(voterName);
+ Player voter = Members.player(voterName);
if (voter != null && voter.isOnline()) {
voter.sendTitle("§aSession Completed!", "§7View the report in your inventory", 20, 60, 20);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/government/stability/StateStability.java b/src/main/java/net/tfminecraft/simplefactions/government/stability/StateStability.java
index 1cbbe463..b44d3a3e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/government/stability/StateStability.java
+++ b/src/main/java/net/tfminecraft/simplefactions/government/stability/StateStability.java
@@ -81,7 +81,7 @@ private static boolean seated(Faction faction, Guild richest) {
if (faction.getGovernment().getCouncilMembers() == null || richest.getMembers() == null) return false;
for (String member : richest.getMembers()) {
for (String seat : faction.getGovernment().getCouncilMembers()) {
- if (member != null && member.equalsIgnoreCase(seat)) return true;
+ if (member != null && member.equals(seat)) return true;
}
}
return false;
diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java b/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
index 7d934ced..8af07128 100644
--- a/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
+++ b/src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
@@ -390,7 +390,7 @@ public void dummify(Player p) {
}
dummy = dummy+x;
members.set(i, dummy);
- p.sendMessage("§a"+dummy+" replaced "+member);
+ p.sendMessage("§a"+dummy+" replaced "+Members.display(member));
if(isLeader(member)) {
p.sendMessage("§a"+dummy+" became leader");
if(isBase()) host.setLeader(dummy);
diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/loans/LoanBook.java b/src/main/java/net/tfminecraft/simplefactions/guild/loans/LoanBook.java
index fb75d588..6da0bf38 100644
--- a/src/main/java/net/tfminecraft/simplefactions/guild/loans/LoanBook.java
+++ b/src/main/java/net/tfminecraft/simplefactions/guild/loans/LoanBook.java
@@ -7,6 +7,8 @@
import org.bukkit.Material;
import org.bukkit.entity.Player;
+
+import net.tfminecraft.simplefactions.identity.Members;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.persistence.PersistentDataType;
@@ -77,12 +79,12 @@ public static ItemStack getLoanBook(Loan loan) {
"\n" + //
"Issuer:\n" + //
"-------------------\n" + //
- loan.getIssuer().getLeader()+"\n" + //
+ Members.display(loan.getIssuer().getLeader())+"\n" + //
"\n" + //
"-------------------\n" + //
"Loan Taker:\n" + //
"\n" + //
- (loan.getBorrower() != null ? loan.getBorrower().getLeader() : "[SIGN BOOK TO ACCEPT]") + "\n" + //
+ (loan.getBorrower() != null ? Members.display(loan.getBorrower().getLeader()) : "[SIGN BOOK TO ACCEPT]") + "\n" + //
"-------------------\n" + //
"This contract is binding upon signing.");
meta.getPersistentDataContainer().set(Keys.STRING_KEY, PersistentDataType.STRING, loan.getIssuer().getId());
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java
index 9f1c0818..b27f89be 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/SessionManager.java
@@ -142,9 +142,9 @@ public void run() {
private void recordChatVote(Player player, Vote vote) {
if (!player.isOnline()) return;
- String playerName = player.getName();
+ String key = Members.key(player);
- Faction faction = FactionManager.getByMember(playerName);
+ Faction faction = FactionManager.getByMember(key);
if (faction == null) return;
Council council = faction.getGovernment().getCouncil();
@@ -162,7 +162,7 @@ private void recordChatVote(Player player, Vote vote) {
}
}
- if (session.recordVote(playerName, vote)) {
+ if (session.recordVote(key, vote)) {
player.sendMessage("§aYour vote (" + vote.getDisplay() + ") has been recorded!");
Sound sound = switch (vote) {
case YAY -> Sound.BLOCK_NOTE_BLOCK_CHIME;
diff --git a/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java b/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java
index 3dbec89e..7276a5f1 100644
--- a/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java
+++ b/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java
@@ -32,6 +32,7 @@
import net.tfminecraft.simplefactions.diplomacy.DiplomacyHandler;
import net.tfminecraft.simplefactions.diplomacy.Relation;
import net.tfminecraft.simplefactions.guild.Guild;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.loaders.GuildLoader;
import net.tfminecraft.simplefactions.loaders.RankLoader;
import net.tfminecraft.simplefactions.loaders.TierLoader;
@@ -693,11 +694,11 @@ public boolean isLeader(String member) {
}
/** Whether the player's current character leads this realm. */
public boolean isLeader(org.bukkit.entity.Player player) {
- return isLeader(net.tfminecraft.simplefactions.identity.Members.key(player));
+ return isLeader(Members.key(player));
}
/** Whether the player's current character belongs to this realm. */
public boolean isMember(org.bukkit.entity.Player player) {
- return isMember(net.tfminecraft.simplefactions.identity.Members.key(player));
+ return isMember(Members.key(player));
}
public boolean canBecomeLeader(String member) {
if(isLeader(member)) return false;
@@ -1013,7 +1014,7 @@ public void updateTier() {
temp = getHighestTitle().getTier();
}
if(tier == null || !tier.getId().equalsIgnoreCase(temp.getId())) tier = new Tier(temp, -1);
- Player p = Bukkit.getPlayerExact(leader);
+ Player p = Members.player(leader);
for(Faction subject : RelationManager.getSubjects(this)) {
if(subject.getTier().getTier() > tier.getTier()) {
RelationManager.endVassalage(subject, this, false);
@@ -1425,8 +1426,7 @@ public void provinceCap() {
public int numOnline() {
int count = 0;
for(String m : guildHandler.getAllMembers()){
- Player p = Bukkit.getPlayerExact(m);
- if(p != null && p.isOnline()) count++;
+ if(Members.isOnline(m)) count++;
}
return count;
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/government/CharacterKeyedPoliticsTest.java b/src/test/java/net/tfminecraft/simplefactions/government/CharacterKeyedPoliticsTest.java
new file mode 100644
index 00000000..73416a84
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/government/CharacterKeyedPoliticsTest.java
@@ -0,0 +1,224 @@
+package net.tfminecraft.simplefactions.government;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.bukkit.Material;
+import org.bukkit.command.Command;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.BookMeta;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import net.tfminecraft.simplefactions.government.election.Candidate;
+import net.tfminecraft.simplefactions.government.election.Election;
+import net.tfminecraft.simplefactions.government.movement.Action;
+import net.tfminecraft.simplefactions.government.movement.Movement;
+import net.tfminecraft.simplefactions.government.movement.MovementJoinCopy;
+import net.tfminecraft.simplefactions.government.movement.PoliticalAction;
+import net.tfminecraft.simplefactions.government.movement.admin.MovementAdminService;
+import net.tfminecraft.simplefactions.government.movement.admin.MovementTabCompletion;
+import net.tfminecraft.simplefactions.government.proposal.Proposal;
+import net.tfminecraft.simplefactions.guild.TestCharacters;
+import net.tfminecraft.simplefactions.objects.Faction;
+import net.tfminecraft.simplefactions.testsupport.FactionDomainFixture;
+import net.tfminecraft.simplefactions.testsupport.PersistenceFilesFixture;
+
+/** Government, elections and movements store character keys and show character names. */
+class CharacterKeyedPoliticsTest {
+ private PersistenceFilesFixture disk;
+ private FactionDomainFixture fixture;
+ private TestCharacters characters;
+ private String ruler;
+ private String ash;
+ private String zed;
+ private Faction realm;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ disk = new PersistenceFilesFixture();
+ java.nio.file.Files.createDirectories(disk.root.resolve("Data"));
+ fixture = new FactionDomainFixture();
+ characters = TestCharacters.install();
+ ruler = characters.character(UUID.randomUUID(), "Queen Ilsa");
+ ash = characters.character(UUID.randomUUID(), "Ash Vale");
+ zed = characters.character(UUID.randomUUID(), "Zed Moor");
+ realm = fixture.saved("realm", ruler);
+ realm.addMember(ash);
+ realm.addMember(zed);
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ try {
+ fixture.close();
+ } finally {
+ disk.close();
+ }
+ }
+
+ private Player playing(String account, String key) {
+ Player player = fixture.player(account);
+ characters.play(player, key);
+ return player;
+ }
+
+ @Test
+ void theRulerActsThroughTheirCurrentCharacterOnly() {
+ UUID account = UUID.randomUUID();
+ String alt = characters.character(account, "Other Self");
+ Player player = playing("RulerAccount", ruler);
+ Government government = realm.getGovernment();
+ assertTrue(realm.isLeader(player));
+ assertTrue(government.canPropose(player));
+ assertTrue(government.isCouncilMember(player));
+ assertFalse(government.canProposePolitical(player, Action.CHANGE_LEADER));
+
+ characters.play(player, alt);
+ assertFalse(realm.isLeader(player));
+ assertFalse(government.canPropose(player));
+ assertFalse(government.isCouncilMember(player));
+ assertFalse(government.canProposeOrStartMovement(player));
+
+ characters.play(player, null);
+ assertFalse(government.canPropose(player));
+ assertFalse(government.canProposeOrStartMovement(player));
+ assertFalse(government.canProposePolitical(player, Action.CHANGE_LEADER));
+ }
+
+ @Test
+ void councilQuorumCountsCharactersBeingPlayed() {
+ Council council = realm.getGovernment().getCouncil();
+ council.addMemberForce(ash);
+ council.addMemberForce(zed);
+ Player account = playing("Account", ruler);
+ assertFalse(council.hasEnoughValidVoters());
+ playing("AshAccount", ash);
+ assertFalse(council.hasEnoughValidVoters());
+ playing("ZedAccount", zed);
+ assertTrue(council.hasEnoughValidVoters());
+ characters.play(account, null);
+ assertFalse(council.hasEnoughValidVoters());
+ assertTrue(council.canRemainMember(ash));
+ assertFalse(council.canBeMember(ruler, true, true));
+ }
+
+ @Test
+ void electionTiesAreOrderedByCharacterName() {
+ Election election = realm.getGovernment().getElection();
+ election.restoreFromData(
+ Map.of("LEADER", new ArrayList<>(List.of(zed, ash))),
+ Map.of("LEADER", Map.of(ruler, zed)),
+ Map.of("LEADER", Map.of(zed, 2, ash, 2)),
+ List.of(ruler));
+ assertEquals(List.of(ash, zed), election.getWinners(Candidate.LEADER));
+ assertEquals(1, election.getVotes(Candidate.LEADER, zed));
+ election.removeCandidate(Candidate.LEADER, zed);
+ assertEquals(0, election.getVotes(Candidate.LEADER, zed));
+
+ List lore = new ArrayList<>();
+ election.applyReasons(lore, Candidate.LEADER, playing("Outsider", null));
+ assertFalse(lore.isEmpty());
+ lore.clear();
+ election.applyReasons(lore, Candidate.LEADER, playing("ZedAccount", zed));
+ assertTrue(lore.isEmpty());
+ }
+
+ @Test
+ void movementsStoreCitizensByKeyAndShowTheirNames() {
+ Proposal proposal = new Proposal(ash, realm.getGovernment());
+ proposal.setPoliticalActionProposal(new PoliticalAction(Action.CHANGE_LEADER));
+ realm.getGovernment().startMovement(ash, proposal);
+ Movement movement = realm.getGovernment().getMovementByLeader(ash);
+ assertTrue(movement.isLeader(ash));
+ assertTrue(movement.canBeLeader(ash));
+ assertEquals(movement.getCauses().getFirst(), movement.getCauseByLeader(ash));
+
+ Player zedPlayer = playing("ZedAccount", zed);
+ assertTrue(movement.quickJoinCheck(zedPlayer));
+ assertEquals(zed, movement.getJoiningAs(zedPlayer));
+ assertNull(movement.getJoiningAs(playing("Stranger", null)));
+ movement.join(movement.getJoiningAs(zedPlayer), null);
+ assertTrue(movement.isMember(zed));
+ assertFalse(movement.quickJoinCheck(zedPlayer));
+ assertTrue(String.join("\n", movement.getSupporters().getFormattedList()).contains("Zed Moor"));
+ assertTrue(MovementJoinCopy.alreadyCauseMember(true, zed).contains("Zed Moor"));
+
+ proposal.setTarget(zed);
+ String book = pages(proposal.getAsBook());
+ assertTrue(book.contains("Proposed by: Ash Vale"));
+ assertTrue(book.contains("Target: Zed Moor"));
+ }
+
+ @Test
+ void staffPickCitizensAndWantedLeadersByCharacterName() {
+ Proposal proposal = new Proposal(ash, realm.getGovernment());
+ proposal.setPoliticalActionProposal(new PoliticalAction(Action.CHANGE_LEADER));
+ realm.getGovernment().startMovement(ash, proposal);
+ Movement movement = realm.getGovernment().getMovementByLeader(ash);
+
+ assertTrue(MovementAdminService.listLines().getFirst().contains("leader: §fAsh Vale"));
+ assertTrue(MovementAdminService.hostCitizenNames(movement).contains("Zed Moor"));
+ assertTrue(MovementAdminService.wantedLeaderNames(movement).contains("Zed Moor"));
+
+ MovementAdminService.Result missing = MovementAdminService.target(movement.getId(), "0", "Nobody Here");
+ assertFalse(missing.ok());
+ MovementAdminService.Result set = MovementAdminService.target(movement.getId(), "0", "zed moor");
+ assertTrue(set.ok(), set.message());
+ assertTrue(set.message().endsWith("to Zed Moor."));
+ assertEquals(zed, proposal.getTarget());
+
+ MovementAdminService.Result joined = MovementAdminService.joinSupporter(movement.getId(), "citizen", zed);
+ assertTrue(joined.ok(), joined.message());
+ realm.forceRemoveMember(zed);
+ MovementAdminService.Result left = MovementAdminService.leaveSupporter(movement.getId(), "citizen", "Zed Moor");
+ assertTrue(left.ok(), left.message());
+ assertFalse(movement.getSupporters().getCitizens().contains(zed));
+ }
+
+ @Test
+ void staffCommandsAcceptAndCompleteCharacterNamesWithSpaces() {
+ Proposal proposal = new Proposal(ash, realm.getGovernment());
+ proposal.setPoliticalActionProposal(new PoliticalAction(Action.CHANGE_LEADER));
+ realm.getGovernment().startMovement(ash, proposal);
+ Movement movement = realm.getGovernment().getMovementByLeader(ash);
+ Player staff = playing("Staff", null);
+ when(staff.hasPermission(org.mockito.ArgumentMatchers.anyString())).thenReturn(true);
+ when(staff.isOp()).thenReturn(true);
+ Command command = mock(Command.class);
+ when(command.getName()).thenReturn("movement");
+ MovementTabCompletion tabs = new MovementTabCompletion();
+ String id = movement.getId();
+
+ assertEquals(List.of("Moor"), tabs.onTabComplete(staff, command, "movement",
+ new String[] {"admin", "target", id, "0", "Zed", ""}));
+ assertEquals(List.of("Zed Moor"), tabs.onTabComplete(staff, command, "movement",
+ new String[] {"admin", "join", id, "supporter", "citizen", "Z"}));
+ assertEquals(List.of("Moor"), tabs.onTabComplete(staff, command, "movement",
+ new String[] {"admin", "join", id, "cause", "0", "citizen", "Zed", "M"}));
+ assertEquals(List.of(), tabs.onTabComplete(staff, command, "movement",
+ new String[] {"admin", "join", id, "supporter", "guild", "x", "y"}));
+ assertEquals(List.of(), tabs.onTabComplete(staff, command, "movement",
+ new String[] {"admin", "join", id, "supporter", "other", "x"}));
+
+ new net.tfminecraft.simplefactions.government.movement.admin.MovementCommandManager()
+ .onCommand(staff, command, "movement", new String[] {"admin", "target", id, "0", "Zed", "Moor"});
+ assertEquals(zed, proposal.getTarget());
+ }
+
+ private static String pages(ItemStack item) {
+ assertEquals(Material.WRITTEN_BOOK, item.getType());
+ return org.bukkit.ChatColor.stripColor(String.join("\n", ((BookMeta) item.getItemMeta()).getPages()));
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/government/GovernmentCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/government/GovernmentCoverageTest.java
index aa76bee3..1f212fb8 100644
--- a/src/test/java/net/tfminecraft/simplefactions/government/GovernmentCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/government/GovernmentCoverageTest.java
@@ -265,7 +265,8 @@ void governmentPermissionChecksUseRealCouncilGuildAndSubjectRoles() {
fixture.saved("foreign", "Iris");
assertFalse(government.isCouncilMember((String) null));
assertFalse(government.isCouncilMember((org.bukkit.entity.Player) null));
- assertTrue(government.isCouncilMember("aLiCe"));
+ assertTrue(government.isCouncilMember("Alice"));
+ assertFalse(government.isCouncilMember("aLiCe"));
assertTrue(government.isCouncilMember(fixture.player("Bob")));
assertFalse(government.isCouncilMember("Cara"));
assertTrue(government.canPropose(fixture.player("Alice")));
@@ -457,7 +458,8 @@ void movementRoundTripRetainsAllProposalKindsPoolsBackersAndFrozenState() {
political.setPoliticalActionProposal(new PoliticalAction(Action.CHANGE_LEADER));
political.setTarget("Cara");
government.startMovement("Bob", law);
- Movement movement = government.getMovementByLeader("bOB");
+ assertNull(government.getMovementByLeader("bOB"));
+ Movement movement = government.getMovementByLeader("Bob");
assertNotNull(movement);
assertTrue(government.hasMovements());
assertSame(movement, government.getMovementById(movement.getId().toUpperCase()));
diff --git a/src/test/java/net/tfminecraft/simplefactions/government/GovernmentProcessesCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/government/GovernmentProcessesCoverageTest.java
index 76b174d3..20ea2500 100644
--- a/src/test/java/net/tfminecraft/simplefactions/government/GovernmentProcessesCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/government/GovernmentProcessesCoverageTest.java
@@ -170,7 +170,8 @@ void electionRanksLiveAndArchivedResultsWithStableAlphabeticalTiesAndIndependent
election.serializeVotes().get("LEADER").clear();
election.serializePreviousVotes().get("LEADER").clear();
assertEquals(3, election.getCandidates(Candidate.LEADER).size());
- assertEquals(1, election.getVotes(Candidate.LEADER, "cARA"));
+ assertEquals(1, election.getVotes(Candidate.LEADER, "Cara"));
+ assertEquals(0, election.getVotes(Candidate.LEADER, "cARA"));
election.start();
assertNull(election.getVote(Candidate.LEADER, "Alice"));
election.addVote(Candidate.LEADER, "Alice", "Drew");
@@ -357,7 +358,7 @@ void sessionTimesOutBeforeSelectionAndRejectsInsufficientQuorumWithoutConsumingP
assertEquals(
List.of(proposal),
faction.getGovernment().getCouncil().getProposalHandler().getProposals());
- when(fixture.online.get("Bob").isOnline()).thenReturn(false);
+ fixture.online.remove("Bob");
Session denied = session(faction);
denied.onLanternClick(lantern());
assertFalse(denied.isStarted());
diff --git a/src/test/java/net/tfminecraft/simplefactions/government/movement/MovementCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/government/movement/MovementCoverageTest.java
index 7fac3218..f3d27811 100644
--- a/src/test/java/net/tfminecraft/simplefactions/government/movement/MovementCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/government/movement/MovementCoverageTest.java
@@ -145,7 +145,8 @@ void organizationRespectsPhaseBoundsAndOnlyAdjacentPhasesCanBeSelected() {
Movement movement = movement(faction, "Bob");
assertSame(faction, movement.getFaction());
assertTrue(movement.hasLeader());
- assertTrue(movement.isLeader("bOB"));
+ assertTrue(movement.isLeader("Bob"));
+ assertFalse(movement.isLeader("bOB"));
assertFalse(movement.isLeader("Cara"));
assertEquals(Phase.GATHERING, movement.getPhase());
assertEquals(20, movement.getMaxOrganization());
diff --git a/src/test/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminServiceTest.java
index 17a79f03..958a3ef8 100644
--- a/src/test/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminServiceTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/government/movement/admin/MovementAdminServiceTest.java
@@ -42,7 +42,8 @@ void joinSupporter_citizen_callsJoin() {
when(movement.getId()).thenReturn("alice_movement");
when(movement.isFrozen()).thenReturn(false);
when(movement.getSupporters()).thenReturn(supporters);
- when(movement.getFaction()).thenReturn(mock(Faction.class));
+ Faction host = hostWith("Bob");
+ when(movement.getFaction()).thenReturn(host);
when(movement.canJoin(eq("Bob"), isNull(), eq(false))).thenReturn(true);
doAnswer(invocation -> {
supporters.addCitizen("Bob");
@@ -160,6 +161,8 @@ void ineligible_doesNotJoin() {
Movement movement = mock(Movement.class);
when(movement.isFrozen()).thenReturn(false);
when(movement.getSupporters()).thenReturn(new Pool());
+ Faction host = hostWith("Bob");
+ when(movement.getFaction()).thenReturn(host);
when(movement.joinBlockReason(eq("Bob"), isNull(), eq(true)))
.thenReturn("§cBob is not a citizen of Lantan.");
@@ -182,7 +185,8 @@ void joinCause_callsJoinWithCause() {
when(movement.getCauses()).thenReturn(List.of(cause));
when(cause.getPool()).thenReturn(pool);
when(cause.getIndex()).thenReturn(0);
- when(movement.getFaction()).thenReturn(mock(Faction.class));
+ Faction host = hostWith("Bob");
+ when(movement.getFaction()).thenReturn(host);
when(movement.canJoin(eq("Bob"), eq(cause), eq(false))).thenReturn(true);
doAnswer(invocation -> {
pool.addCitizen("Bob");
@@ -262,6 +266,7 @@ void target_setsWantedLeaderWithoutOnlineCheck() {
when(cause.getProposal()).thenReturn(proposal);
when(proposal.isPoliticalActionProposal()).thenReturn(true);
when(proposal.getPoliticalAction()).thenReturn(new net.tfminecraft.simplefactions.government.movement.PoliticalAction(net.tfminecraft.simplefactions.government.movement.Action.CHANGE_LEADER));
+ when(host.getMembers()).thenReturn(List.of("dummy_11"));
when(host.canBecomeLeader("dummy_11")).thenReturn(true);
try (MockedStatic factions = mockStatic(FactionManager.class);
@@ -272,4 +277,11 @@ void target_setsWantedLeaderWithoutOnlineCheck() {
verify(proposal).setTarget("dummy_11");
}
}
+
+ private static Faction hostWith(String... members) {
+ Faction host = mock(Faction.class);
+ when(host.getCompleteMemberList()).thenReturn(List.of(members));
+ when(host.getMembers()).thenReturn(List.of(members));
+ return host;
+ }
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/GuildCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/GuildCoverageTest.java
index 60672c41..9773d286 100644
--- a/src/test/java/net/tfminecraft/simplefactions/guild/GuildCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/guild/GuildCoverageTest.java
@@ -87,7 +87,8 @@ void realmGuildLeadershipTracksTheCurrentFactionLeader() {
when(fixture.host.getLeader()).thenReturn("Successor");
assertEquals("Successor", guild.getLeader());
assertFalse(guild.isLeader("Leader"));
- assertTrue(guild.isLeader("successor"));
+ assertFalse(guild.isLeader("successor"));
+ assertTrue(guild.isLeader("Successor"));
assertTrue(guild.isLeader(fixture.ui.player("Successor")));
}
@@ -177,28 +178,33 @@ void savedGuildDataSurvivesJsonRoundTripAndRestoresFinancialSettings() {
}
@Test
- void membershipsAndInvitationsAreCaseInsensitiveAndLeaveTheMainGuildOnce() {
+ void membershipsAndInvitationsMatchExactKeysAndLeaveTheMainGuildOnce() {
Guild guild = fixture.guild("guild", 0);
guild.invite(null);
- guild.invite("BOB");
- guild.invite("bob");
- assertEquals(List.of("BOB"), guild.getInvites());
+ guild.invite("Bob");
+ guild.invite("Bob");
+ assertEquals(List.of("Bob"), guild.getInvites());
assertTrue(guild.isInvited("Bob"));
+ assertFalse(guild.isInvited("BOB"));
assertFalse(guild.consumeInvite("missing"));
when(fixture.main.isMember("Bob")).thenReturn(true);
guild.addMember("Bob");
verify(fixture.main).kick("Bob");
assertTrue(guild.getInvites().isEmpty());
- guild.addMember("BOB");
+ guild.addMember("Bob");
assertEquals(List.of("Leader", "Alice", "Bob"), guild.getMembers());
- assertTrue(guild.isMember(fixture.ui.player("bOb")));
+ assertTrue(guild.isMember(fixture.ui.player("Bob")));
+ assertFalse(guild.isMember(fixture.ui.player("bOb")));
guild.kick("bOB");
+ assertTrue(guild.isMember("Bob"));
+ guild.kick("Bob");
assertFalse(guild.isMember("Bob"));
guild.kick("missing");
assertEquals(2, guild.getMembers().size());
- assertNull(Guild.findIgnoreCase(null, "Bob"));
- assertNull(Guild.findIgnoreCase(List.of("Bob"), null));
- assertEquals("Alice", Guild.findIgnoreCase(java.util.Arrays.asList(null, "Alice"), "ALICE"));
+ assertNull(Guild.find(null, "Bob"));
+ assertNull(Guild.find(List.of("Bob"), null));
+ assertNull(Guild.find(java.util.Arrays.asList(null, "Alice"), "ALICE"));
+ assertEquals("Alice", Guild.find(java.util.Arrays.asList(null, "Alice"), "Alice"));
}
@ParameterizedTest
diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/GuildDeleteCommandTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/GuildDeleteCommandTest.java
index 919bdead..a124bb3e 100644
--- a/src/test/java/net/tfminecraft/simplefactions/guild/GuildDeleteCommandTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/guild/GuildDeleteCommandTest.java
@@ -49,16 +49,24 @@ void aLeaderCannotDeleteGuildFinancesThatStillNeedSettling(FinancialBlock block)
}
}
- @ParameterizedTest
- @ValueSource(strings = {"Leader", "lEaDeR"})
- void aLeaderCanDeleteAnEmptyDebtFreeGuildAndReturnItsMembers(String name) {
- try (Fixture fixture = new Fixture(name, false)) {
+ @Test
+ void aLeaderCanDeleteAnEmptyDebtFreeGuildAndReturnItsMembers() {
+ try (Fixture fixture = new Fixture("Leader", false)) {
assertTrue(fixture.run("delete", "smiths"));
fixture.assertDeleted();
}
}
+ @Test
+ void aKeyThatOnlyDiffersInCaseIsNotTheLeader() {
+ try (Fixture fixture = new Fixture("lEaDeR", false)) {
+ assertTrue(fixture.run("delete", "smiths"));
+
+ fixture.assertUntouched();
+ }
+ }
+
@Test
void aLeaderCanDeleteAGuildWithoutABank() {
try (Fixture fixture = new Fixture("Leader", false)) {
diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/GuildInviteTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/GuildInviteTest.java
index 6437cf0b..88395924 100644
--- a/src/test/java/net/tfminecraft/simplefactions/guild/GuildInviteTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/guild/GuildInviteTest.java
@@ -17,29 +17,32 @@
class GuildInviteTest {
@Test
- void inviteMatchesAndConsumesIgnoreCase() throws Exception {
+ void inviteMatchesAndConsumesExactKeys() throws Exception {
Guild guild = mock(Guild.class, withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
setField(guild, "invites", new ArrayList());
guild.invite("steve");
- assertTrue(guild.isInvited("Steve"));
- guild.invite("STEVE");
+ assertTrue(guild.isInvited("steve"));
+ assertFalse(guild.isInvited("Steve"));
+ guild.invite("steve");
assertEquals(1, guild.getInvites().size());
- assertTrue(guild.consumeInvite("Steve"));
+ assertFalse(guild.consumeInvite("STEVE"));
+ assertTrue(guild.consumeInvite("steve"));
assertFalse(guild.isInvited("steve"));
assertTrue(guild.getInvites().isEmpty());
}
@Test
- void findIgnoreCaseReturnsStoredSpelling() {
+ void findReturnsOnlyAnExactKey() {
ArrayList names = new ArrayList<>();
names.add("Steve");
- assertEquals("Steve", Guild.findIgnoreCase(names, "steve"));
- assertEquals(null, Guild.findIgnoreCase(names, "Alex"));
+ assertEquals("Steve", Guild.find(names, "Steve"));
+ assertEquals(null, Guild.find(names, "steve"));
+ assertEquals(null, Guild.find(names, "Alex"));
}
@Test
- void kickRemovesIgnoreCase() throws Exception {
+ void kickRemovesOnlyAnExactKey() throws Exception {
Guild guild = mock(Guild.class, withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
ArrayList members = new ArrayList<>();
members.add("Steve");
@@ -47,6 +50,8 @@ void kickRemovesIgnoreCase() throws Exception {
setField(guild, "host", mock(Faction.class));
guild.kick("steve");
+ assertEquals(1, members.size());
+ guild.kick("Steve");
assertTrue(members.isEmpty());
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/SharedAccountRivalGuildsTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/SharedAccountRivalGuildsTest.java
new file mode 100644
index 00000000..13c7cde7
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/guild/SharedAccountRivalGuildsTest.java
@@ -0,0 +1,76 @@
+package net.tfminecraft.simplefactions.guild;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.UUID;
+
+import org.bukkit.entity.Player;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.simplefactions.managers.FactionManager;
+import net.tfminecraft.simplefactions.objects.Faction;
+import net.tfminecraft.simplefactions.testsupport.FactionDomainFixture;
+
+/** One account, two characters, two rival guilds: each character acts only for its own guild. */
+class SharedAccountRivalGuildsTest {
+ private FactionDomainFixture fixture;
+ private TestCharacters characters;
+
+ @BeforeEach
+ void setUp() {
+ fixture = new FactionDomainFixture();
+ characters = TestCharacters.install();
+ }
+
+ @AfterEach
+ void tearDown() {
+ fixture.close();
+ }
+
+ @Test
+ void eachCharacterOfOneAccountLeadsOnlyItsOwnRivalGuild() {
+ UUID account = UUID.randomUUID();
+ String aldric = characters.character(account, "Aldric Vane");
+ String mira = characters.character(account, "Mira Thorn");
+ assertNotEquals(aldric, mira);
+ assertEquals(Members.accountId(aldric), Members.accountId(mira));
+
+ Faction north = fixture.saved("north", characters.character(UUID.randomUUID(), "North King"));
+ Faction south = fixture.saved("south", characters.character(UUID.randomUUID(), "South Queen"));
+ Guild smiths = fixture.guild(north, "smiths", aldric);
+ Guild masons = fixture.guild(south, "masons", mira);
+ Player player = fixture.player("SharedAccount");
+
+ characters.play(player, aldric);
+ assertSame(smiths, FactionManager.getGuildByMember(Members.key(player)));
+ assertSame(north, FactionManager.getByMember(Members.key(player)));
+ assertTrue(smiths.isLeader(player));
+ assertTrue(smiths.isMember(player));
+ assertFalse(masons.isLeader(player));
+ assertFalse(masons.isMember(player));
+ assertFalse(south.isMember(player));
+ assertEquals("Aldric Vane", Members.name(player));
+
+ characters.play(player, mira);
+ assertSame(masons, FactionManager.getGuildByMember(Members.key(player)));
+ assertSame(south, FactionManager.getByMember(Members.key(player)));
+ assertTrue(masons.isLeader(player));
+ assertFalse(smiths.isLeader(player));
+ assertFalse(smiths.isMember(player));
+ assertFalse(north.isMember(player));
+ assertEquals("Mira Thorn", Members.display(masons.getLeader()));
+
+ characters.play(player, null);
+ assertNull(FactionManager.getGuildByMember(Members.key(player)));
+ assertFalse(smiths.isLeader(player));
+ assertFalse(masons.isLeader(player));
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/TestCharacters.java b/src/test/java/net/tfminecraft/simplefactions/guild/TestCharacters.java
new file mode 100644
index 00000000..b290feb1
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/guild/TestCharacters.java
@@ -0,0 +1,71 @@
+package net.tfminecraft.simplefactions.guild;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import org.bukkit.entity.Player;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
+import net.tfminecraft.tlibs.character.CharacterStatus;
+
+/** Real character keys for tests: each player plays at most one named character at a time. */
+public final class TestCharacters implements Members.Resolver {
+ private final Map characters = new HashMap<>();
+ private final Map playing = new IdentityHashMap<>();
+
+ /** Installs a fresh resolver; the global test extension restores the default after each test. */
+ public static TestCharacters install() {
+ TestCharacters resolver = new TestCharacters();
+ Members.setResolver(resolver);
+ return resolver;
+ }
+
+ /** Registers a character owned by {@code owner} and returns its member key. */
+ public String character(UUID owner, String name) {
+ CharacterRef ref = new CharacterRef(owner, UUID.randomUUID());
+ String key = Members.key(ref);
+ characters.put(key, new CharacterInfo(ref, name, CharacterStatus.ALIVE, "human", Set.of(), 0L, Instant.EPOCH));
+ return key;
+ }
+
+ /** Starts a session for {@code key} on that player; null ends it. */
+ public void play(Player player, String key) {
+ if (key == null) playing.remove(player);
+ else playing.put(player, key);
+ }
+
+ @Override
+ public Optional session(Player player) {
+ return Optional.ofNullable(playing.get(player))
+ .map(key -> new CharacterSession(characters.get(key).ref(), 1L));
+ }
+
+ @Override
+ public boolean isCurrent(CharacterSession session) {
+ return true;
+ }
+
+ @Override
+ public String key(Player player) {
+ return playing.get(player);
+ }
+
+ @Override
+ public Optional info(String key) {
+ return Optional.ofNullable(characters.get(key));
+ }
+
+ @Override
+ public Optional online(String key) {
+ return playing.entrySet().stream().filter(entry -> entry.getValue().equals(key))
+ .map(Map.Entry::getKey).findFirst();
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java
index 21a9e5a9..7f5c0e3b 100644
--- a/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/objects/DomainConfigurationBoundaryCoverageTest.java
@@ -469,11 +469,15 @@ void guildMembershipQueriesAndForcedRemovalAgreeWithoutChangingLeaders() {
Guild traders = fixture.guild(faction, "traders", "Bob");
traders.addMember("Traveler");
var guilds = faction.getGuildHandler();
- assertSame(traders, guilds.getGuildByMember("traveler"));
+ assertSame(traders, guilds.getGuildByMember("Traveler"));
+ assertNull(guilds.getGuildByMember("traveler"));
assertNull(guilds.getGuildByMember("missing"));
- assertTrue(guilds.isGuildLeader("BOB"));
+ assertTrue(guilds.isGuildLeader("Bob"));
+ assertFalse(guilds.isGuildLeader("BOB"));
assertFalse(guilds.isGuildLeader("Traveler"));
guilds.forceKick("traveler");
+ assertSame(traders, guilds.getGuildByMember("Traveler"));
+ guilds.forceKick("Traveler");
assertNull(guilds.getGuildByMember("Traveler"));
assertFalse(faction.isMember("Traveler"));
assertEquals(List.of("Bob"), traders.getMembers());
diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java
index d9f20319..73573eba 100644
--- a/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/objects/FactionCoverageTest.java
@@ -131,21 +131,24 @@ void onePrestigePassAsksForEachMembersPlaytimeOnceWithoutChangingPrestige() {
}
@Test
- void factionInvitationsAreCaseInsensitiveAndCanBeConsumedExactlyOnce() {
+ void factionInvitationsMatchExactKeysAndCanBeConsumedExactlyOnce() {
Faction faction = fixture.saved("home", "Alice");
faction.invite(null);
faction.invite("Bob");
- faction.invite("BOB");
+ faction.invite("Bob");
assertEquals(List.of("Bob"), faction.getInvited());
- assertTrue(faction.isInvited("bob"));
+ assertTrue(faction.isInvited("Bob"));
+ assertFalse(faction.isInvited("bob"));
assertFalse(faction.consumeInvite("Cara"));
- assertTrue(faction.consumeInvite("bOB"));
+ assertFalse(faction.consumeInvite("bOB"));
+ assertTrue(faction.consumeInvite("Bob"));
assertFalse(faction.consumeInvite("Bob"));
assertTrue(faction.getInvited().isEmpty());
faction.setInvited(new ArrayList<>(List.of("Cara")));
- assertTrue(faction.isInvited("cara"));
+ assertTrue(faction.isInvited("Cara"));
faction.addMember("Bob");
- assertTrue(faction.isMember("bob"));
+ assertTrue(faction.isMember("Bob"));
+ assertFalse(faction.isMember("bob"));
assertFalse(faction.isMember("missing"));
}
@@ -693,8 +696,6 @@ void onlineCountProsperityAndCapacityUseTheCurrentRosterAndTerritory() {
faction.addMember("Bob");
faction.addMember("Cara");
fixture.player("Alice");
- var offline = fixture.player("Bob");
- when(offline.isOnline()).thenReturn(false);
fixture.player("Visitor");
assertEquals(1, faction.numOnline());
var north = new Province(7, "PLAINS", 1);
diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/FactionInviteTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/FactionInviteTest.java
index 8550a7fa..4ff76137 100644
--- a/src/test/java/net/tfminecraft/simplefactions/objects/FactionInviteTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/objects/FactionInviteTest.java
@@ -15,15 +15,17 @@
class FactionInviteTest {
@Test
- void inviteMatchesAndConsumesIgnoreCase() throws Exception {
+ void inviteMatchesAndConsumesExactKeys() throws Exception {
Faction faction = mock(Faction.class, withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
setField(faction, "invited", new ArrayList());
faction.invite("steve");
- assertTrue(faction.isInvited("Steve"));
- faction.invite("STEVE");
+ assertFalse(faction.isInvited("Steve"));
+ assertTrue(faction.isInvited("steve"));
+ faction.invite("steve");
assertEquals(1, faction.getInvited().size());
- assertTrue(faction.consumeInvite("Steve"));
+ assertFalse(faction.consumeInvite("Steve"));
+ assertTrue(faction.consumeInvite("steve"));
assertFalse(faction.isInvited("steve"));
assertTrue(faction.getInvited().isEmpty());
}
From f261d298c6cda5f1ec409b991b860c17b7ed623d Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:23:05 +0000
Subject: [PATCH 05/35] refactor: key war, diplomacy and requests by character
Member strings in the war, raid, civil war and request code are now
character member keys. Combat state stays keyed by account; warband
rejoin waits for the same character's session to start.
Co-Authored-By: Claude Opus 5.5
---
.../managers/RelationManager.java | 14 ++--
.../simplefactions/managers/WarManager.java | 10 +--
.../campaign/CampaignBattleJoinService.java | 29 +++----
.../campaign/CampaignBattleLaunchService.java | 5 +-
.../CampaignBattleOutcomeService.java | 7 +-
.../campaign/CampaignBattleRosterService.java | 3 +-
.../CampaignBattleSignupReminderService.java | 3 +-
.../CampaignNavalAutoLossReminderService.java | 3 +-
.../warband/CampaignWarbandBattleService.java | 16 ++--
.../warband/CampaignWarbandSignupService.java | 23 +++---
.../military/BattleCasualtyService.java | 5 +-
.../war/battle/ui/BattleCommandManager.java | 31 ++++----
.../war/battle/warband/Warband.java | 3 +-
.../warband/WarbandMembershipListener.java | 10 ++-
.../warband/WarbandMembershipService.java | 71 ++++++++++-------
.../battle/warband/WarbandRejoinState.java | 12 ++-
.../AttackerNavalContestService.java | 5 +-
.../CampaignOffensiveForfeitService.java | 5 +-
.../raid/CampaignRaidJoinService.java | 4 +-
.../raid/CampaignRaidWarbandService.java | 28 +++----
.../war/campaign/raid/RaidCommandManager.java | 2 +-
.../fight/CampaignRaidBattleEndService.java | 3 +-
.../raid/fight/CampaignRaidBattleService.java | 3 +-
.../raid/fight/CampaignRaidLaunchService.java | 3 +-
.../CampaignRaidMusterReminderService.java | 3 +-
.../intruder/CampaignRaidIntruderService.java | 11 +--
.../runtime/BattleAutoresolveService.java | 6 +-
.../runtime/BattleScheduleLookups.java | 11 ++-
.../pick/BattleInstallationPickService.java | 8 +-
.../war/civilwar/CivilWarStartService.java | 2 +-
.../civilwar/CivilWarTempRebelFactory.java | 6 +-
.../wartime/CivilWarUntangleService.java | 2 +-
.../war/freeze/PreparationFreeze.java | 11 +--
.../freeze/VfBuildersConstructionFreeze.java | 16 +++-
.../CampaignBattleJoinServiceTest.java | 4 +-
.../BattleRosterBoundaryCoverageTest.java | 16 +++-
.../BattleRosterLifecycleCoverageTest.java | 54 ++++++-------
.../warband/WarbandMembershipServiceTest.java | 72 +++++++++++++++--
.../CampaignRaidLifecycleCoverageTest.java | 18 ++---
...CampaignScheduleLifecycleCoverageTest.java | 56 ++++++++++++--
.../VfBuildersConstructionFreezeTest.java | 77 +++++++++++++++++++
41 files changed, 456 insertions(+), 215 deletions(-)
create mode 100644 src/test/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreezeTest.java
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java
index b54222ec..0e009a39 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/RelationManager.java
@@ -282,7 +282,7 @@ public static boolean setRelation(
relation.setType(r);
origin.setRelation(target, relation);
if(reverseChange) {
- Player l = Bukkit.getPlayerExact(target.getLeader());
+ Player l = Members.player(target.getLeader());
if(l != null && l.isOnline()) {
l.sendMessage(StringFormatter.formatHex("#a89977Your relation with "+origin.getName()+" #a89977has been changed to "+r.getLink().getName()));
}
@@ -622,7 +622,7 @@ public static boolean setAttitude(Player p, Attitude a, Faction target, Faction
}
private static void sendRequest(Player sender, Faction f, RelationType type) {
- Player p = Bukkit.getPlayerExact(f.getLeader());
+ Player p = Members.player(f.getLeader());
if(p == null || !p.isOnline()) {
sender.sendMessage("§cCannot send request, target faction leader is not online!");
return;
@@ -646,7 +646,7 @@ public static void acceptRequest(Player p) {
p.sendMessage("§cThis diplomacy request is no longer valid. Ask for a new request.");
return;
}
- Player sp = Bukkit.getPlayerExact(sender.getLeader());
+ Player sp = Members.player(sender.getLeader());
// The war may have started after the request was sent
String blocked = wartimeBlock(req.getType(), reciever, sender);
if(blocked != null) {
@@ -660,7 +660,7 @@ public static void acceptRequest(Player p) {
}
private static void sendTradeRequest(Player sender, Faction f, RelationType type) {
- Player p = Bukkit.getPlayerExact(f.getLeader());
+ Player p = Members.player(f.getLeader());
if(p == null || !p.isOnline()) {
sender.sendMessage("§cCannot send request, target faction leader is not online!");
return;
@@ -684,14 +684,14 @@ public static void acceptTradeRequest(Player p) {
p.sendMessage("§cThis diplomacy request is no longer valid. Ask for a new request.");
return;
}
- Player sp = Bukkit.getPlayerExact(sender.getLeader());
+ Player sp = Members.player(sender.getLeader());
if (applyTradeRelation(p, req.getType(), reciever, sender, false) && sp != null && sp.isOnline()) {
sp.sendMessage(reciever.getName()+" §aaccepted your request and set trade to "+req.getType().getName());
}
}
private static void sendTreatyRequest(Player sender, Faction f, RelationType type) {
- Player p = Bukkit.getPlayerExact(f.getLeader());
+ Player p = Members.player(f.getLeader());
if(p == null || !p.isOnline()) {
sender.sendMessage("§cCannot send request, target faction leader is not online!");
return;
@@ -715,7 +715,7 @@ public static void acceptTreatyRequest(Player p) {
p.sendMessage("§cThis diplomacy request is no longer valid. Ask for a new request.");
return;
}
- Player sp = Bukkit.getPlayerExact(sender.getLeader());
+ Player sp = Members.player(sender.getLeader());
if (applyTreatyRelation(p, req.getType(), reciever, sender, false) && sp != null && sp.isOnline()) {
sp.sendMessage(reciever.getName()+" §aaccepted your request and set treaty to "+req.getType().getName());
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java
index 20fe37c0..9db1673e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/WarManager.java
@@ -334,7 +334,7 @@ private static void notifyWarDeclared(Iterable members) {
if (members == null) return;
for (String member : members) {
if (member == null || member.isBlank()) continue;
- Player p = Bukkit.getPlayerExact(member);
+ Player p = Members.player(member);
if (p != null && p.isOnline()) {
p.sendTitle("§cWar Declared!", "§e/war list §7to view", 10, 120, 10);
declaredHorn.accept(p);
@@ -444,7 +444,7 @@ private static void notifyWarEnded(War w, WarEndReason reason) {
members.addAll(BattleSideMembers.collectEligibleMemberNames(w.getAttackers()));
members.addAll(BattleSideMembers.collectEligibleMemberNames(w.getDefenders()));
for (String member : members) {
- Player p = Bukkit.getPlayerExact(member);
+ Player p = Members.player(member);
if (p != null && p.isOnline()) {
p.sendMessage(message);
}
@@ -489,7 +489,7 @@ public static void sendRequest(Player sender, Faction origin, Faction target, Wa
sender.sendMessage(result.message());
return;
}
- Player p = Bukkit.getPlayerExact(target.getLeader());
+ Player p = Members.player(target.getLeader());
if(p == null || !p.isOnline()) {
sender.sendMessage("§cCannot send request, target faction leader is not online!");
return;
@@ -528,7 +528,7 @@ public static void acceptRequest(Player p) {
WarCommitmentService.snapshotLevyForFighter(war, reciever);
//A secondary only counts as a participant once it has joined, so contracts are re-checked here
net.tfminecraft.simplefactions.mercenary.contract.MercenaryLoyaltyWatcher.onWarJoined(reciever);
- Player sp = Bukkit.getPlayerExact(origin.getLeader());
+ Player sp = Members.player(origin.getLeader());
if(sp != null && sp.isOnline()) sp.sendMessage(reciever.getName()+" §aaccepted your call to arms");
p.sendMessage("§aYour faction has joined the "+war.getName());
notifyWarDeclared(reciever.getMembers());
@@ -556,7 +556,7 @@ public static void declineCallToArms(Player called, WarRequest req, boolean expl
}
Faction origin = req.getFaction();
if (origin != null && origin.getLeader() != null) {
- Player caller = Bukkit.getPlayerExact(origin.getLeader());
+ Player caller = Members.player(origin.getLeader());
if (caller != null && caller.isOnline()) {
String who = receiver != null ? receiver.getName() : "They";
caller.sendMessage(who + " §cdeclined your call to arms");
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinService.java
index bbc61af3..9f45d9e3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.battle.campaign;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.battle.campaign.warband.CampaignWarbandBattleService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -73,7 +74,7 @@ public static String validateWarbandMemberJoin(
if (joiningPlayer == null) {
return "Invalid campaign warband join";
}
- return validateWarbandMemberJoin(war, battle, sideId, warband, joiningPlayer.getName(), joiningPlayer.getUniqueId());
+ return validateWarbandMemberJoin(war, battle, sideId, warband, Members.key(joiningPlayer), joiningPlayer.getUniqueId());
}
public static String validateWarbandMemberJoin(
@@ -81,8 +82,8 @@ public static String validateWarbandMemberJoin(
Battle battle,
String sideId,
Warband warband,
- String joiningPlayerName) {
- return validateWarbandMemberJoin(war, battle, sideId, warband, joiningPlayerName, null);
+ String memberKey) {
+ return validateWarbandMemberJoin(war, battle, sideId, warband, memberKey, null);
}
public static String validateWarbandMemberJoin(
@@ -90,12 +91,12 @@ public static String validateWarbandMemberJoin(
Battle battle,
String sideId,
Warband warband,
- String joiningPlayerName,
+ String memberKey,
java.util.UUID playerId) {
- if (war == null || battle == null || warband == null || joiningPlayerName == null) {
+ if (war == null || battle == null || warband == null || memberKey == null) {
return "Invalid campaign warband join";
}
- Faction faction = FactionManager.getByMember(joiningPlayerName);
+ Faction faction = FactionManager.getByMember(memberKey);
if (faction == null) {
return "You must be in a faction to join this campaign battle";
}
@@ -104,11 +105,11 @@ public static String validateWarbandMemberJoin(
return "Your faction is not on this battle side";
}
String mercenaryError = validateMercenaryRoster(
- war, battle, sideId, battleSide, joiningPlayerName, playerId);
+ war, battle, sideId, battleSide, memberKey, playerId);
if (mercenaryError != null) {
return mercenaryError;
}
- Side playerSide = rosterSideFor(war, joiningPlayerName, faction);
+ Side playerSide = rosterSideFor(war, memberKey, faction);
if (playerSide == null || playerSide != battleSide) {
return "Your faction is not on this battle side";
}
@@ -117,16 +118,16 @@ public static String validateWarbandMemberJoin(
}
if (battle.hasStarted()) {
return CampaignWarbandBattleService.validateMidBattleJoin(
- war, battle, sideId, warband, joiningPlayerName, playerId);
+ war, battle, sideId, warband, memberKey, playerId);
}
return validateRosterHasRoom(war, battle, sideId, warband, 1);
}
- public static Side rosterSideFor(War war, String playerName, Faction faction) {
+ public static Side rosterSideFor(War war, String memberKey, Faction faction) {
if (war == null) {
return null;
}
- Side contracted = MercenaryEngagements.sideFor(war, playerName);
+ Side contracted = MercenaryEngagements.sideFor(war, memberKey);
if (contracted != null) {
return contracted;
}
@@ -138,10 +139,10 @@ public static String validateMercenaryRoster(
Battle battle,
String sideId,
Side battleSide,
- String joiningPlayerName,
+ String memberKey,
java.util.UUID playerId) {
MercenaryEngagements.Engagement engagement =
- MercenaryEngagements.forPlayer(war, joiningPlayerName);
+ MercenaryEngagements.forPlayer(war, memberKey);
if (engagement == null) {
return null;
}
@@ -151,7 +152,7 @@ public static String validateMercenaryRoster(
}
Side opposing = war.getOppositeSide(engagement.hirer());
if (!net.tfminecraft.simplefactions.mercenary.contract.MercenaryLoyalty.canDeployAgainst(
- joiningPlayerName, opposing)) {
+ memberKey, opposing)) {
return "You cannot march on your own realm";
}
BattleSide roster = battle.getSideById(sideId);
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java
index 81edd2ef..6efd99a9 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.battle.campaign;
+import net.tfminecraft.simplefactions.identity.Members;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.List;
@@ -277,13 +278,13 @@ private static void sendToOnlineAdmins(String message) {
private static void broadcastToBelligerents(War war, String message) {
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getAttackers())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getDefenders())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleOutcomeService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleOutcomeService.java
index 53ef2c99..5bb5d762 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleOutcomeService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleOutcomeService.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.battle.campaign;
+import net.tfminecraft.simplefactions.identity.Members;
import java.util.Map;
import java.util.Optional;
@@ -229,7 +230,7 @@ public static void notifyPostBattleChoicePending(War war) {
: "§eChoose §6attack §eor §6accept white peace §eon the campaign map.";
for (String memberName : BattleSideMembers.collectEligibleMemberNames(
CampaignCoalitionService.toSide(war, leaderCoalition))) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
@@ -260,13 +261,13 @@ private static void broadcastBattleEnded(
return;
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getAttackers())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(resultLine);
}
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getDefenders())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(resultLine);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleRosterService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleRosterService.java
index 3ba7ae5a..9d8627da 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleRosterService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleRosterService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.battle.campaign;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.battle.campaign.warband.CampaignWarbandSignupService;
import java.time.Instant;
@@ -153,7 +154,7 @@ private static void broadcastJoinReady(War war, Battle battle) {
private static void notifySide(Side side, String message) {
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleSignupReminderService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleSignupReminderService.java
index 0fe44bc4..37acee3d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleSignupReminderService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleSignupReminderService.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.battle.campaign;
+import net.tfminecraft.simplefactions.identity.Members;
import java.time.Instant;
import java.util.List;
@@ -86,7 +87,7 @@ private static void broadcastToUnassignedSide(
net.tfminecraft.simplefactions.war.core.Side side,
String message) {
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player == null || !player.isOnline()) {
continue;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignNavalAutoLossReminderService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignNavalAutoLossReminderService.java
index b3fcd207..29c8d14c 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignNavalAutoLossReminderService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignNavalAutoLossReminderService.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.battle.campaign;
+import net.tfminecraft.simplefactions.identity.Members;
import java.time.Instant;
import org.bukkit.Bukkit;
@@ -46,7 +47,7 @@ public static void processReminders(War war, Instant now) {
if (offensiveLeader == null || offensiveLeader.getLeader() == null) {
return;
}
- Player player = Bukkit.getPlayerExact(offensiveLeader.getLeader());
+ Player player = Members.player(offensiveLeader.getLeader());
if (player == null || !player.isOnline()) {
return;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandBattleService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandBattleService.java
index a939c9c4..99738a4f 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandBattleService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandBattleService.java
@@ -25,16 +25,16 @@ public static String validateMidBattleJoin(
Battle battle,
String sideId,
Warband warband,
- String joiningPlayerName,
+ String memberKey,
UUID playerId) {
- if (war == null || battle == null || warband == null || joiningPlayerName == null) {
+ if (war == null || battle == null || warband == null || memberKey == null) {
return "Invalid campaign warband join";
}
if (!battle.hasStarted()) {
return null;
}
net.tfminecraft.simplefactions.objects.Faction faction =
- net.tfminecraft.simplefactions.managers.FactionManager.getByMember(joiningPlayerName);
+ net.tfminecraft.simplefactions.managers.FactionManager.getByMember(memberKey);
if (faction == null) {
return "You must be in a faction to join this campaign battle";
}
@@ -44,12 +44,12 @@ public static String validateMidBattleJoin(
return "Your faction is not on this battle side";
}
String mercenaryError = CampaignBattleJoinService.validateMercenaryRoster(
- war, battle, sideId, battleSide, joiningPlayerName, playerId);
+ war, battle, sideId, battleSide, memberKey, playerId);
if (mercenaryError != null) {
return mercenaryError;
}
net.tfminecraft.simplefactions.war.core.Side playerSide =
- CampaignBattleJoinService.rosterSideFor(war, joiningPlayerName, faction);
+ CampaignBattleJoinService.rosterSideFor(war, memberKey, faction);
if (playerSide == null || playerSide != battleSide) {
return "Your faction is not on this battle side";
}
@@ -176,8 +176,8 @@ private static BattleSide findSideForWarband(Warband warband) {
public static boolean isWarSideMainLeader(
net.tfminecraft.simplefactions.war.core.War war,
Warband warband,
- String playerName) {
- if (war == null || warband == null || playerName == null || playerName.isBlank()) {
+ String memberKey) {
+ if (war == null || warband == null || memberKey == null || memberKey.isBlank()) {
return false;
}
String sideId = warband.getCampaignSideId();
@@ -193,7 +193,7 @@ public static boolean isWarSideMainLeader(
}
for (net.tfminecraft.simplefactions.war.core.Participant par : side.getMainParticipants()) {
if (par.getLeader() != null
- && playerName.equalsIgnoreCase(par.getLeader().getLeader())) {
+ && memberKey.equals(par.getLeader().getLeader())) {
return true;
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandSignupService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandSignupService.java
index 65139363..b5dd4989 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandSignupService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/warband/CampaignWarbandSignupService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.battle.campaign.warband;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.battle.campaign.CampaignBattleJoinService;
import java.time.Instant;
import java.util.UUID;
@@ -45,30 +46,30 @@ public static String signup(Player player, Warband warband, Faction playerFactio
if (player == null || warband == null) {
return "Invalid warband signup";
}
- return signupMember(player.getUniqueId(), player.getName(), warband, playerFaction, player);
+ return signupMember(player.getUniqueId(), Members.key(player), warband, playerFaction, player);
}
- public static String signupMember(UUID playerId, String playerName, Warband warband, Faction playerFaction) {
- return signupMember(playerId, playerName, warband, playerFaction, null);
+ public static String signupMember(UUID playerId, String memberKey, Warband warband, Faction playerFaction) {
+ return signupMember(playerId, memberKey, warband, playerFaction, null);
}
public static String signupMember(
UUID playerId,
- String playerName,
+ String memberKey,
Warband warband,
Faction playerFaction,
Player onlinePlayer) {
- return signupMember(playerId, playerName, warband, playerFaction, onlinePlayer, CampaignClock.now());
+ return signupMember(playerId, memberKey, warband, playerFaction, onlinePlayer, CampaignClock.now());
}
public static String signupMember(
UUID playerId,
- String playerName,
+ String memberKey,
Warband warband,
Faction playerFaction,
Player onlinePlayer,
Instant now) {
- if (playerId == null || playerName == null || warband == null) {
+ if (playerId == null || memberKey == null || warband == null) {
return "Invalid warband signup";
}
if (onlinePlayer != null) {
@@ -90,14 +91,14 @@ public static String signupMember(
}
if (ctx != null) {
String joinError = CampaignBattleJoinService.validateWarbandMemberJoin(
- ctx.war(), ctx.battle(), ctx.sideId(), warband, playerName, playerId);
+ ctx.war(), ctx.battle(), ctx.sideId(), warband, memberKey, playerId);
if (joinError != null) {
return joinError;
}
}
boolean firstSignup = warband.getRealMemberCount() == 0;
- applyLeaderRules(ctx, warband, playerId, playerName);
+ applyLeaderRules(ctx, warband, playerId, memberKey);
warband.addMember(playerId);
if (ctx != null && firstSignup) {
@@ -115,12 +116,12 @@ private static void applyLeaderRules(
CampaignBattleContext ctx,
Warband warband,
UUID playerId,
- String playerName) {
+ String memberKey) {
if (warband.isPendingLeader()) {
warband.setLeaderId(playerId);
return;
}
- if (ctx != null && CampaignWarbandBattleService.isWarSideMainLeader(ctx.war(), warband, playerName)) {
+ if (ctx != null && CampaignWarbandBattleService.isWarSideMainLeader(ctx.war(), warband, memberKey)) {
warband.setLeaderId(playerId);
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/military/BattleCasualtyService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/military/BattleCasualtyService.java
index 22579bbc..a5d0e8b8 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/military/BattleCasualtyService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/military/BattleCasualtyService.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.battle.military;
+import net.tfminecraft.simplefactions.identity.Members;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
@@ -353,13 +354,13 @@ private static void broadcastLosses(War war, int attackerLosses, int defenderLos
+ defenderLosses
+ " regiments.";
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getAttackers())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getDefenders())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java
index 5cfde1da..61047fd2 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/ui/BattleCommandManager.java
@@ -157,7 +157,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
p.sendMessage("§cOnly the leader can dismiss members!");
return true;
}
- Player kickTarget = Bukkit.getPlayerExact(args[1]);
+ Player kickTarget = onlineTarget(args[1]);
if (kickTarget != null && kickTarget.getUniqueId().equals(w.getLeaderId())) {
p.sendMessage("§cYou cannot dismiss the leader!");
return true;
@@ -168,12 +168,8 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
w.removePlayer(kickTarget);;
BattlePersistenceService.persistWarband(w);
- p.sendMessage("§aKicked "+args[1]);
- for(Player pl : Bukkit.getOnlinePlayers()) {
- if(pl.getName().equalsIgnoreCase(args[1])) {
- pl.sendMessage("§a"+p.getName()+ " kicked you from "+w.getId());
- }
- }
+ p.sendMessage("§aKicked "+Members.name(kickTarget));
+ kickTarget.sendMessage("§a"+Members.name(p)+ " kicked you from "+w.getId());
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("setleader") && args.length == 2) {
Warband w = WarbandManager.getByPlayer(p);
@@ -185,7 +181,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
p.sendMessage("§cOnly the leader can set a new leader!");
return true;
}
- Player newLeader = Bukkit.getPlayerExact(args[1]);
+ Player newLeader = onlineTarget(args[1]);
if (newLeader == null || !w.hasMember(newLeader)) {
p.sendMessage("§cPlayer is not in the warband");
return true;
@@ -198,7 +194,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
BattlePersistenceService.persistWarband(w);
for(Player pl : Bukkit.getOnlinePlayers()) {
if (w.hasMember(pl)) {
- pl.sendMessage("§a"+args[1]+ " is the new warband leader!");
+ pl.sendMessage("§a"+Members.name(newLeader)+ " is the new warband leader!");
}
}
return true;
@@ -212,7 +208,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
p.sendMessage("§cOnly the leader can invite new members!");
return true;
}
- Player invitee = Bukkit.getPlayerExact(args[1]);
+ Player invitee = onlineTarget(args[1]);
if (invitee == null) {
p.sendMessage("§cPlayer must be online to receive an invitation");
return true;
@@ -223,13 +219,9 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
w.invite(invitee);
BattlePersistenceService.persistWarband(w);
- p.sendMessage("§aInvited "+args[1]);
- for(Player pl : Bukkit.getOnlinePlayers()) {
- if(pl.getName().equalsIgnoreCase(args[1])) {
- pl.sendMessage("§a"+p.getName()+ " invited you to "+w.getId());
- pl.sendMessage("§aType /warband join "+w.getId() +"§a to join");
- }
- }
+ p.sendMessage("§aInvited "+Members.name(invitee));
+ invitee.sendMessage("§a"+Members.name(p)+ " invited you to "+w.getId());
+ invitee.sendMessage("§aType /warband join "+w.getId() +"§a to join");
return true;
} else if(cmd.getName().equalsIgnoreCase(cmd1) && args[0].equalsIgnoreCase("join") && args.length == 2) {
if(WarbandManager.getByPlayer(p) != null) {
@@ -522,6 +514,11 @@ private static Integer parseInteger(Player player, String value) {
}
}
+ /** An online player named by Minecraft or character name. */
+ private static Player onlineTarget(String input) {
+ return Members.player(Members.findOnline(input));
+ }
+
private void warnIfNotSiege(Player p, Battle b) {
if (b.getBattleType() != BattleType.SIEGE) {
p.sendMessage("§eWarning: battle type is not siege");
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/Warband.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/Warband.java
index 2d888bed..79865587 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/Warband.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/Warband.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.battle.warband;
+import net.tfminecraft.simplefactions.identity.Members;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
@@ -223,7 +224,7 @@ public String getMemberDisplayName(UUID memberId) {
}
Player online = Bukkit.getPlayer(memberId);
if (online != null && online.getName() != null) {
- return online.getName();
+ return Members.name(online);
}
OfflinePlayer offline = Bukkit.getOfflinePlayer(memberId);
if (offline.getName() != null && !offline.getName().isBlank()) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipListener.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipListener.java
index 68d53311..167eba8a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipListener.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipListener.java
@@ -2,17 +2,19 @@
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
+import net.tfminecraft.tlibs.character.CharacterSessionStartEvent;
+
public class WarbandMembershipListener implements Listener {
@EventHandler
public void onQuit(PlayerQuitEvent e) {
- WarbandMembershipService.getInstance().handleQuit(e.getPlayer().getUniqueId());
+ WarbandMembershipService.getInstance().handleQuit(e.getPlayer());
}
+ /** Joining alone restores nothing; the place returns with the same character. */
@EventHandler
- public void onJoin(PlayerJoinEvent e) {
- WarbandMembershipService.getInstance().handleJoin(e.getPlayer());
+ public void onSessionStart(CharacterSessionStartEvent e) {
+ WarbandMembershipService.getInstance().handleSessionStart(e.getPlayer(), e.getSession());
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java
index 273cc0a6..998b57c3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipService.java
@@ -16,6 +16,8 @@
import net.tfminecraft.simplefactions.war.battle.engine.core.Battle;
import net.tfminecraft.simplefactions.war.battle.engine.core.BattleManager;
import net.tfminecraft.simplefactions.war.battle.engine.core.BattleSide;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
public class WarbandMembershipService {
private static WarbandMembershipService instance;
@@ -34,21 +36,47 @@ public static void resetForTests() {
}
public void handleQuit(UUID playerId) {
+ handleQuit(playerId, getOnlinePlayer(playerId));
+ }
+
+ public void handleQuit(Player player) {
+ if (player != null) {
+ handleQuit(player.getUniqueId(), player);
+ }
+ }
+
+ private void handleQuit(UUID playerId, Player player) {
Warband warband = WarbandManager.getByMemberId(playerId);
if (warband == null) {
return;
}
- Faction faction = resolveFactionForWarband(warband, playerId);
- detachFromBattle(playerId, warband);
- pendingRejoin.put(playerId, new WarbandRejoinState(warband.getId(), faction));
+ Faction faction = resolveFactionForWarband(warband, player);
+ // Only the character that left may take the place back.
+ CharacterRef character = Members.session(player).map(CharacterSession::ref).orElse(null);
+ detachFromBattle(player, warband);
+ if (character != null) {
+ pendingRejoin.put(playerId, new WarbandRejoinState(warband.getId(), faction, character));
+ }
warband.removeMember(playerId);
}
- public boolean handleJoin(Player player) {
- return attemptRejoin(player.getUniqueId(), player.getName(), player);
+ /** Restores the place only when the character that quit starts playing again. */
+ public boolean handleSessionStart(Player player, CharacterSession session) {
+ if (player == null || session == null) {
+ return false;
+ }
+ WarbandRejoinState state = pendingRejoin.get(player.getUniqueId());
+ if (state == null) {
+ return false;
+ }
+ if (!state.isFor(session.ref())) {
+ pendingRejoin.remove(player.getUniqueId());
+ return false;
+ }
+ return attemptRejoin(player.getUniqueId(), Members.key(player), player);
}
- boolean attemptRejoin(UUID playerId, String playerName, Player onlinePlayer) {
+ boolean attemptRejoin(UUID playerId, String memberKey, Player onlinePlayer) {
WarbandRejoinState state = pendingRejoin.remove(playerId);
if (state == null) {
return false;
@@ -57,8 +85,8 @@ boolean attemptRejoin(UUID playerId, String playerName, Player onlinePlayer) {
if (warband == null || !state.belongsTo(warband)) {
return false;
}
- Faction playerFaction = warband.isFaction() ? FactionManager.getByMember(playerName) : null;
- if (!evaluateRejoin(warband, playerId, playerFaction, state)) {
+ Faction playerFaction = warband.isFaction() ? FactionManager.getByMember(memberKey) : null;
+ if (!evaluateRejoin(warband, playerId, memberKey, playerFaction, state)) {
return false;
}
warband.addMember(playerId);
@@ -76,7 +104,8 @@ public void clearPendingRejoin(UUID playerId) {
pendingRejoin.remove(playerId);
}
- public boolean evaluateRejoin(Warband warband, UUID playerId, Faction playerFaction, WarbandRejoinState state) {
+ public boolean evaluateRejoin(
+ Warband warband, UUID playerId, String memberKey, Faction playerFaction, WarbandRejoinState state) {
if (warband.isFaction()) {
if (!state.hasFaction()) {
return false;
@@ -91,7 +120,7 @@ public boolean evaluateRejoin(Warband warband, UUID playerId, Faction playerFact
CampaignBattleContext ctx = CampaignBattleJoinService.findCampaignBattleForWarband(warband);
if (ctx != null && ctx.battle().hasStarted()) {
return CampaignBattleJoinService.validateWarbandMemberJoin(
- ctx.war(), ctx.battle(), ctx.sideId(), warband, playerNameFromId(playerId), playerId) == null;
+ ctx.war(), ctx.battle(), ctx.sideId(), warband, memberKey, playerId) == null;
}
if (ctx != null) {
return CampaignBattleJoinService.validateRosterHasRoom(
@@ -106,17 +135,6 @@ public boolean evaluateRejoin(Warband warband, UUID playerId, Faction playerFact
return true;
}
- private String playerNameFromId(UUID playerId) {
- Player player = getOnlinePlayer(playerId);
- if (player != null && player.getName() != null) {
- return player.getName();
- }
- if (Bukkit.getOfflinePlayer(playerId).getName() != null) {
- return Bukkit.getOfflinePlayer(playerId).getName();
- }
- return null;
- }
-
private Player getOnlinePlayer(UUID playerId) {
if (Bukkit.getServer() == null) {
return null;
@@ -124,19 +142,14 @@ private Player getOnlinePlayer(UUID playerId) {
return Bukkit.getPlayer(playerId);
}
- private Faction resolveFactionForWarband(Warband warband, UUID playerId) {
- if (!warband.isFaction()) {
- return null;
- }
- Player player = getOnlinePlayer(playerId);
- if (player == null) {
+ private Faction resolveFactionForWarband(Warband warband, Player player) {
+ if (!warband.isFaction() || player == null) {
return null;
}
return FactionManager.getByMember(Members.key(player));
}
- private void detachFromBattle(UUID playerId, Warband warband) {
- Player player = getOnlinePlayer(playerId);
+ private void detachFromBattle(Player player, Warband warband) {
if (player == null) {
return;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandRejoinState.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandRejoinState.java
index 9d90c866..3499e664 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandRejoinState.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandRejoinState.java
@@ -1,18 +1,24 @@
package net.tfminecraft.simplefactions.war.battle.warband;
-import java.util.UUID;
-
import net.tfminecraft.simplefactions.objects.Faction;
+import net.tfminecraft.tlibs.character.CharacterRef;
public final class WarbandRejoinState {
private final String warbandId;
private final String factionId;
private final Warband originalWarband;
+ private final CharacterRef character;
- public WarbandRejoinState(String warbandId, Faction faction) {
+ public WarbandRejoinState(String warbandId, Faction faction, CharacterRef character) {
this.warbandId = warbandId;
this.factionId = faction != null ? faction.getId() : null;
this.originalWarband = WarbandManager.getByString(warbandId);
+ this.character = character;
+ }
+
+ /** Whether the character that quit is the one now playing. */
+ boolean isFor(CharacterRef ref) {
+ return character != null && character.equals(ref);
}
boolean belongsTo(Warband warband) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/AttackerNavalContestService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/AttackerNavalContestService.java
index c10eceaa..d5a6a305 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/AttackerNavalContestService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/AttackerNavalContestService.java
@@ -2,6 +2,7 @@
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.progression.CampaignCoalitionService.CampaignCoalition;
import net.tfminecraft.simplefactions.war.campaign.progression.postbattle.CampaignPostBattleChoiceService;
import java.util.Optional;
@@ -157,13 +158,13 @@ private static void broadcastAutoLoss(War war) {
}
String message = CampaignUiCopy.navalAutoLossBroadcast();
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getAttackers())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getDefenders())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/postbattle/CampaignOffensiveForfeitService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/postbattle/CampaignOffensiveForfeitService.java
index 6e6c4742..bbb51280 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/postbattle/CampaignOffensiveForfeitService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/progression/postbattle/CampaignOffensiveForfeitService.java
@@ -2,6 +2,7 @@
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.progression.CampaignCapabilityService;
import net.tfminecraft.simplefactions.war.campaign.progression.BelligerentRole;
import net.tfminecraft.simplefactions.war.campaign.progression.CampaignCoalitionService.CampaignCoalition;
@@ -82,13 +83,13 @@ private static void broadcastForfeit(
+ winnerLabel
+ " wins by forfeit.";
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getAttackers())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(war.getDefenders())) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidJoinService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidJoinService.java
index b0782747..f6b9618d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidJoinService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidJoinService.java
@@ -77,7 +77,7 @@ public static List listJoinableRaidIds(Faction faction) {
public static JoinResult join(
War war,
UUID playerId,
- String playerName,
+ String memberKey,
Faction faction,
String raidId,
Instant now) {
@@ -111,7 +111,7 @@ public static JoinResult join(
return JoinResult.REJECTED_MOUNTED_ON_VEHICLE;
}
raid.getMusterParticipantIds().add(playerKey);
- CampaignRaidWarbandService.signupAttacker(war, raid, playerId, playerName);
+ CampaignRaidWarbandService.signupAttacker(war, raid, playerId, memberKey);
return JoinResult.OK;
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java
index 339a680b..5ddd820a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidWarbandService.java
@@ -10,9 +10,10 @@
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
-import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
+import net.tfminecraft.tlibs.character.CharacterSessionStartEvent;
+
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.managers.WarManager;
import net.tfminecraft.simplefactions.objects.Faction;
@@ -116,8 +117,8 @@ private static Warband raidWarband(String id, String sideId) {
return band != null && band.isFaction() && sideId.equals(band.getCampaignSideId()) ? band : null;
}
- public static void signupAttacker(War war, CampaignRaid raid, UUID playerId, String playerName) {
- if (war == null || raid == null || playerId == null || playerName == null) {
+ public static void signupAttacker(War war, CampaignRaid raid, UUID playerId, String memberKey) {
+ if (war == null || raid == null || playerId == null || memberKey == null) {
return;
}
createRaidWarbands(war, raid);
@@ -125,12 +126,12 @@ public static void signupAttacker(War war, CampaignRaid raid, UUID playerId, Str
if (warband == null || warband.hasMember(playerId)) {
return;
}
- applyRaidLeaderRules(war, warband, playerId, playerName);
+ applyRaidLeaderRules(war, warband, playerId, memberKey);
warband.addMember(playerId);
}
- public static void signupDefender(War war, CampaignRaid raid, UUID playerId, String playerName) {
- if (war == null || raid == null || playerId == null || playerName == null) {
+ public static void signupDefender(War war, CampaignRaid raid, UUID playerId, String memberKey) {
+ if (war == null || raid == null || playerId == null || memberKey == null) {
return;
}
createRaidWarbands(war, raid);
@@ -138,7 +139,7 @@ public static void signupDefender(War war, CampaignRaid raid, UUID playerId, Str
if (warband == null || warband.hasMember(playerId)) {
return;
}
- applyRaidLeaderRules(war, warband, playerId, playerName);
+ applyRaidLeaderRules(war, warband, playerId, memberKey);
warband.addMember(playerId);
}
@@ -154,14 +155,14 @@ public static void enrollOnlineDefenders(War war, CampaignRaid raid) {
return;
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(defenderSide)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player == null || !player.isOnline()) {
continue;
}
if (WarbandManager.getByMemberId(player.getUniqueId()) != null) {
continue;
}
- signupDefender(war, raid, player.getUniqueId(), player.getName());
+ signupDefender(war, raid, player.getUniqueId(), memberName);
}
}
@@ -191,7 +192,7 @@ public static void tryEnrollDefenderOnLogin(Player player) {
if (playerCoalition != defendingCoalition) {
continue;
}
- signupDefender(war, raid, player.getUniqueId(), player.getName());
+ signupDefender(war, raid, player.getUniqueId(), Members.key(player));
return;
}
}
@@ -239,12 +240,12 @@ private static void ensureWarband(War war, CampaignRaid raid, boolean attacker)
WarbandManager.addWarband(warband);
}
- private static void applyRaidLeaderRules(War war, Warband warband, UUID playerId, String playerName) {
+ private static void applyRaidLeaderRules(War war, Warband warband, UUID playerId, String memberKey) {
if (warband.isPendingLeader()) {
warband.setLeaderId(playerId);
return;
}
- if (CampaignWarbandBattleService.isWarSideMainLeader(war, warband, playerName)) {
+ if (CampaignWarbandBattleService.isWarSideMainLeader(war, warband, memberKey)) {
warband.setLeaderId(playerId);
}
}
@@ -271,8 +272,9 @@ private static void deleteIfPresent(Warband warband) {
public static final class Listener implements org.bukkit.event.Listener {
private final Map pendingLeaderPromotion = new ConcurrentHashMap<>();
+ /** Defenders enrol once their character is playing, not on login. */
@EventHandler
- public void onJoin(PlayerJoinEvent event) {
+ public void onSessionStart(CharacterSessionStartEvent event) {
CampaignRaidWarbandService.tryEnrollDefenderOnLogin(event.getPlayer());
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java
index 7807643e..42a081cb 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/RaidCommandManager.java
@@ -47,7 +47,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
}
JoinResult result = CampaignRaidJoinService.join(
- war, player.getUniqueId(), player.getName(), faction, args[1], CampaignClock.now());
+ war, player.getUniqueId(), Members.key(player), faction, args[1], CampaignClock.now());
if (result != JoinResult.OK) {
String message = CampaignRaidMessages.messageForJoinResult(result);
if (message != null) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleEndService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleEndService.java
index ec0c6751..f2e167cb 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleEndService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleEndService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid.fight;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaid;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidService;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidMessages;
@@ -65,7 +66,7 @@ private static void broadcastToSide(Side side, String message) {
return;
}
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java
index a3c7ad6f..746d3c1d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid.fight;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaid;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidService;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidWarbandService;
@@ -241,7 +242,7 @@ private static void alertDefenders(War war, CampaignRaid raid, Installation targ
: war.getDefenders();
String targetName = target.getName();
for (String memberName : BattleSideMembers.collectEligibleMemberNames(defendingSide)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendTitle("§cRAID INCOMING", "§eDefend " + targetName, 10, 120, 10);
player.playSound(player, Sound.ITEM_GOAT_HORN_SOUND_2, SoundCategory.MASTER, 10f, 0.6f);
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidLaunchService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidLaunchService.java
index b8308ea8..1234241b 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidLaunchService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidLaunchService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid.fight;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaid;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidService;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidResults;
@@ -62,7 +63,7 @@ private static void broadcastRaidStarted(War war, CampaignRaid raid) {
private static void broadcastToSide(Side side, String message) {
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidMusterReminderService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidMusterReminderService.java
index 1b26accd..96556128 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidMusterReminderService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidMusterReminderService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid.fight;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaid;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidService;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidState;
@@ -186,7 +187,7 @@ private static void broadcastToAttackerCoalition(War war, CampaignRaid raid, Str
? war.getAttackers()
: war.getDefenders();
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderService.java
index 9c1ac179..e72625b0 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/intruder/CampaignRaidIntruderService.java
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.war.campaign.raid.intruder;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaid;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidService;
import net.tfminecraft.simplefactions.war.campaign.raid.CampaignRaidState;
@@ -64,7 +65,7 @@ public static void onProvinceEnter(Player player, int provinceId) {
if (raid == null) {
continue;
}
- if (!shouldPenalize(war, raid, player.getUniqueId(), player.getName(), provinceId)) {
+ if (!shouldPenalize(war, raid, player.getUniqueId(), Members.key(player), provinceId)) {
continue;
}
warnOnEnter(player, raid);
@@ -80,7 +81,7 @@ public static void processTick() {
}
for (Player player : Bukkit.getOnlinePlayers()) {
int provinceId = ProvincePresenceService.getInstance().getCurrentProvince(player.getUniqueId());
- if (!shouldPenalize(war, raid, player.getUniqueId(), player.getName(), provinceId)) {
+ if (!shouldPenalize(war, raid, player.getUniqueId(), Members.key(player), provinceId)) {
continue;
}
player.sendMessage(CampaignRaidMessages.INTRUDER);
@@ -93,9 +94,9 @@ static boolean shouldPenalize(
War war,
CampaignRaid raid,
UUID playerId,
- String playerName,
+ String memberKey,
int playerProvinceId) {
- if (war == null || raid == null || playerId == null || playerName == null) {
+ if (war == null || raid == null || playerId == null || memberKey == null) {
return false;
}
if (raid.getState() != CampaignRaidState.FIGHTING) {
@@ -108,7 +109,7 @@ static boolean shouldPenalize(
if (target == null || playerProvinceId != target.getProvince()) {
return false;
}
- Faction faction = FactionManager.getByMember(playerName);
+ Faction faction = FactionManager.getByMember(memberKey);
CampaignCoalition coalition = CampaignRaidService.coalitionForFaction(war, faction);
if (coalition == null || coalition != raid.getAttackerCoalition()) {
return false;
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java
index 8da5b940..cb5d6ea0 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java
@@ -86,7 +86,7 @@ public static SendResult sendProposeRequest(Player proposer, War war, Belligeren
Faction opposingLeaderFaction = opposingLeaderFaction(war, side);
- Player target = Bukkit.getPlayerExact(opposingLeaderFaction.getLeader());
+ Player target = Members.player(opposingLeaderFaction.getLeader());
if (target == null || !target.isOnline()) {
return SendResult.OPPOSING_LEADER_OFFLINE;
}
@@ -135,7 +135,7 @@ public static void acceptRequest(Player acceptor) {
Faction proposerFaction = req.getFaction();
if (proposerFaction != null) {
- Player proposer = Bukkit.getPlayerExact(proposerFaction.getLeader());
+ Player proposer = Members.player(proposerFaction.getLeader());
if (proposer != null && proposer.isOnline()) {
proposer.sendMessage(acceptorFaction.getName() + " §aaccepted autoresolve");
}
@@ -331,7 +331,7 @@ private static String winnerLabel(War war, CampaignCoalition winner) {
private static void sendToSide(Side side, String message) {
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player player = Bukkit.getPlayerExact(memberName);
+ Player player = Members.player(memberName);
if (player != null && player.isOnline()) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java
index 3442f2d7..e6236d0f 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleScheduleLookups.java
@@ -47,17 +47,16 @@ public static Function uuidToFactionForWar(War war) {
};
}
- // Existing configuration identifies offline profiles by player name, not UUID.
- @SuppressWarnings("deprecation")
+ /** Battle votes stay keyed by account, so a member key maps to the account that plays it. */
public static Function memberNameToUuid() {
- return name -> {
- if (name == null || name.isBlank()) {
+ return key -> {
+ if (key == null || key.isBlank()) {
return null;
}
if (Bukkit.getServer() == null) {
- return spoofMemberUuid(name);
+ return spoofMemberUuid(key);
}
- return Bukkit.getOfflinePlayer(name).getUniqueId();
+ return Members.accountId(key).orElse(null);
};
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/pick/BattleInstallationPickService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/pick/BattleInstallationPickService.java
index 0df02798..e020d5f2 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/pick/BattleInstallationPickService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/pick/BattleInstallationPickService.java
@@ -35,15 +35,15 @@ private BattleInstallationPickService() {}
public static InstallationPickToggleResult togglePick(
War war,
Faction faction,
- String playerName,
+ String memberKey,
String installationId) {
- return togglePick(war, faction, playerName, installationId, CampaignClock.now());
+ return togglePick(war, faction, memberKey, installationId, CampaignClock.now());
}
public static InstallationPickToggleResult togglePick(
War war,
Faction faction,
- String playerName,
+ String memberKey,
String installationId,
Instant now) {
if (war == null || !war.isActive()) {
@@ -52,7 +52,7 @@ public static InstallationPickToggleResult togglePick(
if (faction == null || war.getSide(faction) == null) {
return InstallationPickToggleResult.REJECTED_NOT_PARTICIPANT;
}
- if (playerName == null || !faction.isLeader(playerName)) {
+ if (memberKey == null || !faction.isLeader(memberKey)) {
return InstallationPickToggleResult.REJECTED_NOT_LEADER;
}
if (installationId == null || installationId.isBlank()) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarStartService.java b/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarStartService.java
index a82e5599..9b639c83 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarStartService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarStartService.java
@@ -393,7 +393,7 @@ private static void moveToRebelMain(Faction host, Faction rebels, String player,
private static void recordMemberMove(AppliedStart applied, CivilWarMemberMove move) {
for (CivilWarMemberMove existing : applied.memberMoves) {
- if (existing != null && move.player().equalsIgnoreCase(existing.player())) {
+ if (existing != null && move.player().equals(existing.player())) {
return;
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarTempRebelFactory.java b/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarTempRebelFactory.java
index 996f14dd..99b3b495 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarTempRebelFactory.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/civilwar/CivilWarTempRebelFactory.java
@@ -13,12 +13,12 @@ public final class CivilWarTempRebelFactory {
private CivilWarTempRebelFactory() {}
- public static Faction create(Faction host, String leaderName) {
- if (host == null || leaderName == null) {
+ public static Faction create(Faction host, String leaderKey) {
+ if (host == null || leaderKey == null) {
return null;
}
String id = uniqueId(host);
- Faction rebels = new Faction(id, leaderName);
+ Faction rebels = new Faction(id, leaderKey);
applyRebelIdentity(rebels, host);
FactionManager.addFaction(rebels);
return rebels;
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/civilwar/wartime/CivilWarUntangleService.java b/src/main/java/net/tfminecraft/simplefactions/war/civilwar/wartime/CivilWarUntangleService.java
index 88b471d0..8efc0380 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/civilwar/wartime/CivilWarUntangleService.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/civilwar/wartime/CivilWarUntangleService.java
@@ -154,7 +154,7 @@ private static void restoreMembers(Faction host, CivilWarSnapshot snapshot, WarE
}
boolean wantedLeader = attackerWin
&& wanted != null
- && wanted.equalsIgnoreCase(move.player());
+ && wanted.equals(move.player());
host.getGuildHandler().forceKick(move.player());
if (wantedLeader) {
host.getOrCreateMainGuild().addMember(move.player());
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/freeze/PreparationFreeze.java b/src/main/java/net/tfminecraft/simplefactions/war/freeze/PreparationFreeze.java
index 957ae6b9..b7104579 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/freeze/PreparationFreeze.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/freeze/PreparationFreeze.java
@@ -1,5 +1,6 @@
package net.tfminecraft.simplefactions.war.freeze;
+import net.tfminecraft.simplefactions.identity.Members;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
@@ -101,12 +102,12 @@ public static boolean isFrozen(MercenaryCompany company) {
}
/** Freeze covering a player's own projects, through their faction or their company. */
- public static Instant frozenUntil(String playerName, Instant now) {
- if (playerName == null || !anyWarFrozen(now)) {
+ public static Instant frozenUntil(String memberKey, Instant now) {
+ if (memberKey == null || !anyWarFrozen(now)) {
return null;
}
- Instant latest = frozenUntil(FactionManager.getByMember(playerName), now);
- return later(latest, frozenUntil(MercenaryCompanies.findByMember(playerName), now));
+ Instant latest = frozenUntil(FactionManager.getByMember(memberKey), now);
+ return later(latest, frozenUntil(MercenaryCompanies.findByMember(memberKey), now));
}
/** Refusal for a hire by this faction, or null when hiring is open. */
@@ -173,7 +174,7 @@ private static void notifyMembers(War war, long hours) {
String message = "§eThe battle was postponed. For the next " + hours + "h your regiments, installations, "
+ "war upgrades, mercenary companies and vehicle projects are frozen, and no mercenaries can be hired.";
for (String member : members) {
- Player player = Bukkit.getPlayerExact(member);
+ Player player = Members.player(member);
if (player != null) {
player.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreeze.java b/src/main/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreeze.java
index daaec6c7..34d67800 100644
--- a/src/main/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreeze.java
+++ b/src/main/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreeze.java
@@ -4,10 +4,12 @@
import java.util.UUID;
import org.bukkit.Bukkit;
-import org.bukkit.OfflinePlayer;
+import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.Characters;
import net.tfminecraft.vfbuilders.api.ConstructionFreeze;
/** Holds VFBuilders projects started by players whose battle was postponed. */
@@ -25,12 +27,20 @@ public static void register(Plugin plugin) {
@Override
public String freezeReason(UUID constructorUuid) {
- OfflinePlayer player = Bukkit.getOfflinePlayer(constructorUuid);
Instant now = Instant.now();
- Instant until = PreparationFreeze.frozenUntil(player.getName(), now);
+ Instant until = PreparationFreeze.frozenUntil(constructorKey(constructorUuid), now);
if (until == null) {
return null;
}
return "battle postponed (" + PreparationFreeze.formatRemaining(until, now) + ")";
}
+
+ /** Projects belong to accounts: the character playing now, else the one played last. */
+ static String constructorKey(UUID constructorUuid) {
+ Player online = Bukkit.getPlayer(constructorUuid);
+ if (online != null) {
+ return Members.key(online);
+ }
+ return Characters.lastPlayed(constructorUuid).map(info -> Members.key(info.ref())).orElse(null);
+ }
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinServiceTest.java
index 5e66a625..0ef35f07 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinServiceTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleJoinServiceTest.java
@@ -194,7 +194,7 @@ void campaignRejoin_allowedWhenRosterHasRoom() {
BattleManager.addBattle(battle);
WarbandManager.addWarband(warband);
- WarbandRejoinState state = new WarbandRejoinState(warband.getId(), attacker);
+ WarbandRejoinState state = new WarbandRejoinState(warband.getId(), attacker, null);
WarbandMembershipService service = WarbandMembershipService.getInstance();
try (MockedStatic factions = mockStatic(FactionManager.class);
@@ -207,7 +207,7 @@ void campaignRejoin_allowedWhenRosterHasRoom() {
.thenReturn(5);
bukkit.when(() -> Bukkit.getPlayer(any(UUID.class))).thenReturn(null);
- assertTrue(service.evaluateRejoin(warband, leaderId, attacker, state));
+ assertTrue(service.evaluateRejoin(warband, leaderId, "leader", attacker, state));
}
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterBoundaryCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterBoundaryCoverageTest.java
index 32b2d53e..fdf42450 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterBoundaryCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterBoundaryCoverageTest.java
@@ -25,6 +25,7 @@
import org.bukkit.OfflinePlayer;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerJoinEvent;
+import net.tfminecraft.tlibs.character.CharacterSessionStartEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
@@ -100,7 +101,7 @@ void unfinishedAndOrdinaryFieldBattlesCannotBeEndedByTheRaidRule() {
}
@Test
- void quitAndJoinEventsRestoreAnOpenManualRosterWithoutDuplicatingMembers() {
+ void quitAndSessionStartEventsRestoreAnOpenManualRosterWithoutDuplicatingMembers() {
Warband band = new Warband("event_rejoin", rig.alice);
band.setLocked(false);
WarbandManager.addWarband(band);
@@ -108,11 +109,20 @@ void quitAndJoinEventsRestoreAnOpenManualRosterWithoutDuplicatingMembers() {
listener.onQuit(new PlayerQuitEvent(rig.alice, "left"));
assertFalse(band.hasMember(rig.alice));
assertNotNull(WarbandMembershipService.getInstance().getPendingRejoin(rig.alice.getUniqueId()));
- listener.onJoin(new PlayerJoinEvent(rig.alice, "joined"));
+ // Logging in alone restores nothing: the listener only answers character sessions.
+ assertTrue(
+ java.util.Arrays.stream(WarbandMembershipListener.class.getDeclaredMethods())
+ .noneMatch(
+ method ->
+ method.getParameterCount() == 1
+ && method.getParameterTypes()[0] == PlayerJoinEvent.class));
+ assertFalse(band.hasMember(rig.alice));
+ var session = net.tfminecraft.simplefactions.identity.Members.session(rig.alice).orElseThrow();
+ listener.onSessionStart(new CharacterSessionStartEvent(rig.alice, session));
assertTrue(band.hasMember(rig.alice));
assertEquals(1, band.getMemberCount());
assertNull(WarbandMembershipService.getInstance().getPendingRejoin(rig.alice.getUniqueId()));
- listener.onJoin(new PlayerJoinEvent(rig.alice, "joined"));
+ listener.onSessionStart(new CharacterSessionStartEvent(rig.alice, session));
assertEquals(1, band.getMemberCount());
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterLifecycleCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterLifecycleCoverageTest.java
index 55501026..4ac05f65 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterLifecycleCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/BattleRosterLifecycleCoverageTest.java
@@ -41,7 +41,6 @@
import net.tfminecraft.vehicleframework.vehicles.ActiveVehicle;
import org.bukkit.Bukkit;
import org.bukkit.Location;
-import org.bukkit.OfflinePlayer;
import org.bukkit.boss.BossBar;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
@@ -75,6 +74,10 @@ void setup() throws Exception {
membership = WarbandMembershipService.getInstance();
}
+ private static net.tfminecraft.tlibs.character.CharacterSession session(Player player) {
+ return net.tfminecraft.simplefactions.identity.Members.session(player).orElseThrow();
+ }
+
@AfterEach
void cleanup() throws Exception {
if (rig != null) rig.close();
@@ -97,11 +100,11 @@ void quitDetachesThePlayerAndJoiningRestoresTheSameOpenRosterAndBossBar() {
assertFalse(life.getPlayers().contains(rig.alice));
assertEquals(band.getId(), membership.getPendingRejoin(rig.alice.getUniqueId()).getWarbandId());
assertFalse(membership.getPendingRejoin(rig.alice.getUniqueId()).hasFaction());
- assertTrue(membership.handleJoin(rig.alice));
+ assertTrue(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertTrue(band.hasMember(rig.alice));
assertTrue(life.getPlayers().contains(rig.alice));
assertNull(membership.getPendingRejoin(rig.alice.getUniqueId()));
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
}
@Test
@@ -111,7 +114,7 @@ void aDeletedWarbandCannotTransferItsPendingMemberIntoAnUnrelatedReplacement() {
WarbandManager.deleteWarband(original);
Warband replacement = openBand("reused_name", rig.bob);
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertEquals(Set.of(rig.bob.getUniqueId()), new HashSet<>(replacement.getMemberIds()));
assertNull(membership.getPendingRejoin(rig.alice.getUniqueId()));
}
@@ -123,12 +126,12 @@ void lockedRejoinRequiresAStillValidInvitation() {
band.invite(rig.alice);
band.setLocked(true);
membership.handleQuit(rig.alice.getUniqueId());
- assertTrue(membership.handleJoin(rig.alice));
+ assertTrue(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertTrue(band.hasMember(rig.alice));
membership.handleQuit(rig.alice.getUniqueId());
band.uninvite(rig.alice);
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertFalse(band.hasMember(rig.alice));
assertTrue(band.hasMember(rig.bob));
}
@@ -139,11 +142,11 @@ void deletedOrExplicitlyClearedRejoinStateDoesNotCreateMembership() {
Warband band = openBand("deleted", rig.alice);
membership.handleQuit(rig.alice.getUniqueId());
WarbandManager.deleteWarband(band);
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
Warband another = openBand("cleared", rig.alice);
membership.handleQuit(rig.alice.getUniqueId());
membership.clearPendingRejoin(rig.alice.getUniqueId());
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertFalse(another.hasMember(rig.alice));
}
@@ -161,7 +164,7 @@ void membershipSingletonStartsEmptyAndResetDiscardsOnlyItsPendingSessions() thro
assertNotSame(fresh, WarbandMembershipService.getInstance());
assertNull(WarbandMembershipService.getInstance().getPendingRejoin(rig.alice.getUniqueId()));
assertFalse(band.hasMember(rig.alice));
- assertTrue(fresh.handleJoin(rig.alice));
+ assertTrue(fresh.handleSessionStart(rig.alice, session(rig.alice)));
assertTrue(band.hasMember(rig.alice));
}
@@ -180,7 +183,7 @@ void quittingAnUnassignedWarbandLeavesOtherBattleRostersAndBarsUntouched() {
assertTrue(otherBattle.hasStarted());
assertTrue(other.hasMember(other.getLeaderId()));
for (BossBar bar : rig.bars) verify(bar, never()).removePlayer(rig.alice);
- assertTrue(membership.handleJoin(rig.alice));
+ assertTrue(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertNull(BattleManager.getBattleByMemberId(rig.alice.getUniqueId()));
}
@@ -229,7 +232,7 @@ void campaignMembersRejoinTheSameFactionBeforeAndDuringBattle(boolean started) {
membership.handleQuit(rig.alice.getUniqueId());
assertEquals(
rig.attacker.getId(), membership.getPendingRejoin(rig.alice.getUniqueId()).getFactionId());
- assertTrue(membership.handleJoin(rig.alice));
+ assertTrue(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertTrue(band.hasMember(rig.alice));
}
@@ -239,25 +242,22 @@ void pendingFactionRosterRejectsAFullSideAndADeletedFaction() {
Battle battle = battle("limited_battle", band, true);
membership.handleQuit(rig.alice.getUniqueId());
for (int i = 0; i < 4; i++) band.addMember(UUID.randomUUID());
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertFalse(band.hasMember(rig.alice));
band.addPlayer(rig.alice);
membership.handleQuit(rig.alice.getUniqueId());
FactionManager.factions.remove(rig.attacker);
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertFalse(band.hasMember(rig.alice));
assertFalse(battle.hasStarted());
}
@Test
- void factionRejoinUsesSavedOfflineNameWhenTheOnlineLookupHasGoneAway() {
+ void factionRejoinUsesTheCapturedMemberKeyWhenTheOnlineLookupHasGoneAway() {
Warband band = factionBand("offline_name");
Battle battle = battle("offline_name_battle", band, true);
assertNull(battle.start());
membership.handleQuit(rig.alice.getUniqueId());
- OfflinePlayer profile = mock(OfflinePlayer.class);
- when(profile.getName()).thenReturn("Alice");
- when(Bukkit.getOfflinePlayer(rig.alice.getUniqueId())).thenReturn(profile);
when(Bukkit.getPlayer(rig.alice.getUniqueId())).thenReturn(null);
assertTrue(membership.attemptRejoin(rig.alice.getUniqueId(), "Alice", null));
assertTrue(band.hasMember(rig.alice));
@@ -266,41 +266,41 @@ void factionRejoinUsesSavedOfflineNameWhenTheOnlineLookupHasGoneAway() {
membership.evaluateRejoin(
band,
rig.alice.getUniqueId(),
+ "Alice",
rig.defender,
- new WarbandRejoinState(band.getId(), rig.attacker)));
+ new WarbandRejoinState(band.getId(), rig.attacker, null)));
}
@Test
- void factionMembershipChangesAndAnUnavailableOfflineNameRefuseRejoin() {
+ void factionMembershipChangesAndAMissingMemberKeyRefuseRejoin() {
Warband band = factionBand("changed_membership");
Player member = rig.player("Member");
rig.attacker.addMember("Member");
band.addPlayer(member);
membership.handleQuit(member.getUniqueId());
rig.attacker.forceRemoveMember("Member");
- assertFalse(membership.handleJoin(member));
+ assertFalse(membership.handleSessionStart(member, session(member)));
assertFalse(band.hasMember(member));
assertTrue(band.hasMember(rig.alice));
membership.handleQuit(rig.alice.getUniqueId());
assertTrue(
- membership.handleJoin(rig.alice), "An unchanged faction can rejoin a standalone band");
+ membership.handleSessionStart(rig.alice, session(rig.alice)), "An unchanged faction can rejoin a standalone band");
Battle battle = battle("unknown_profile_battle", band, true);
assertNull(battle.start());
membership.handleQuit(rig.alice.getUniqueId());
when(Bukkit.getPlayer(rig.alice.getUniqueId())).thenReturn(null);
- when(Bukkit.getOfflinePlayer(rig.alice.getUniqueId())).thenReturn(mock(OfflinePlayer.class));
- assertFalse(membership.attemptRejoin(rig.alice.getUniqueId(), "Alice", null));
+ assertFalse(membership.attemptRejoin(rig.alice.getUniqueId(), null, null));
assertFalse(band.hasMember(rig.alice));
}
@Test
- void quittingWithoutAnOnlineProfileDoesNotInventARejoinFaction() {
+ void quittingWithoutAnOnlineCharacterLeavesNoRejoinState() {
Warband band = factionBand("offline_quit");
rig.domain.online.remove("Alice");
membership.handleQuit(rig.alice.getUniqueId());
- assertFalse(membership.getPendingRejoin(rig.alice.getUniqueId()).hasFaction());
+ assertNull(membership.getPendingRejoin(rig.alice.getUniqueId()));
rig.domain.online.put("Alice", rig.alice);
- assertFalse(membership.handleJoin(rig.alice));
+ assertFalse(membership.handleSessionStart(rig.alice, session(rig.alice)));
assertFalse(band.hasMember(rig.alice));
}
@@ -365,7 +365,7 @@ void raidMusterBlocksVehicleEntryAndTheStartedFightReleasesIt() {
assertTrue(defenders.hasMember(rig.bob));
membership.handleQuit(rig.bob.getUniqueId());
rig.bars.forEach(bar -> clearInvocations(bar));
- assertTrue(membership.handleJoin(rig.bob));
+ assertTrue(membership.handleSessionStart(rig.bob, session(rig.bob)));
assertTrue(defenders.hasMember(rig.bob));
for (BossBar bar : rig.bars) verify(bar, never()).addPlayer(rig.bob);
fighting.end();
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipServiceTest.java
index c06e080f..ec6e8b3b 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipServiceTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/battle/warband/WarbandMembershipServiceTest.java
@@ -11,10 +11,14 @@
import java.lang.reflect.Field;
import java.util.UUID;
+import org.bukkit.entity.Player;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.objects.Faction;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
import net.tfminecraft.simplefactions.war.core.War;
import net.tfminecraft.simplefactions.war.battle.template.BattleTemplate;
@@ -22,6 +26,7 @@ class WarbandMembershipServiceTest {
private UUID leaderId;
private UUID memberId;
private WarbandMembershipService service;
+ private Player member;
@BeforeEach
void setUp() {
@@ -30,13 +35,16 @@ void setUp() {
leaderId = UUID.randomUUID();
memberId = UUID.randomUUID();
service = WarbandMembershipService.getInstance();
+ member = mock(Player.class);
+ when(member.getUniqueId()).thenReturn(memberId);
+ when(member.getName()).thenReturn("member");
}
@Test
void quit_savesRejoinStateAndRemovesMember() {
Warband warband = createOpenWarband("alpha", leaderId, memberId);
- service.handleQuit(memberId);
+ service.handleQuit(member);
assertNull(WarbandManager.getByMemberId(memberId));
WarbandRejoinState state = service.getPendingRejoin(memberId);
@@ -49,7 +57,7 @@ void quit_savesRejoinStateAndRemovesMember() {
@Test
void join_restoresWhenWarbandStillOpen() {
createOpenWarband("alpha", leaderId, memberId);
- service.handleQuit(memberId);
+ service.handleQuit(member);
assertTrue(service.attemptRejoin(memberId, "member", null));
assertNull(service.getPendingRejoin(memberId));
@@ -59,7 +67,7 @@ void join_restoresWhenWarbandStillOpen() {
@Test
void join_skipsWhenWarbandDeleted() {
Warband warband = createOpenWarband("alpha", leaderId, memberId);
- service.handleQuit(memberId);
+ service.handleQuit(member);
WarbandManager.deleteWarband(warband);
assertFalse(service.attemptRejoin(memberId, "member", null));
@@ -76,9 +84,63 @@ void evaluateRejoin_factionMismatchBlocksRejoin() throws Exception {
Warband warband = createOpenWarband("alpha", leaderId, memberId);
markAsFaction(warband);
- WarbandRejoinState state = new WarbandRejoinState("alpha", slotFaction);
+ WarbandRejoinState state = new WarbandRejoinState("alpha", slotFaction, null);
+
+ assertFalse(service.evaluateRejoin(warband, memberId, "member", otherFaction, state));
+ }
+
+ @Test
+ void rejoinNeedsPendingStateAndAFactionCapturedForAFactionWarband() throws Exception {
+ Warband warband = createOpenWarband("alpha", leaderId, memberId);
+ markAsFaction(warband);
+
+ assertFalse(service.attemptRejoin(memberId, "member", null));
+ assertFalse(service.evaluateRejoin(
+ warband, memberId, "member", null, new WarbandRejoinState("alpha", null, null)));
+ }
+
+ @Test
+ void quitWithoutACharacterLeavesNoRejoinState() {
+ Warband warband = createOpenWarband("alpha", leaderId, memberId);
+
+ service.handleQuit((Player) null);
+ assertTrue(warband.hasMember(memberId));
+ service.handleQuit(memberId);
+
+ assertFalse(warband.hasMember(memberId));
+ assertNull(service.getPendingRejoin(memberId));
+ }
+
+ @Test
+ void sessionStart_restoresTheSameCharacter() {
+ Warband warband = createOpenWarband("alpha", leaderId, memberId);
+ service.handleQuit(member);
+
+ assertTrue(service.handleSessionStart(member, Members.session(member).orElseThrow()));
+ assertTrue(warband.hasMember(memberId));
+ assertNull(service.getPendingRejoin(memberId));
+ }
+
+ @Test
+ void sessionStart_dropsTheStateForAnotherCharacterOnTheSameAccount() {
+ Warband warband = createOpenWarband("alpha", leaderId, memberId);
+ service.handleQuit(member);
+ CharacterSession other = new CharacterSession(new CharacterRef(memberId, UUID.randomUUID()), 2L);
+
+ assertFalse(service.handleSessionStart(member, other));
+ assertNull(service.getPendingRejoin(memberId));
+ assertFalse(warband.hasMember(memberId));
+ assertFalse(service.handleSessionStart(member, Members.session(member).orElseThrow()));
+ assertFalse(warband.hasMember(memberId));
+ }
+
+ @Test
+ void sessionStart_ignoresMissingArgumentsAndPlayersWithNothingPending() {
+ CharacterSession session = Members.session(member).orElseThrow();
- assertFalse(service.evaluateRejoin(warband, memberId, otherFaction, state));
+ assertFalse(service.handleSessionStart(null, session));
+ assertFalse(service.handleSessionStart(member, null));
+ assertFalse(service.handleSessionStart(member, session));
}
// Exercise the retained compatibility entry point so legacy behavior stays covered.
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidLifecycleCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidLifecycleCoverageTest.java
index dd9d42ac..203626b5 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidLifecycleCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/campaign/raid/CampaignRaidLifecycleCoverageTest.java
@@ -483,9 +483,9 @@ void defenderLoginEnrollsOnlyAnEligibleFreeDefenderDuringTheFight() {
Warband existing = new Warband("lifecycle_existing", reserved);
WarbandManager.addWarband(existing);
var listener = new CampaignRaidWarbandService.Listener();
- listener.onJoin(
- new org.bukkit.event.player.PlayerJoinEvent(
- late, net.kyori.adventure.text.Component.empty()));
+ listener.onSessionStart(
+ new net.tfminecraft.tlibs.character.CharacterSessionStartEvent(
+ late, net.tfminecraft.simplefactions.identity.Members.session(late).orElseThrow()));
assertNotNull(CampaignRaidWarbandService.getDefenderWarband(raid));
assertEquals(0, CampaignRaidWarbandService.getDefenderWarband(raid).getRealMemberCount());
CampaignRaidLaunchService.startFight(rig.war, raid.getMusterEndsAt());
@@ -493,9 +493,9 @@ void defenderLoginEnrollsOnlyAnEligibleFreeDefenderDuringTheFight() {
assertFalse(defenders.hasMember(late));
assertFalse(defenders.hasMember(reserved));
when(late.isOnline()).thenReturn(true);
- listener.onJoin(
- new org.bukkit.event.player.PlayerJoinEvent(
- late, net.kyori.adventure.text.Component.empty()));
+ listener.onSessionStart(
+ new net.tfminecraft.tlibs.character.CharacterSessionStartEvent(
+ late, net.tfminecraft.simplefactions.identity.Members.session(late).orElseThrow()));
assertTrue(defenders.hasMember(late));
assertSame(existing, WarbandManager.getByPlayer(reserved));
CampaignRaidWarbandService.tryEnrollDefenderOnLogin(rig.alice);
@@ -1595,9 +1595,9 @@ void reverseRaidLeaderAuthorityAndLateLoginFollowTheWarbandCoalition(boolean sch
when(rig.alice.isOnline()).thenReturn(true);
new CampaignRaidWarbandService.Listener()
- .onJoin(
- new org.bukkit.event.player.PlayerJoinEvent(
- rig.alice, net.kyori.adventure.text.Component.empty()));
+ .onSessionStart(
+ new net.tfminecraft.tlibs.character.CharacterSessionStartEvent(
+ rig.alice, net.tfminecraft.simplefactions.identity.Members.session(rig.alice).orElseThrow()));
assertAll(
() -> assertTrue(CampaignWarbandBattleService.isWarSideMainLeader(rig.war, raiders, "Bob")),
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/campaign/schedule/CampaignScheduleLifecycleCoverageTest.java b/src/test/java/net/tfminecraft/simplefactions/war/campaign/schedule/CampaignScheduleLifecycleCoverageTest.java
index 12cad4af..9955dc9f 100644
--- a/src/test/java/net/tfminecraft/simplefactions/war/campaign/schedule/CampaignScheduleLifecycleCoverageTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/war/campaign/schedule/CampaignScheduleLifecycleCoverageTest.java
@@ -422,6 +422,48 @@ void trimmerPreservesRequiredFightsAndBorderWhileRemovingOptionalKindsFromEachLe
CampaignScheduleTrimmer.maxBattlesForGoal(null));
}
+ /** Name-keyed members, plus one offline character the provider knows. */
+ private static void knowCharacter(String key, UUID account) {
+ var names = net.tfminecraft.simplefactions.testsupport.NameKeyedMembers.INSTANCE;
+ net.tfminecraft.simplefactions.identity.Members.setResolver(
+ new net.tfminecraft.simplefactions.identity.Members.Resolver() {
+ @Override
+ public java.util.Optional session(
+ org.bukkit.entity.Player player) {
+ return names.session(player);
+ }
+
+ @Override
+ public boolean isCurrent(net.tfminecraft.tlibs.character.CharacterSession session) {
+ return true;
+ }
+
+ @Override
+ public String key(org.bukkit.entity.Player player) {
+ return names.key(player);
+ }
+
+ @Override
+ public java.util.Optional info(String other) {
+ if (!key.equals(other)) return java.util.Optional.empty();
+ return java.util.Optional.of(
+ new net.tfminecraft.tlibs.character.CharacterInfo(
+ new net.tfminecraft.tlibs.character.CharacterRef(account, UUID.fromString(key)),
+ "Offline",
+ net.tfminecraft.tlibs.character.CharacterStatus.ALIVE,
+ null,
+ Set.of(),
+ 0,
+ Instant.EPOCH));
+ }
+
+ @Override
+ public java.util.Optional online(String other) {
+ return names.online(other);
+ }
+ });
+ }
+
@Test
void rosterLookupsResolveOnlineOfflineAndFallbackIdentitiesWithoutInventingMembership() {
var live = BattleScheduleLookups.uuidToFaction();
@@ -439,9 +481,13 @@ void rosterLookupsResolveOnlineOfflineAndFallbackIdentitiesWithoutInventingMembe
var carol = rig.player("Carol");
assertSame(
outsider, BattleScheduleLookups.uuidToFactionForWar(rig.war).apply(carol.getUniqueId()));
- rig.attacker.getOrCreateMainGuild().addMember("Offline");
- UUID offline = BattleScheduleLookups.memberNameToUuid().apply("Offline");
- assertSame(rig.attacker, BattleScheduleLookups.uuidToFactionForWar(rig.war).apply(offline));
+ UUID account = UUID.randomUUID();
+ String offlineKey = UUID.randomUUID().toString();
+ knowCharacter(offlineKey, account);
+ rig.attacker.getOrCreateMainGuild().addMember(offlineKey);
+ assertEquals(account, BattleScheduleLookups.memberNameToUuid().apply(offlineKey));
+ assertNull(BattleScheduleLookups.memberNameToUuid().apply("Unknown"));
+ assertSame(rig.attacker, BattleScheduleLookups.uuidToFactionForWar(rig.war).apply(account));
when(Bukkit.getServer()).thenReturn(null);
assertEquals(
BattleScheduleLookups.spoofMemberUuid("Offline"),
@@ -478,7 +524,7 @@ void lifecycleTransitionsClearPriorTargetsAndPreserveVoteMembershipAndCalendarRu
BattleScheduleService.castSpoofVotes(
rig.war,
21,
- BattleScheduleLookups.memberNameToUuid(),
+ BattleScheduleLookups.spoofMemberNameToUuid(),
BelligerentRole.ATTACKER,
BelligerentRole.DEFENDER);
assertEquals(2, added);
@@ -487,7 +533,7 @@ void lifecycleTransitionsClearPriorTargetsAndPreserveVoteMembershipAndCalendarRu
BattleScheduleService.castSpoofVotes(
rig.war,
21,
- BattleScheduleLookups.memberNameToUuid(),
+ BattleScheduleLookups.spoofMemberNameToUuid(),
BelligerentRole.ATTACKER,
BelligerentRole.DEFENDER));
assertEquals(
diff --git a/src/test/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreezeTest.java b/src/test/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreezeTest.java
new file mode 100644
index 00000000..39b62305
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/war/freeze/VfBuildersConstructionFreezeTest.java
@@ -0,0 +1,77 @@
+package net.tfminecraft.simplefactions.war.freeze;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.ServicesManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterIdentity;
+import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterStatus;
+
+class VfBuildersConstructionFreezeTest {
+ private final UUID account = UUID.randomUUID();
+ private final ServicesManager services = mock(ServicesManager.class);
+ private MockedStatic bukkit;
+
+ @BeforeEach
+ void setUp() {
+ bukkit = mockStatic(Bukkit.class);
+ bukkit.when(Bukkit::isPrimaryThread).thenReturn(true);
+ bukkit.when(Bukkit::getServicesManager).thenReturn(services);
+ PreparationFreeze.setActiveWars(List::of);
+ }
+
+ @AfterEach
+ void tearDown() {
+ PreparationFreeze.setActiveWars(null);
+ bukkit.close();
+ }
+
+ @Test
+ void anOnlineBuilderIsJudgedByTheCharacterPlayingNow() {
+ Player builder = mock(Player.class);
+ when(builder.getName()).thenReturn("Builder");
+ bukkit.when(() -> Bukkit.getPlayer(account)).thenReturn(builder);
+
+ assertEquals("Builder", VfBuildersConstructionFreeze.constructorKey(account));
+ assertNull(new VfBuildersConstructionFreeze().freezeReason(account));
+ }
+
+ @Test
+ void anOfflineBuilderIsJudgedByTheCharacterPlayedLast() {
+ UUID characterId = UUID.randomUUID();
+ CharacterIdentity identity = mock(CharacterIdentity.class);
+ when(services.load(CharacterIdentity.class)).thenReturn(identity);
+ when(identity.lastPlayed(any())).thenReturn(Optional.empty());
+ when(identity.lastPlayed(account)).thenReturn(Optional.of(new CharacterInfo(
+ new CharacterRef(account, characterId), "Hilde", CharacterStatus.ALIVE, null, Set.of(), 0,
+ Instant.EPOCH)));
+
+ assertEquals(Members.key(characterId), VfBuildersConstructionFreeze.constructorKey(account));
+ assertNull(VfBuildersConstructionFreeze.constructorKey(UUID.randomUUID()));
+ }
+
+ @Test
+ void anOfflineBuilderWithoutTheCharacterProviderHasNoCharacter() {
+ assertNull(VfBuildersConstructionFreeze.constructorKey(account));
+ }
+}
From 5204182e3e7751050f5615fe963d43435efdd257 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:24:27 +0000
Subject: [PATCH 06/35] wip: tests for members, migration and quarantine
---
.../identity/LeaderCharactersTest.java | 162 +++---
.../simplefactions/identity/MembersTest.java | 228 ++++++++
.../identity/SharedLeaderCharactersTest.java | 273 ++--------
.../migration/CharacterMigrationTest.java | 493 ++++++++++++++++++
.../migration/QuarantineCommandsTest.java | 161 ++++++
.../testsupport/SharedIdentity.java | 165 ++++++
6 files changed, 1168 insertions(+), 314 deletions(-)
create mode 100644 src/test/java/net/tfminecraft/simplefactions/identity/MembersTest.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/migration/CharacterMigrationTest.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/migration/QuarantineCommandsTest.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/testsupport/SharedIdentity.java
diff --git a/src/test/java/net/tfminecraft/simplefactions/identity/LeaderCharactersTest.java b/src/test/java/net/tfminecraft/simplefactions/identity/LeaderCharactersTest.java
index 500d2853..aed9072f 100644
--- a/src/test/java/net/tfminecraft/simplefactions/identity/LeaderCharactersTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/identity/LeaderCharactersTest.java
@@ -26,6 +26,7 @@
import net.tfminecraft.tlibs.character.CharacterRef;
import net.tfminecraft.tlibs.character.CharacterSession;
import net.tfminecraft.tlibs.character.CharacterSessionStartEvent;
+import net.tfminecraft.simplefactions.testsupport.SharedIdentity;
class LeaderCharactersTest {
@@ -50,7 +51,7 @@ void readsTheActiveCharacterOfAnOnlineLeader() {
void keepsTheRememberedNameWhileTheLeaderIsOffline() {
online(Map.of());
LeaderCharacters.Remembered result =
- LeaderCharacters.resolve("rushork", "Grunk the Bold", "RUSHORK");
+ LeaderCharacters.resolve("rushork", "Grunk the Bold", "rushork");
assertEquals("Grunk the Bold", result.name());
}
@@ -99,7 +100,7 @@ void withoutRpCharactersARememberedNameIsDropped() {
void anUnavailableProbeIsNotMistakenForAnOfflineLeader() {
LeaderCharacters.setProbe(new LeaderCharacters.Probe() {
@Override
- public String activeCharacterName(String player) {
+ public String characterName(String key) {
return null;
}
@@ -141,94 +142,97 @@ void refreshingLeavesARealmsOwnGuildToTheRealm() {
}
@Test
- void sharedStartImmediatelyRefreshesMatchingRealmAndGuildOnly() {
- try (var identity = new SharedLeaderCharactersTest.Fixture(); var world = new LeaderWorld()) {
- Player leader = identity.player("rushork");
- CharacterSession ready = identity.activate(leader, UUID.randomUUID(), "Grunk the Bold");
-
- new LeaderCharacterListener().onCharacterStart(new CharacterSessionStartEvent(leader, ready));
-
- verify(world.realm).rememberLeaderCharacter("Grunk the Bold", "rushork");
- verify(world.guild).rememberLeaderCharacter("Grunk the Bold", "rushork");
- verify(world.base, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
- org.mockito.ArgumentMatchers.any());
- verify(world.unrelated, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
- org.mockito.ArgumentMatchers.any());
- verify(world.map).markLeaderNamesChanged();
- identity.bukkit.verify(org.bukkit.Bukkit::getScheduler, never());
+ void sharedStartRefreshesOnlyTheRealmAndGuildThatCharacterLeads() {
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ Player leader = identity.connect("rushork");
+ CharacterRef grunk = identity.character(leader.getUniqueId(), "Grunk the Bold");
+ try (var world = new LeaderWorld(Members.key(grunk))) {
+ CharacterSession ready = identity.play(leader, grunk);
+
+ new LeaderCharacterListener().onCharacterStart(new CharacterSessionStartEvent(leader, ready));
+
+ verify(world.realm).rememberLeaderCharacter("Grunk the Bold", Members.key(grunk));
+ verify(world.guild).rememberLeaderCharacter("Grunk the Bold", Members.key(grunk));
+ verify(world.base, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
+ org.mockito.ArgumentMatchers.any());
+ verify(world.unrelated, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
+ org.mockito.ArgumentMatchers.any());
+ verify(world.map).markLeaderNamesChanged();
+ }
}
}
@Test
- void staleStartCannotRefreshNamesAfterAToBToAOrProviderLoss() {
- try (var identity = new SharedLeaderCharactersTest.Fixture(); var world = new LeaderWorld()) {
- Player leader = identity.player("rushork");
- UUID characterA = UUID.randomUUID();
- CharacterSession originalA = identity.activate(leader, characterA, "Original A");
- CharacterSession b = identity.activate(leader, UUID.randomUUID(), "B");
- CharacterSession freshA = identity.activate(leader, characterA, "Fresh A");
- LeaderCharacterListener listener = new LeaderCharacterListener();
-
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, originalA));
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, b));
- identity.unregister();
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, freshA));
- verify(world.realm, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
- org.mockito.ArgumentMatchers.any());
- verify(world.map, never()).markLeaderNamesChanged();
-
- identity.register();
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, freshA));
- verify(world.realm).rememberLeaderCharacter("Fresh A", "rushork");
- verify(world.guild).rememberLeaderCharacter("Fresh A", "rushork");
- verify(world.map).markLeaderNamesChanged();
+ void anotherCharacterOfTheLeadersPlayerRefreshesNothing() {
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ Player leader = identity.connect("rushork");
+ CharacterRef grunk = identity.character(leader.getUniqueId(), "Grunk");
+ CharacterRef other = identity.character(leader.getUniqueId(), "Someone else");
+ try (var world = new LeaderWorld(Members.key(grunk))) {
+ CharacterSession session = identity.play(leader, other);
+ new LeaderCharacterListener().onCharacterStart(new CharacterSessionStartEvent(leader, session));
+ verify(world.realm, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
+ org.mockito.ArgumentMatchers.any());
+ verify(world.map, never()).markLeaderNamesChanged();
+ }
}
}
@Test
- void startRequiresOnlineOriginalConnectionAndCorrectSessionOwner() {
- try (var identity = new SharedLeaderCharactersTest.Fixture(); var world = new LeaderWorld()) {
- Player leader = identity.player("rushork");
+ void staleOrForeignStartsRefreshNothing() {
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ Player leader = identity.connect("rushork");
UUID owner = leader.getUniqueId();
- CharacterSession ready = identity.activate(leader, UUID.randomUUID(), "Grunk");
- LeaderCharacterListener listener = new LeaderCharacterListener();
- when(leader.isOnline()).thenReturn(false);
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, ready));
- when(leader.isOnline()).thenReturn(true);
- identity.connections.put(owner, mock(Player.class));
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, ready));
- identity.connections.put(owner, leader);
- CharacterSession foreign = new CharacterSession(
- new CharacterRef(UUID.randomUUID(), UUID.randomUUID()), 123);
- identity.sessions.put(foreign.ref().ownerId(), foreign);
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, foreign));
- verify(world.realm, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
- org.mockito.ArgumentMatchers.any());
- verify(world.map, never()).markLeaderNamesChanged();
-
- listener.onCharacterStart(new CharacterSessionStartEvent(leader, ready));
- verify(world.realm).rememberLeaderCharacter("Grunk", "rushork");
+ CharacterRef grunk = identity.character(owner, "Grunk");
+ try (var world = new LeaderWorld(Members.key(grunk))) {
+ LeaderCharacterListener listener = new LeaderCharacterListener();
+ CharacterSession stale = identity.play(leader, grunk);
+ identity.play(leader, grunk);
+ listener.onCharacterStart(new CharacterSessionStartEvent(leader, stale));
+ CharacterSession ready = identity.play(leader, grunk);
+ when(leader.isOnline()).thenReturn(false);
+ listener.onCharacterStart(new CharacterSessionStartEvent(leader, ready));
+ when(leader.isOnline()).thenReturn(true);
+ identity.connections.put(owner, mock(Player.class));
+ listener.onCharacterStart(new CharacterSessionStartEvent(leader, ready));
+ identity.connections.put(owner, leader);
+ CharacterSession foreign = new CharacterSession(
+ new CharacterRef(UUID.randomUUID(), grunk.characterId()), 123);
+ listener.onCharacterStart(new CharacterSessionStartEvent(leader, foreign));
+ verify(world.realm, never()).rememberLeaderCharacter(org.mockito.ArgumentMatchers.any(),
+ org.mockito.ArgumentMatchers.any());
+
+ listener.onCharacterStart(new CharacterSessionStartEvent(leader, ready));
+ verify(world.realm).rememberLeaderCharacter("Grunk", Members.key(grunk));
+ }
}
}
@Test
void sharedUnknownPreservesRealmAndGuildNamesButProviderLossClearsBoth() {
- try (var identity = new SharedLeaderCharactersTest.Fixture(); var world = new LeaderWorld()) {
- when(world.realm.getLeaderCharacter()).thenReturn("Remembered ruler");
- when(world.realm.getLeaderCharacterOf()).thenReturn("RUSHORK");
- when(world.guild.getLeaderCharacter()).thenReturn("Remembered guild leader");
- when(world.guild.getLeaderCharacterOf()).thenReturn("RUSHORK");
-
- assertFalse(LeaderCharacters.refresh(world.realm));
- assertFalse(LeaderCharacters.refresh(world.guild));
- verify(world.realm).rememberLeaderCharacter("Remembered ruler", "rushork");
- verify(world.guild).rememberLeaderCharacter("Remembered guild leader", "rushork");
-
- identity.unregister();
- assertTrue(LeaderCharacters.refresh(world.realm));
- assertTrue(LeaderCharacters.refresh(world.guild));
- verify(world.realm).rememberLeaderCharacter(null, null);
- verify(world.guild).rememberLeaderCharacter(null, null);
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ String key = Members.key(UUID.randomUUID());
+ try (var world = new LeaderWorld(key)) {
+ when(world.realm.getLeaderCharacter()).thenReturn("Remembered ruler");
+ when(world.realm.getLeaderCharacterOf()).thenReturn(key);
+ when(world.guild.getLeaderCharacter()).thenReturn("Remembered guild leader");
+ when(world.guild.getLeaderCharacterOf()).thenReturn(key);
+
+ assertFalse(LeaderCharacters.refresh(world.realm));
+ assertFalse(LeaderCharacters.refresh(world.guild));
+ verify(world.realm).rememberLeaderCharacter("Remembered ruler", key);
+ verify(world.guild).rememberLeaderCharacter("Remembered guild leader", key);
+
+ identity.unregister();
+ assertTrue(LeaderCharacters.refresh(world.realm));
+ assertTrue(LeaderCharacters.refresh(world.guild));
+ verify(world.realm).rememberLeaderCharacter(null, null);
+ verify(world.guild).rememberLeaderCharacter(null, null);
+ }
}
}
@@ -241,9 +245,9 @@ private static final class LeaderWorld implements AutoCloseable {
final Guild unrelated = mock(Guild.class);
final MapSystem map = mock(MapSystem.class);
- LeaderWorld() {
- when(realm.getLeader()).thenReturn("rushork");
- when(guild.getLeader()).thenReturn("rushork");
+ LeaderWorld(String leader) {
+ when(realm.getLeader()).thenReturn(leader);
+ when(guild.getLeader()).thenReturn(leader);
when(base.isBase()).thenReturn(true);
when(unrelated.getLeader()).thenReturn("anotheraccount");
GuildHandler handler = mock(GuildHandler.class);
diff --git a/src/test/java/net/tfminecraft/simplefactions/identity/MembersTest.java b/src/test/java/net/tfminecraft/simplefactions/identity/MembersTest.java
new file mode 100644
index 00000000..5429a336
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/identity/MembersTest.java
@@ -0,0 +1,228 @@
+package net.tfminecraft.simplefactions.identity;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.junit.jupiter.api.Test;
+
+import net.tfminecraft.simplefactions.testsupport.SharedIdentity;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
+
+class MembersTest {
+
+ @Test
+ void aPlayerIsTheCharacterOfTheirCurrentSessionOnly() {
+ try (var identity = new SharedIdentity()) {
+ Player player = identity.connect("Steve");
+ CharacterRef ada = identity.character(player.getUniqueId(), "Ada");
+ CharacterRef bea = identity.character(player.getUniqueId(), "Bea");
+ assertNull(Members.key(player), "no session, no member");
+ assertTrue(Members.session(player).isEmpty());
+
+ CharacterSession first = identity.play(player, ada);
+ assertEquals(Members.key(ada), Members.key(player));
+ assertEquals(ada.characterId().toString(), Members.key(player));
+ assertTrue(Members.is(player, Members.key(ada)));
+ assertFalse(Members.is(player, Members.key(bea)));
+ assertEquals(player, Members.player(Members.key(ada)));
+ assertTrue(Members.online(Members.key(bea)).isEmpty(), "the same account playing Ada is not Bea");
+ assertEquals("Ada", Members.name(player));
+ assertTrue(Members.isCurrent(first));
+
+ identity.play(player, bea);
+ assertFalse(Members.isCurrent(first));
+ assertEquals(Members.key(bea), Members.key(player));
+ assertFalse(Members.isOnline(Members.key(ada)));
+ assertTrue(Members.isOnline(Members.key(bea)));
+ }
+ }
+
+ @Test
+ void aSessionOfAnotherAccountIsNotThePlayers() {
+ try (var identity = new SharedIdentity()) {
+ Player player = identity.connect("Steve");
+ CharacterRef foreign = identity.character(UUID.randomUUID(), "Foreign");
+ identity.sessions.put(player.getUniqueId(), new CharacterSession(foreign, 9));
+ assertNull(Members.key(player));
+ assertEquals("Steve", Members.name(player));
+ identity.unregister();
+ assertNull(Members.key(player));
+ assertFalse(Members.isCurrent(new CharacterSession(foreign, 9)));
+ assertTrue(Members.info(Members.key(foreign)).isEmpty());
+ }
+ }
+
+ @Test
+ void displayNamesCharactersWithoutLeakingKeys() {
+ try (var identity = new SharedIdentity()) {
+ CharacterRef ada = identity.character(identity.account("Steve"), "§6Ada");
+ CharacterRef blank = identity.character(identity.account("Alex"), " ");
+ assertEquals("Ada", Members.display(Members.key(ada)));
+ assertEquals("Unknown", Members.display(Members.key(blank)));
+ assertEquals("Unknown", Members.display(Members.key(UUID.randomUUID())));
+ assertEquals("Unknown", Members.display((String) null));
+ assertEquals("Unknown", Members.display(" "));
+ assertEquals("Unresolved member", Members.display(Members.QUARANTINED + "q1"));
+ assertEquals("dummy_1", Members.display("dummy_1"));
+ assertEquals(List.of("Ada", "Unknown"), Members.display(List.of(Members.key(ada), Members.key(UUID.randomUUID()))));
+ assertEquals(List.of(), Members.display((List) null));
+ assertEquals(ada, Members.ref(Members.key(ada)).orElseThrow());
+ assertEquals(ada.ownerId(), Members.accountId(Members.key(ada)).orElseThrow());
+ assertTrue(Members.ref("dummy_1").isEmpty());
+ assertTrue(Members.ref(null).isEmpty());
+ assertTrue(Members.info(null).isEmpty());
+ assertEquals("Unknown", Members.name(null));
+ }
+ }
+
+ @Test
+ void staffSeeWhichAccountPlaysACharacter() {
+ try (var identity = new SharedIdentity()) {
+ CharacterRef ada = identity.character(identity.account("Steve"), "Ada");
+ Player staff = mock(Player.class);
+ when(staff.hasPermission(anyString())).thenReturn(true);
+ Player player = mock(Player.class);
+ assertEquals("Ada §7(Steve)", Members.display(staff, Members.key(ada)));
+ assertEquals("Ada", Members.display(player, Members.key(ada)));
+ assertEquals("Unknown", Members.display(staff, Members.key(UUID.randomUUID())));
+ assertEquals("Steve", Members.accountName(Members.key(ada)));
+ assertNull(Members.accountName(Members.key(UUID.randomUUID())));
+ }
+ }
+
+ @Test
+ void typedNamesFindMembersByCharacterOrOnlineAccount() {
+ try (var identity = new SharedIdentity()) {
+ Player steve = identity.connect("Steve");
+ CharacterRef ada = identity.character(steve.getUniqueId(), "Ada Lovelace");
+ identity.play(steve, ada);
+ CharacterRef twinA = identity.character(identity.account("A"), "Twin");
+ CharacterRef twinB = identity.character(identity.account("B"), "Twin");
+ String adaKey = Members.key(ada);
+ Set keys = Set.of(adaKey, Members.key(twinA), Members.key(twinB));
+
+ assertEquals(adaKey, Members.find(keys, "Steve"));
+ assertEquals(adaKey, Members.find(keys, "\"ada lovelace\""));
+ assertEquals(adaKey, Members.find(keys, adaKey.toUpperCase()));
+ assertNull(Members.find(keys, "Twin"), "ambiguous");
+ assertNull(Members.find(keys, "Nobody"));
+ assertNull(Members.find(Set.of(Members.key(twinA)), "Steve"), "online, but not a member here");
+ assertNull(Members.find(null, "x"));
+ assertNull(Members.find(keys, null));
+
+ assertEquals(adaKey, Members.findOnline("Steve"));
+ assertEquals(adaKey, Members.findOnline("Ada Lovelace"));
+ assertEquals(Members.key(twinA), Members.findOnline(Members.key(twinA)));
+ assertNull(Members.findOnline("Twin"));
+ assertNull(Members.findOnline(null));
+ assertEquals(adaKey, Members.ofAccountName("Steve"));
+ assertNull(Members.ofAccountName(null));
+ }
+ }
+
+ @Test
+ void findOnlineRefusesAmbiguousCharacterNames() {
+ try (var identity = new SharedIdentity()) {
+ Player one = identity.connect("One");
+ Player two = identity.connect("Two");
+ identity.play(one, identity.character(one.getUniqueId(), "Twin"));
+ identity.play(two, identity.character(two.getUniqueId(), "Twin"));
+ assertNull(Members.findOnline("Twin"));
+ Player sender = mock(Player.class);
+ assertNull(Members.resolveOnline(sender, "Twin"));
+ verify(sender).sendMessage("§cSeveral characters share that name. Use their Minecraft name.");
+ assertNull(Members.resolveOnline(sender, "Nobody"));
+ verify(sender).sendMessage("§cNo online player found by that character or Minecraft name.");
+ assertEquals(one, Members.resolveOnline(sender, "One"));
+ assertNull(Members.resolveOnline(null, "Nobody"));
+ }
+ }
+
+ @Test
+ void resolveOnlineFindsAUniqueCharacterName() {
+ try (var identity = new SharedIdentity()) {
+ Player one = identity.connect("One");
+ identity.play(one, identity.character(one.getUniqueId(), "Solo"));
+ assertEquals(one, Members.resolveOnline(null, "\"solo\""));
+ }
+ }
+
+ @Test
+ void anOfflineAccountStandsForItsLegacyHeir() {
+ try (var identity = new SharedIdentity()) {
+ UUID account = identity.account("Steve");
+ CharacterRef heir = identity.character(account, "Ada");
+ assertNull(Members.ofAccountOrHeir("Steve"));
+ identity.heirs.put(account, heir);
+ assertEquals(Members.key(heir), Members.ofAccountOrHeir("Steve"));
+ assertNull(Members.ofAccountOrHeir("Unknown"));
+ assertNull(Members.ofAccountOrHeir(null));
+ Player steve = identity.connect("Alex");
+ CharacterRef bea = identity.character(steve.getUniqueId(), "Bea");
+ identity.play(steve, bea);
+ assertEquals(Members.key(bea), Members.ofAccountOrHeir("Alex"));
+ identity.unregister();
+ assertNull(Members.ofAccountOrHeir("Steve"));
+ }
+ }
+
+ @Test
+ void noServerMeansNobodyIsOnline() {
+ try (var bukkit = mockStatic(Bukkit.class)) {
+ assertNull(Members.findOnline("Steve"));
+ assertNull(Members.ofAccountOrHeir("Steve"));
+ assertNull(Members.accountName(Members.key(UUID.randomUUID())));
+ bukkit.when(Bukkit::getServer).thenThrow(new IllegalStateException("no server"));
+ assertNull(Members.findOnline("Steve"));
+ assertNull(Members.ofAccountOrHeir("Steve"));
+ }
+ }
+
+ @Test
+ void keysAreLowerCaseCharacterIds() {
+ UUID id = UUID.randomUUID();
+ assertEquals(id.toString(), Members.key(id));
+ assertNull(Members.key((UUID) null));
+ assertNull(Members.key((CharacterRef) null));
+ assertNull(Members.key((Player) null));
+ assertEquals(id, Members.characterId(id.toString().toUpperCase()));
+ assertNull(Members.characterId("1-1-1-1-1"));
+ assertNull(Members.characterId("Steve"));
+ assertNull(Members.characterId(null));
+ assertTrue(Members.isQuarantined("quarantine:q1"));
+ assertFalse(Members.isQuarantined(null));
+ assertTrue(Members.online(null).isEmpty());
+ assertTrue(Members.online(" ").isEmpty());
+ assertTrue(Members.session(null).isEmpty());
+ assertFalse(Members.isCurrent(null));
+ }
+
+ @Test
+ void completionsOfferTheUntypedRestOfAName() {
+ assertEquals(List.of(), Members.completions(List.of("Ada"), new String[] {"invite"}));
+ assertEquals(List.of("Lovelace"), Members.completions(java.util.Arrays.asList("Ada Lovelace", null, "Bea"),
+ new String[] {"invite", "ada", "lo"}));
+ assertEquals(List.of("Ada"), Members.completions(List.of("§6Ada", "Ada"), new String[] {"invite", "a"}));
+ }
+
+ @Test
+ void theResolverCanBeReplacedAndRestored() {
+ Members.Resolver custom = mock(Members.Resolver.class);
+ Player player = mock(Player.class);
+ when(custom.key(player)).thenReturn("custom");
+ Members.setResolver(custom);
+ assertEquals("custom", Members.key(player));
+ Members.setResolver(null);
+ try (var identity = new SharedIdentity()) {
+ assertNull(Members.key(player));
+ }
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/identity/SharedLeaderCharactersTest.java b/src/test/java/net/tfminecraft/simplefactions/identity/SharedLeaderCharactersTest.java
index a7dba492..4381ea61 100644
--- a/src/test/java/net/tfminecraft/simplefactions/identity/SharedLeaderCharactersTest.java
+++ b/src/test/java/net/tfminecraft/simplefactions/identity/SharedLeaderCharactersTest.java
@@ -4,265 +4,68 @@
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
import java.util.UUID;
-import org.bukkit.Bukkit;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.Server;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.PluginManager;
-import org.bukkit.plugin.ServicePriority;
-import org.bukkit.plugin.SimpleServicesManager;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
-import net.tfminecraft.tlibs.character.CharacterIdentity;
-import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.simplefactions.testsupport.SharedIdentity;
import net.tfminecraft.tlibs.character.CharacterRef;
-import net.tfminecraft.tlibs.character.CharacterSession;
import net.tfminecraft.tlibs.character.CharacterStatus;
-import net.tfminecraft.tlibs.character.Characters;
class SharedLeaderCharactersTest {
- @Test
- void readyLeaderUsesCanonicalNameRatherThanMaskOrAlias() {
- try (var fixture = new Fixture()) {
- Player leader = fixture.player("rushork");
- fixture.activate(leader, UUID.randomUUID(), "Grunk the Bold");
- when(fixture.provider.displayName(leader)).thenReturn("A hooded traveller");
-
- assertEquals("A hooded traveller", Characters.displayName(leader));
- clearInvocations(fixture.provider);
- assertEquals(new LeaderCharacters.Remembered("Grunk the Bold", "rushork"),
- LeaderCharacters.resolve("rushork", null, null));
- verify(fixture.provider, never()).displayName(any());
- verify(fixture.plugin, never()).getDataFolder();
- verify(fixture.plugins, never()).getPlugin(anyString());
- }
- }
-
- @Test
- void pendingOnlineLeaderKeepsOnlyTheirRememberedNameWithoutOfflineFallback() {
- try (var fixture = new Fixture()) {
- Player leader = fixture.player("rushork");
- fixture.lastPlayed.put(leader.getUniqueId(),
- info(leader.getUniqueId(), UUID.randomUUID(), "Old directory name"));
-
- assertEquals(LeaderCharacters.Remembered.NONE,
- LeaderCharacters.resolve("rushork", null, null));
- assertEquals(new LeaderCharacters.Remembered("Remembered", "rushork"),
- LeaderCharacters.resolve("rushork", "Remembered", "RUSHORK"));
- assertEquals(LeaderCharacters.Remembered.NONE,
- LeaderCharacters.resolve("rushork", "Another ruler", "someoneelse"));
- verify(fixture.provider, never()).lastPlayed(any());
- }
- }
-
- @Test
- void sharedLookupSeesProviderArrivalAndLossWithoutReinstallingTheProbe() {
- try (var fixture = new Fixture()) {
- Player leader = fixture.player("rushork");
- fixture.activate(leader, UUID.randomUUID(), "Grunk");
- fixture.unregister();
- assertEquals(LeaderCharacters.Remembered.NONE,
- LeaderCharacters.resolve("rushork", "Remembered", "rushork"));
-
- fixture.register();
- assertEquals("Grunk", LeaderCharacters.resolve("rushork", null, null).name());
- fixture.unregister();
- assertEquals(LeaderCharacters.Remembered.NONE,
- LeaderCharacters.resolve("rushork", "Grunk", "rushork"));
- }
- }
-
- @Test
- void onlineLookupRejectsForeignSessionsAndMismatchedOrMissingSnapshots() {
- try (var fixture = new Fixture()) {
- Player leader = fixture.player("rushork");
- UUID owner = leader.getUniqueId();
- UUID characterId = UUID.randomUUID();
- CharacterSession session = fixture.activate(leader, characterId, "Grunk");
- fixture.sessions.put(owner,
- new CharacterSession(new CharacterRef(UUID.randomUUID(), characterId), 100));
- assertNull(LeaderCharacters.resolve("rushork", null, null).name());
-
- fixture.sessions.put(owner, session);
- fixture.infos.put(characterId, info(UUID.randomUUID(), characterId, "Someone else's name"));
- assertNull(LeaderCharacters.resolve("rushork", null, null).name());
- fixture.infos.put(characterId, info(owner, UUID.randomUUID(), "Wrong character"));
- assertNull(LeaderCharacters.resolve("rushork", null, null).name());
- fixture.infos.remove(characterId);
- assertNull(LeaderCharacters.resolve("rushork", null, null).name());
- }
+ @AfterEach
+ void reset() {
+ LeaderCharacters.reset();
}
@Test
- void snapshotCallbackCannotPublishAnOldGenerationAfterAToBToA() {
- try (var fixture = new Fixture()) {
- Player leader = fixture.player("rushork");
- UUID characterId = UUID.randomUUID();
- CharacterSession original = fixture.activate(leader, characterId, "Original A");
- CharacterInfo originalInfo = fixture.infos.get(characterId);
- when(fixture.provider.info(characterId)).thenAnswer(call -> {
- fixture.activate(leader, UUID.randomUUID(), "B");
- fixture.activate(leader, characterId, "Fresh A");
- return Optional.of(originalInfo);
- });
-
- assertEquals("Remembered", LeaderCharacters.resolve("rushork", "Remembered", "rushork").name());
- verify(fixture.provider).isCurrent(original);
- assertFalse(Characters.isCurrent(original));
+ void leaderKeyNamesItsOwnCharacterEvenWhileOffline() {
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ UUID account = identity.account("rushork");
+ CharacterRef grunk = identity.character(account, "§6Grunk the Bold");
+ identity.character(account, "Another character of the same player");
+ String key = Members.key(grunk);
+
+ assertEquals(new LeaderCharacters.Remembered("Grunk the Bold", key),
+ LeaderCharacters.resolve(key, null, null));
+ verify(identity.provider, never()).lastPlayed(any());
+ verify(identity.provider, never()).displayName(any());
}
}
@Test
- void snapshotCallbackCannotPublishAfterDisconnectOrConnectionReplacement() {
- for (boolean disconnect : new boolean[] {true, false}) {
- try (var fixture = new Fixture()) {
- Player leader = fixture.player("rushork");
- UUID owner = leader.getUniqueId();
- UUID characterId = UUID.randomUUID();
- CharacterSession original = fixture.activate(leader, characterId, "Grunk");
- CharacterInfo snapshot = fixture.infos.get(characterId);
- when(fixture.provider.info(characterId)).thenAnswer(call -> {
- if (disconnect) when(leader.isOnline()).thenReturn(false);
- else fixture.connections.put(owner, mock(Player.class));
- return Optional.of(snapshot);
- });
-
- assertNull(LeaderCharacters.resolve("rushork", null, null).name());
- assertTrue(Characters.isCurrent(original), "Provider token alone cannot prove the connection");
- when(leader.isOnline()).thenReturn(true);
- fixture.connections.put(owner, leader);
- doReturn(Optional.of(snapshot)).when(fixture.provider).info(characterId);
- assertEquals("Grunk", LeaderCharacters.resolve("rushork", null, null).name());
- }
+ void deadLeaderStillShowsTheirName() {
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ CharacterRef fallen = identity.character(identity.account("rushork"), "Grunk", CharacterStatus.DEAD);
+ assertEquals("Grunk", LeaderCharacters.resolve(Members.key(fallen), null, null).name());
}
}
@Test
- void offlineLeaderUsesCachedOwnerAndLastPlayedEvenWhenTheCharacterIsDead() {
- try (var fixture = new Fixture()) {
- UUID owner = fixture.cached("rushork");
- fixture.lastPlayed.put(owner, new CharacterInfo(new CharacterRef(owner, UUID.randomUUID()),
- "Grunk remembered", CharacterStatus.DEAD, null, Set.of(), 60, Instant.EPOCH));
-
- assertEquals("Grunk remembered", LeaderCharacters.resolve("rushork", null, null).name());
- fixture.bukkit.verify(() -> Bukkit.getOfflinePlayer(anyString()), never());
- verify(fixture.plugin, never()).getDataFolder();
- verify(fixture.provider, never()).currentSession(any());
+ void unknownCharacterKeepsOnlyItsOwnRememberedName() {
+ try (var identity = new SharedIdentity()) {
+ LeaderCharacters.useSharedIdentity();
+ String key = Members.key(UUID.randomUUID());
+ assertEquals(LeaderCharacters.Remembered.NONE, LeaderCharacters.resolve(key, null, null));
+ assertEquals(new LeaderCharacters.Remembered("Remembered", key),
+ LeaderCharacters.resolve(key, "Remembered", key));
+ assertEquals(LeaderCharacters.Remembered.NONE,
+ LeaderCharacters.resolve(key, "Another ruler", Members.key(UUID.randomUUID())));
}
}
@Test
- void uncachedUnknownAndForeignOfflineResultsCannotInventAName() {
- try (var fixture = new Fixture()) {
- assertNull(LeaderCharacters.resolve("uncached", null, null).name());
- verify(fixture.provider, never()).lastPlayed(any());
- UUID owner = fixture.cached("rushork");
- assertEquals("Remembered", LeaderCharacters.resolve("rushork", "Remembered", "rushork").name());
- fixture.lastPlayed.put(owner, info(UUID.randomUUID(), UUID.randomUUID(), "Wrong owner"));
- assertNull(LeaderCharacters.resolve("rushork", null, null).name());
- fixture.bukkit.verify(() -> Bukkit.getOfflinePlayer(anyString()), never());
- }
- }
-
- static CharacterInfo info(UUID owner, UUID characterId, String name) {
- return new CharacterInfo(new CharacterRef(owner, characterId), name,
- CharacterStatus.ALIVE, null, Set.of(), 0, Instant.EPOCH);
- }
-
- /** Real facade and registry; only Bukkit's server boundary and provider data are controlled. */
- static final class Fixture implements AutoCloseable {
- final MockedStatic bukkit = mockStatic(Bukkit.class);
- final SimpleServicesManager services = new SimpleServicesManager();
- final Plugin plugin = mock(Plugin.class);
- final PluginManager plugins = mock(PluginManager.class);
- final CharacterIdentity provider = mock(CharacterIdentity.class);
- final Map sessions = new HashMap<>();
- final Map infos = new HashMap<>();
- final Map lastPlayed = new HashMap<>();
- final Map online = new HashMap<>();
- final Map connections = new HashMap<>();
- final Map cached = new HashMap<>();
- private long generation;
-
- Fixture() {
- Server server = mock(Server.class);
- when(server.getPluginManager()).thenReturn(plugins);
- when(server.getServicesManager()).thenReturn(services);
- bukkit.when(Bukkit::getServer).thenReturn(server);
- bukkit.when(Bukkit::getPluginManager).thenReturn(plugins);
- bukkit.when(Bukkit::getServicesManager).thenReturn(services);
- bukkit.when(Bukkit::isPrimaryThread).thenReturn(true);
- bukkit.when(() -> Bukkit.getPlayerExact(anyString()))
- .thenAnswer(call -> online.get(call.getArgument(0)));
- bukkit.when(() -> Bukkit.getPlayer(any(UUID.class)))
- .thenAnswer(call -> connections.get(call.getArgument(0)));
- bukkit.when(() -> Bukkit.getOfflinePlayerIfCached(anyString()))
- .thenAnswer(call -> cached.get(call.getArgument(0)));
- when(provider.currentSession(any())).thenAnswer(call -> {
- Player player = call.getArgument(0);
- return Optional.ofNullable(sessions.get(player.getUniqueId()));
- });
- when(provider.isCurrent(any())).thenAnswer(call -> {
- CharacterSession session = call.getArgument(0);
- return session != null && session.equals(sessions.get(session.ref().ownerId()));
- });
- when(provider.info(any())).thenAnswer(call -> Optional.ofNullable(infos.get(call.getArgument(0))));
- when(provider.lastPlayed(any())).thenAnswer(call -> Optional.ofNullable(lastPlayed.get(call.getArgument(0))));
- register();
+ void providerLossForgetsNamesUntilItReturns() {
+ try (var identity = new SharedIdentity()) {
LeaderCharacters.useSharedIdentity();
- }
-
- Player player(String name) {
- Player player = mock(Player.class);
- UUID owner = UUID.randomUUID();
- when(player.getUniqueId()).thenReturn(owner);
- when(player.getName()).thenReturn(name);
- when(player.isOnline()).thenReturn(true);
- online.put(name, player);
- connections.put(owner, player);
- return player;
- }
-
- UUID cached(String name) {
- UUID owner = UUID.randomUUID();
- OfflinePlayer player = mock(OfflinePlayer.class);
- when(player.getUniqueId()).thenReturn(owner);
- cached.put(name, player);
- return owner;
- }
-
- CharacterSession activate(Player player, UUID characterId, String name) {
- UUID owner = player.getUniqueId();
- CharacterSession session = new CharacterSession(new CharacterRef(owner, characterId), ++generation);
- sessions.put(owner, session);
- infos.put(characterId, info(owner, characterId, name));
- return session;
- }
-
- void register() {
- services.register(CharacterIdentity.class, provider, plugin, ServicePriority.Normal);
- }
-
- void unregister() {
- services.unregister(CharacterIdentity.class, provider);
- }
-
- @Override public void close() {
- try {
- unregister();
- LeaderCharacters.reset();
- } finally {
- bukkit.close();
- }
+ String key = Members.key(identity.character(identity.account("rushork"), "Grunk"));
+ identity.unregister();
+ assertEquals(LeaderCharacters.Remembered.NONE, LeaderCharacters.resolve(key, "Grunk", key));
+ identity.register();
+ assertEquals("Grunk", LeaderCharacters.resolve(key, null, null).name());
}
}
}
diff --git a/src/test/java/net/tfminecraft/simplefactions/migration/CharacterMigrationTest.java b/src/test/java/net/tfminecraft/simplefactions/migration/CharacterMigrationTest.java
new file mode 100644
index 00000000..532e2214
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/migration/CharacterMigrationTest.java
@@ -0,0 +1,493 @@
+package net.tfminecraft.simplefactions.migration;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterRef;
+
+class CharacterMigrationTest {
+ @TempDir
+ Path root;
+
+ private final Logger logger = mock(Logger.class);
+ private final Map accounts = new HashMap<>();
+ private final Map heirs = new HashMap<>();
+ private int heirLookups;
+
+ private final CharacterMigration.Heirs fakeHeirs = new CharacterMigration.Heirs() {
+ @Override
+ public Optional account(String name) {
+ return Optional.ofNullable(accounts.get(name.toLowerCase(java.util.Locale.ROOT)));
+ }
+
+ @Override
+ public Optional heir(UUID account) {
+ heirLookups++;
+ return Optional.ofNullable(heirs.get(account));
+ }
+ };
+
+ /** An account with a reviewed heir; returns the heir's member key. */
+ private String player(String... names) {
+ UUID account = UUID.randomUUID();
+ for (String name : names) accounts.put(name.toLowerCase(java.util.Locale.ROOT), account);
+ CharacterRef heir = new CharacterRef(account, UUID.randomUUID());
+ heirs.put(account, heir);
+ return Members.key(heir);
+ }
+
+ /** An account the server knows, but with no reviewed heir. */
+ private UUID heirless(String name) {
+ UUID account = UUID.randomUUID();
+ accounts.put(name.toLowerCase(java.util.Locale.ROOT), account);
+ return account;
+ }
+
+ private CharacterMigration migration() {
+ return new CharacterMigration(root, fakeHeirs, logger);
+ }
+
+ private void write(String store, String json) throws IOException {
+ Path file = root.resolve(store);
+ Files.createDirectories(file.getParent());
+ Files.writeString(file, json);
+ }
+
+ private JsonObject read(String store) throws IOException {
+ return JsonParser.parseString(Files.readString(root.resolve(store))).getAsJsonObject();
+ }
+
+ private static List strings(JsonElement array) {
+ return array.getAsJsonArray().asList().stream().map(JsonElement::getAsString).toList();
+ }
+
+ private static final String FACTION = """
+ {
+ "id": "rome", "leader": "Caesar", "leader character": "Gaius", "leader character of": "Caesar",
+ "guilds": [
+ {"id": "rome", "leader": "Caesar", "members": ["Caesar", "Brutus", "OldBrutus", "Ghost"],
+ "dividendEligible": ["Caesar", "Brutus", "OldBrutus"],
+ "citizen taxes": {"Brutus": 2.5, "OldBrutus": 1.5, "Ghost": 4.0},
+ "deposits today": {"Caesar": 10},
+ "ledger last day": {"CITIZENS": {"Brutus": 1.0, "oldbrutus": 2.0}, "GUILD_TAXES": {"smiths": 3.0},
+ "DEPOSITS": {"Caesar": 5.0}},
+ "ledger lifetime": {"CITIZENS": {"Ghost": 7.0}},
+ "company": {"enlisted": ["Brutus", "OldBrutus"], "activeWageOverrides": {"Brutus": 5.0, "OldBrutus": 9.0},
+ "peacetimeWageOverrides": {"Ghost": 1.0}, "pendingWages": {"Brutus": 3.0, "OldBrutus": 4.0}}},
+ {"id": "smiths", "leader": "Ghost", "members": ["Ghost", "Cassius"], "leader character": "Spooky"}
+ ],
+ "governmentData": {
+ "council members": ["Brutus", "Cassius"],
+ "eligible voters": ["Caesar", "Brutus", "OldBrutus"],
+ "election candidates": {"LEADER": ["Brutus", "OldBrutus"]},
+ "election votes": {"LEADER": {"Caesar": "Brutus", "Brutus": "Cassius", "OldBrutus": "Caesar"}},
+ "previous votes": {"COUNCIL": {"Brutus": 2, "OldBrutus": 3}},
+ "proposals": ["Brutus:law:economy:free_trade", "Cassius:action:CHANGE_LEADER:Brutus", "broken", "Ghost:tax:CITIZENS:x:5.0"],
+ "movements": [
+ {"id": "m1", "leader": "Cassius", "supporters": {"citizens": ["Cassius", "Brutus"], "guilds": ["smiths"]},
+ "causes": [{"leader": "Cassius", "members": {"citizens": ["Cassius"]},
+ "proposal": {"proposer": "Cassius", "action key": "CHANGE_LEADER", "target": "Brutus"}},
+ {"leader": "Brutus", "proposal": {"proposer": "Brutus", "action key": "END_WAR", "target": "gaul"}}]}
+ ]
+ }
+ }
+ """;
+
+ @Test
+ void migratesEveryNameFieldOfARealmAndMergesNamesOfOneAccount() throws IOException {
+ String caesar = player("Caesar");
+ String brutus = player("Brutus", "OldBrutus");
+ String cassius = player("Cassius");
+ write("Data/rome.json", FACTION);
+
+ CharacterMigration.Result result = migration().run();
+ assertEquals(1, result.migrated());
+
+ JsonObject rome = read("Data/rome.json");
+ assertEquals(caesar, rome.get("leader").getAsString());
+ assertFalse(rome.has("leader character"));
+ assertFalse(rome.has("leader character of"));
+ JsonObject base = rome.getAsJsonArray("guilds").get(0).getAsJsonObject();
+ JsonObject smiths = rome.getAsJsonArray("guilds").get(1).getAsJsonObject();
+ String ghost = smiths.get("leader").getAsString();
+ assertTrue(Members.isQuarantined(ghost));
+ // The quarantined leader of the smiths no longer also sits in the realm's own guild.
+ assertEquals(List.of(caesar, brutus), strings(base.get("members")));
+ assertEquals(List.of(caesar, brutus), strings(base.get("dividendEligible")));
+ assertEquals(4.0, base.getAsJsonObject("citizen taxes").get(brutus).getAsDouble());
+ assertEquals(4.0, base.getAsJsonObject("citizen taxes").get(ghost).getAsDouble());
+ assertEquals(10, base.getAsJsonObject("deposits today").get(caesar).getAsLong());
+ JsonObject lastDay = base.getAsJsonObject("ledger last day");
+ assertEquals(3.0, lastDay.getAsJsonObject("CITIZENS").get(brutus).getAsDouble());
+ assertEquals(3.0, lastDay.getAsJsonObject("GUILD_TAXES").get("smiths").getAsDouble());
+ assertEquals(5.0, lastDay.getAsJsonObject("DEPOSITS").get(caesar).getAsDouble());
+ assertEquals(7.0, base.getAsJsonObject("ledger lifetime").getAsJsonObject("CITIZENS").get(ghost).getAsDouble());
+ JsonObject company = base.getAsJsonObject("company");
+ assertEquals(List.of(brutus), strings(company.get("enlisted")));
+ assertEquals(5.0, company.getAsJsonObject("activeWageOverrides").get(brutus).getAsDouble());
+ assertEquals(1.0, company.getAsJsonObject("peacetimeWageOverrides").get(ghost).getAsDouble());
+ assertEquals(7.0, company.getAsJsonObject("pendingWages").get(brutus).getAsDouble());
+ assertNotNull(base.get("incarnation").getAsString());
+
+ assertEquals(List.of(ghost, cassius), strings(smiths.get("members")));
+ assertFalse(smiths.has("leader character"));
+ assertNotEquals(base.get("incarnation"), smiths.get("incarnation"));
+
+ JsonObject government = rome.getAsJsonObject("governmentData");
+ assertEquals(List.of(brutus, cassius), strings(government.get("council members")));
+ assertEquals(List.of(caesar, brutus), strings(government.get("eligible voters")));
+ assertEquals(List.of(brutus), strings(government.getAsJsonObject("election candidates").get("LEADER")));
+ JsonObject ballots = government.getAsJsonObject("election votes").getAsJsonObject("LEADER");
+ assertEquals(brutus, ballots.get(caesar).getAsString());
+ assertEquals(cassius, ballots.get(brutus).getAsString());
+ assertEquals(2, ballots.size());
+ assertEquals(5, government.getAsJsonObject("previous votes").getAsJsonObject("COUNCIL").get(brutus).getAsLong());
+ assertEquals(List.of(brutus + ":law:economy:free_trade", cassius + ":action:CHANGE_LEADER:" + brutus,
+ "broken", ghost + ":tax:CITIZENS:x:5.0"), strings(government.get("proposals")));
+ JsonObject movement = government.getAsJsonArray("movements").get(0).getAsJsonObject();
+ assertEquals(cassius, movement.get("leader").getAsString());
+ assertEquals(List.of(cassius, brutus), strings(movement.getAsJsonObject("supporters").get("citizens")));
+ assertEquals(List.of("smiths"), strings(movement.getAsJsonObject("supporters").get("guilds")));
+ JsonObject cause = movement.getAsJsonArray("causes").get(0).getAsJsonObject();
+ assertEquals(cassius, cause.get("leader").getAsString());
+ assertEquals(List.of(cassius), strings(cause.getAsJsonObject("members").get("citizens")));
+ assertEquals(cassius, cause.getAsJsonObject("proposal").get("proposer").getAsString());
+ assertEquals(brutus, cause.getAsJsonObject("proposal").get("target").getAsString());
+ JsonObject warEnd = movement.getAsJsonArray("causes").get(1).getAsJsonObject();
+ assertEquals("gaul", warEnd.getAsJsonObject("proposal").get("target").getAsString());
+
+ Quarantine quarantine = Quarantine.load(root.resolve(CharacterMigration.QUARANTINE));
+ assertEquals(1, quarantine.records().size());
+ Quarantine.Record record = quarantine.records().iterator().next();
+ assertEquals(Quarantine.NAME, record.kind);
+ assertEquals("Ghost", record.original);
+ assertEquals("Data/rome.json", record.store);
+ assertEquals(ghost, record.token);
+ assertTrue(record.holdsLeader());
+ assertTrue(record.occurrences.stream().anyMatch(o -> o.path.equals("$.guilds[smiths].leader")));
+ assertTrue(record.occurrences.stream().anyMatch(o -> o.path.equals("$.guilds[rome].citizen taxes[Ghost]")
+ && o.fragment.getAsJsonObject().get("Ghost").getAsDouble() == 4.0));
+
+ assertArrayEquals(FACTION.getBytes(StandardCharsets.UTF_8),
+ Files.readAllBytes(root.resolve("migrations/backup/Data/rome.json")));
+ MigrationManifest manifest = MigrationManifest.load(root.resolve(CharacterMigration.MANIFEST));
+ assertTrue(manifest.complete);
+ assertEquals(MigrationManifest.DONE, manifest.entry("Data/rome.json").state);
+ assertEquals(MigrationFiles.sha256(FACTION), manifest.entry("Data/rome.json").hash);
+ }
+
+ @Test
+ void incarnationsAreStableAcrossARedo() throws IOException {
+ player("Caesar", "Brutus", "OldBrutus", "Cassius", "Ghost");
+ write("Data/rome.json", FACTION);
+ migration().run();
+ String first = read("Data/rome.json").getAsJsonArray("guilds").get(0).getAsJsonObject().get("incarnation").getAsString();
+ Files.writeString(root.resolve("Data/rome.json"), FACTION);
+ Files.delete(root.resolve(CharacterMigration.MANIFEST));
+ migration().run();
+ assertEquals(first, read("Data/rome.json").getAsJsonArray("guilds").get(0).getAsJsonObject().get("incarnation").getAsString());
+ }
+
+ @Test
+ void keepsAnExistingIncarnationAndLeavesKeysAndTokensAlone() throws IOException {
+ String key = Members.key(UUID.randomUUID());
+ write("Data/gaul.json", "{\"id\":\"gaul\",\"leader\":\"" + key + "\",\"guilds\":[{\"id\":\"gaul\",\"incarnation\":\"fixed\","
+ + "\"leader\":\"" + key.toUpperCase() + "\",\"members\":[\"quarantine:q1\"]}]}");
+ migration().run();
+ JsonObject gaul = read("Data/gaul.json");
+ JsonObject guild = gaul.getAsJsonArray("guilds").get(0).getAsJsonObject();
+ assertEquals("fixed", guild.get("incarnation").getAsString());
+ assertEquals(key, guild.get("leader").getAsString());
+ assertEquals(List.of("quarantine:q1"), strings(guild.get("members")));
+ assertEquals(0, heirLookups);
+ }
+
+ @Test
+ void aCharacterInSeveralGuildsOfARealmKeepsOnlyOne() throws IOException {
+ String caesar = player("Caesar");
+ String brutus = player("Brutus", "OldBrutus");
+ String cassius = player("Cassius", "Cass");
+ write("Data/rome.json", """
+ {"id":"rome","leader":"Caesar","guilds":[
+ {"id":"rome","leader":"Caesar","members":["Caesar","Brutus","Cass", 5]},
+ {"id":"smiths","leader":"Cassius","members":["Cassius","OldBrutus"]},
+ {"id":"bakers","leader":"Brutus","members":["Brutus","Cass"]},
+ {"id":"empty"}]}
+ """);
+ migration().run();
+ JsonArray guilds = read("Data/rome.json").getAsJsonArray("guilds");
+ assertEquals(List.of(caesar), strings(guilds.get(0).getAsJsonObject().get("members")).subList(0, 1));
+ assertEquals(2, guilds.get(0).getAsJsonObject().getAsJsonArray("members").size());
+ assertEquals(List.of(cassius), strings(guilds.get(1).getAsJsonObject().get("members")));
+ assertEquals(List.of(brutus), strings(guilds.get(2).getAsJsonObject().get("members")));
+ org.mockito.Mockito.verify(logger, org.mockito.Mockito.atLeastOnce())
+ .warning(org.mockito.ArgumentMatchers.contains("several guilds"));
+ }
+
+ @Test
+ void migratesWarsLoginsAndVehicleStoresAndLeavesCombatFilesAlone() throws IOException {
+ String brutus = player("Brutus", "OldBrutus");
+ String cassius = player("Cassius");
+ UUID orphanAccount = heirless("Orphan");
+ CharacterRef brutusHeir = heirs.get(accounts.get("brutus"));
+ write("Wars/war_1.json", "{\"id\":1,\"civilWarWantedLeaderName\":\"Brutus\",\"civilWarMemberMoves\":"
+ + "[{\"player\":\"Cassius\",\"originGuildId\":\"smiths\"},{\"player\":\"Orphan\"}],\"battleVotes\":{\"x\":[1]}}");
+ write("Wars/notes.txt", "ignored");
+ write("Cache/logins.json", "{\"brutus\":4,\"oldbrutus\":1,\"cassius\":2}");
+ write("Cache/vehicle_fees.json", "{\"lastOwners\":{\"v1\":\"Brutus\",\"v2\":\"Orphan\"},\"paidBuilds\":[{\"payerUuid\":\""
+ + brutusHeir.ownerId() + "\",\"amount\":5},{\"payerUuid\":\"" + orphanAccount + "\",\"amount\":2},{\"payerUuid\":\"nope\"},{}]}");
+ write("Cache/vehicles_registry.json", "[{\"playerUuid\":\"" + brutusHeir.ownerId() + "\",\"vehicleUuid\":\"v1\"},"
+ + "{\"vehicleUuid\":\"v3\"}, 7]");
+ String warband = "{\"id\":\"w\",\"leaderId\":\"" + UUID.randomUUID() + "\"}";
+ write("Warbands/warband_w.json", warband);
+ write("Battles/battle_b.json", "{\"id\":\"b\"}");
+
+ migration().run();
+
+ JsonObject war = read("Wars/war_1.json");
+ assertEquals(brutus, war.get("civilWarWantedLeaderName").getAsString());
+ JsonArray moves = war.getAsJsonArray("civilWarMemberMoves");
+ assertEquals(cassius, moves.get(0).getAsJsonObject().get("player").getAsString());
+ assertTrue(Members.isQuarantined(moves.get(1).getAsJsonObject().get("player").getAsString()));
+ JsonObject logins = read("Cache/logins.json");
+ assertEquals(1.0, logins.get(brutus).getAsDouble());
+ assertEquals(2, logins.get(cassius).getAsInt());
+ JsonObject fees = read("Cache/vehicle_fees.json");
+ assertEquals(brutus, fees.getAsJsonObject("lastOwners").get("v1").getAsString());
+ assertTrue(Members.isQuarantined(fees.getAsJsonObject("lastOwners").get("v2").getAsString()));
+ JsonArray builds = fees.getAsJsonArray("paidBuilds");
+ assertEquals(brutusHeir.characterId().toString(), builds.get(0).getAsJsonObject().get("payerUuid").getAsString());
+ assertEquals(orphanAccount.toString(), builds.get(1).getAsJsonObject().get("payerUuid").getAsString());
+ assertEquals("nope", builds.get(2).getAsJsonObject().get("payerUuid").getAsString());
+ JsonArray registry = JsonParser.parseString(Files.readString(root.resolve("Cache/vehicles_registry.json"))).getAsJsonArray();
+ assertEquals(brutusHeir.characterId().toString(), registry.get(0).getAsJsonObject().get("playerUuid").getAsString());
+ assertEquals(warband, Files.readString(root.resolve("Warbands/warband_w.json")));
+
+ Quarantine quarantine = Quarantine.load(root.resolve(CharacterMigration.QUARANTINE));
+ Quarantine.Record account = quarantine.records().stream()
+ .filter(record -> Quarantine.ACCOUNT.equals(record.kind)).findFirst().orElseThrow();
+ assertEquals(orphanAccount.toString(), account.token);
+ assertEquals("Cache/vehicle_fees.json", account.store);
+ assertFalse(account.holdsLeader());
+ MigrationManifest manifest = MigrationManifest.load(root.resolve(CharacterMigration.MANIFEST));
+ assertEquals(MigrationManifest.DONE, manifest.entry("Warbands/warband_w.json").state);
+ assertEquals(MigrationManifest.DONE, manifest.entry("Battles/battle_b.json").state);
+ }
+
+ @Test
+ void anInterruptedStoreIsRestoredFromItsBackupAndRedone() throws IOException {
+ String caesar = player("Caesar");
+ String original = "{\"id\":\"rome\",\"leader\":\"Caesar\",\"guilds\":[]}";
+ write("Data/rome.json", original);
+ write("migrations/backup/Data/rome.json", original);
+ MigrationManifest manifest = new MigrationManifest();
+ manifest.mark("Data/rome.json", MigrationManifest.IN_PROGRESS, "migrations/backup/Data/rome.json",
+ MigrationFiles.sha256(original));
+ manifest.mark("Data/done.json", MigrationManifest.DONE, "migrations/backup/Data/done.json", "x");
+ manifest.save(root.resolve(CharacterMigration.MANIFEST));
+ // A half-finished earlier attempt already rewrote the store.
+ write("Data/rome.json", "{\"id\":\"rome\",\"leader\":\"" + Members.key(UUID.randomUUID()) + "\"}");
+ write("Data/done.json", "{\"id\":\"done\",\"leader\":\"Caesar\"}");
+
+ CharacterMigration.Result result = migration().run();
+
+ assertEquals(1, result.migrated());
+ assertEquals(caesar, read("Data/rome.json").get("leader").getAsString());
+ assertEquals("Caesar", read("Data/done.json").get("leader").getAsString());
+ org.mockito.Mockito.verify(logger).warning(org.mockito.ArgumentMatchers.contains("Restored Data/rome.json"));
+ }
+
+ @Test
+ void anInterruptedStoreWithoutAGoodBackupStopsTheMigration() throws IOException {
+ write("Data/rome.json", "{\"id\":\"rome\"}");
+ MigrationManifest manifest = new MigrationManifest();
+ manifest.mark("Data/rome.json", MigrationManifest.IN_PROGRESS, "migrations/backup/Data/rome.json", "abc");
+ manifest.save(root.resolve(CharacterMigration.MANIFEST));
+ assertThrows(IOException.class, () -> migration().run());
+ write("migrations/backup/Data/rome.json", "{\"id\":\"other\"}");
+ assertThrows(IOException.class, () -> migration().run());
+ assertFalse(MigrationManifest.load(root.resolve(CharacterMigration.MANIFEST)).complete);
+ }
+
+ @Test
+ void aCompletedMigrationNeverRunsAgain() throws IOException {
+ player("Caesar");
+ write("Data/rome.json", "{\"id\":\"rome\",\"leader\":\"Caesar\"}");
+ CharacterMigration migration = migration();
+ assertFalse(migration.complete());
+ migration.run();
+ assertTrue(migration.complete());
+ String migrated = Files.readString(root.resolve("Data/rome.json"));
+ write("Data/new.json", "{\"id\":\"new\",\"leader\":\"Caesar\"}");
+ assertEquals(new CharacterMigration.Result(0, 0, 0, 0), migration.run());
+ assertEquals(migrated, Files.readString(root.resolve("Data/rome.json")));
+ assertEquals("Caesar", read("Data/new.json").get("leader").getAsString());
+ }
+
+ @Test
+ void anUnreadableStoreIsLeftAsItIsAndReported() throws IOException {
+ write("Data/broken.json", "{not json");
+ write("Data/list.json", "[1,2]");
+ CharacterMigration.Result result = migration().run();
+ assertEquals(1, result.skipped());
+ assertEquals("{not json", Files.readString(root.resolve("Data/broken.json")));
+ assertEquals(MigrationManifest.UNREADABLE,
+ MigrationManifest.load(root.resolve(CharacterMigration.MANIFEST)).entry("Data/broken.json").state);
+ }
+
+ @Test
+ void aFreshServerCompletesWithNothingToDo() throws IOException {
+ assertEquals(new CharacterMigration.Result(0, 0, 0, 0), migration().run());
+ assertTrue(migration().complete());
+ }
+
+ @Test
+ void staffAssignmentsReplaceTheTokenEverywhereAtTheNextStart() throws IOException {
+ String caesar = player("Caesar");
+ String brutus = player("Brutus");
+ write("Data/rome.json", """
+ {"id":"rome","leader":"Ghost","guilds":[
+ {"id":"rome","leader":"Ghost","members":["Ghost","Brutus"],"citizen taxes":{"Ghost":2.0,"Brutus":1.0},
+ "company":{"pendingWages":{"Ghost":3.0},"activeWageOverrides":{"Ghost":2.0,"Brutus":7.0}}}],
+ "governmentData":{"election votes":{"LEADER":{"Ghost":"Brutus","Brutus":"Ghost"}},
+ "previous votes":{"LEADER":{"Ghost":2,"Brutus":1}},"election candidates":{"LEADER":["Ghost","Brutus"]}}}
+ """);
+ write("Cache/logins.json", "{\"ghost\":5,\"brutus\":3}");
+ migration().run();
+ Path quarantineFile = root.resolve(CharacterMigration.QUARANTINE);
+ Quarantine quarantine = Quarantine.load(quarantineFile);
+ assertEquals(2, quarantine.records().size());
+ for (Quarantine.Record record : quarantine.records()) record.assigned = brutus;
+ quarantine.save();
+
+ CharacterMigration.Result result = migration().run();
+
+ assertEquals(2, result.assignmentsApplied());
+ JsonObject rome = read("Data/rome.json");
+ assertEquals(brutus, rome.get("leader").getAsString());
+ JsonObject base = rome.getAsJsonArray("guilds").get(0).getAsJsonObject();
+ assertEquals(List.of(brutus), strings(base.get("members")));
+ assertEquals(3.0, base.getAsJsonObject("citizen taxes").get(brutus).getAsDouble());
+ assertEquals(3.0, base.getAsJsonObject("company").getAsJsonObject("pendingWages").get(brutus).getAsDouble());
+ assertEquals(2.0, base.getAsJsonObject("company").getAsJsonObject("activeWageOverrides").get(brutus).getAsDouble());
+ JsonObject government = rome.getAsJsonObject("governmentData");
+ assertEquals(brutus, government.getAsJsonObject("election votes").getAsJsonObject("LEADER").get(brutus).getAsString());
+ assertEquals(3, government.getAsJsonObject("previous votes").getAsJsonObject("LEADER").get(brutus).getAsLong());
+ assertEquals(List.of(brutus), strings(government.getAsJsonObject("election candidates").get("LEADER")));
+ assertEquals(3, read("Cache/logins.json").get(brutus).getAsInt());
+ assertNotEquals(caesar, brutus);
+ Quarantine after = Quarantine.load(quarantineFile);
+ assertTrue(after.records().stream().allMatch(record -> record.applied));
+ Quarantine.Record first = after.records().iterator().next();
+ assertTrue(Files.exists(root.resolve("migrations/backup/assign-" + first.id + "/" + first.store)));
+ assertEquals(0, migration().run().assignmentsApplied());
+ }
+
+ @Test
+ void anAssignmentWhoseStoreIsGoneOrNoLongerHoldsTheTokenIsStillMarkedApplied() throws IOException {
+ Path quarantineFile = root.resolve(CharacterMigration.QUARANTINE);
+ write("Data/rome.json", "{\"id\":\"rome\"}");
+ migration().run();
+ Quarantine quarantine = Quarantine.load(quarantineFile);
+ Quarantine.Record gone = new Quarantine.Record();
+ gone.id = "qgone";
+ gone.store = "Data/gone.json";
+ gone.token = "quarantine:qgone";
+ gone.original = "Gone";
+ gone.assigned = Members.key(UUID.randomUUID());
+ Quarantine.Record moved = new Quarantine.Record();
+ moved.id = "qmoved";
+ moved.store = "Data/rome.json";
+ moved.token = "quarantine:qmoved";
+ moved.original = "Moved";
+ moved.assigned = gone.assigned;
+ Quarantine.Record open = new Quarantine.Record();
+ open.id = "qopen";
+ open.store = "Data/rome.json";
+ open.token = "quarantine:qopen";
+ open.original = "Open";
+ quarantine.merge(List.of(gone, moved, open));
+ quarantine.save();
+ CharacterMigration.Result result = migration().run();
+ assertEquals(2, result.assignmentsApplied());
+ assertEquals(1, result.quarantined());
+ assertFalse(Files.exists(root.resolve("migrations/backup/assign-qmoved/Data/rome.json")));
+ }
+
+ @Test
+ void aRedoneStoreKeepsEarlierAssignments() throws IOException {
+ Quarantine quarantine = Quarantine.load(root.resolve("q.json"));
+ Quarantine.Record record = new Quarantine.Record();
+ record.id = "q1";
+ record.assigned = "abc";
+ record.applied = true;
+ quarantine.merge(List.of(record));
+ Quarantine.Record again = new Quarantine.Record();
+ again.id = "q1";
+ quarantine.merge(List.of(again));
+ assertEquals("abc", quarantine.get("q1").assigned);
+ assertTrue(quarantine.get("q1").applied);
+ assertNull(quarantine.get(null));
+ assertNull(quarantine.get("missing"));
+ }
+
+ @Test
+ void unreadableManifestsAndQuarantinesAreErrors() throws IOException {
+ write(CharacterMigration.MANIFEST, "{oops");
+ assertThrows(IOException.class, () -> migration().run());
+ write(CharacterMigration.MANIFEST, "null");
+ assertThrows(IOException.class, () -> migration().complete());
+ Files.delete(root.resolve(CharacterMigration.MANIFEST));
+ write(CharacterMigration.QUARANTINE, "{oops");
+ assertThrows(IOException.class, () -> migration().run());
+ write(CharacterMigration.QUARANTINE, "null");
+ assertThrows(IOException.class, () -> Quarantine.load(root.resolve(CharacterMigration.QUARANTINE)));
+ }
+
+ @Test
+ void replaceMergesByTheFieldsMeaning() {
+ assertEquals(StoreMapper.Merge.MIN, CharacterMigration.mergeFor(""));
+ assertEquals(StoreMapper.Merge.SUM, CharacterMigration.mergeFor("/guilds/pendingWages"));
+ assertEquals(StoreMapper.Merge.SUM, CharacterMigration.mergeFor("/governmentData/previous votes/LEADER"));
+ assertEquals(StoreMapper.Merge.FIRST, CharacterMigration.mergeFor("/governmentData/election votes/LEADER"));
+ assertTrue(CharacterMigration.memberList("/guilds/members"));
+ assertTrue(CharacterMigration.memberList("/governmentData/election candidates/LEADER"));
+ assertFalse(CharacterMigration.memberList("/governmentData/proposals"));
+ JsonElement tree = JsonParser.parseString("{\"a\":[\"t\",\"t\",1],\"b\":{\"x\":\"y\"}}");
+ assertTrue(CharacterMigration.replace(tree, "t", "k", ""));
+ assertEquals("{\"a\":[\"k\",\"k\",1],\"b\":{\"x\":\"y\"}}", tree.toString());
+ assertFalse(CharacterMigration.replace(tree, "zzz", "k", ""));
+ assertEquals(new com.google.gson.JsonPrimitive(1.5), StoreMapper.merge(
+ new com.google.gson.JsonPrimitive(1), new com.google.gson.JsonPrimitive(0.5), StoreMapper.Merge.SUM));
+ assertEquals(new com.google.gson.JsonPrimitive("a"), StoreMapper.merge(
+ new com.google.gson.JsonPrimitive("a"), new com.google.gson.JsonPrimitive(2), StoreMapper.Merge.SUM));
+ }
+
+ @Test
+ void characterKeysAreLowerCaseCharacterIds() {
+ UUID id = UUID.randomUUID();
+ assertEquals(id.toString(), CharacterMigration.characterKey(" " + id.toString().toUpperCase() + " "));
+ assertNull(CharacterMigration.characterKey("Brutus"));
+ assertNull(CharacterMigration.characterKey(null));
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/migration/QuarantineCommandsTest.java b/src/test/java/net/tfminecraft/simplefactions/migration/QuarantineCommandsTest.java
new file mode 100644
index 00000000..8303c32c
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/migration/QuarantineCommandsTest.java
@@ -0,0 +1,161 @@
+package net.tfminecraft.simplefactions.migration;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import org.bukkit.command.CommandSender;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import net.tfminecraft.simplefactions.SimpleFactions;
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.simplefactions.testsupport.SharedIdentity;
+import net.tfminecraft.tlibs.character.CharacterRef;
+
+class QuarantineCommandsTest {
+ @TempDir
+ Path root;
+
+ private SimpleFactions previous;
+ private final CommandSender staff = mock(CommandSender.class);
+
+ @BeforeEach
+ void plugin() {
+ previous = SimpleFactions.plugin;
+ SimpleFactions plugin = mock(SimpleFactions.class);
+ when(plugin.getDataFolder()).thenReturn(root.toFile());
+ when(plugin.getLogger()).thenReturn(mock(Logger.class));
+ SimpleFactions.plugin = plugin;
+ when(staff.hasPermission("simplefactions.admin")).thenReturn(true);
+ }
+
+ @AfterEach
+ void restore() {
+ SimpleFactions.plugin = previous;
+ }
+
+ private Quarantine.Record record(String id, boolean leader) throws IOException {
+ Quarantine quarantine = Quarantine.load(root.resolve(CharacterMigration.QUARANTINE));
+ Quarantine.Record record = new Quarantine.Record();
+ record.id = id;
+ record.store = "Data/rome.json";
+ record.kind = Quarantine.NAME;
+ record.original = "Ghost";
+ record.token = Members.QUARANTINED + id;
+ record.occurrences.add(new Quarantine.Occurrence(leader ? "$.guilds[rome].leader" : "$.guilds[rome].members[1]",
+ new com.google.gson.JsonPrimitive("Ghost")));
+ quarantine.merge(List.of(record));
+ quarantine.save();
+ return record;
+ }
+
+ @Test
+ void onlyStaffMayUseIt() {
+ assertTrue(QuarantineCommands.matches(new String[] {"admin", "QUARANTINE"}));
+ assertFalse(QuarantineCommands.matches(new String[] {"admin"}));
+ assertFalse(QuarantineCommands.matches(new String[] {"list", "quarantine"}));
+ CommandSender player = mock(CommandSender.class);
+ assertTrue(QuarantineCommands.handle(player, new String[] {"admin", "quarantine", "list"}));
+ verify(player).sendMessage("§a[SimpleFactions]§c You do not have access to this command");
+ }
+
+ @Test
+ void listsRecordsWithTheirState() throws IOException {
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "list"});
+ verify(staff).sendMessage("§aNothing is quarantined.");
+
+ record("q1", true);
+ Quarantine.Record second = record("q2", false);
+ second.occurrences.add(second.occurrences.getFirst());
+ Quarantine quarantine = Quarantine.load(root.resolve(CharacterMigration.QUARANTINE));
+ quarantine.get("q2").assigned = "abc";
+ quarantine.merge(List.of(second));
+ quarantine.get("q2").assigned = "abc";
+ Quarantine.Record third = new Quarantine.Record();
+ third.id = "q3";
+ third.store = "Cache/logins.json";
+ third.kind = Quarantine.NAME;
+ third.original = "Old";
+ third.assigned = "def";
+ third.applied = true;
+ quarantine.merge(List.of(third));
+ quarantine.get("q3").assigned = "def";
+ quarantine.get("q3").applied = true;
+ quarantine.save();
+
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "list"});
+ verify(staff).sendMessage("§eQuarantined faction records:");
+ verify(staff).sendMessage(contains("q1 §7name §fGhost §7in Data/rome.json (1 place, holds a leader's seat) §cunresolved"));
+ verify(staff).sendMessage(contains("q2 §7name §fGhost §7in Data/rome.json (2 places) §6assigned to abc, applies at the next restart"));
+ verify(staff).sendMessage(contains("q3 §7name §fOld §7in Cache/logins.json (0 places) §agiven to def"));
+ }
+
+ @Test
+ void assignsACharacterForTheNextStart() throws IOException {
+ record("q1", true);
+ try (var identity = new SharedIdentity()) {
+ CharacterRef ada = identity.character(identity.account("Steve"), "Ada");
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "assign", "q1",
+ ada.characterId().toString().toUpperCase()});
+ verify(staff).sendMessage("§aRecord q1 (Ghost) goes to Ada at the next restart, before factions load.");
+ assertEquals(Members.key(ada),
+ Quarantine.load(root.resolve(CharacterMigration.QUARANTINE)).get("q1").assigned);
+ }
+ }
+
+ @Test
+ void refusesUnknownRecordsCharactersAndFinishedRecords() throws IOException {
+ record("q1", false);
+ try (var identity = new SharedIdentity()) {
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "assign", "nope", "x"});
+ verify(staff).sendMessage("§cNo quarantine record nope");
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "assign", "q1", "Steve"});
+ verify(staff).sendMessage("§cThat is not a character ID");
+ String unknown = UUID.randomUUID().toString();
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "assign", "q1", unknown});
+ verify(staff).sendMessage("§cNo character with ID " + unknown);
+ Quarantine quarantine = Quarantine.load(root.resolve(CharacterMigration.QUARANTINE));
+ quarantine.get("q1").assigned = "done";
+ quarantine.get("q1").applied = true;
+ quarantine.save();
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "assign", "q1", unknown});
+ verify(staff).sendMessage("§cRecord q1 was already given to done");
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "what"});
+ verify(staff).sendMessage("§cUsage: /faction admin quarantine list | assign ");
+ }
+ }
+
+ @Test
+ void reportsAnUnreadableOrUnwritableQuarantine() throws IOException {
+ Files.createDirectories(root.resolve("migrations"));
+ Files.writeString(root.resolve(CharacterMigration.QUARANTINE), "{oops");
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "list"});
+ verify(staff).sendMessage(startsWith("§cCannot read the quarantine: "));
+
+ Files.delete(root.resolve(CharacterMigration.QUARANTINE));
+ record("q1", false);
+ try (var identity = new SharedIdentity()) {
+ CharacterRef ada = identity.character(identity.account("Steve"), "Ada");
+ // A directory where the temporary file would go makes the save fail.
+ Path migrations = root.resolve("migrations");
+ migrations.toFile().setWritable(false);
+ try {
+ QuarantineCommands.handle(staff, new String[] {"admin", "quarantine", "assign", "q1", ada.characterId().toString()});
+ } finally {
+ migrations.toFile().setWritable(true);
+ }
+ verify(staff).sendMessage("§cCannot save the quarantine; nothing changed");
+ assertNull(Quarantine.load(root.resolve(CharacterMigration.QUARANTINE)).get("q1").assigned);
+ }
+ }
+}
diff --git a/src/test/java/net/tfminecraft/simplefactions/testsupport/SharedIdentity.java b/src/test/java/net/tfminecraft/simplefactions/testsupport/SharedIdentity.java
new file mode 100644
index 00000000..5851e05a
--- /dev/null
+++ b/src/test/java/net/tfminecraft/simplefactions/testsupport/SharedIdentity.java
@@ -0,0 +1,165 @@
+package net.tfminecraft.simplefactions.testsupport;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.ServicePriority;
+import org.bukkit.plugin.SimpleServicesManager;
+import org.mockito.MockedStatic;
+
+import net.tfminecraft.simplefactions.identity.Members;
+import net.tfminecraft.tlibs.character.CharacterIdentity;
+import net.tfminecraft.tlibs.character.CharacterInfo;
+import net.tfminecraft.tlibs.character.CharacterRef;
+import net.tfminecraft.tlibs.character.CharacterSession;
+import net.tfminecraft.tlibs.character.CharacterStatus;
+
+/**
+ * The real TLibs character facade over a mocked provider, with Bukkit's
+ * server boundary stubbed, and {@link Members} reading from it. Accounts and
+ * characters are created by the test.
+ */
+public final class SharedIdentity implements AutoCloseable {
+ public final MockedStatic bukkit = org.mockito.Mockito.mockStatic(Bukkit.class);
+ public final Server server = mock(Server.class);
+ public final SimpleServicesManager services = new SimpleServicesManager();
+ public final Plugin plugin = mock(Plugin.class);
+ public final PluginManager plugins = mock(PluginManager.class);
+ public final CharacterIdentity provider = mock(CharacterIdentity.class);
+ public final Map sessions = new HashMap<>();
+ public final Map infos = new HashMap<>();
+ public final Map heirs = new HashMap<>();
+ public final Map online = new HashMap<>();
+ public final Map connections = new HashMap<>();
+ public final Map cached = new HashMap<>();
+ public boolean heirsReady = true;
+ private long generation;
+
+ public SharedIdentity() {
+ when(server.getPluginManager()).thenReturn(plugins);
+ when(server.getServicesManager()).thenReturn(services);
+ bukkit.when(Bukkit::getServer).thenReturn(server);
+ bukkit.when(Bukkit::getPluginManager).thenReturn(plugins);
+ bukkit.when(Bukkit::getServicesManager).thenReturn(services);
+ bukkit.when(Bukkit::isPrimaryThread).thenReturn(true);
+ bukkit.when(Bukkit::getOnlinePlayers).thenAnswer(call -> new ArrayList<>(connections.values()));
+ bukkit.when(() -> Bukkit.getPlayerExact(anyString())).thenAnswer(call -> online.get(call.getArgument(0)));
+ bukkit.when(() -> Bukkit.getPlayer(any(UUID.class))).thenAnswer(call -> connections.get(call.getArgument(0)));
+ bukkit.when(() -> Bukkit.getOfflinePlayerIfCached(anyString())).thenAnswer(call -> cached.get(call.getArgument(0)));
+ bukkit.when(() -> Bukkit.getOfflinePlayer(any(UUID.class))).thenAnswer(call -> {
+ OfflinePlayer account = mock(OfflinePlayer.class);
+ UUID id = call.getArgument(0);
+ for (Map.Entry entry : cached.entrySet()) {
+ if (id.equals(entry.getValue().getUniqueId())) when(account.getName()).thenReturn(entry.getKey());
+ }
+ return account;
+ });
+ when(provider.currentSession(any())).thenAnswer(call -> {
+ Player player = call.getArgument(0);
+ return Optional.ofNullable(sessions.get(player.getUniqueId()));
+ });
+ when(provider.isCurrent(any())).thenAnswer(call -> {
+ CharacterSession session = call.getArgument(0);
+ return session != null && session.equals(sessions.get(session.ref().ownerId()));
+ });
+ when(provider.info(any())).thenAnswer(call -> Optional.ofNullable(infos.get(call.getArgument(0))));
+ when(provider.characters(any())).thenAnswer(call -> {
+ UUID owner = call.getArgument(0);
+ List list = new ArrayList<>();
+ for (CharacterInfo info : infos.values()) if (info.ref().ownerId().equals(owner)) list.add(info);
+ return Optional.of(list);
+ });
+ when(provider.legacyHeir(any())).thenAnswer(call -> Optional.ofNullable(heirs.get(call.getArgument(0))));
+ when(provider.legacyHeirsReady()).thenAnswer(call -> heirsReady);
+ register();
+ Members.useSharedIdentity();
+ }
+
+ /** A known account: cached by name, offline. */
+ public UUID account(String name) {
+ UUID owner = UUID.nameUUIDFromBytes(("account:" + name).getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ OfflinePlayer player = mock(OfflinePlayer.class);
+ when(player.getUniqueId()).thenReturn(owner);
+ when(player.getName()).thenReturn(name);
+ cached.put(name, player);
+ return owner;
+ }
+
+ /** A living character of {@code owner}. */
+ public CharacterRef character(UUID owner, String name, String... traits) {
+ return character(owner, name, CharacterStatus.ALIVE, traits);
+ }
+
+ public CharacterRef character(UUID owner, String name, CharacterStatus status, String... traits) {
+ CharacterRef ref = new CharacterRef(owner, UUID.randomUUID());
+ infos.put(ref.characterId(), new CharacterInfo(ref, name, status, null, Set.of(traits), 0, Instant.EPOCH));
+ return ref;
+ }
+
+ /** An online connection for an account, playing nothing yet. */
+ public Player connect(String accountName) {
+ UUID owner = account(accountName);
+ Player player = mock(Player.class);
+ when(player.getUniqueId()).thenReturn(owner);
+ when(player.getName()).thenReturn(accountName);
+ when(player.isOnline()).thenReturn(true);
+ online.put(accountName, player);
+ connections.put(owner, player);
+ return player;
+ }
+
+ /** Starts a new session of {@code ref} for its online account. */
+ public CharacterSession play(Player player, CharacterRef ref) {
+ CharacterSession session = new CharacterSession(ref, ++generation);
+ sessions.put(player.getUniqueId(), session);
+ return session;
+ }
+
+ public void stopPlaying(Player player) {
+ sessions.remove(player.getUniqueId());
+ }
+
+ public static String key(CharacterRef ref) {
+ return Members.key(ref);
+ }
+
+ public void register() {
+ services.register(CharacterIdentity.class, provider, plugin, ServicePriority.Normal);
+ }
+
+ public void unregister() {
+ services.unregister(CharacterIdentity.class, provider);
+ }
+
+ public Collection players() {
+ return connections.values();
+ }
+
+ @Override
+ public void close() {
+ try {
+ unregister();
+ NameKeyedMembers.install();
+ } finally {
+ bukkit.close();
+ }
+ }
+}
From 624b8875edac796831fa1884c1c5beefcdfe7800 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Mon, 12 Oct 2026 17:24:44 +0000
Subject: [PATCH 07/35] refactor: key menus and tab completion by character
Menus, creators and tab completion use member keys: lookups by the
current character, names shown through Members, heads from the account
that plays the character. Plugin menus capture the opener's character
session and close with a short message when clicked after a switch.
Co-Authored-By: Claude Opus 5.5
---
.../managers/InventoryManager.java | 64 +++++-
.../CampaignInstallationPickView.java | 2 +-
.../inventory/CampaignRaidLaunchView.java | 3 +-
.../managers/inventory/CompanyCreator.java | 11 +-
.../managers/inventory/CompanyView.java | 8 +-
.../managers/inventory/ContractView.java | 6 +-
.../managers/inventory/ElectionCreator.java | 12 +-
.../managers/inventory/ElectionView.java | 16 +-
.../managers/inventory/EspionageView.java | 22 +--
.../managers/inventory/FactionCreator.java | 14 +-
.../managers/inventory/GovernmentCreator.java | 26 ++-
.../managers/inventory/GovernmentView.java | 28 +--
.../managers/inventory/GuildCreator.java | 9 +-
.../managers/inventory/MemberHeads.java | 18 ++
.../managers/inventory/MovementCreator.java | 18 +-
.../managers/inventory/MovementView.java | 38 ++--
.../managers/inventory/ReportedMenus.java | 6 +-
.../managers/inventory/VehicleFeeView.java | 4 +-
.../simplefactions/utils/TabCompletion.java | 57 +++---
.../InventoryManagerRegressionTest.java | 8 +-
.../InventoryManagerStaleMenuTest.java | 184 ++++++++++++++++++
.../managers/TabCompletionCoverageTest.java | 49 ++++-
.../inventory/CharacterKeyFixture.java | 89 +++++++++
.../inventory/FactionMenusCoverageTest.java | 4 -
.../GovernmentMenusCoverageTest.java | 4 -
.../inventory/MemberNamesInMenusTest.java | 81 ++++++++
26 files changed, 622 insertions(+), 159 deletions(-)
create mode 100644 src/main/java/net/tfminecraft/simplefactions/managers/inventory/MemberHeads.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/managers/InventoryManagerStaleMenuTest.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/managers/inventory/CharacterKeyFixture.java
create mode 100644 src/test/java/net/tfminecraft/simplefactions/managers/inventory/MemberNamesInMenusTest.java
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java
index 23412cc5..db725175 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java
@@ -10,6 +10,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@@ -18,11 +20,14 @@
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
+import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
+import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
@@ -97,6 +102,7 @@
import net.tfminecraft.simplefactions.objects.handler.VehicleFeeHandler;
import net.tfminecraft.simplefactions.government.proposal.TaxTarget;
import net.tfminecraft.simplefactions.keys.Keys;
+import net.tfminecraft.tlibs.character.CharacterSession;
import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter;
public class InventoryManager implements Listener{
@@ -112,7 +118,11 @@ public class InventoryManager implements Listener{
public HashMap dividendChange = new HashMap<>();
public HashMap donationChange = new HashMap<>();
public HashMap slotChanges = new HashMap<>();
+ /** The character session each open menu belongs to, by account. */
+ private final Map> menuSessions = new HashMap<>();
+ private static final String CONFIRM_TITLE = "§7Confirm Action";
+
InventoryUpdater updater = new InventoryUpdater(this);
public InventoryUpdater getUpdater() {
@@ -529,7 +539,7 @@ public void taxChat(Player p, AsyncPlayerChatEvent e) {
p.sendMessage("§4Type 'cancel' to cancel.");
return;
}
- Proposal proposal = new Proposal(p.getName(), f.getGovernment());
+ Proposal proposal = new Proposal(Members.key(p), f.getGovernment());
TaxLawChange tax = new TaxLawChange(change.getTarget(), change.getId(), amount);
proposal.setTaxProposal(tax);
taxChange.remove(p);
@@ -560,18 +570,18 @@ private void submitRateProposal(Player p, Faction f, Proposal proposal) {
} else if(gov.canProposeOrStartMovement(p)) {
Movement movement = gov.getMovementByMember(Members.key(p));
if(movement != null) {
- movement.createCause(p.getName(), proposal);
+ movement.createCause(Members.key(p), proposal);
p.sendMessage("§aProposal added to your movement! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
return;
}
- if (net.tfminecraft.simplefactions.war.civilwar.CivilWarHostMovementRules.blocksHostGuildStart(f, p.getName())) {
+ if (net.tfminecraft.simplefactions.war.civilwar.CivilWarHostMovementRules.blocksHostGuildStart(f, Members.key(p))) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
p.sendMessage(net.tfminecraft.simplefactions.war.civilwar.CivilWarCopy.ONE_PROVINCE_HOST_GUILD);
return;
}
- gov.startMovement(p.getName(), proposal);
+ gov.startMovement(Members.key(p), proposal);
p.sendTitle("", "§cMovement Started", 20, 80, 20);
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
p.sendMessage("§aMovement started! Rally support for your proposal by sharing it with your faction and allies!");
@@ -620,7 +630,7 @@ public void feeChat(Player p, AsyncPlayerChatEvent e) {
p.sendMessage("§4Type 'cancel' to cancel.");
return;
}
- Proposal proposal = new Proposal(p.getName(), f.getGovernment());
+ Proposal proposal = new Proposal(Members.key(p), f.getGovernment());
proposal.setFeeProposal(new FeeChange(kind, change.getVehicleTypeId(), amount));
feeChange.remove(p);
submitRateProposal(p, f, proposal);
@@ -831,7 +841,7 @@ public void slotChat(Player p, AsyncPlayerChatEvent e) {
return;
}
MercenaryResult result = company.getContractHandler()
- .proposeSlots(contract.getId(), p.getName(), slots, System.currentTimeMillis());
+ .proposeSlots(contract.getId(), Members.key(p), slots, System.currentTimeMillis());
slotChanges.remove(p);
p.sendMessage((result.ok() ? "§a" : "§c") + result.message());
p.playSound(p, result.ok() ? Sound.ENTITY_PLAYER_LEVELUP : Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f);
@@ -1059,6 +1069,10 @@ public ItemStack createBackButton(SFGUI gui) {
@EventHandler
public void dragInWarGui(InventoryDragEvent e) {
+ if (e.getWhoClicked() instanceof Player p && closeIfStale(p, e.getView())) {
+ e.setCancelled(true);
+ return;
+ }
Inventory inv = e.getView().getTopInventory();
// Menu contents are replaced on redraw. Keep player items out of every menu,
// including holderless confirmations, while allowing drags within their own inventory.
@@ -1074,6 +1088,38 @@ public void clearPendingOnQuit(PlayerQuitEvent e) {
confirming.remove(e.getPlayer());
}
slotChanges.remove(e.getPlayer());
+ menuSessions.remove(e.getPlayer().getUniqueId());
+ }
+
+ @EventHandler
+ public void captureMenuSession(InventoryOpenEvent e) {
+ if (e.getPlayer() instanceof Player p && isMenu(e.getView())) {
+ menuSessions.put(p.getUniqueId(), Members.session(p));
+ }
+ }
+
+ @EventHandler
+ public void releaseMenuSession(InventoryCloseEvent e) {
+ menuSessions.remove(e.getPlayer().getUniqueId());
+ }
+
+ private static boolean isMenu(InventoryView view) {
+ return isPluginMenu(view.getTopInventory()) || CONFIRM_TITLE.equals(view.getTitle());
+ }
+
+ /**
+ * Closes a menu opened for a character the player is no longer playing. A
+ * menu opened before its session was captured takes the current one.
+ */
+ private boolean closeIfStale(Player p, InventoryView view) {
+ if (!isMenu(view)) return false;
+ Optional current = Members.session(p);
+ Optional opened = menuSessions.computeIfAbsent(p.getUniqueId(), id -> current);
+ if (opened.isPresent() && Members.isCurrent(opened.get()) && opened.equals(current)) return false;
+ menuSessions.remove(p.getUniqueId());
+ p.closeInventory();
+ p.sendMessage("§cThat menu belonged to your previous character.");
+ return true;
}
private static boolean isPluginMenu(Inventory inv) {
@@ -1096,6 +1142,10 @@ public static boolean isTopInventoryClick(int rawSlot, int topSize) {
@EventHandler
public void clickButton(InventoryClickEvent e) {
Player p = (Player) e.getWhoClicked();
+ if (closeIfStale(p, e.getView())) {
+ e.setCancelled(true);
+ return;
+ }
Inventory inv = e.getView().getTopInventory();
if (inv.getHolder() instanceof SFInventoryHolder
&& ((SFInventoryHolder) inv.getHolder()).getType() == SFGUI.PLAYER_LEDGER_VIEW) {
@@ -1691,7 +1741,7 @@ public void clickButton(InventoryClickEvent e) {
p.closeInventory();
return;
}
- MercenaryResult result = MercenaryCompanyService.requestFormation(guild, p.getName(), name);
+ MercenaryResult result = MercenaryCompanyService.requestFormation(guild, Members.key(p), name);
p.sendMessage((result.ok() ? "§a" : "§c") + result.message());
if (!result.ok()) {
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java
index a4b5611b..b31e7e0a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignInstallationPickView.java
@@ -177,7 +177,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player player) {
String installationName = installation != null ? installation.getName() : installationId;
InstallationPickToggleResult result = BattleInstallationPickService.togglePick(
- war, viewerFaction, player.getName(), installationId);
+ war, viewerFaction, Members.key(player), installationId);
player.sendMessage(toggleMessage(result, installationName));
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java
index 86730dbc..185b38e3 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CampaignRaidLaunchView.java
@@ -7,7 +7,6 @@
import java.time.Instant;
import java.util.List;
-import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
@@ -269,7 +268,7 @@ private void broadcastRaidCalled(War war, Faction launcher, Installation target)
String message = CampaignRaidMessages.buildRaidCalledMessage(launcher, target, raid.getId());
Side side = war.getSide(launcher);
for (String memberName : BattleSideMembers.collectEligibleMemberNames(side)) {
- Player online = Bukkit.getPlayerExact(memberName);
+ Player online = Members.player(memberName);
if (online != null && online.isOnline()) {
online.sendMessage(message);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java
index 9acb27a1..a53caf14 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.war.freeze.PreparationFreeze;
import java.util.ArrayList;
import java.util.List;
@@ -61,7 +63,7 @@ public List buildFormingLore(MercenaryCompany company) {
List lore = new ArrayList<>();
lore.add(StringFormatter.formatHex("#baa875Being founded"));
lore.add("§7Ready in: §e" + TimeFormatter.formatTime(company.getFormationRemaining()));
- lore.add("§7Leader: §e" + company.getLeader());
+ lore.add("§7Leader: §e" + Members.display(company.getLeader()));
lore.add(" ");
lore.add(StringFormatter.formatHex("#877e7cOpens with one slot, costing "
+ Formatter.formatMoney(Cache.mercenarySlotUpkeep) + "d a day"));
@@ -100,7 +102,7 @@ public List buildFoundConfirmLore(Guild guild) {
public List buildCompanyLore(Guild guild, MercenaryCompany company) {
List lore = new ArrayList<>();
- lore.add("§7Leader: §e" + company.getLeader());
+ lore.add("§7Leader: §e" + Members.display(company.getLeader()));
lore.add("§7Home: §e" + HomeSettlementNames.of(guild));
lore.add("§7Slots: §e" + company.getFilledSlots() + "/" + company.getSlots());
lore.add("§7Reputation: " + company.getReputationString());
@@ -232,7 +234,7 @@ public List buildSlotLore(String occupant) {
lore.add(StringFormatter.formatHex("#877e7cAn empty slot blocks expansion"));
lore.add(StringFormatter.formatHex("#877e7cClick to remove this unused slot"));
} else {
- lore.add("§7Mercenary: §e" + occupant);
+ lore.add("§7Mercenary: §e" + Members.display(occupant));
lore.add(StringFormatter.formatHex("#877e7cDismiss from Roster before removing a slot"));
}
lore.add("§7Upkeep: §e" + Formatter.formatMoney(Cache.mercenarySlotUpkeep) + "d");
@@ -291,7 +293,8 @@ public ItemStack createExpandButton(MercenaryCompany company) {
public ItemStack createRosterItem(MercenaryCompany company, String member, boolean leader) {
ItemStack item = new ItemStack(Material.PLAYER_HEAD, 1);
ItemMeta meta = item.getItemMeta();
- meta.setDisplayName("§e" + member);
+ meta.setDisplayName("§e" + Members.display(member));
+ MemberHeads.apply(meta, member);
List lore = new ArrayList<>();
lore.add("§7Company: §e" + company.getName());
if (leader) {
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java
index 2fb06c8d..8b95dc9d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java
@@ -213,13 +213,13 @@ private void clickSlots(
int visibleSlots = Math.min(company.getSlots(), LAST_SLOT_BUTTON - FIRST_SLOT_BUTTON + 1);
int slotIndex = company.getSlots() - visibleSlots + slot - FIRST_SLOT_BUTTON;
if (slotIndex < company.getSlots() && slotIndex >= company.getEnlisted().size()) {
- report(p, MercenaryCompanyService.removeSlot(guild, p.getName()));
+ report(p, MercenaryCompanyService.removeSlot(guild, Members.key(p)));
slotsView(p, guild, inventory);
}
return;
}
if (e.getSlot() == EXPAND_BUTTON) {
- report(p, MercenaryCompanyService.expand(guild, p.getName()));
+ report(p, MercenaryCompanyService.expand(guild, Members.key(p)));
slotsView(p, guild, inventory);
return;
}
@@ -235,7 +235,7 @@ private void clickRoster(
InventoryClickEvent e, Inventory inventory, Player p, Guild guild, MercenaryCompany company) {
String member = stringKey(e.getCurrentItem());
if (member == null) return;
- report(p, MercenaryCompanyService.kick(guild, p.getName(), member));
+ report(p, MercenaryCompanyService.kick(guild, Members.key(p), member));
rosterView(p, guild, inventory);
}
@@ -250,7 +250,7 @@ private void clickUpgrade(
}
String id = stringKey(e.getCurrentItem());
if (id == null) return;
- report(p, MercenaryCompanyService.upgrade(guild, p.getName(), id));
+ report(p, MercenaryCompanyService.upgrade(guild, Members.key(p), id));
companyUpgradeView(p, guild, inventory);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java
index b3aef9db..e28e2991 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ContractView.java
@@ -179,7 +179,7 @@ private void clickDetail(
long now = System.currentTimeMillis();
if (e.getSlot() == CHANGE_SLOTS_BUTTON && company.isLeader(Members.key(p))) {
if (contract.hasPendingSlots(now)) {
- report(p, company.getContractHandler().withdrawSlots(contractId, p.getName(), now));
+ report(p, company.getContractHandler().withdrawSlots(contractId, Members.key(p), now));
detailView(p, guild, inventory, contractId);
} else {
inv.beginSlotChange(p, guild, contract);
@@ -189,9 +189,9 @@ private void clickDetail(
if (!contract.hasPendingSlots(now) || !canSign(p, contract)) return;
switch (e.getSlot()) {
case ACCEPT_SLOTS_BUTTON -> report(p, company.getContractHandler()
- .acceptSlots(contractId, contract.getHirer(), p.getName(), now));
+ .acceptSlots(contractId, contract.getHirer(), Members.key(p), now));
case DECLINE_SLOTS_BUTTON -> report(p, company.getContractHandler()
- .declineSlots(contractId, contract.getHirer(), p.getName(), now));
+ .declineSlots(contractId, contract.getHirer(), Members.key(p), now));
default -> {
return;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java
index ca316a72..36ddfb0e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionCreator.java
@@ -42,22 +42,22 @@ public ItemStack createCandidateTypeItem(Player p, Faction f, Candidate c) {
} else {
lore.add(StringFormatter.formatHex("#67cc64Candidates:"));
for(String candidate : candidates) {
- lore.add(StringFormatter.formatHex("§f- #e3d5a1"+candidate + " §7(" + Represents.represents(f, candidate) +"§7)"));
+ lore.add(StringFormatter.formatHex("§f- #e3d5a1"+Members.display(candidate) + " §7(" + Represents.represents(f, candidate) +"§7)"));
}
}
lore.add("");
- if(e.isCandiate(c, p.getName())) {
+ if(e.isCandiate(c, Members.key(p))) {
// Already a candidate
if(!e.isActive()) {
lore.add(StringFormatter.formatHex("#ba7872Click to withdraw"));
}
- } else if(!e.canBeCandidate(c, p.getName())) {
+ } else if(!e.canBeCandidate(c, Members.key(p))) {
e.applyReasons(lore, c, p);
} else {
lore.add(StringFormatter.formatHex("#28ed70Click to sign up"));
}
if(gov.hasElection() && e.canVote(Members.key(p))){
- if(!e.hasVoted(c, p.getName())) {
+ if(!e.hasVoted(c, Members.key(p))) {
lore.add(StringFormatter.formatHex("#28ed70Click to view candidates"));
} else {
lore.add(StringFormatter.formatHex("#ab483fYou have already voted for a candidate"));
@@ -75,8 +75,8 @@ public ItemStack createCandidateItem(Faction f, Candidate candidateType, String
ItemStack item = new ItemStack(org.bukkit.Material.PLAYER_HEAD);
SkullMeta skull = (SkullMeta) item.getItemMeta();
skull.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
- skull.setOwningPlayer(org.bukkit.Bukkit.getOfflinePlayer(candidateName));
- skull.setDisplayName(StringFormatter.formatHex("#84c468" + candidateName));
+ MemberHeads.apply(skull, candidateName);
+ skull.setDisplayName(StringFormatter.formatHex("#84c468" + Members.display(candidateName)));
List lore = new ArrayList<>();
lore.add(StringFormatter.formatHex("#67cc64" + Represents.represents(f, candidateName)));
skull.setLore(lore);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java
index 855bb9fa..ffb33ced 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ElectionView.java
@@ -105,17 +105,17 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
Candidate c = Candidate.valueOf(id);
Election election = f.getGovernment().getElection();
if(election.isActive()) {
- if(election.canVote(Members.key(p)) && !election.hasVoted(c, p.getName())) {
+ if(election.canVote(Members.key(p)) && !election.hasVoted(c, Members.key(p))) {
votingView(p, f, c);
}
} else {
- if(election.isCandiate(c, p.getName())) {
- election.removeCandidate(c, p.getName());
+ if(election.isCandiate(c, Members.key(p))) {
+ election.removeCandidate(c, Members.key(p));
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
p.sendMessage("§aWithdrawn from candidacy for "+c.getName());
electionView(p, f);
- } else if(election.canBeCandidate(c, p.getName())) {
- election.addCandidate(c, p.getName());
+ } else if(election.canBeCandidate(c, Members.key(p))) {
+ election.addCandidate(c, Members.key(p));
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
p.sendMessage("§aSigned up as a candidate for "+c.getName());
electionView(p, f);
@@ -132,11 +132,11 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
try {
Candidate type = Candidate.valueOf(cid);
if(election.canVote(Members.key(p)) && election.isActive()) {
- if(!election.hasVoted(type, p.getName()) &&
+ if(!election.hasVoted(type, Members.key(p)) &&
election.getCandidates(type).contains(candidateName)) {
- election.addVote(type, p.getName(), candidateName);
+ election.addVote(type, Members.key(p), candidateName);
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
- p.sendMessage("§aVoted for " + candidateName + " as " + type.getName());
+ p.sendMessage("§aVoted for " + Members.display(candidateName) + " as " + type.getName());
p.closeInventory();
return;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java
index 29194aa2..72f8aaca 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/EspionageView.java
@@ -3,7 +3,6 @@
import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.espionage.EspionageService;
-import net.tfminecraft.simplefactions.espionage.CharacterNames;
import net.tfminecraft.simplefactions.espionage.IntelligenceLedger;
import net.tfminecraft.simplefactions.espionage.SpecialPosition;
import net.tfminecraft.simplefactions.espionage.EspionageConfig;
@@ -76,7 +75,7 @@ static ItemStack reportHeader(Player viewer, IntelligenceReport report) {
var spy = observer == null ? null : observer.getEspionage().getSpymaster();
boolean missingSpymaster = !EspionageService.hasSpymaster(observer);
String last = missingSpymaster ? "\u00a77The Spymaster's office stands vacant; no findings reach your court."
- : "\u00a77These are today\u2019s findings, delivered by your spymaster, " + CharacterNames.of(spy.playerName) + ".";
+ : "\u00a77These are today\u2019s findings, delivered by your spymaster, " + Members.display(spy.playerName) + ".";
List lore = new ArrayList<>(List.of("\u00a77Report quality: \u00a7e" + (missingSpymaster ? "Absent"
: report == null ? IntelligenceTier.UNKNOWN.label() : report.qualityLabel()),
report == null ? "\u00a77No dated account has yet reached your court." : "\u00a77Dated " + report.loreDate(), last));
@@ -163,12 +162,12 @@ public static ItemStack factionItem(Player viewer, Faction target, MenuItemType
return switch (type) {
case GOVERNMENT -> {
ItemStack head = styled(Material.PLAYER_HEAD, "#93c9a7Government:",
- "#9c9775§l" + target.getRulerTitle() + ": #c2bea7" + CharacterNames.display(viewer, target.getLeader(), target),
+ "#9c9775§l" + target.getRulerTitle() + ": #c2bea7" + Members.display(viewer, target.getLeader()),
"#85c265Administrative Power§7: §e" + value(report, "Administrative power", ""),
"#85c265Stability: " + IntelligenceReport.stabilityRange(report), " ",
"#b8ae61Ruling System: #d4c9ae" + target.getGovernmentString());
SkullMeta meta = (SkullMeta) head.getItemMeta();
- meta.setOwningPlayer(Bukkit.getOfflinePlayer(target.getLeader()));
+ MemberHeads.apply(meta, target.getLeader());
head.setItemMeta(meta);
yield head;
}
@@ -221,7 +220,7 @@ public static ItemStack foreignPositionsItem(Player viewer, Faction faction) {
for (var office : SpecialPosition.values()) {
var holder = EspionageService.canViewExact(viewer, faction) ? faction.getEspionage().holder(office) : null;
String name = EspionageService.canViewExact(viewer, faction)
- ? holder == null ? "Vacant" : CharacterNames.display(viewer, holder.playerName)
+ ? holder == null ? "Vacant" : Members.display(viewer, holder.playerName)
: report == null ? "Unknown" : report.officeHolder(office);
String aptitude = EspionageService.canViewExact(viewer, faction)
? Integer.toString(switch (office) {
@@ -253,7 +252,7 @@ public static void positions(Player viewer, Faction faction, InventoryManager ma
for (SpecialPosition office : SpecialPosition.values()) {
var holder = office == SpecialPosition.SPYMASTER ? EspionageService.spymaster(faction) : faction.getEspionage().holder(office);
ItemStack option = item(Material.ENDER_EYE, office.label(),
- "\u00a77Office holder: " + (holder == null ? "Vacant" : CharacterNames.display(viewer, holder.playerName)),
+ "\u00a77Office holder: " + (holder == null ? "Vacant" : Members.display(viewer, holder.playerName)),
"\u00a7aClick to inspect this office.");
var meta = option.getItemMeta();
meta.getPersistentDataContainer().set(new NamespacedKey(SimpleFactions.plugin, "special-office"),
@@ -271,7 +270,7 @@ public static void spymasterOffice(Player viewer, Faction faction, InventoryMana
27, "\u00a77Spymaster's Office");
SpecialPositionAssignment holder = EspionageService.spymaster(faction);
long now = System.currentTimeMillis();
- ItemStack head = item(Material.PLAYER_HEAD, "Spymaster: " + (holder == null ? "Vacant" : CharacterNames.display(viewer, holder.playerName)),
+ ItemStack head = item(Material.PLAYER_HEAD, "Spymaster: " + (holder == null ? "Vacant" : Members.display(viewer, holder.playerName)),
"§7Aptitude: §e" + EspionageService.effectiveAptitude(faction, holder) + "/100",
buildUpLine(holder, now),
holder == null ? "§7Falls to the faction leader once they have an active character."
@@ -378,17 +377,18 @@ private static ItemStack conduct(boolean offense, int reduction) {
private static void candidates(Player viewer, Faction faction, InventoryManager manager, int page) {
if (!EspionageService.isOwn(viewer, faction) || !faction.isLeader(Members.key(viewer))) return;
List members = faction.getMembers().stream().distinct()
- .filter(name -> EspionageService.eligible(faction, name)).sorted(String.CASE_INSENSITIVE_ORDER).toList();
+ .filter(name -> EspionageService.eligible(faction, name))
+ .sorted(java.util.Comparator.comparing(Members::display, String.CASE_INSENSITIVE_ORDER)).toList();
int safePage = Math.max(0, Math.min(page, Math.max(0, (members.size() - 1) / 45)));
Inventory inventory = Bukkit.createInventory(new SFInventoryHolder(faction.getId(), SFGUI.SPYMASTER_SELECT, safePage),
54, "§7Appoint Spymaster");
int start = safePage * 45;
for (int index = start; index < Math.min(start + 45, members.size()); index++) {
String name = members.get(index);
- ItemStack head = item(Material.PLAYER_HEAD, CharacterNames.display(viewer, name),
+ ItemStack head = item(Material.PLAYER_HEAD, Members.display(viewer, name),
"§7Guild: " + net.tfminecraft.simplefactions.utils.Represents.represents(faction, name),
buildUpTerms(),
- Bukkit.getPlayerExact(name) == null ? "§8Must be online with an active character." : "§aClick to appoint as Spymaster.");
+ !Members.isOnline(name) ? "§8Must be online with an active character." : "§aClick to appoint as Spymaster.");
var meta = head.getItemMeta();
meta.getPersistentDataContainer().set(new NamespacedKey(SimpleFactions.plugin, "spy-candidate"),
PersistentDataType.STRING, name);
@@ -468,7 +468,7 @@ else if (slot == 11 || slot == 15) {
else if (event.getCurrentItem() != null && event.getCurrentItem().hasItemMeta()) {
String candidate = event.getCurrentItem().getItemMeta().getPersistentDataContainer()
.get(new NamespacedKey(SimpleFactions.plugin, "spy-candidate"), PersistentDataType.STRING);
- if (candidate != null && EspionageService.appoint(viewer, faction, Bukkit.getPlayerExact(candidate))) {
+ if (candidate != null && EspionageService.appoint(viewer, faction, Members.player(candidate))) {
spymasterOffice(viewer, faction, manager);
}
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java
index 6d0dd38c..e9bea4e7 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/FactionCreator.java
@@ -3,14 +3,12 @@
import net.tfminecraft.simplefactions.identity.Members;
import net.tfminecraft.simplefactions.espionage.EspionageService;
-import net.tfminecraft.simplefactions.espionage.CharacterNames;
import net.tfminecraft.simplefactions.util.LegacyModelData;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
-import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
@@ -95,7 +93,7 @@ public List listLore(Player p, Faction f) {
if(realmSize > 0 && realmSize-f.getProvinces().size() > 0) lore.add(StringFormatter.formatHex("#d4c9aeRealm Size: #7a706a"+realmSize+" #a39ba8("+(realmSize-f.getProvinces().size())+" from subjects)"));
else if(realmSize > 0) lore.add(StringFormatter.formatHex("#d4c9aeRealm Size: #7a706a"+realmSize));
lore.add(" ");
- lore.add(StringFormatter.formatHex("#9c9775"+f.getRulerTitle()+": #c2bea7"+CharacterNames.display(p, f.getLeader(), f)));
+ lore.add(StringFormatter.formatHex("#9c9775"+f.getRulerTitle()+": #c2bea7"+Members.display(p, f.getLeader())));
lore.add(StringFormatter.formatHex("#b8ae61Ruling System: #d4c9ae"+f.getGovernmentString()));
lore.add(StringFormatter.formatHex("#b8ae61Culture: #d4c9ae"+f.getCulture()));
lore.add(StringFormatter.formatHex("#b8ae61Religion: #d4c9ae"+f.getReligion()));
@@ -294,8 +292,8 @@ public ItemStack createMenuItem(Player p, Faction f, MenuItemType t) {
i = new ItemStack(Material.PLAYER_HEAD, 1);
SkullMeta m = (SkullMeta) i.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
- m.setDisplayName(StringFormatter.formatHex("#9c9775§l"+f.getRulerTitle()+": #c2bea7"+CharacterNames.display(p, f.getLeader(), f)));
- m.setOwningPlayer(Bukkit.getOfflinePlayer(f.getLeader()));
+ m.setDisplayName(StringFormatter.formatHex("#9c9775§l"+f.getRulerTitle()+": #c2bea7"+Members.display(p, f.getLeader())));
+ MemberHeads.apply(m, f.getLeader());
List lore = new ArrayList();
lore.add(StringFormatter.formatHex("#b8ae61Ruling System: #d4c9ae"+f.getGovernmentString()));
m.setLore(lore);
@@ -305,10 +303,10 @@ public ItemStack createMenuItem(Player p, Faction f, MenuItemType t) {
SkullMeta m = (SkullMeta) i.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
m.setDisplayName(StringFormatter.formatHex("#93c9a7Government:"));
- m.setOwningPlayer(Bukkit.getOfflinePlayer(f.getLeader()));
+ MemberHeads.apply(m, f.getLeader());
List lore = new ArrayList();
Government gov = f.getGovernment();
- lore.add(StringFormatter.formatHex("#9c9775§l"+f.getRulerTitle()+": #c2bea7"+CharacterNames.display(p, f.getLeader(), f)));
+ lore.add(StringFormatter.formatHex("#9c9775§l"+f.getRulerTitle()+": #c2bea7"+Members.display(p, f.getLeader())));
double power = Formatter.formatDouble(gov.getPower());
double maxPower = Formatter.formatDouble(gov.getMaxPower());
String powerString = ((power < 0) ? "§c" : "") + power+"/"+((maxPower < 0) ? "§c" : "") + maxPower;
@@ -408,7 +406,7 @@ public ItemStack createMenuItem(Player p, Faction f, MenuItemType t) {
ItemMeta m = i.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
List memberNames = new ArrayList<>(f.getMembers());
- memberNames.removeIf(name -> name.equalsIgnoreCase(f.getLeader()));
+ memberNames.removeIf(name -> name.equals(f.getLeader()));
memberNames.add(0, f.getLeader());
m.setDisplayName(StringFormatter.formatHex("#b8ae61Members: #7fbd73"+memberNames.size()));
List lore = net.tfminecraft.simplefactions.espionage.RosterLore.faction(p, f).stream().map(StringFormatter::formatHex).toList();
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java
index 9ee81d0b..465617cb 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentCreator.java
@@ -69,7 +69,7 @@ public ItemStack createGovernmentItem(Player p, Faction f) {
m.setDisplayName(StringFormatter.formatHex("#93c9a7Government:"));
List lore = new ArrayList();
Government gov = f.getGovernment();
- lore.add(StringFormatter.formatHex("#9c9775§l"+f.getRulerTitle()+": #c2bea7"+net.tfminecraft.simplefactions.espionage.CharacterNames.display(p, f.getLeader(), f)));
+ lore.add(StringFormatter.formatHex("#9c9775§l"+f.getRulerTitle()+": #c2bea7"+Members.display(p, f.getLeader())));
double power = Formatter.formatDouble(gov.getPower());
double maxPower = Formatter.formatDouble(gov.getMaxPower());
String powerString = ((power < 0) ? "§c" : "") + power+"/"+((maxPower < 0) ? "§c" : "") + maxPower;
@@ -98,7 +98,7 @@ public ItemStack createCouncilItem(Faction f) {
if(gov.getCouncil().getCurrentSize() > 0) {
lore.add(StringFormatter.formatHex("#93c9a7Members:"));
for(String member : gov.getCouncilMembers()) {
- lore.add(StringFormatter.formatHex("#d4bb98- "+member + " §7("+Represents.represents(f, member)+")"));
+ lore.add(StringFormatter.formatHex("#d4bb98- "+Members.display(member) + " §7("+Represents.represents(f, member)+")"));
}
}
m.setLore(lore);
@@ -146,7 +146,7 @@ public ItemStack createElectionItem(Player p, Faction f) {
color = "#c74d32"; // ineligible
}
- lore.add(formatElectionLine(i++, name, votes, totalVotes, color));
+ lore.add(formatElectionLine(i++, Members.display(name), votes, totalVotes, color));
}
}
@@ -175,7 +175,7 @@ public ItemStack createElectionItem(Player p, Faction f) {
color = "#c74d32"; // ineligible
}
- lore.add(formatElectionLine(i++, name, votes, totalVotes, color));
+ lore.add(formatElectionLine(i++, Members.display(name), votes, totalVotes, color));
}
}
}
@@ -456,7 +456,7 @@ public ItemStack createTaxTypeItem(Player p, Faction f, TaxTarget target, boolea
else lore.add(StringFormatter.formatHex("#28ed70Click to specific rates"));
} else {
Government gov = f.getGovernment();
- Proposal proposal = new Proposal(p.getName(), gov);
+ Proposal proposal = new Proposal(Members.key(p), gov);
proposal.setTaxProposal(new TaxLawChange(target, "all", 50));
if(gov.canProposeOrStartMovement(p) && gov.canBeProposed(proposal)) {
lore.add(StringFormatter.formatHex("#525d5dCurrent Rate: #e3d5a1"+f.getTaxRate(target, null, false)+"%"+ " §8(§7"+f.getTaxRate(target, null, true)+"% effective§8)"));
@@ -514,7 +514,7 @@ public ItemStack createSpecificTaxItem(Player p, Faction f, String id, TaxTarget
lore.add(StringFormatter.formatHex("#3f4040(#767a77Base Rate: #928d7a"+baseRate+"%#3f4040)"));
}
Government gov = f.getGovernment();
- Proposal proposal = new Proposal(p.getName(), gov);
+ Proposal proposal = new Proposal(Members.key(p), gov);
proposal.setTaxProposal(new TaxLawChange(target, id, 50));
if(gov.canProposeOrStartMovement(p) && gov.canBeProposed(proposal)) lore.add(StringFormatter.formatHex("#28ed70Click to propose a change"));
else lore.add(StringFormatter.formatHex("#89504eAnother proposal is active for this target."));
@@ -619,9 +619,7 @@ public ItemStack createCouncilMemberItem(Player player, Faction f, int slot) {
} else {
item = new ItemStack(Material.PLAYER_HEAD);
ItemMeta skullMeta = item.getItemMeta();
- if(skullMeta instanceof org.bukkit.inventory.meta.SkullMeta) {
- ((org.bukkit.inventory.meta.SkullMeta) skullMeta).setOwner(memberName);
- }
+ MemberHeads.apply(skullMeta, memberName);
item.setItemMeta(skullMeta);
}
@@ -638,7 +636,7 @@ public ItemStack createCouncilMemberItem(Player player, Faction f, int slot) {
}
m.setLore(lore);
} else {
- m.setDisplayName(StringFormatter.formatHex("#93c9a7" + memberName));
+ m.setDisplayName(StringFormatter.formatHex("#93c9a7" + Members.display(memberName)));
List lore = new ArrayList<>();
// Display wealth and ranking
@@ -685,16 +683,14 @@ public ItemStack createPotentialMemberItem(Player player, Faction f, String memb
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
ItemMeta skullMeta = item.getItemMeta();
- if(skullMeta instanceof org.bukkit.inventory.meta.SkullMeta) {
- ((org.bukkit.inventory.meta.SkullMeta) skullMeta).setOwner(member);
- }
+ MemberHeads.apply(skullMeta, member);
item.setItemMeta(skullMeta);
ItemMeta m = item.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
- m.setDisplayName(StringFormatter.formatHex("#93c9a7" + member));
+ m.setDisplayName(StringFormatter.formatHex("#93c9a7" + Members.display(member)));
List lore = new ArrayList<>();
// Display wealth and ranking
@@ -739,7 +735,7 @@ public ItemStack createCurrentProposalItem(Player p, Faction f, Proposal proposa
m.setDisplayName(StringFormatter.formatHex(proposal.isLawProposal() ? "#93c9a7Law Proposal"
: proposal.isFeeProposal() ? "#93c9a7Fee Proposal" : "#93c9a7Tax Proposal"));
List lore = new ArrayList();
- lore.add(StringFormatter.formatHex("#85c265Proposed by: #c2bea7"+proposal.getProposer()));
+ lore.add(StringFormatter.formatHex("#85c265Proposed by: #c2bea7"+Members.display(proposal.getProposer())));
LoreWriter.applyProposalLore(proposal, lore, p, f, m);
m.setLore(lore);
item.setItemMeta(m);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java
index 1e554b17..f484f7b9 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GovernmentView.java
@@ -77,7 +77,7 @@ public void governmentView(Player player, Faction f, Inventory i) {
i.setItem(24, creator.createProposalListItem(player, f));
i.setItem(33, creator.createMovementListItem(player, f));
Government gov = f.getGovernment();
- if(gov.getCouncil().canBeMember(player.getName(), true, true)) i.setItem(21, creator.createToggleRefuseButton(player, f));
+ if(gov.getCouncil().canBeMember(Members.key(player), true, true)) i.setItem(21, creator.createToggleRefuseButton(player, f));
if(gov.hasElections()) i.setItem(14, creator.createElectionItem(player, f));
if(gov.getCouncil().canHostSession() && Members.is(player, f.getLeader())) {
i.setItem(23, creator.createStartCouncilButton(player, f));
@@ -371,7 +371,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
Government gov = f.getGovernment();
// Foreign viewers see this menu too; only those offered the button may use it.
if(!EspionageService.canViewExact(p, f)
- || !gov.getCouncil().canBeMember(p.getName(), true, true)) return;
+ || !gov.getCouncil().canBeMember(Members.key(p), true, true)) return;
gov.getCouncil().toggleRefuse(Members.key(p));
governmentView(p, f, inventory);
p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f);
@@ -518,7 +518,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
}
Government gov = f.getGovernment();
double cost = law.getCost()*law.getCompatibility(f.getLawHandler().getGroup(group).getCurrent().getId());
- Proposal proposal = new Proposal(p.getName(), gov);
+ Proposal proposal = new Proposal(Members.key(p), gov);
proposal.setLawProposal(law);
if(!gov.hasCouncil() && f.isLeader(Members.key(p))) {
if(gov.getPower() < cost) {
@@ -554,18 +554,18 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
- movement.createCause(p.getName(), proposal);
+ movement.createCause(Members.key(p), proposal);
p.sendMessage("§aProposal added to your movement! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
return;
}
- if (CivilWarHostMovementRules.blocksHostGuildStart(f, p.getName())) {
+ if (CivilWarHostMovementRules.blocksHostGuildStart(f, Members.key(p))) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
p.sendMessage(CivilWarCopy.ONE_PROVINCE_HOST_GUILD);
return;
}
- gov.startMovement(p.getName(), proposal);
+ gov.startMovement(Members.key(p), proposal);
p.sendMessage("§aMovement started! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
@@ -595,7 +595,7 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
return;
}
- Proposal proposal = new Proposal(p.getName(), gov);
+ Proposal proposal = new Proposal(Members.key(p), gov);
PoliticalAction politicalAction = PoliticalActionLoader.getByAction(action);
proposal.setPoliticalActionProposal(politicalAction);
@@ -606,18 +606,18 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
- movement.createCause(p.getName(), proposal);
+ movement.createCause(Members.key(p), proposal);
p.sendMessage("§aProposal added to your movement! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
return;
}
- if (CivilWarHostMovementRules.blocksHostGuildStart(f, p.getName())) {
+ if (CivilWarHostMovementRules.blocksHostGuildStart(f, Members.key(p))) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
p.sendMessage(CivilWarCopy.ONE_PROVINCE_HOST_GUILD);
return;
}
- gov.startMovement(p.getName(), proposal);
+ gov.startMovement(Members.key(p), proposal);
p.sendMessage("§aMovement started! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
@@ -864,7 +864,7 @@ private void submitWarEnd(Player p, Faction f, Action action, String warId, bool
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
return;
}
- Proposal proposal = new Proposal(p.getName(), gov);
+ Proposal proposal = new Proposal(Members.key(p), gov);
PoliticalAction politicalAction = PoliticalActionLoader.getByAction(action);
proposal.setPoliticalActionProposal(politicalAction);
proposal.setTarget(warId);
@@ -892,18 +892,18 @@ private void submitWarEnd(Player p, Faction f, Action action, String warId, bool
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
- movement.createCause(p.getName(), proposal);
+ movement.createCause(Members.key(p), proposal);
p.sendMessage("§aProposal added to your movement! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
return;
}
- if (CivilWarHostMovementRules.blocksHostGuildStart(f, p.getName())) {
+ if (CivilWarHostMovementRules.blocksHostGuildStart(f, Members.key(p))) {
p.playSound(p, Sound.ENTITY_VILLAGER_NO, 1f, 1f);
p.sendMessage(CivilWarCopy.ONE_PROVINCE_HOST_GUILD);
return;
}
- gov.startMovement(p.getName(), proposal);
+ gov.startMovement(Members.key(p), proposal);
p.sendMessage("§aMovement started! Rally support for your proposal by sharing it with your faction and allies!");
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
governmentView(p, f, null);
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java
index 789ba892..3ec7363e 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java
@@ -1,9 +1,10 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import net.tfminecraft.simplefactions.espionage.EspionageAccess;
import net.tfminecraft.simplefactions.espionage.IntelligenceLedger;
import net.tfminecraft.simplefactions.espionage.EspionageService;
-import net.tfminecraft.simplefactions.espionage.CharacterNames;
import net.tfminecraft.simplefactions.war.freeze.PreparationFreeze;
import net.tfminecraft.simplefactions.util.LegacyModelData;
@@ -13,7 +14,6 @@
import java.util.List;
import java.util.Map;
-import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
@@ -160,7 +160,7 @@ public ItemStack createMenuItem(Player p, Guild guild, MenuItemType t) {
SkullMeta m = (SkullMeta) i.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
m.setDisplayName(StringFormatter.formatHex("#9c9775§lLeader: #c2bea7"+ReportedMenus.guildLeader(p, guild)));
- if (!ReportedMenus.guildLeader(p, guild).endsWith("Unknown")) m.setOwningPlayer(Bukkit.getOfflinePlayer(guild.getLeader()));
+ if (!ReportedMenus.guildLeader(p, guild).endsWith("Unknown")) MemberHeads.apply(m, guild.getLeader());
i.setItemMeta(m);
} else if(t.equals(MenuItemType.WEALTH)) {
i = new ItemStack(Material.GOLD_NUGGET, 1);
@@ -734,8 +734,9 @@ private static void addLedgerSourceSection(List lore, String header, Map
int count = 0;
for (var e : LedgerHistory.descending(amounts)) {
if (e.getValue() <= 0) continue;
+ // Citizen and deposit rows are member keys; guild and faction names pass through unchanged.
lore.add(StringFormatter.formatHex(
- "#d4c9ae" + e.getKey()
+ "#d4c9ae" + Members.display(e.getKey())
+ "#7a706a: #7fbd73+"
+ String.format("%.2f", e.getValue()) + "d"
));
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MemberHeads.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MemberHeads.java
new file mode 100644
index 00000000..8e456c3c
--- /dev/null
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MemberHeads.java
@@ -0,0 +1,18 @@
+package net.tfminecraft.simplefactions.managers.inventory;
+
+import org.bukkit.Bukkit;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.inventory.meta.SkullMeta;
+
+import net.tfminecraft.simplefactions.identity.Members;
+
+/** Skull faces for members: the head of the account that plays the character. */
+public final class MemberHeads {
+
+ private MemberHeads() {}
+
+ public static void apply(ItemMeta meta, String key) {
+ if (!(meta instanceof SkullMeta skull)) return;
+ Members.accountId(key).ifPresent(id -> skull.setOwningPlayer(Bukkit.getOfflinePlayer(id)));
+ }
+}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementCreator.java
index 67c75b22..349e9872 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementCreator.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementCreator.java
@@ -45,7 +45,7 @@ public ItemStack createMovementLeaderItem(Player p, Movement movement) {
}
m.setDisplayName(StringFormatter.formatHex("#c5e0e3Movement Leader"));
List lore = new ArrayList<>();
- lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + (movement.hasLeader() ? movement.getLeader() : "None")));
+ lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + (movement.hasLeader() ? Members.display(movement.getLeader()) : "None")));
lore.add(" ");
lore.add(StringFormatter.formatHex("#7a7a7aThe leader coordinates the movement"));
lore.add(StringFormatter.formatHex("#7a7a7aand represents its interests."));
@@ -159,11 +159,11 @@ public ItemStack createCausesButton(Movement movement) {
public ItemStack createCauseItem(Cause cause, Player p, Faction f) {
ItemStack item = new ItemStack(Material.WRITABLE_BOOK);
ItemMeta m = item.getItemMeta();
- m.setDisplayName(StringFormatter.formatHex("#c5e0e3"+(cause.hasLeader() ? cause.getLeader()+"'s Cause" : "Disorganized Cause")));
+ m.setDisplayName(StringFormatter.formatHex("#c5e0e3"+(cause.hasLeader() ? Members.display(cause.getLeader())+"'s Cause" : "Disorganized Cause")));
List lore = new ArrayList<>();
lore.add(StringFormatter.formatHex("#9c9775Action: #c2bea7" + cause.getAction().getDisplay()));
if (cause.hasLeader()) {
- lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + cause.getLeader()));
+ lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + Members.display(cause.getLeader())));
} else {
lore.add(StringFormatter.formatHex("#c74d32No Leader"));
}
@@ -200,7 +200,7 @@ public ItemStack createCauseLeaderItem(Player p, Cause cause) {
m.setDisplayName(StringFormatter.formatHex("#c5e0e3Cause Leader"));
List lore = new ArrayList<>();
if (cause.hasLeader()) {
- lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + cause.getLeader()));
+ lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + Members.display(cause.getLeader())));
} else {
lore.add(StringFormatter.formatHex("#c74d32No Leader Assigned"));
}
@@ -306,7 +306,7 @@ public ItemStack createEndMovementConfirmItem(Movement movement) {
public ItemStack createMovementListItem(Movement movement) {
ItemStack item = new ItemStack(Material.BLAZE_POWDER);
ItemMeta m = item.getItemMeta();
- m.setDisplayName(StringFormatter.formatHex("#d1743b" + movement.getLeader() + "'s Movement "));
+ m.setDisplayName(StringFormatter.formatHex("#d1743b" + Members.display(movement.getLeader()) + "'s Movement "));
List lore = new ArrayList<>();
appendMovementSummaryLore(lore, movement);
lore.add(" ");
@@ -318,7 +318,7 @@ public ItemStack createMovementListItem(Movement movement) {
}
private void appendMovementSummaryLore(List lore, Movement movement) {
- lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + movement.getLeader()));
+ lore.add(StringFormatter.formatHex("#9c9775Leader: #c2bea7" + Members.display(movement.getLeader())));
lore.add(StringFormatter.formatHex("#9c9775Organization: #c2bea7" + Formatter.formatDouble(movement.getOrganization()) +
"/"+movement.getMaxOrganization()+" §7(" + (movement.getOrganizationGain() > 0 ? "+" : "#d65c5c") + movement.getOrganizationGain()) + " §7per day)");
lore.add(StringFormatter.formatHex("#c45749Power: #d87638"+movement.getPower()+"% #7a7a7aof " + movement.getFaction().getName()));
@@ -509,7 +509,7 @@ public ItemStack createAvailableCauseSlot(Movement movement, int index) {
public ItemStack createDemandItem(Cause cause, Movement movement) {
ItemStack item = new ItemStack(Material.PAPER);
ItemMeta m = item.getItemMeta();
- m.setDisplayName(StringFormatter.formatHex("#c5e0e3Demand: " + (cause.hasLeader() ? cause.getLeader() : "Leaderless")));
+ m.setDisplayName(StringFormatter.formatHex("#c5e0e3Demand: " + (cause.hasLeader() ? Members.display(cause.getLeader()) : "Leaderless")));
List lore = new ArrayList<>();
lore.add(StringFormatter.formatHex("#9c9775Action: #c2bea7" + cause.getAction().getDisplay()));
lore.add(" ");
@@ -622,7 +622,7 @@ public ItemStack createTargetButton(Player p, Cause cause) {
List lore = new ArrayList<>();
if (proposal.hasTarget()) {
- lore.add(StringFormatter.formatHex("#9c9775Target: #c2bea7" + proposal.getTarget()));
+ lore.add(StringFormatter.formatHex("#9c9775Target: #c2bea7" + Members.display(proposal.getTarget())));
} else {
lore.add(StringFormatter.formatHex("#c74d32No Target Selected"));
lore.add(StringFormatter.formatHex("#d65c5c-5 Organization Gain Penalty"));
@@ -657,7 +657,7 @@ public ItemStack createPotentialTargetItem(Player player, Faction f, String memb
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
ItemMeta m = item.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP);
- m.setDisplayName(StringFormatter.formatHex("#c5e0e3" + member));
+ m.setDisplayName(StringFormatter.formatHex("#c5e0e3" + Members.display(member)));
List lore = new ArrayList<>();
lore.add(StringFormatter.formatHex("#7a7a7aSelect this player as the"));
lore.add(StringFormatter.formatHex("#7a7a7atarget for leadership change."));
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java
index 57808f74..b3f5746a 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/MovementView.java
@@ -80,7 +80,7 @@ public Cause displayedCause(Inventory inventory, Movement movement) {
public void movementView(Player player, Faction f, Movement movement, Inventory i) {
boolean open = i == null;
if (i == null) {
- i = Bukkit.createInventory(new SFInventoryHolder(movement.getId(), SFGUI.MOVEMENT_VIEW), 54, "Movement: " + movement.getLeader());
+ i = Bukkit.createInventory(new SFInventoryHolder(movement.getId(), SFGUI.MOVEMENT_VIEW), 54, "Movement: " + Members.display(movement.getLeader()));
}
i.clear();
@@ -451,7 +451,7 @@ else if (slot == 19) {
}
}
}
- Player factionLeader = Bukkit.getPlayer(f.getLeader());
+ Player factionLeader = Members.player(f.getLeader());
if (factionLeader != null && factionLeader.isOnline()) {
LogManager.movement(
"SEND_DEMANDS movementId=%s faction=%s power=%.1f",
@@ -460,7 +460,7 @@ else if (slot == 19) {
movement.getPower());
demandsView(factionLeader, f, movement, null);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 1.5f);
- p.sendMessage(StringFormatter.formatHex("§7Demands sent to " + f.getLeader()));
+ p.sendMessage(StringFormatter.formatHex("§7Demands sent to " + Members.display(f.getLeader())));
} else {
p.sendMessage(StringFormatter.formatHex("§cThe faction leader must be online to send demands!"));
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1);
@@ -481,7 +481,7 @@ else if (slot == 25) {
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1);
return;
}
- Player movementLeader = Bukkit.getPlayer(movement.getLeader());
+ Player movementLeader = Members.player(movement.getLeader());
if (movementLeader == null || !movementLeader.isOnline()) {
p.sendMessage(StringFormatter.formatHex("§cThe movement leader must be online to demand a disband."));
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1);
@@ -494,7 +494,7 @@ else if (slot == 25) {
movement.getPower());
crackdownView(movementLeader, f, movement, null);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 1.5f);
- p.sendMessage(StringFormatter.formatHex("§7Disband demand sent to " + movement.getLeader()));
+ p.sendMessage(StringFormatter.formatHex("§7Disband demand sent to " + Members.display(movement.getLeader())));
}
// Phase buttons (slots 28-31)
else if (slot >= 28 && slot <= 31) {
@@ -654,9 +654,9 @@ private void handleDemandsViewClick(InventoryClickEvent e, Movement movement, Pl
// Notify movement leader if online
if (movement.hasLeader()) {
- Player movementLeader = Bukkit.getPlayer(movement.getLeader());
+ Player movementLeader = Members.player(movement.getLeader());
if (movementLeader != null && movementLeader.isOnline()) {
- movementLeader.sendMessage(StringFormatter.formatHex("§a" + f.getLeader() + " has accepted your demands!"));
+ movementLeader.sendMessage(StringFormatter.formatHex("§a" + Members.name(p) + " has accepted your demands!"));
}
}
@@ -684,9 +684,9 @@ else if (slot == 33) {
// Notify movement leader if online
if (movement.hasLeader()) {
- Player movementLeader = Bukkit.getPlayer(movement.getLeader());
+ Player movementLeader = Members.player(movement.getLeader());
if (movementLeader != null && movementLeader.isOnline()) {
- movementLeader.sendMessage(StringFormatter.formatHex("§c" + f.getLeader() + " has declined your demands!"));
+ movementLeader.sendMessage(StringFormatter.formatHex("§c" + Members.name(p) + " has declined your demands!"));
movementLeader.sendMessage(StringFormatter.formatHex("§7A civil war has begun!"));
}
}
@@ -709,9 +709,9 @@ private void handleCrackdownViewClick(InventoryClickEvent e, Movement movement,
movement.getId(),
f.getId(),
movement.getPower());
- Player factionLeader = Bukkit.getPlayer(f.getLeader());
+ Player factionLeader = Members.player(f.getLeader());
if (factionLeader != null && factionLeader.isOnline()) {
- factionLeader.sendMessage(StringFormatter.formatHex("§a" + p.getName() + " disbanded their movement."));
+ factionLeader.sendMessage(StringFormatter.formatHex("§a" + Members.name(p) + " disbanded their movement."));
}
f.getGovernment().endMovement(movement);
p.closeInventory();
@@ -730,9 +730,9 @@ private void handleCrackdownViewClick(InventoryClickEvent e, Movement movement,
p.sendMessage(StringFormatter.formatHex("§cYou have refused to disband."));
p.sendMessage(StringFormatter.formatHex("§7A civil war has begun!"));
p.playSound(p.getLocation(), "entity.ender_dragon.growl", 1, 0.8f);
- Player factionLeader = Bukkit.getPlayer(f.getLeader());
+ Player factionLeader = Members.player(f.getLeader());
if (factionLeader != null && factionLeader.isOnline()) {
- factionLeader.sendMessage(StringFormatter.formatHex("§c" + p.getName() + " refused to disband!"));
+ factionLeader.sendMessage(StringFormatter.formatHex("§c" + Members.name(p) + " refused to disband!"));
factionLeader.sendMessage(StringFormatter.formatHex("§7A civil war has begun!"));
}
p.closeInventory();
@@ -752,17 +752,17 @@ private boolean canPlayerCreateCause(Player p, Movement movement) {
}
private Object getSupporterObject(Player p, Movement movement) {
- String playerName = p.getName();
+ String key = Members.key(p);
// Check if player is in supporters as a citizen
- if (movement.getSupporters().getCitizens().contains(playerName)) {
- return playerName;
+ if (movement.getSupporters().getCitizens().contains(key)) {
+ return key;
}
// Check if player's guild is a supporter
- Member relation = movement.getFaction().getRelationToFaction(playerName);
+ Member relation = movement.getFaction().getRelationToFaction(key);
if (relation == Member.GUILD_LEADER || relation == Member.GUILD_MEMBER) {
- Guild guild = FactionManager.getGuildByMember(playerName);
+ Guild guild = FactionManager.getGuildByMember(key);
if (guild != null && movement.getSupporters().getGuilds().contains(guild)) {
return guild;
}
@@ -770,7 +770,7 @@ private Object getSupporterObject(Player p, Movement movement) {
// Check if player's faction is a supporter
if (relation == Member.VASSAL_LEADER || relation == Member.VASSAL_MEMBER) {
- Faction faction = FactionManager.getByMember(playerName);
+ Faction faction = FactionManager.getByMember(key);
if (faction != null && movement.getSupporters().getFactions().contains(faction)) {
return faction;
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ReportedMenus.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ReportedMenus.java
index b33fe1a6..2507e025 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ReportedMenus.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/ReportedMenus.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -45,7 +47,7 @@ public static ItemStack mask(ItemStack template, String title, List lore
public static String guildLeader(Player viewer, Guild guild) {
if (EspionageService.canViewExact(viewer, guild.getFaction()) || guild.getFaction().isLeader(guild.getLeader()))
- return CharacterNames.display(viewer, guild.getLeader(), guild.getFaction());
+ return Members.display(viewer, guild.getLeader());
var report = EspionageService.report(viewer, guild.getFaction());
var names = report == null ? List.of() : report.details("guild-leader", "guild-leader:" + guild.getId());
return names.isEmpty() ? "\u00a77Unknown" : names.getFirst();
@@ -129,7 +131,7 @@ public static void government(Inventory inventory, Player viewer, Faction factio
prepare(inventory);
var report = EspionageService.report(viewer, faction);
inventory.setItem(10, mask(new ItemStack(Material.WRITABLE_BOOK), "#93c9a7Government:", List.of(
- "#9c9775" + faction.getRulerTitle() + ": #c2bea7" + CharacterNames.display(viewer, faction.getLeader(), faction),
+ "#9c9775" + faction.getRulerTitle() + ": #c2bea7" + Members.display(viewer, faction.getLeader()),
"#85c265Administrative Power: " + (report == null ? "Unknown" : report.display("Administrative power")),
"", "#b8ae61Ruling System: #d4c9ae" + faction.getGovernmentString())));
inventory.setItem(11, mask(IconGetter.getIconOrDefault("stability", Material.GREEN_DYE),
diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/VehicleFeeView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/VehicleFeeView.java
index 6fdca0b2..393da586 100644
--- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/VehicleFeeView.java
+++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/VehicleFeeView.java
@@ -1,5 +1,7 @@
package net.tfminecraft.simplefactions.managers.inventory;
+import net.tfminecraft.simplefactions.identity.Members;
+
import java.util.ArrayList;
import java.util.List;
@@ -212,7 +214,7 @@ private void startInput(Player p, Faction f, FeeKind kind, String vehicleTypeId)
private static boolean canPropose(Player p, Faction f, FeeKind kind, String vehicleTypeId) {
Government gov = f.getGovernment();
- Proposal proposal = new Proposal(p.getName(), gov);
+ Proposal proposal = new Proposal(Members.key(p), gov);
proposal.setFeeProposal(new FeeChange(kind, vehicleTypeId, 0.0));
return gov.canProposeOrStartMovement(p) && gov.canBeProposed(proposal);
}
diff --git a/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java b/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java
index ceeaab8a..1716cc9d 100644
--- a/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java
+++ b/src/main/java/net/tfminecraft/simplefactions/utils/TabCompletion.java
@@ -82,11 +82,11 @@ private List completeCompany(Player p, String[] args) {
completions.add("1");
return completions;
}
- if(args.length == 2 && args[0].equalsIgnoreCase("kick")) {
+ if(args.length >= 2 && args[0].equalsIgnoreCase("kick")) {
if(guild != null && guild.getCompany() != null) {
- completions.addAll(guild.getCompany().getEnlisted());
+ completions.addAll(Members.display(guild.getCompany().getEnlisted()));
}
- return filtered(completions, args[1]);
+ return Members.completions(completions, args);
}
if(args.length == 2 && args[0].equalsIgnoreCase("invite")) {
for(Player online : Bukkit.getOnlinePlayers()) {
@@ -120,6 +120,11 @@ private List completeMercenaries(String[] args) {
return completions;
}
+ /** Drops the subcommand, so a member name that starts at the third word completes like one at the second. */
+ private static String[] afterFirst(String[] args) {
+ return java.util.Arrays.copyOfRange(args, 1, args.length);
+ }
+
private List filtered(List completions, String prefix) {
String lower = prefix == null ? "" : prefix.toLowerCase();
completions.removeIf(s -> !s.toLowerCase().startsWith(lower));
@@ -454,44 +459,45 @@ else if(cmd.getName().equalsIgnoreCase("faction") && args.length >= 0 && args.le
List completions = new ArrayList();
for(Player p : Bukkit.getOnlinePlayers()) {
completions.add(p.getName());
- completions.add(net.tfminecraft.simplefactions.espionage.CharacterNames.of(p.getName()));
+ completions.add(Members.name(p));
}
- return net.tfminecraft.simplefactions.espionage.CharacterNames.inviteCompletions(completions, args);
+ return Members.completions(completions, args);
}
- } else if(cmd.getName().equalsIgnoreCase("guild") && args.length == 2 && args[0].equalsIgnoreCase("kick")){
+ } else if(cmd.getName().equalsIgnoreCase("guild") && args.length >= 2 && args[0].equalsIgnoreCase("kick")){
if(sender instanceof Player){
Player p = (Player) sender;
Guild g = FactionManager.getGuildByLeader(Members.key(p));
List