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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ public List<UUID> owners() {
.sorted(Comparator.comparing(this::ownerName, String.CASE_INSENSITIVE_ORDER)).toList();
}

private String ownerToken(UUID id, boolean creating) {
var access = net.tfminecraft.companionpets.ownership.CharacterAccess.resolve(id.toString());
if (!id.equals(access) || (creating && !net.tfminecraft.companionpets.ownership.CharacterAccess.alive(id))) return null;
String name = ownerName(id);
// Owner arguments consume one token. Confirm names select exactly this directory identity.
if (name != null && !name.isBlank() && name.chars().noneMatch(Character::isWhitespace)
&& !name.equalsIgnoreCase("unassigned")
&& id.equals(net.tfminecraft.companionpets.ownership.CharacterAccess.resolve(name))
&& net.tfminecraft.tlibs.character.Characters.findByName(name).size() == 1) return name;
return id.toString();
}

public List<Pet> owned(UUID owner) {
return runtime.store().ofCharacter(owner).stream().sorted(Comparator.comparing((Pet p) -> p.name(), String.CASE_INSENSITIVE_ORDER).thenComparing(Pet::id)).toList();
}
Expand Down Expand Up @@ -209,9 +221,20 @@ public List<String> complete(CommandSender sender, String[] args) {
if (args.length == 2) {
if (action.equals("egg")) { choices.add("all"); choices.addAll(runtime.config().types().keySet()); }
else if (List.of("list", "find", "create").contains(action)) {
owners().forEach(id -> { if (!ownerName(id).equals("Unknown player")) choices.add(ownerName(id)); });
if (action.equals("create")) {
Bukkit.getOnlinePlayers().forEach(p -> choices.add(p.getName()));
boolean creating = action.equals("create");
owners().stream().map(id -> ownerToken(id, creating)).filter(Objects::nonNull).forEach(choices::add);
if (action.equals("list")) choices.add("unassigned");
if (creating) {
for (Player player : Bukkit.getOnlinePlayers()) {
var session = net.tfminecraft.companionpets.ownership.CharacterAccess.capture(player);
if (session == null) continue;
String token = ownerToken(session.character(), true);
if (token != null) choices.add(token);
if (session.character().equals(
net.tfminecraft.companionpets.ownership.CharacterAccess.resolve(player.getName()))
&& net.tfminecraft.tlibs.character.Characters.findByName(player.getName()).size() < 2)
choices.add(player.getName());
}
}
}
} else if (action.equals("egg")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private Pet add(UUID owner, String type, String name, boolean stored) {
add(staff.getUniqueId(), "wolf", "Staff pet", true);
add(owner.getUniqueId(), "wolf", "Toby Junior", true);
for (String action : List.of("list", "find")) {
assertEquals(List.of("Owner", "Staff"), commands.complete(staff, new String[]{action, ""}));
assertEquals(action.equals("list") ? List.of("Owner", "Staff", "unassigned") : List.of("Owner", "Staff"), commands.complete(staff, new String[]{action, ""}));
assertEquals(List.of("Toby", "Toby Junior"), commands.complete(staff, new String[]{action, "Owner", ""}));
assertEquals(List.of("Junior"), commands.complete(staff, new String[]{action, "Owner", "Toby", "J"}));
assertTrue(commands.complete(staff, new String[]{action, "Staff", "Toby"}).isEmpty());
Expand All @@ -376,7 +376,7 @@ private Pet add(UUID owner, String type, String name, boolean stored) {
assertThrows(IllegalArgumentException.class, () -> commands.resolveOwner("NotCached"));
assertEquals(List.of("Owner", "Staff"), commands.complete(staff, new String[]{"create", ""}));
for (String action : List.of("list", "find")) {
assertEquals(List.of("Owner"), commands.complete(staff, new String[]{action, ""}));
assertEquals(action.equals("list") ? List.of("Owner", "unassigned") : List.of("Owner"), commands.complete(staff, new String[]{action, ""}));
assertEquals(List.of("Toby"), commands.complete(staff, new String[]{action, "Owner", ""}));
}
command("list", "Owner");
Expand Down Expand Up @@ -513,6 +513,79 @@ private Pet add(UUID owner, String type, String name, boolean stored) {
assertEquals(Set.of("Toby", "Retry"), new HashSet<>(afterRetry.of(owner.getUniqueId()).stream().map(Pet::name).toList()));
}

@Test void ownerCandidatesRoundTripThroughTheDirectoryForMultiwordDuplicateAndUnresolvedOwners() {
var infos = new LinkedHashMap<UUID, net.tfminecraft.tlibs.character.CharacterInfo>();
var duplicateAccount = server.addPlayer("Duplicate");
for (String name : List.of("Old Owner", "Duplicate", "Duplicate", "Solo", "unassigned", "Dead Owner")) {
UUID id = UUID.randomUUID();
UUID account = name.equals("Duplicate") && infos.values().stream().noneMatch(info -> info.name().equals("Duplicate"))
? duplicateAccount.getUniqueId() : UUID.randomUUID();
var ref = new net.tfminecraft.tlibs.character.CharacterRef(account, id);
infos.put(id, new net.tfminecraft.tlibs.character.CharacterInfo(ref, name,
name.equals("Dead Owner") ? net.tfminecraft.tlibs.character.CharacterStatus.DEAD
: net.tfminecraft.tlibs.character.CharacterStatus.ALIVE, null, Set.of(), 0, null));
Pet saved = add(account, "wolf", "Pet " + name, true); saved.ownership(account, id);
}
UUID unresolved = UUID.randomUUID(); pet.ownership(owner.getUniqueId(), unresolved);
var identity = new net.tfminecraft.tlibs.character.CharacterIdentity() {
public Optional<net.tfminecraft.tlibs.character.CharacterSession> currentSession(org.bukkit.entity.Player player) {
return player == null ? Optional.empty() : infos.values().stream()
.filter(info -> info.ref().ownerId().equals(player.getUniqueId()))
.map(info -> new net.tfminecraft.tlibs.character.CharacterSession(info.ref(), 1)).findFirst();
}
public boolean isCurrent(net.tfminecraft.tlibs.character.CharacterSession session) {
return currentSession(server.getPlayer(session.ref().ownerId())).filter(session::equals).isPresent();
}
public Optional<net.tfminecraft.tlibs.character.CharacterInfo> info(UUID id) { return Optional.ofNullable(infos.get(id)); }
public Optional<net.tfminecraft.tlibs.character.CharacterInfo> lastPlayed(UUID account) { return Optional.empty(); }
public Optional<List<net.tfminecraft.tlibs.character.CharacterInfo>> characters(UUID account) { return Optional.of(infos.values().stream().filter(info -> info.ref().ownerId().equals(account)).toList()); }
public List<net.tfminecraft.tlibs.character.CharacterInfo> findByName(String name) { return infos.values().stream().filter(info -> info.name().equalsIgnoreCase(name)).toList(); }
public String displayName(org.bukkit.entity.Player player) { return player.getName(); }
};
server.getServicesManager().unregister(net.tfminecraft.tlibs.character.CharacterIdentity.class,
server.getServicesManager().load(net.tfminecraft.tlibs.character.CharacterIdentity.class));
server.getServicesManager().register(net.tfminecraft.tlibs.character.CharacterIdentity.class, identity,
plugin, org.bukkit.plugin.ServicePriority.Highest);
forbidOfflineScan = true;
assertEquals("Unknown character", net.tfminecraft.companionpets.ownership.CharacterAccess.name(unresolved));
assertEquals("Unassigned character", net.tfminecraft.companionpets.ownership.CharacterAccess.name(null));
for (String action : List.of("list", "find", "create")) {
var choices = commands.complete(staff, new String[]{action, ""});
assertFalse(choices.contains("Old Owner")); assertFalse(choices.contains("Duplicate"));
assertFalse(choices.contains("Unknown character")); assertFalse(choices.contains("Unassigned character"));
assertFalse(choices.contains(unresolved.toString()));
for (var entry : infos.entrySet()) {
UUID id = entry.getKey(); var info = entry.getValue();
String token = info.name().equals("Solo") ? "Solo" : id.toString();
if (action.equals("create") && !info.alive()) { assertFalse(choices.contains(token)); continue; }
assertTrue(choices.contains(token), action + " missing " + token + " from " + choices);
assertEquals(id, commands.resolveOwner(token));
assertEquals(List.of(token), commands.complete(staff, new String[]{action, token.substring(0, Math.min(8, token.length())).toUpperCase(Locale.ROOT)}));
}
}
var first = infos.values().iterator().next();
var console = server.getConsoleSender();
commands.execute(console, "list", first.ref().characterId().toString());
assertEquals("Pets belonging to Old Owner:", console.nextMessage());
assertTrue(console.nextMessage().contains("Pet Old Owner"));
// A missing provider must not turn the fallback label into an owner argument.
server.getServicesManager().unregister(net.tfminecraft.tlibs.character.CharacterIdentity.class,
server.getServicesManager().load(net.tfminecraft.tlibs.character.CharacterIdentity.class));
assertEquals(List.of("unassigned"), commands.complete(staff, new String[]{"list", ""}));
}

@Test void listUnassignedCompletesWithPrefixesAndOnlyTheListPermission() {
assertEquals(List.of("unassigned"), commands.complete(staff, new String[]{"list", "un"}));
assertEquals(List.of("unassigned"), commands.complete(staff, new String[]{"LIST", "UnA"}));
assertTrue(commands.complete(staff, new String[]{"list", ""}).contains("unassigned"));
assertTrue(commands.complete(staff, new String[]{"find", "un"}).isEmpty());
assertTrue(commands.complete(staff, new String[]{"create", "un"}).isEmpty());
commands.execute(server.getConsoleSender(), "list", "unassigned");
assertTrue(server.getConsoleSender().nextMessage().contains("Unassigned or held pet records"));
staff.setOp(false); staff.addAttachment(plugin, "companionpets.admin.list", false);
assertTrue(commands.complete(staff, new String[]{"list", "un"}).isEmpty());
}

@Test void modelledEggDeliveryPreservesConfiguredIdentityAndCompletesOnlineTargetsAndAmounts() throws Exception {
configure("""
pets:
Expand Down
Loading