Merge pull request #2928 from Multiverse/perm-revamp-3

feat: Revamp teleport world entry checking
This commit is contained in:
Ben Woo 2023-08-30 23:38:58 +08:00 committed by GitHub
commit cbf44cb918
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 719 additions and 77 deletions

View File

@ -11,6 +11,7 @@ import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.commandtools.MVCommandManager;
import com.onarandombox.MultiverseCore.commandtools.MultiverseCommand;
import com.onarandombox.MultiverseCore.destination.DestinationsProvider;
@ -49,17 +50,17 @@ public class TeleportCommand extends MultiverseCommand {
) {
// TODO Add warning if teleporting too many players at once.
String playerName = players.length == 1
? issuer.getPlayer() == players[0] ? "you" : players[0].getName()
: players.length + " players";
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
"{player}", playerName, "{destination}", destination.toString());
CompletableFuture.allOf(Arrays.stream(players)
.map(player -> this.destinationsProvider.playerTeleportAsync(issuer, player, destination))
.toArray(CompletableFuture[]::new))
.thenRun(() -> {
String playerName = players.length == 1
? issuer.getPlayer() == players[0] ? "you" : players[0].getName()
: players.length + " players";
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
"{player}", playerName, "{destination}", destination.toString());
});
.thenRun(() -> Logging.finer("Async teleport completed."));
}
@Override

View File

@ -1,5 +1,6 @@
package com.onarandombox.MultiverseCore.economy;
import com.onarandombox.MultiverseCore.api.MVWorld;
import jakarta.inject.Inject;
import org.bukkit.Material;
import org.bukkit.World;
@ -91,6 +92,35 @@ public class MVEconomist {
return "Sorry, you don't have enough " + (isItemCurrency(currency) ? "items" : "funds") + ". " + message;
}
/**
* Pays for a given amount of currency either from the player's economy account or inventory if the currency.
*
* @param player the player to deposit currency into.
* @param world the world to take entry fee from.
*/
public void payEntryFee(Player player, MVWorld world) {
payEntryFee(player, world.getPrice(), world.getCurrency());
}
/**
* Pays for a given amount of currency either from the player's economy account or inventory if the currency
*
* @param player the player to take currency from.
* @param price the amount to take.
* @param currency the type of currency.
*/
public void payEntryFee(Player player, double price, Material currency) {
if (price == 0D) {
return;
}
if (price < 0) {
this.deposit(player, -price, currency);
} else {
this.withdraw(player, price, currency);
}
}
/**
* Deposits a given amount of currency either into the player's economy account or inventory if the currency
* is not null.

View File

@ -16,12 +16,17 @@ import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVWorld;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.api.SafeTTeleporter;
import com.onarandombox.MultiverseCore.commandtools.MVCommandManager;
import com.onarandombox.MultiverseCore.config.MVCoreConfig;
import com.onarandombox.MultiverseCore.economy.MVEconomist;
import com.onarandombox.MultiverseCore.event.MVRespawnEvent;
import com.onarandombox.MultiverseCore.inject.InjectableListener;
import com.onarandombox.MultiverseCore.teleportation.TeleportQueue;
import com.onarandombox.MultiverseCore.utils.MVPermissions;
import com.onarandombox.MultiverseCore.utils.PermissionTools;
import com.onarandombox.MultiverseCore.utils.result.ResultChain;
import com.onarandombox.MultiverseCore.world.entrycheck.EntryFeeResult;
import com.onarandombox.MultiverseCore.world.entrycheck.WorldEntryCheckerProvider;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import org.bukkit.GameMode;
@ -54,6 +59,9 @@ public class MVPlayerListener implements InjectableListener {
private final SafeTTeleporter safeTTeleporter;
private final Server server;
private final TeleportQueue teleportQueue;
private final MVEconomist economist;
private final WorldEntryCheckerProvider worldEntryCheckerProvider;
private final Provider<MVCommandManager> commandManagerProvider;
private final Map<String, String> playerWorld = new ConcurrentHashMap<String, String>();
@ -66,8 +74,10 @@ public class MVPlayerListener implements InjectableListener {
Provider<MVPermissions> mvPermsProvider,
SafeTTeleporter safeTTeleporter,
Server server,
TeleportQueue teleportQueue
) {
TeleportQueue teleportQueue,
MVEconomist economist,
WorldEntryCheckerProvider worldEntryCheckerProvider,
Provider<MVCommandManager> commandManagerProvider) {
this.plugin = plugin;
this.config = config;
this.worldManagerProvider = worldManagerProvider;
@ -76,12 +86,19 @@ public class MVPlayerListener implements InjectableListener {
this.safeTTeleporter = safeTTeleporter;
this.server = server;
this.teleportQueue = teleportQueue;
this.economist = economist;
this.worldEntryCheckerProvider = worldEntryCheckerProvider;
this.commandManagerProvider = commandManagerProvider;
}
private MVWorldManager getWorldManager() {
return worldManagerProvider.get();
}
private MVCommandManager getCommandManager() {
return commandManagerProvider.get();
}
private MVPermissions getMVPerms() {
return mvPermsProvider.get();
}
@ -189,7 +206,7 @@ public class MVPlayerListener implements InjectableListener {
return;
}
Player teleportee = event.getPlayer();
CommandSender teleporter = teleportee;
CommandSender teleporter;
Optional<String> teleporterName = teleportQueue.popFromQueue(teleportee.getName());
if (teleporterName.isPresent()) {
if (teleporterName.equals("CONSOLE")) {
@ -198,6 +215,8 @@ public class MVPlayerListener implements InjectableListener {
} else {
teleporter = this.server.getPlayerExact(teleporterName.get());
}
} else {
teleporter = teleportee;
}
Logging.finer("Inferred sender '" + teleporter + "' from name '"
+ teleporterName + "', fetched from name '" + teleportee.getName() + "'");
@ -215,50 +234,20 @@ public class MVPlayerListener implements InjectableListener {
this.stateSuccess(teleportee.getName(), toWorld.getAlias());
return;
}
// TODO: Refactor these lines.
// Charge the teleporter
event.setCancelled(!pt.playerHasMoneyToEnter(fromWorld, toWorld, teleporter, teleportee, true));
if (event.isCancelled() && teleporter != null) {
Logging.fine("Player '" + teleportee.getName()
+ "' was DENIED ACCESS to '" + toWorld.getAlias()
+ "' because '" + teleporter.getName()
+ "' don't have the FUNDS required to enter it.");
return;
}
// Check if player is allowed to enter the world if we're enforcing permissions
if (config.getEnforceAccess()) {
event.setCancelled(!pt.playerCanGoFromTo(fromWorld, toWorld, teleporter, teleportee));
if (event.isCancelled() && teleporter != null) {
Logging.fine("Player '" + teleportee.getName()
+ "' was DENIED ACCESS to '" + toWorld.getAlias()
+ "' because '" + teleporter.getName()
+ "' don't have: multiverse.access." + event.getTo().getWorld().getName());
return;
}
} else {
Logging.fine("Player '" + teleportee.getName()
+ "' was allowed to go to '" + toWorld.getAlias() + "' because enforceaccess is off.");
}
// Does a limit actually exist?
if (toWorld.getPlayerLimit() > -1) {
// Are there equal or more people on the world than the limit?
if (toWorld.getCBWorld().getPlayers().size() >= toWorld.getPlayerLimit()) {
// Ouch the world is full, lets see if the player can bypass that limitation
if (!pt.playerCanBypassPlayerLimit(toWorld, teleporter, teleportee)) {
Logging.fine("Player '" + teleportee.getName()
+ "' was DENIED ACCESS to '" + toWorld.getAlias()
+ "' because the world is full and '" + teleporter.getName()
+ "' doesn't have: mv.bypass.playerlimit." + event.getTo().getWorld().getName());
ResultChain entryResult = worldEntryCheckerProvider.forSender(teleporter).canEnterWorld(fromWorld, toWorld)
.onSuccessReason(EntryFeeResult.Success.class, reason -> {
if (reason == EntryFeeResult.Success.ENOUGH_MONEY) {
economist.payEntryFee((Player) teleporter, toWorld);
// Send payment receipt
}
})
.onFailure(results -> {
event.setCancelled(true);
return;
}
}
}
getCommandManager().getCommandIssuer(teleporter).sendError(results.getLastResultMessage());
});
// By this point anything cancelling the event has returned on the method, meaning the teleport is a success \o/
this.stateSuccess(teleportee.getName(), toWorld.getAlias());
Logging.fine("Teleport result: %s", entryResult);
}
private void stateSuccess(String playerName, String worldName) {
@ -305,6 +294,10 @@ public class MVPlayerListener implements InjectableListener {
if (event.getTo() == null) {
return;
}
if (config.isUsingCustomPortalSearch()) {
event.setSearchRadius(config.getCustomPortalSearchRadius());
}
MVWorld fromWorld = getWorldManager().getMVWorld(event.getFrom().getWorld().getName());
MVWorld toWorld = getWorldManager().getMVWorld(event.getTo().getWorld().getName());
if (event.getFrom().getWorld().equals(event.getTo().getWorld())) {
@ -312,28 +305,14 @@ public class MVPlayerListener implements InjectableListener {
Logging.finer("Player '" + event.getPlayer().getName() + "' is portaling to the same world.");
return;
}
event.setCancelled(!pt.playerHasMoneyToEnter(fromWorld, toWorld, event.getPlayer(), event.getPlayer(), true));
if (event.isCancelled()) {
Logging.fine("Player '" + event.getPlayer().getName()
+ "' was DENIED ACCESS to '" + event.getTo().getWorld().getName()
+ "' because they don't have the FUNDS required to enter.");
return;
}
if (config.getEnforceAccess()) {
event.setCancelled(!pt.playerCanGoFromTo(fromWorld, toWorld, event.getPlayer(), event.getPlayer()));
if (event.isCancelled()) {
Logging.fine("Player '" + event.getPlayer().getName()
+ "' was DENIED ACCESS to '" + event.getTo().getWorld().getName()
+ "' because they don't have: multiverse.access." + event.getTo().getWorld().getName());
}
} else {
Logging.fine("Player '" + event.getPlayer().getName()
+ "' was allowed to go to '" + event.getTo().getWorld().getName()
+ "' because enforceaccess is off.");
}
if (!config.isUsingCustomPortalSearch()) {
event.setSearchRadius(config.getCustomPortalSearchRadius());
}
ResultChain entryResult = worldEntryCheckerProvider.forSender(event.getPlayer()).canEnterWorld(fromWorld, toWorld)
.onFailure(results -> {
event.setCancelled(true);
getCommandManager().getCommandIssuer(event.getPlayer()).sendError(results.getLastResultMessage());
});
Logging.fine("Teleport result: %s", entryResult);
}
private void sendPlayerToDefaultWorld(final Player player) {

View File

@ -0,0 +1,8 @@
package com.onarandombox.MultiverseCore.permissions;
public class CorePermissions {
public static String WORLD_ACCESS = "multiverse.access";
public static String WORLD_EXEMPT = "multiverse.exempt";
public static String GAMEMODE_BYPASS = "mv.bypass.gamemode";
public static String PLAYERLIMIT_BYPASS = "mv.bypass.playerlimit";
}

View File

@ -0,0 +1,39 @@
package com.onarandombox.MultiverseCore.permissions;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.api.MVWorld;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
@Service
public class CorePermissionsChecker {
public boolean hasWorldAccessPermission(@NotNull CommandSender sender, @NotNull MVWorld world) {
return hasPermission(sender, concatPermission(CorePermissions.WORLD_ACCESS, world.getName()));
}
public boolean hasWorldExemptPermission(@NotNull CommandSender sender, @NotNull MVWorld world) {
return hasPermission(sender, concatPermission(CorePermissions.WORLD_EXEMPT, world.getName()));
}
public boolean hasPlayerLimitBypassPermission(@NotNull CommandSender sender, @NotNull MVWorld world) {
return hasPermission(sender, concatPermission(CorePermissions.PLAYERLIMIT_BYPASS, world.getName()));
}
public boolean hasGameModeBypassPermission(@NotNull CommandSender sender, @NotNull MVWorld world) {
return hasPermission(sender, concatPermission(CorePermissions.GAMEMODE_BYPASS, world.getName()));
}
private String concatPermission(String permission, String...child) {
return permission + "." + String.join(".", child);
}
private boolean hasPermission(CommandSender sender, String permission) {
if (sender.hasPermission(permission)) {
Logging.finer("Checking to see if sender [%s] has permission [%s]... YES", sender.getName(), permission);
return true;
}
Logging.finer("Checking to see if sender [%s] has permission [%s]... NO", sender.getName(), permission);
return false;
}
}

View File

@ -80,7 +80,20 @@ public enum MVCorei18n implements MessageKeyProvider {
// debug command
DEBUG_INFO_OFF,
DEBUG_INFO_ON;
DEBUG_INFO_ON,
// entry check
ENTRYCHECK_BLACKLISTED,
ENTRYCHECK_NOTENOUGHMONEY,
ENTRYCHECK_CANNOTPAYENTRYFEE,
ENTRYCHECK_EXCEEDPLAYERLIMIT,
ENTRYCHECK_NOWORLDACCESS,
// generic
GENERIC_SUCCESS,
GENERIC_FAILURE
;
private final MessageKey key = MessageKey.of("mv-core." + this.name().replace('_', '.').toLowerCase());

View File

@ -7,7 +7,7 @@ import org.jetbrains.annotations.Nullable;
/**
* Captures string replacements for {@link Message}s.
*/
public final class MessageReplacement {
public final class MessageReplacement {
/**
* Creates a replacement key for the given key string.

View File

@ -0,0 +1,11 @@
package com.onarandombox.MultiverseCore.utils.result;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
public interface FailureReason extends MessageKeyProvider {
default MessageKey getMessageKey() {
return MVCorei18n.GENERIC_FAILURE.getMessageKey();
}
}

View File

@ -0,0 +1,140 @@
package com.onarandombox.MultiverseCore.utils.result;
import com.onarandombox.MultiverseCore.utils.message.Message;
import com.onarandombox.MultiverseCore.utils.message.MessageReplacement;
import org.jetbrains.annotations.NotNull;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
public sealed interface Result<S extends SuccessReason, F extends FailureReason> permits Result.Success, Result.Failure {
static <F extends FailureReason, S extends SuccessReason> Result<S, F> success(S successReason, MessageReplacement...replacements) {
return new Success<>(successReason, replacements);
}
static <F extends FailureReason, S extends SuccessReason> Result<S, F> failure(F failureReason, MessageReplacement...replacements) {
return new Failure<>(failureReason, replacements);
}
boolean isSuccess();
boolean isFailure();
S getSuccessReason();
F getFailureReason();
@NotNull Message getReasonMessage();
default Result<S, F> onSuccess(Consumer<S> consumer) {
if (this.isSuccess()) {
consumer.accept(this.getSuccessReason());
}
return this;
}
default Result<S, F> onFailure(Consumer<F> consumer) {
if (this.isFailure()) {
consumer.accept(this.getFailureReason());
}
return this;
}
default Result<S, F> onSuccessReason(S successReason, Consumer<S> consumer) {
if (this.isSuccess() && this.getSuccessReason() == successReason) {
consumer.accept(this.getSuccessReason());
}
return this;
}
default Result<S, F> onFailureReason(F failureReason, Consumer<F> consumer) {
if (this.isFailure() && this.getFailureReason() == failureReason) {
consumer.accept(this.getFailureReason());
}
return this;
}
final class Success<F extends FailureReason, S extends SuccessReason> implements Result<S, F> {
private final S successReason;
private final MessageReplacement[] replacements;
public Success(S successReason, MessageReplacement[] replacements) {
this.successReason = successReason;
this.replacements = replacements;
}
@Override
public boolean isSuccess() {
return true;
}
@Override
public boolean isFailure() {
return false;
}
@Override
public S getSuccessReason() {
return successReason;
}
@Override
public F getFailureReason() {
throw new NoSuchElementException("No reason for failure");
}
@Override
public @NotNull Message getReasonMessage() {
return Message.of(successReason, "Success!", replacements);
}
@Override
public String toString() {
return "Success{" +
"reason=" + successReason +
'}';
}
}
final class Failure<S extends SuccessReason, F extends FailureReason> implements Result<S, F> {
private final F failureReason;
private final MessageReplacement[] replacements;
public Failure(F failureReason, MessageReplacement[] replacements) {
this.failureReason = failureReason;
this.replacements = replacements;
}
@Override
public boolean isSuccess() {
return false;
}
@Override
public boolean isFailure() {
return true;
}
@Override
public S getSuccessReason() {
throw new NoSuchElementException("No reason for success");
}
@Override
public F getFailureReason() {
return failureReason;
}
@Override
public @NotNull Message getReasonMessage() {
return Message.of(failureReason, "Success!", replacements);
}
@Override
public String toString() {
return "Failure{" +
"reason=" + failureReason +
'}';
}
}
}

View File

@ -0,0 +1,143 @@
package com.onarandombox.MultiverseCore.utils.result;
import com.google.common.collect.Iterables;
import com.onarandombox.MultiverseCore.utils.message.Message;
import io.vavr.control.Option;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ResultChain {
public static Builder builder() {
return new Builder(true);
}
public static Builder builder(boolean stopOnFailure) {
return new Builder(stopOnFailure);
}
private final boolean isSuccess;
private final List<Result<?, ?>> results;
ResultChain(boolean isSuccess, List<Result<?, ?>> results) {
this.isSuccess = isSuccess;
this.results = results;
}
public boolean isSuccess() {
return isSuccess;
}
public boolean isFailure() {
return !isSuccess;
}
public ResultChain onSuccess(Runnable successRunnable) {
if (isSuccess) {
successRunnable.run();
}
return this;
}
public ResultChain onFailure(Runnable failureRunnable) {
if (isFailure()) {
failureRunnable.run();
}
return this;
}
public ResultChain onSuccess(Consumer<ResultChain> successRunnable) {
if (isSuccess) {
successRunnable.accept(this);
}
return this;
}
public ResultChain onFailure(Consumer<ResultChain> failureRunnable) {
if (isFailure()) {
failureRunnable.accept(this);
}
return this;
}
public <S extends SuccessReason> ResultChain onSuccessReason(Class<S> successReasonClass, Consumer<S> successConsumer) {
getSuccessReason(successReasonClass).peek(successConsumer);
return this;
}
public <F extends FailureReason> ResultChain onFailureReason(Class<F> failureReasonClass, Consumer<F> failureConsumer) {
getFailureReason(failureReasonClass).peek(failureConsumer);
return this;
}
public <S extends SuccessReason> ResultChain onSuccessReason(S successReason, Runnable successRunnable) {
getSuccessReason(successReason.getClass()).filter(successReason::equals).peek(reason -> successRunnable.run());
return this;
}
public <S extends SuccessReason> Option<S> getSuccessReason(Class<S> successReasonClass) {
if (isFailure()) {
return Option.none();
}
return Option.ofOptional(results.stream()
.map(Result::getSuccessReason)
.filter(successReasonClass::isInstance)
.map(successReasonClass::cast)
.findFirst());
}
public <F extends FailureReason> Option<F> getFailureReason(Class<F> failureReasonClass) {
if (isSuccess()) {
return Option.none();
}
return Option.ofOptional(results.stream()
.map(Result::getFailureReason)
.filter(failureReasonClass::isInstance)
.map(failureReasonClass::cast)
.findFirst());
}
public Message getLastResultMessage() {
return Iterables.getLast(results).getReasonMessage();
}
@Override
public String toString() {
return "ResultGroup{" +
"isSuccess=" + isSuccess +
", results={" + results.stream().map(Objects::toString).collect(Collectors.joining(", ")) + "}" +
'}';
}
public static class Builder {
private final boolean stopOnFailure;
private final List<Result<?, ?>> results;
private boolean isSuccess = true;
public Builder(boolean stopOnFailure) {
this.stopOnFailure = stopOnFailure;
this.results = new ArrayList<>();
}
public Builder then(Supplier<Result<?, ?>> resultSupplier) {
if (!isSuccess && stopOnFailure) {
return this;
}
Result<?, ?> result = resultSupplier.get();
if (result.isFailure()) {
isSuccess = false;
}
results.add(result);
return this;
}
public ResultChain build() {
return new ResultChain(isSuccess, results);
}
}
}

View File

@ -0,0 +1,11 @@
package com.onarandombox.MultiverseCore.utils.result;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
public interface SuccessReason extends MessageKeyProvider {
default MessageKey getMessageKey() {
return MVCorei18n.GENERIC_SUCCESS.getMessageKey();
}
}

View File

@ -22,7 +22,7 @@ public class EntryFee extends SerializationConfig {
@Nullable
private Material currency;
private final Material DISABLED_MATERIAL = Material.AIR;
public static final Material DISABLED_MATERIAL = Material.AIR;
public EntryFee() {
super();

View File

@ -0,0 +1,30 @@
package com.onarandombox.MultiverseCore.world.entrycheck;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
import com.onarandombox.MultiverseCore.utils.result.FailureReason;
import com.onarandombox.MultiverseCore.utils.result.SuccessReason;
public class BlacklistResult {
public enum Success implements SuccessReason {
UNKNOWN_FROM_WORLD,
BYPASSED_BLACKLISTED,
NOT_BLACKLISTED
}
public enum Failure implements FailureReason {
BLACKLISTED(MVCorei18n.ENTRYCHECK_BLACKLISTED);
private final MessageKeyProvider message;
Failure(MessageKeyProvider message) {
this.message = message;
}
@Override
public MessageKey getMessageKey() {
return message.getMessageKey();
}
}
}

View File

@ -0,0 +1,33 @@
package com.onarandombox.MultiverseCore.world.entrycheck;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
import com.onarandombox.MultiverseCore.utils.result.FailureReason;
import com.onarandombox.MultiverseCore.utils.result.SuccessReason;
public class EntryFeeResult {
public enum Success implements SuccessReason {
FREE_ENTRY,
ENOUGH_MONEY,
EXEMPT_FROM_ENTRY_FEE,
CONSOLE_OR_BLOCK_COMMAND_SENDER
}
public enum Failure implements FailureReason {
NOT_ENOUGH_MONEY(MVCorei18n.ENTRYCHECK_NOTENOUGHMONEY),
CANNOT_PAY_ENTRY_FEE(MVCorei18n.ENTRYCHECK_CANNOTPAYENTRYFEE);
private final MessageKeyProvider message;
Failure(MessageKeyProvider message) {
this.message = message;
}
@Override
public MessageKey getMessageKey() {
return message.getMessageKey();
}
}
}

View File

@ -0,0 +1,30 @@
package com.onarandombox.MultiverseCore.world.entrycheck;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
import com.onarandombox.MultiverseCore.utils.result.FailureReason;
import com.onarandombox.MultiverseCore.utils.result.SuccessReason;
public class PlayerLimitResult {
public enum Success implements SuccessReason {
NO_PLAYERLIMIT,
WITHIN_PLAYERLIMIT,
BYPASS_PLAYERLIMIT
}
public enum Failure implements FailureReason {
EXCEED_PLAYERLIMIT(MVCorei18n.ENTRYCHECK_EXCEEDPLAYERLIMIT);
private final MessageKeyProvider message;
Failure(MessageKeyProvider message) {
this.message = message;
}
@Override
public MessageKey getMessageKey() {
return message.getMessageKey();
}
}
}

View File

@ -0,0 +1,29 @@
package com.onarandombox.MultiverseCore.world.entrycheck;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
import com.onarandombox.MultiverseCore.utils.result.FailureReason;
import com.onarandombox.MultiverseCore.utils.result.SuccessReason;
public class WorldAccessResult {
public enum Success implements SuccessReason {
NO_ENFORCE_WORLD_ACCESS,
HAS_WORLD_ACCESS
}
public enum Failure implements FailureReason {
NO_WORLD_ACCESS(MVCorei18n.ENTRYCHECK_NOWORLDACCESS);
private final MessageKeyProvider message;
Failure(MessageKeyProvider message) {
this.message = message;
}
@Override
public MessageKey getMessageKey() {
return message.getMessageKey();
}
}
}

View File

@ -0,0 +1,102 @@
package com.onarandombox.MultiverseCore.world.entrycheck;
import com.onarandombox.MultiverseCore.api.MVWorld;
import com.onarandombox.MultiverseCore.config.MVCoreConfig;
import com.onarandombox.MultiverseCore.economy.MVEconomist;
import com.onarandombox.MultiverseCore.permissions.CorePermissionsChecker;
import com.onarandombox.MultiverseCore.utils.result.Result;
import com.onarandombox.MultiverseCore.utils.result.ResultChain;
import com.onarandombox.MultiverseCore.world.configuration.EntryFee;
import org.bukkit.Material;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.onarandombox.MultiverseCore.utils.message.MessageReplacement.replace;
public class WorldEntryChecker {
private final @NotNull MVCoreConfig config;
private final @NotNull MVEconomist economist;
private final @NotNull CorePermissionsChecker permissionsChecker;
private final @NotNull CommandSender sender;
public WorldEntryChecker(
@NotNull MVCoreConfig config,
@NotNull CorePermissionsChecker permissionsChecker,
@NotNull MVEconomist economist,
@NotNull CommandSender sender
) {
this.config = config;
this.permissionsChecker = permissionsChecker;
this.economist = economist;
this.sender = sender;
}
public ResultChain canStayInWorld(@NotNull MVWorld world) {
return canEnterWorld(null, world);
}
public ResultChain canEnterWorld(@Nullable MVWorld fromWorld, @NotNull MVWorld toWorld) {
return ResultChain.builder()
.then(() -> canAccessWorld(toWorld))
.then(() -> isWithinPlayerLimit(toWorld))
.then(() -> isNotBlacklisted(fromWorld, toWorld))
.then(() -> canPayEntryFee(toWorld))
.build();
}
public Result<WorldAccessResult.Success, WorldAccessResult.Failure> canAccessWorld(@NotNull MVWorld world) {
if (!config.getEnforceAccess()) {
return Result.success(WorldAccessResult.Success.NO_ENFORCE_WORLD_ACCESS);
}
return permissionsChecker.hasWorldAccessPermission(this.sender, world)
? Result.success(WorldAccessResult.Success.HAS_WORLD_ACCESS)
: Result.failure(WorldAccessResult.Failure.NO_WORLD_ACCESS);
}
public Result<PlayerLimitResult.Success, PlayerLimitResult.Failure> isWithinPlayerLimit(@NotNull MVWorld world) {
final int playerLimit = world.getPlayerLimit();
if (playerLimit <= -1) {
return Result.success(PlayerLimitResult.Success.NO_PLAYERLIMIT);
}
if (permissionsChecker.hasPlayerLimitBypassPermission(sender, world)) {
return Result.success(PlayerLimitResult.Success.BYPASS_PLAYERLIMIT);
}
return playerLimit > world.getCBWorld().getPlayers().size()
? Result.success(PlayerLimitResult.Success.WITHIN_PLAYERLIMIT)
: Result.failure(PlayerLimitResult.Failure.EXCEED_PLAYERLIMIT);
}
public Result<BlacklistResult.Success, BlacklistResult.Failure> isNotBlacklisted(@Nullable MVWorld fromWorld, @NotNull MVWorld toWorld) {
if (fromWorld == null) {
return Result.success(BlacklistResult.Success.UNKNOWN_FROM_WORLD);
}
return toWorld.getWorldBlacklist().contains(fromWorld.getName())
? Result.failure(BlacklistResult.Failure.BLACKLISTED, replace("{world}").with(fromWorld.getAlias()))
: Result.success(BlacklistResult.Success.NOT_BLACKLISTED);
}
public Result<EntryFeeResult.Success, EntryFeeResult.Failure> canPayEntryFee(MVWorld world) {
double price = world.getPrice();
Material currency = world.getCurrency();
if (price == 0D && (currency == null || currency == EntryFee.DISABLED_MATERIAL)) {
return Result.success(EntryFeeResult.Success.FREE_ENTRY);
}
if (sender instanceof ConsoleCommandSender || sender instanceof BlockCommandSender) {
return Result.success(EntryFeeResult.Success.CONSOLE_OR_BLOCK_COMMAND_SENDER);
}
if (permissionsChecker.hasWorldExemptPermission(sender, world)) {
return Result.success(EntryFeeResult.Success.EXEMPT_FROM_ENTRY_FEE);
}
if (!(sender instanceof Player player)) {
return Result.failure(EntryFeeResult.Failure.CANNOT_PAY_ENTRY_FEE);
}
return economist.isPlayerWealthyEnough(player, price, currency)
? Result.success(EntryFeeResult.Success.ENOUGH_MONEY)
: Result.failure(EntryFeeResult.Failure.NOT_ENOUGH_MONEY, replace("{amount}").with("$##")); //TODO
}
}

View File

@ -0,0 +1,32 @@
package com.onarandombox.MultiverseCore.world.entrycheck;
import com.onarandombox.MultiverseCore.config.MVCoreConfig;
import com.onarandombox.MultiverseCore.economy.MVEconomist;
import com.onarandombox.MultiverseCore.permissions.CorePermissionsChecker;
import jakarta.inject.Inject;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
@Service
public class WorldEntryCheckerProvider {
private final @NotNull MVCoreConfig config;
private final @NotNull MVEconomist economist;
private final @NotNull CorePermissionsChecker permissionsChecker;
@Inject
WorldEntryCheckerProvider(
@NotNull MVCoreConfig config,
@NotNull MVEconomist economist,
@NotNull CorePermissionsChecker permissionsChecker
) {
this.config = config;
this.economist = economist;
this.permissionsChecker = permissionsChecker;
}
public @NotNull WorldEntryChecker forSender(@NotNull CommandSender sender) {
return new WorldEntryChecker(config, permissionsChecker, economist, sender);
}
}

View File

@ -114,3 +114,14 @@ mv-core.unload.success=&aUnloaded world '{world}'!
# /mv usage
mv-core.usage.description=Show Multiverse-Core command usage.
# entry check
mv-core.entrycheck.blacklisted='{world}' is blacklisted.
mv-core.entrycheck.notenoughmoney=you do not have enough money to pay entry fee. You are required to pay {amount}.
mv-core.entrycheck.cannotpayentryfee=you do not have the ability to pay entry fee.
mv-core.entrycheck.exceedplayerlimit=the world has reached its player limit.
mv-core.entrycheck.noworldaccess=you do not have permissions to access the world.
# generic
mv-core.generic.success=Success!
mv-core.generic.failure=Failed!