Skip to content

v3 API improvements, multi currency support #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fcc61ae
Bumped version to 2.4.0-pre1
NonSwag Jul 14, 2025
a3d9d47
Added `CurrencyHolder` interface
NonSwag Jul 14, 2025
dee4109
Added `Currency` interface and improved currency handling
NonSwag Jul 14, 2025
34ca1ed
Refactored to use `Currency` for balance operations
NonSwag Jul 14, 2025
2e0bbad
Added TODOs for improving `ServiceConvertCommand`
NonSwag Jul 14, 2025
1360f73
Bumped version to 3.0.0-pre1
NonSwag Jul 15, 2025
79f78cb
Added `@Contract` annotations for API methods
NonSwag Jul 15, 2025
59b9368
Added support for nullable `World` parameters
NonSwag Jul 15, 2025
5972a74
Added `hasMultiWorldSupport` to controllers
NonSwag Jul 15, 2025
573ed17
Updated Javadocs for method clarity
NonSwag Jul 15, 2025
6e8da2e
Removed `@ApiStatus.NonExtendable` from `Currency` methods
NonSwag Jul 15, 2025
5dbd5fc
Renamed `getController` to `getHolder`
NonSwag Jul 15, 2025
ef53068
Removed redundant exception handling in bank methods
NonSwag Jul 15, 2025
f647dee
Removed redundant `deleteAccounts` method
NonSwag Jul 15, 2025
d0563b4
Commented out incomplete conversion logic
NonSwag Jul 15, 2025
33c02e0
Added `@since` annotations across API interfaces
NonSwag Jul 15, 2025
1ade9e7
Added nullable `World` support to controllers
NonSwag Jul 15, 2025
08d10a7
Added methods for singular and plural locale mappings
NonSwag Jul 16, 2025
5888939
Refactored currency and account APIs for flexibility
NonSwag Jul 19, 2025
dde9dc3
[ci skip] Improved Javadoc parameter descriptions
NonSwag Jul 19, 2025
aebf7b9
Enhanced currency and account APIs for precision and flexibility
NonSwag Jul 19, 2025
283fe4d
Added exception to `editCurrency` method in `Currency`
NonSwag Jul 19, 2025
1b2065c
Added `deleteCurrency(Currency)` method to `CurrencyHolder`
NonSwag Jul 19, 2025
80b8c6f
Reintroduced `toBuilder` method in `Currency`
NonSwag Jul 19, 2025
51849d1
Decoupled `CurrencyHolder` from controllers and services
NonSwag Jul 19, 2025
8498008
Added `hasBank` methods and improved `Currency` linkage
NonSwag Jul 19, 2025
9c9187f
Implemented `createCurrency` in `CurrencyHolder`
NonSwag Jul 22, 2025
8bebc8d
Improved Javadoc and parameter handling in banking APIs
NonSwag Jul 22, 2025
e722729
Added `canDeposit`, `canWithdraw`, and `canHold` methods
NonSwag Jul 22, 2025
5ef3b6c
[ci skip] Added `@since 3.0.0` annotations to API methods
NonSwag Jul 22, 2025
97d065f
[ci skip] Bump version to 3.0.0-pre6
NonSwag Jul 22, 2025
dc68490
[ci skip] Updated supported game versions to include 1.21.8
NonSwag Jul 24, 2025
8c33828
Merge branch 'main' into feat/v3
NonSwag Jul 24, 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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = "net.thenextlvl.services"
version = "2.3.0"
version = "3.0.0-pre6"

java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
gameVersions=1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7
gameVersions=1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ private void loadServiceEconomyWrapper() {
services.register(EconomyController.class, wrapper, economy.getPlugin(), economy.getPriority());

if (!economy.getProvider().hasBankSupport()) return;
var banks = new BankServiceWrapper(economy.getProvider(), economy.getPlugin(), this);
var banks = new BankServiceWrapper(wrapper.getCurrencyHolder(), economy.getProvider(), economy.getPlugin(), this);
services.register(BankController.class, banks, economy.getPlugin(), economy.getPriority());
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ LiteralArgumentBuilder<CommandSourceStack> create() {
.then(Commands.literal("permissions").then(permissions()));
}

// todo: clean this whole mess up
// create a proper conversion api
// compare source and target and warn about potential data loss
// implement proper error handling
// add progress tracking

private ArgumentBuilder<CommandSourceStack, ?> banks() {
return Commands.argument("source", new BankArgumentType(plugin, (c, e) -> true))
.then(Commands.argument("target", new BankArgumentType(plugin, (context, controller) ->
Expand Down Expand Up @@ -134,13 +140,17 @@ private int convertPermissions(CommandContext<CommandSourceStack> context) {
private final class BankConverter extends PlayerConverter<BankController> {
@Override
public CompletableFuture<Void> convert(OfflinePlayer player, BankController source, BankController target) {
return source.loadBanks().thenAccept(banks -> banks.forEach(bank ->
bank.getWorld().map(world -> target.createBank(bank.getOwner(), bank.getName(), world))
.orElseGet(() -> target.createBank(bank.getOwner(), bank.getName()))
.thenAccept(targetBank -> {
targetBank.setBalance(bank.getBalance());
bank.getMembers().forEach(targetBank::addMember);
})));
return CompletableFuture.completedFuture(null);
// todo: convert all currencies
// todo: convert balance with currencies
// fixme:
// return source.loadBanks().thenAccept(banks -> banks.forEach(bank ->
// bank.getWorld().map(world -> target.createBank(bank.getOwner(), bank.getName(), world))
// .orElseGet(() -> target.createBank(bank.getOwner(), bank.getName()))
// .thenAccept(targetBank -> {
// targetBank.setBalance(bank.getBalance());
// bank.getMembers().forEach(targetBank::addMember);
// })));
}
}

Expand Down Expand Up @@ -191,14 +201,16 @@ public CompletableFuture<Void> convert(EconomyController source, EconomyControll
}

public CompletableFuture<Void> convert(Account account, EconomyController source, EconomyController target) {
return account.getWorld().map(world -> target.tryGetAccount(account.getOwner(), world)
.thenCompose(account1 -> account1.map(CompletableFuture::completedFuture)
.orElseGet(() -> target.createAccount(account.getOwner(), world)))
.thenAccept(account1 -> account1.setBalance(account.getBalance())))
.orElseGet(() -> target.tryGetAccount(account.getOwner())
.thenCompose(account1 -> account1.map(CompletableFuture::completedFuture)
.orElseGet(() -> target.createAccount(account.getOwner())))
.thenAccept(account1 -> account1.setBalance(account.getBalance())));
return CompletableFuture.completedFuture(null);
// fixme
// return account.getWorld().map(world -> target.tryGetAccount(account.getOwner(), world)
// .thenCompose(account1 -> account1.map(CompletableFuture::completedFuture)
// .orElseGet(() -> target.createAccount(account.getOwner(), world)))
// .thenAccept(account1 -> account1.setBalance(account.getBalance())))
// .orElseGet(() -> target.tryGetAccount(account.getOwner())
// .thenCompose(account1 -> account1.map(CompletableFuture::completedFuture)
// .orElseGet(() -> target.createAccount(account.getOwner())))
// .thenAccept(account1 -> account1.setBalance(account.getBalance())));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import net.thenextlvl.service.api.chat.ChatProfile;
import net.thenextlvl.service.model.chat.GroupManagerChatProfile;
import org.anjocaido.groupmanager.GroupManager;
import org.anjocaido.groupmanager.dataholder.OverloadedWorldHolder;
import org.anjocaido.groupmanager.dataholder.WorldDataHolder;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
Expand All @@ -21,55 +21,20 @@ public class GroupManagerChatController implements ChatController {
private final GroupManager groupManager = JavaPlugin.getPlugin(GroupManager.class);

@Override
public CompletableFuture<ChatProfile> loadProfile(OfflinePlayer player) {
return getProfile(player)
.map(CompletableFuture::completedFuture)
.orElseGet(() -> CompletableFuture.completedFuture(null));
}

@Override
public CompletableFuture<ChatProfile> loadProfile(OfflinePlayer player, World world) {
return getProfile(player, world)
.map(CompletableFuture::completedFuture)
.orElseGet(() -> CompletableFuture.completedFuture(null));
}

@Override
public CompletableFuture<ChatProfile> loadProfile(UUID uuid) {
return getProfile(uuid)
.map(CompletableFuture::completedFuture)
.orElseGet(() -> CompletableFuture.completedFuture(null));
}

@Override
public CompletableFuture<ChatProfile> loadProfile(UUID uuid, World world) {
public CompletableFuture<ChatProfile> loadProfile(UUID uuid, @Nullable World world) {
return getProfile(uuid, world)
.map(CompletableFuture::completedFuture)
.orElseGet(() -> CompletableFuture.completedFuture(null));
}

@Override
public Optional<ChatProfile> getProfile(OfflinePlayer player) {
var holder = groupManager.getWorldsHolder().getDefaultWorld();
return getProfile(holder, player.getUniqueId(), player.getName());
public Optional<ChatProfile> getProfile(UUID uuid, @Nullable World world) {
return getProfile(getHolder(world), uuid, null);
}

@Override
public Optional<ChatProfile> getProfile(OfflinePlayer player, World world) {
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
return getProfile(holder, player.getUniqueId(), player.getName());
}

@Override
public Optional<ChatProfile> getProfile(UUID uuid) {
var holder = groupManager.getWorldsHolder().getDefaultWorld();
return getProfile(holder, uuid, null);
}

@Override
public Optional<ChatProfile> getProfile(UUID uuid, World world) {
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
return getProfile(holder, uuid, null);
private @Nullable OverloadedWorldHolder getHolder(@Nullable World world) {
if (world == null) return groupManager.getWorldsHolder().getDefaultWorld();
return groupManager.getWorldsHolder().getWorldData(world.getName());
}

private Optional<ChatProfile> getProfile(@Nullable WorldDataHolder holder, UUID uuid, @Nullable String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.util.Optional;
import java.util.UUID;
Expand All @@ -25,33 +26,24 @@ public LuckPermsChatController(Plugin plugin) {
}

@Override
public CompletableFuture<ChatProfile> loadProfile(UUID uuid) {
return luckPerms.getUserManager().loadUser(uuid).thenApply(user ->
new LuckPermsChatProfile(user, QueryOptions.defaultContextualOptions()));
}

@Override
public CompletableFuture<ChatProfile> loadProfile(UUID uuid, World world) {
public CompletableFuture<ChatProfile> loadProfile(UUID uuid, @Nullable World world) {
return luckPerms.getUserManager().loadUser(uuid).thenApply(user -> {
var options = QueryOptions.contextual(ImmutableContextSet.of("world", world.getName()));
return new LuckPermsChatProfile(user, options);
return new LuckPermsChatProfile(user, getOptions(world));
});
}

@Override
public Optional<ChatProfile> getProfile(UUID uuid) {
return Optional.ofNullable(luckPerms.getUserManager().getUser(uuid)).map(user ->
new LuckPermsChatProfile(user, QueryOptions.defaultContextualOptions()));
}

@Override
public Optional<ChatProfile> getProfile(UUID uuid, World world) {
public Optional<ChatProfile> getProfile(UUID uuid, @Nullable World world) {
return Optional.ofNullable(luckPerms.getUserManager().getUser(uuid)).map(user -> {
var options = QueryOptions.contextual(ImmutableContextSet.of("world", world.getName()));
return new LuckPermsChatProfile(user, options);
return new LuckPermsChatProfile(user, getOptions(world));
});
}

private QueryOptions getOptions(@Nullable World world) {
if (world == null) return QueryOptions.defaultContextualOptions();
return QueryOptions.contextual(ImmutableContextSet.of("world", world.getName()));
}

@Override
public Plugin getPlugin() {
return plugin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import net.thenextlvl.service.model.group.GroupManagerGroup;
import net.thenextlvl.service.model.permission.GroupManagerPermissionHolder;
import org.anjocaido.groupmanager.GroupManager;
import org.anjocaido.groupmanager.dataholder.OverloadedWorldHolder;
import org.anjocaido.groupmanager.dataholder.WorldDataHolder;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
Expand All @@ -32,59 +32,30 @@ public CompletableFuture<Group> createGroup(String name) {
}

@Override
public CompletableFuture<Group> createGroup(String name, World world) {
public CompletableFuture<Group> createGroup(String name, @Nullable World world) {
if (world == null) return createGroup(name);
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
if (holder == null) return CompletableFuture.completedFuture(null);
return CompletableFuture.completedFuture(new GroupManagerGroup(holder.createGroup(name)));
}

@Override
public CompletableFuture<Group> loadGroup(String name) {
return CompletableFuture.completedFuture(getGroup(name).orElse(null));
}

@Override
public CompletableFuture<Group> loadGroup(String name, World world) {
public CompletableFuture<Group> loadGroup(String name, @Nullable World world) {
return CompletableFuture.completedFuture(getGroup(name, world).orElse(null));
}

@Override
public CompletableFuture<GroupHolder> loadGroupHolder(OfflinePlayer player) {
return CompletableFuture.completedFuture(getGroupHolder(player).orElse(null));
}

@Override
public CompletableFuture<GroupHolder> loadGroupHolder(OfflinePlayer player, World world) {
return CompletableFuture.completedFuture(getGroupHolder(player, world).orElse(null));
}

@Override
public CompletableFuture<GroupHolder> loadGroupHolder(UUID uuid) {
return CompletableFuture.completedFuture(getGroupHolder(uuid).orElse(null));
}

@Override
public CompletableFuture<GroupHolder> loadGroupHolder(UUID uuid, World world) {
public CompletableFuture<GroupHolder> loadGroupHolder(UUID uuid, @Nullable World world) {
return CompletableFuture.completedFuture(getGroupHolder(uuid, world).orElse(null));
}

@Override
public CompletableFuture<Set<Group>> loadGroups() {
return CompletableFuture.completedFuture(getGroups());
}

@Override
public CompletableFuture<Set<Group>> loadGroups(World world) {
public CompletableFuture<Set<Group>> loadGroups(@Nullable World world) {
return CompletableFuture.completedFuture(getGroups(world));
}

@Override
public CompletableFuture<Boolean> deleteGroup(Group group) {
return deleteGroup(group.getName());
}

@Override
public CompletableFuture<Boolean> deleteGroup(Group group, World world) {
public CompletableFuture<Boolean> deleteGroup(Group group, @Nullable World world) {
return deleteGroup(group.getName(), world);
}

Expand All @@ -94,7 +65,8 @@ public CompletableFuture<Boolean> deleteGroup(String name) {
}

@Override
public CompletableFuture<Boolean> deleteGroup(String name, World world) {
public CompletableFuture<Boolean> deleteGroup(String name, @Nullable World world) {
if (world == null) return deleteGroup(name);
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
if (holder != null) CompletableFuture.completedFuture(holder.removeGroup(name));
return CompletableFuture.completedFuture(null);
Expand All @@ -107,35 +79,17 @@ public Optional<Group> getGroup(String name) {
}

@Override
public Optional<Group> getGroup(String name, World world) {
public Optional<Group> getGroup(String name, @Nullable World world) {
if (world == null) return getGroup(name);
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
return Optional.ofNullable(holder)
.map(holder1 -> holder1.getGroup(name))
.map(GroupManagerGroup::new);
}

@Override
public Optional<GroupHolder> getGroupHolder(OfflinePlayer player) {
var holder = groupManager.getWorldsHolder().getDefaultWorld();
return getHolder(holder, player.getUniqueId(), player.getName());
}

@Override
public Optional<GroupHolder> getGroupHolder(OfflinePlayer player, World world) {
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
return getHolder(holder, player.getUniqueId(), player.getName());
}

@Override
public Optional<GroupHolder> getGroupHolder(UUID uuid) {
var holder = groupManager.getWorldsHolder().getDefaultWorld();
return getHolder(holder, uuid, null);
}

@Override
public Optional<GroupHolder> getGroupHolder(UUID uuid, World world) {
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
return getHolder(holder, uuid, null);
public Optional<GroupHolder> getGroupHolder(UUID uuid, @Nullable World world) {
return getHolder(getHolder(world), uuid, null);
}

@Override
Expand All @@ -146,14 +100,20 @@ public Set<Group> getGroups() {
}

@Override
public Set<Group> getGroups(World world) {
var holder = groupManager.getWorldsHolder().getWorldData(world.getName());
public Set<Group> getGroups(@Nullable World world) {
if (world == null) return getGroups();
var holder = getHolder(world);
if (holder == null) return Set.of();
return holder.getGroups().values().stream()
.map(GroupManagerGroup::new)
.collect(Collectors.toUnmodifiableSet());
}

private @Nullable OverloadedWorldHolder getHolder(@Nullable World world) {
if (world == null) return groupManager.getWorldsHolder().getDefaultWorld();
return groupManager.getWorldsHolder().getWorldData(world.getName());
}

private Optional<GroupHolder> getHolder(@Nullable WorldDataHolder holder, UUID uuid, @Nullable String name) {
if (holder == null) return Optional.empty();
var user = name != null ? holder.getUser(uuid.toString(), name) : holder.getUser(uuid.toString());
Expand Down
Loading