Skip to content
Merged

5.1 #3244

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c84ae04
Add location safety check for setspawn command
benwoo1110 May 31, 2025
50f6152
Fix sendError message type
benwoo1110 May 31, 2025
d5b2cc8
Support localization for setspawn command output
benwoo1110 May 31, 2025
ae4d33d
Fix PlayerLocation context logic
benwoo1110 May 31, 2025
aab13c1
Add support for multiple params in placeholder
benwoo1110 Jun 1, 2025
1c521e0
Implement time formatting for world time placeholder
benwoo1110 Jun 1, 2025
bb2da15
Fix minecraft time calculation
benwoo1110 Jun 2, 2025
8a96c7b
Don't show seconds for default 12h and 24h format
benwoo1110 Jun 2, 2025
530a8a9
Merge pull request #3243 from Multiverse/feat/time-placeholder
benwoo1110 Jun 2, 2025
232d18f
Merge pull request #3237 from Multiverse/feat/setspawn-safety
benwoo1110 Jun 2, 2025
38231ef
Use kyori adventure whenever possible to support rgb formatting
benwoo1110 Jun 2, 2025
ba78b7d
Merge pull request #3245 from Multiverse/feat/chat-color
benwoo1110 Jun 2, 2025
71eedd3
Add support for passenger and vehicle teleports
benwoo1110 Jun 6, 2025
a3a6219
Add ApiStatus annotations to new and deprecated api
benwoo1110 Jun 6, 2025
41c8c17
Fix config test
benwoo1110 Jun 6, 2025
d89f00a
Merge pull request #3250 from Multiverse/chore/annotations
benwoo1110 Jun 6, 2025
42a8d33
Fix some javadocs of the new attempt methods
benwoo1110 Jun 6, 2025
e9a2025
Merge pull request #3249 from Multiverse/feat/passengers
benwoo1110 Jun 6, 2025
d76a6a6
Refactor issuer aware context handling to extract our similar logic
benwoo1110 Jun 6, 2025
4ddcb3f
Fix teleport command tab complete for in-game with player arg
benwoo1110 Jun 6, 2025
ce263b5
Merge pull request #3252 from Multiverse/feat/improve-context
benwoo1110 Jun 6, 2025
a509655
Implement sender context for config node suggestions
benwoo1110 Jun 10, 2025
058a7c9
Implement SenderNodeSuggester to builders
benwoo1110 Jun 15, 2025
ff26ffd
Merge pull request #3258 from Multiverse/feat/sender-context
benwoo1110 Jun 15, 2025
b91214f
Streamline destination suggestions with helper methods to directly pa…
benwoo1110 Jun 15, 2025
c68936f
Merge pull request #3261 from Multiverse/refactor/destination-suggestion
benwoo1110 Jun 15, 2025
a3b7dd5
Mark CoreConfig#getConfig() as internal use only
benwoo1110 Jun 16, 2025
4f08b9a
Add qol methods whenLoaded and isLoaded to MultiverseCoreApi
benwoo1110 Jun 16, 2025
518d648
Merge pull request #3262 from Multiverse/feat/api-qol
benwoo1110 Jun 16, 2025
c09dacb
Add support for alias name in config node
benwoo1110 Jun 20, 2025
1372626
Merge pull request #3263 from Multiverse/feat/config-alias-name
benwoo1110 Jun 20, 2025
1082f73
Add support config string parsing with sender context
benwoo1110 Jun 20, 2025
59d1c44
Merge pull request #3264 from Multiverse/feat/sender-string-parse
benwoo1110 Jun 20, 2025
9c562d0
Add support for destination parse with sender context
benwoo1110 Jun 20, 2025
5702426
Fix config test due to destination parsing
benwoo1110 Jun 20, 2025
4fd8350
Merge pull request #3265 from Multiverse/feat/destination-sender
benwoo1110 Jun 20, 2025
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 @@ -15,6 +15,7 @@
import jakarta.inject.Provider;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;

Expand Down Expand Up @@ -238,7 +239,11 @@ private void logEnableMessage() {
* Gets the MultiverseCoreApi
*
* @return The MultiverseCoreApi
*
* @deprecated Use {@link MultiverseCoreApi#get()} directly.
*/
@Deprecated(since = "5.1", forRemoval = true)
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
public MultiverseCoreApi getApi() {
return MultiverseCoreApi.get();
}
Expand Down
47 changes: 47 additions & 0 deletions src/main/java/org/mvplugins/multiverse/core/MultiverseCoreApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.bukkit.Bukkit;
import org.bukkit.plugin.ServicePriority;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.config.CoreConfig;
Expand All @@ -15,32 +16,78 @@
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

/**
* Provides access to the MultiverseCore API.
*/
public class MultiverseCoreApi {

private static MultiverseCoreApi instance;
private static final List<Consumer<MultiverseCoreApi>> whenLoadedCallbacks = new ArrayList<>();

static void init(@NotNull MultiverseCore multiverseCore) {
if (instance != null) {
throw new IllegalStateException("MultiverseCoreApi has already been initialized!");
}
instance = new MultiverseCoreApi(multiverseCore.getServiceLocator());
Bukkit.getServicesManager().register(MultiverseCoreApi.class, instance, multiverseCore, ServicePriority.Normal);

whenLoadedCallbacks.forEach(c -> c.accept(instance));
whenLoadedCallbacks.clear();
}

static void shutdown() {
Bukkit.getServicesManager().unregister(instance);
instance = null;
}

/**
* Hook your plugin into the MultiverseCoreApi here to ensure you only start using the API after it has been initialized.
* Use this if you know your plugin may load before Multiverse-Core is fully initialized.
* <br/>
* This handy method removes the need for you to check with plugin manager or listen to plugin enable event.
* <br/>
* Callback will be called immediately if the MultiverseCoreApi has already been initialized.
*
* @param consumer The callback to execute when the MultiverseCoreApi has been initialized.
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public static void whenLoaded(@NotNull Consumer<MultiverseCoreApi> consumer) {
if (instance != null) {
consumer.accept(instance);
} else {
whenLoadedCallbacks.add(consumer);
}
}

/**
* Checks if the MultiverseCoreApi has been initialized.
*
* @return True if the MultiverseCoreApi has been initialized, false otherwise
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public static boolean isLoaded() {
return instance != null;
}

/**
* Gets the MultiverseCoreApi. This will throw an exception if the Multiverse-Core has not been initialized.
* <br/>
* You can check if the MultiverseCoreApi has been initialized with {@link #isLoaded()} before using this method.
* <br/>
* Alternatively, you can use {@link #whenLoaded(Consumer)} to hook into the MultiverseCoreApi if your plugin may
* load before Multiverse-Core is fully initialized.
*
* @return The MultiverseCoreApi
* @throws IllegalStateException if the MultiverseCoreApi has not been initialized
*/
public static @NotNull MultiverseCoreApi get() {
if (instance == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.mvplugins.multiverse.core;

import com.google.common.collect.Lists;
import io.vavr.control.Option;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
Expand All @@ -13,11 +14,16 @@
import org.jvnet.hk2.annotations.Service;

import org.mvplugins.multiverse.core.economy.MVEconomist;
import org.mvplugins.multiverse.core.utils.MinecraftTimeFormatter;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;

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

@Service
final class PlaceholderExpansionHook extends PlaceholderExpansion {

Expand Down Expand Up @@ -68,32 +74,54 @@ public boolean persist() {
@Override
public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) {
// Split string in to an Array with underscores
String[] paramsArray = REPatterns.UNDERSCORE.split(params, 2);
List<String> paramsArray = Lists.newArrayList(REPatterns.UNDERSCORE.split(params));

// No placeholder defined
if (paramsArray.length < 1) {
warning("No placeholder defined");
if (paramsArray.isEmpty()) {
warning("No placeholder key defined");
return null;
}

final var placeholder = paramsArray[0];
Option<LoadedMultiverseWorld> targetWorld;
final var placeholder = paramsArray.removeFirst();

String worldName = parseWorldName(offlinePlayer, paramsArray);
if (worldName == null) return null;

return worldManager.getLoadedWorld(worldName)
.onEmpty(() -> warning("Multiverse World not found: " + worldName))
.map(world -> getWorldPlaceHolderValue(placeholder, paramsArray, world))
.getOrNull();
}

// If no world is defined, use the player's world
if (paramsArray.length == 1) {
if (!offlinePlayer.isOnline()) {
private @Nullable String parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
// No world defined, get from player
if (paramsArray.isEmpty()) {
if (offlinePlayer instanceof Player player) {
return player.getWorld().getName();
} else {
warning("You must specify a world name for non-player placeholders");
return null;
}
targetWorld = worldManager.getLoadedWorld(((Player)offlinePlayer).getWorld());
} else {
targetWorld = worldManager.getLoadedWorld(paramsArray[1]);
}

// Fail if world is null
return targetWorld.map(world -> getWorldPlaceHolderValue(placeholder, world)).getOrNull();
// Try get from params
String paramWorldName = paramsArray.getLast();
if (worldManager.isLoadedWorld(paramWorldName)) {
paramsArray.removeLast();
return paramWorldName;
}

// Param not a world, fallback to player
if (offlinePlayer instanceof Player player) {
return player.getWorld().getName();
}
warning("Multiverse World not found: " + paramWorldName);
return null;
}

private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder, @NotNull LoadedMultiverseWorld world) {
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder,
@NotNull List<String> placeholderParams,
@NotNull LoadedMultiverseWorld world) {
// Switch to find what specific placeholder we want
switch (placeholder.toLowerCase()) {
case "alias" -> {
Expand Down Expand Up @@ -151,7 +179,22 @@ public boolean persist() {
return String.valueOf(world.getSeed());
}
case "time" -> {
return String.valueOf(world.getBukkitWorld().map(World::getTime).getOrElse(0L));
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.getFirst() : "";
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
switch (timeFormat) {
case "" -> {
return String.valueOf(time);
}
case "12h" -> {
return MinecraftTimeFormatter.format12h(time);
}
case "24h" -> {
return MinecraftTimeFormatter.format24h(time);
}
default -> {
return MinecraftTimeFormatter.formatTime(time, timeFormat);
}
}
}
case "type" -> {
return world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@

import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
import org.mvplugins.multiverse.core.command.context.issueraware.IssuerAwareValue;
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction;
import org.mvplugins.multiverse.core.destination.DestinationInstance;
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.utils.StringFormatter;
Expand Down Expand Up @@ -129,6 +132,22 @@ private Collection<String> completeWithPreconditions(
if (context.hasConfig("playerOnly") && !context.getIssuer().isPlayer()) {
return Collections.emptyList();
}
if (context.hasConfig("byIssuerForArg")) {
Boolean byIssuerForArg = Try.of(() -> context.getContextValueByName(IssuerAwareValue.class, context.getConfig("byIssuerForArg")))
.map(IssuerAwareValue::isByIssuer)
.getOrElse(false);
if (!byIssuerForArg) {
return Collections.emptyList();
}
}
if (context.hasConfig("notByIssuerForArg")) {
Boolean byIssuerForArg = Try.of(() -> context.getContextValueByName(IssuerAwareValue.class, context.getConfig("notByIssuerForArg")))
.map(IssuerAwareValue::isByIssuer)
.getOrElse(false);
if (byIssuerForArg) {
return Collections.emptyList();
}
}
if (context.hasConfig("resolveUntil")) {
if (!Try.run(() -> context.getContextValueByName(Object.class, context.getConfig("resolveUntil"))).isSuccess()) {
return Collections.emptyList();
Expand Down Expand Up @@ -195,7 +214,10 @@ private boolean checkPerms(CommandIssuer issuer, RegisteredCommand<?> command) {
}

private Collection<String> suggestDestinations(BukkitCommandCompletionContext context) {
return Try.of(() -> context.getContextValue(Player[].class))
return Try.of(() -> context.getContextValue(PlayerArrayValue.class))
.map(PlayerArrayValue::value)
.recover(IllegalStateException.class, e -> context.getContextValue(Player[].class))
.recover(IllegalStateException.class, e -> new Player[]{context.getContextValue(Player.class)})
.map(players -> {
if (players.length == 0) {
// Most likely console did not specify a player
Expand All @@ -213,9 +235,7 @@ private Collection<String> suggestDestinationsWithPerms(CommandSender teleporter
return destinationsProvider.suggestDestinations(teleporter, deststring).stream()
.filter(packet -> corePermissionsChecker
.checkDestinationPacketPermission(teleporter, Arrays.asList(players), packet))
.map(packet -> packet.destination() instanceof WorldDestination
? packet.destinationString()
: packet.destination().getIdentifier() + ":" + packet.destinationString())
.map(DestinationSuggestionPacket::parsableString)
.toList();
}

Expand Down Expand Up @@ -248,7 +268,7 @@ private Collection<String> suggestGeneratorPlugins(BukkitCommandCompletionContex
private Collection<String> suggestMVConfigValues(BukkitCommandCompletionContext context) {
return Try.of(() -> context.getContextValue(String.class))
.map(propertyName -> config.getStringPropertyHandle()
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET))
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET, context.getSender()))
.getOrElse(Collections.emptyList());
}

Expand Down Expand Up @@ -283,18 +303,18 @@ private Collection<String> suggestMVWorlds(BukkitCommandCompletionContext contex

private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
return world.getStringPropertyHandle().getModifiablePropertyNames(action);
}).getOrElse(Collections.emptyList());
}

private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
String propertyName = context.getContextValue(String.class);
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action);
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
}).getOrElse(Collections.emptyList());
}

Expand Down Expand Up @@ -335,7 +355,7 @@ private Collection<String> suggestSpawnCategoryPropsValue(BukkitCommandCompletio
return world.getEntitySpawnConfig()
.getSpawnCategoryConfig(spawnCategory)
.getStringPropertyHandle()
.getSuggestedPropertyValue(propertyName, context.getInput(), action);
.getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
}).getOrElse(Collections.emptyList());
}
}
Loading
Loading