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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<denareconomy.version>0.7.0</denareconomy.version>
<rpcharacters.version>2.16.0</rpcharacters.version>
<tlibs.version>2.6.0</tlibs.version>
<vehicleframework.version>2.13.0</vehicleframework.version>
<vehicleframework.version>2.15.0</vehicleframework.version>
<vfbuilders.version>2.2.0</vfbuilders.version>
</properties>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,8 @@ void startVehicles() {
if (full) {
VehicleOwnershipServices.registerAuthority(getServer().getServicesManager(), this,
() -> started && LeaderStamps.enabled());
VehicleOwnershipServices.registerGroupAccess(getServer().getServicesManager(), this, vehicleRegistry,
() -> started && LeaderStamps.enabled());
LeaderStamps.enable(true);
getServer().getScheduler().runTaskTimer(this, this::reconcileVehicleUpkeep,
VEHICLE_RECONCILE_TICKS, VEHICLE_RECONCILE_TICKS);
Expand Down Expand Up @@ -1100,8 +1102,7 @@ private void registerVehicleIntegration(boolean full) {
vehicleRegistryClaimService, vehicleFeeStore, vehicleFeeConfirmations, this::saveVehicleFees,
getLogger()), this);
} else {
getServer().getPluginManager().registerEvents(
net.tfminecraft.simplefactions.vehicles.owners.VehicleClaimGuards.create(), this);
net.tfminecraft.simplefactions.vehicles.owners.VehicleClaimGuards.register(getServer().getPluginManager(), this);
}
// VehicleFramework loads its vehicles as it enables, so wait before reading owners.
getServer().getScheduler().runTaskLater(this, this::recordVehicleOwners, 200L);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,54 @@

import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;

import net.tfminecraft.simplefactions.utils.Permissions;

/** Selects a claim guard without loading listener types absent from the installed provider. */
/** Registers a claim guard without loading listener types absent from the installed provider. */
public final class VehicleClaimGuards {
public static final String MESSAGE = "§cClaiming vehicles is off until VehicleFramework is updated.";
static final String CLAIM_EVENT = "net.tfminecraft.vehicleframework.events.VehicleOwnershipChangeEvent";
/** The claim event of VehicleFramework before 2.13; 2.14 removed it, so it is only named, never linked. */
static final String LEGACY_CLAIM_EVENT = "net.tfminecraft.vehicleframework.events.VehicleOwnerClaimedEvent";

private VehicleClaimGuards() {}

public static Listener create() {
return create("net.tfminecraft.vehicleframework.events.VehicleOwnershipChangeEvent",
"net.tfminecraft.vehicleframework.events.VehicleOwnerClaimedEvent");
public static void register(PluginManager plugins, Plugin plugin) {
register(plugins, plugin, CLAIM_EVENT, LEGACY_CLAIM_EVENT);
}

static Listener create(String modernEvent, String legacyEvent) {
if (present(modernEvent)) return new VehicleClaimsOff();
if (present(legacyEvent)) return new LegacyVehicleClaimsOff();
throw new IllegalStateException("VehicleFramework has no supported claim event");
static void register(PluginManager plugins, Plugin plugin, String claimEvent, String legacyEvent) {
if (eventClass(claimEvent) != null) {
plugins.registerEvents(new VehicleClaimsOff(), plugin);
return;
}
Class<? extends Event> legacy = eventClass(legacyEvent);
if (legacy == null) throw new IllegalStateException("VehicleFramework has no supported claim event");
plugins.registerEvent(legacy, new Listener() {}, EventPriority.LOWEST,
(listener, event) -> refuseLegacy(event), plugin, true);
}

private static Class<? extends Event> eventClass(String name) {
try {
return Class.forName(name).asSubclass(Event.class);
} catch (ClassNotFoundException | ClassCastException | LinkageError missing) {
return null;
}
}

private static boolean present(String eventClass) {
/** The legacy event is only a claim; its player is read by name. */
static void refuseLegacy(Event event) {
if (!(event instanceof Cancellable cancellable)) return;
try {
Class.forName(eventClass);
return true;
} catch (ClassNotFoundException | LinkageError missing) {
return false;
Object player = event.getClass().getMethod("getPlayer").invoke(event);
refuse(player instanceof Player p ? p : null, cancellable);
} catch (ReflectiveOperationException | LinkageError unreadable) {
// Without a readable player the claim cannot be judged; VehicleFramework keeps its own rules.
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package net.tfminecraft.simplefactions.vehicles.owners;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.BooleanSupplier;

import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.managers.RelationManager;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.vehicles.pool.FactionVehiclePoolService;
import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRecord;
import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRegistry;
import net.tfminecraft.vehicleframework.api.AccessGroup;
import net.tfminecraft.vehicleframework.api.VehicleAccessGroups;

/**
* VehicleFramework's group switches, answered from current membership. A pool or installation
* vehicle counts from its paying faction and always admits it; any other vehicle counts from its
* owner's character. Guilds mean player guilds, never a faction's main guild, and faction
* vehicles have none. Vassals are the direct vassals only.
*/
public final class VehicleGroupAccess implements VehicleAccessGroups {
private final PlayerVehicleRegistry registry;
private final BooleanSupplier ready;

public VehicleGroupAccess(PlayerVehicleRegistry registry, BooleanSupplier ready) {
this.registry = registry;
this.ready = ready;
}

@Override
public boolean ready() {
return ready.getAsBoolean();
}

@Override
public boolean admits(AccessGroup group, UUID vehicleId, String owner, UUID rider) {
if (group == null || rider == null) return false;
return switch (group) {
case FACTION -> same(faction(vehicleId, owner), FactionManager.getByMember(rider));
case GUILD -> {
Guild guild = guild(vehicleId, owner);
yield guild != null && guild == playerGuild(rider);
}
case ALLY -> {
Faction faction = faction(vehicleId, owner);
Faction other = FactionManager.getByMember(rider);
yield faction != null && other != null && !same(faction, other) && RelationManager.isAlly(faction, other);
}
case VASSAL -> {
Faction faction = faction(vehicleId, owner);
Faction other = FactionManager.getByMember(rider);
yield faction != null && other != null && RelationManager.isOverlord(other, faction);
}
};
}

@Override
public Set<AccessGroup> alwaysOpen(UUID vehicleId, String owner) {
return factionRow(vehicleId) != null ? Set.of(AccessGroup.FACTION) : Set.of();
}

@Override
public Optional<String> describe(AccessGroup group, UUID vehicleId, String owner) {
if (group == AccessGroup.GUILD) {
return Optional.ofNullable(guild(vehicleId, owner)).map(Guild::getName);
}
return Optional.ofNullable(faction(vehicleId, owner)).map(Faction::getName);
}

/** The faction a vehicle's groups count from: its paying faction, else its owner's faction. */
private Faction faction(UUID vehicleId, String owner) {
PlayerVehicleRecord row = factionRow(vehicleId);
if (row != null) return FactionVehiclePoolService.payingFaction(row);
UUID character = OwnerTags.characterId(owner).orElse(null);
return character == null ? null : FactionManager.getByMember(character);
}

private Guild guild(UUID vehicleId, String owner) {
if (factionRow(vehicleId) != null) return null;
return OwnerTags.characterId(owner).map(VehicleGroupAccess::playerGuild).orElse(null);
}

private PlayerVehicleRecord factionRow(UUID vehicleId) {
if (vehicleId == null) return null;
PlayerVehicleRecord row = registry.getByVehicleUuid(vehicleId.toString()).orElse(null);
return LeaderStamps.isFactionRow(row) ? row : null;
}

/** The character's player guild, as member or leader; a faction's main guild does not count. */
static Guild playerGuild(UUID character) {
Guild guild = FactionManager.getGuildByMember(character);
if (guild == null || guild.isBase()) guild = FactionManager.getGuildByLeader(character);
return guild == null || guild.isBase() ? null : guild;
}

private static boolean same(Faction a, Faction b) {
return a != null && b != null && a.getId().equalsIgnoreCase(b.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public final class VehicleOwnershipGate implements Listener {
public static final String FAILED = "§cVehicle ownership could not be checked, so nothing changed.";
public static final String HELD = "§cThis vehicle is held for staff; ask them to assign its owner.";
static final int REMEMBERED_OPERATIONS = 1024;
/**
* The only controls a faction leader may use on a pool or installation vehicle. Matched by
* name: VehicleFramework before 2.15 has no such reasons, and a constant would not link there.
*/
static final Set<String> LEADER_SWITCHES = Set.of("ALLY_ACCESS_TOGGLE", "VASSAL_ACCESS_TOGGLE");

private final PlayerVehicleRegistry registry;
private final VehicleRegistryClaimService claims;
Expand Down Expand Up @@ -81,7 +86,7 @@ public void onChange(VehicleOwnershipChangeEvent event) {
refuse(event, event.getPlayer(), HELD);
return;
}
if (LeaderStamps.isFactionRow(row(vehicle))) {
if (LeaderStamps.isFactionRow(row(vehicle)) && !LEADER_SWITCHES.contains(event.getReason().name())) {
refuse(event, event.getPlayer(), VehicleRegistryClaimListener.FACTION_VEHICLE);
return;
}
Expand Down Expand Up @@ -230,7 +235,7 @@ public void onEditCheck(VehicleOwnershipEditCheckEvent event) {
if (LeaderStamps.isStale(record, event.getVehicle().getOwnerData().getOwner())) {
event.deny();
LeaderStamps.scheduleRestamp(record);
} else if (!event.isMenu()) {
} else if (!event.isMenu() && !LEADER_SWITCHES.contains(event.getReason().orElseThrow().name())) {
event.deny();
}
} catch (RuntimeException | LinkageError error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.ServicesManager;

import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRegistry;
import net.tfminecraft.vehicleframework.api.QuarantinedVehicle;
import net.tfminecraft.vehicleframework.api.VehicleAccessGroups;
import net.tfminecraft.vehicleframework.api.VehicleOwnershipAuthority;
import net.tfminecraft.vehicleframework.api.VehicleQuarantineSource;

/** The two VehicleFramework services SimpleFactions provides; only loaded once the API probe passed. */
/** The VehicleFramework services SimpleFactions provides; only loaded once the API probe passed. */
public final class VehicleOwnershipServices {
static final String GROUP_ACCESS_API = "net.tfminecraft.vehicleframework.api.VehicleAccessGroups";

private VehicleOwnershipServices() {}

/** Lists held-back vehicles for {@code /vf admin quarantine}, even when the vehicle stage stopped early. */
Expand All @@ -32,6 +36,28 @@ public boolean ready() {
}, plugin, ServicePriority.Normal);
}

/**
* Answers the vehicle group switches. VehicleFramework before 2.15 has no such service, so
* nothing is registered and vehicles keep their whitelists only.
*/
public static boolean registerGroupAccess(ServicesManager services, Plugin plugin, PlayerVehicleRegistry registry,
BooleanSupplier ready) {
return registerGroupAccess(services, plugin, registry, ready, GROUP_ACCESS_API);
}

static boolean registerGroupAccess(ServicesManager services, Plugin plugin, PlayerVehicleRegistry registry,
BooleanSupplier ready, String apiClass) {
try {
Class.forName(apiClass);
} catch (ClassNotFoundException | LinkageError missing) {
plugin.getLogger().warning("VehicleFramework 2.15 or newer is needed for faction, guild, ally and vassal "
+ "vehicle access; vehicles keep their whitelists only");
return false;
}
services.register(VehicleAccessGroups.class, new VehicleGroupAccess(registry, ready), plugin, ServicePriority.Normal);
return true;
}

static List<QuarantinedVehicle> quarantined(VehicleQuarantine quarantine) {
List<QuarantinedVehicle> out = new ArrayList<>();
for (VehicleQuarantine.Entry entry : quarantine.list()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1037,8 +1037,8 @@
try (var services = mockStatic(net.kyori.adventure.util.Services.class, CALLS_REAL_METHODS)) {
when(Bukkit.getUnsafe()).thenReturn(mock(org.bukkit.UnsafeValues.class, RETURNS_DEEP_STUBS));
services
.when(() -> net.kyori.adventure.util.Services.service(PluginLoader.class))

Check warning on line 1040 in src/test/java/net/tfminecraft/simplefactions/PluginLifecycleCoverageTest.java

View workflow job for this annotation

GitHub Actions / build

org.bukkit.plugin.PluginLoader in org.bukkit.plugin has been deprecated and marked for removal
.thenReturn(Optional.of(mock(PluginLoader.class)));

Check warning on line 1041 in src/test/java/net/tfminecraft/simplefactions/PluginLifecycleCoverageTest.java

View workflow job for this annotation

GitHub Actions / build

org.bukkit.plugin.PluginLoader in org.bukkit.plugin has been deprecated and marked for removal
TestPluginLoader loader =
new TestPluginLoader(description, domain.ui.server, logger, disk.root);
JavaPlugin constructed =
Expand Down Expand Up @@ -1408,8 +1408,8 @@
try (var services = mockStatic(net.kyori.adventure.util.Services.class, CALLS_REAL_METHODS)) {
when(Bukkit.getUnsafe()).thenReturn(mock(org.bukkit.UnsafeValues.class, RETURNS_DEEP_STUBS));
services
.when(() -> net.kyori.adventure.util.Services.service(PluginLoader.class))

Check warning on line 1411 in src/test/java/net/tfminecraft/simplefactions/PluginLifecycleCoverageTest.java

View workflow job for this annotation

GitHub Actions / build

org.bukkit.plugin.PluginLoader in org.bukkit.plugin has been deprecated and marked for removal
.thenReturn(Optional.of(mock(PluginLoader.class)));

Check warning on line 1412 in src/test/java/net/tfminecraft/simplefactions/PluginLifecycleCoverageTest.java

View workflow job for this annotation

GitHub Actions / build

org.bukkit.plugin.PluginLoader in org.bukkit.plugin has been deprecated and marked for removal
TestPluginLoader loader =
new TestPluginLoader(description, domain.ui.server, logger, disk.root);
loader.unavailable =
Expand Down Expand Up @@ -1605,6 +1605,9 @@
var authority =
services().load(net.tfminecraft.vehicleframework.api.VehicleOwnershipAuthority.class);
assertTrue(authority.ready());
var groups = services().load(net.tfminecraft.vehicleframework.api.VehicleAccessGroups.class);
assertTrue(groups.ready());
assertTrue(groups.alwaysOpen(UUID.randomUUID(), "char_" + leader).isEmpty());
assertEquals(
1,
services()
Expand Down
Loading
Loading