This commit is contained in:
kento2 2026-08-05 21:40:52 +02:00
parent c60f19cafc
commit 2ea2eb5d1e
114 changed files with 671 additions and 609 deletions

View file

@ -0,0 +1,48 @@
package de.kentoj.scrow.bukkit;
import de.kentoj.scrow.bukkit.style.ChatStyleListener;
import de.kentoj.scrow.bukkit.command.economy.CoinsCommand;
import de.kentoj.scrow.bukkit.command.friends.FriendCommand;
import de.kentoj.scrow.bukkit.command.instance.InstanceCache;
import de.kentoj.scrow.bukkit.command.instance.InstanceCommand;
import de.kentoj.scrow.bukkit.command.instance.LobbyCommand;
import de.kentoj.scrow.bukkit.command.instance.PlayCommand;
import de.kentoj.scrow.bukkit.internal.player.prefix.CachedPrefixProvider;
import de.kentoj.scrow.bukkit.internal.player.prefix.LuckPermsPrefixSetter;
import de.kentoj.scrow.bukkit.style.TabStyleListener;
import net.luckperms.api.LuckPermsProvider;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public final class CoreImplPlugin extends JavaPlugin {
@Override
public void onEnable() {
var cachedPrefixProvider = new CachedPrefixProvider();
ScrowAPISurface.initScrowAPI(this, cachedPrefixProvider);
ScrowAPI.playerCommands().register(new CoinsCommand().rootLiteral());
ScrowAPI.playerCommands().register(new FriendCommand().rootLiteral());
var instanceCache = new InstanceCache();
ScrowAPI.commands().register(new InstanceCommand(instanceCache).rootLiteral());
ScrowAPI.playerCommands().register(new PlayCommand().rootLiteral());
ScrowAPI.playerCommands().register(new LobbyCommand().rootLiteral());
var luckperms = LuckPermsProvider.get();
new LuckPermsPrefixSetter(luckperms, cachedPrefixProvider).init(this);
getServer().getPluginManager().registerEvents(new ChatStyleListener(cachedPrefixProvider), this);
getServer().getPluginManager().registerEvents(new TabStyleListener(cachedPrefixProvider), this);
// TODO/FIXME
//registerServer();
}
@Override
public void onDisable() {
ScrowAPISurface.destroyScrowAPI();
}
}

View file

@ -0,0 +1,90 @@
package de.kentoj.scrow.bukkit;
import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrow.bukkit.internal.player.NetworkPlayerWatchdog;
import de.kentoj.scrow.bukkit.internal.region.RegionManagerImpl;
import de.kentoj.scrow.bukkit.internal.region.RegionPlayerTracker;
import de.kentoj.scrow.bukkit.internal.region.RegionTask;
import de.kentoj.scrowlib.economy.InMemoryEconomyService;
import de.kentoj.scrowlib.economy.PostgresEconomyService;
import de.kentoj.scrowlib.friends.friendship.InMemoryFriendshipService;
import de.kentoj.scrowlib.friends.friendship.PostgresFriendshipService;
import de.kentoj.scrowlib.friends.request.InMemoryFriendRequestService;
import de.kentoj.scrowlib.friends.request.PostgresFriendRequestService;
import de.kentoj.scrowlib.instancemanager.InstanceManagerImpl;
import de.kentoj.scrowlib.messaging.InMemoyMessageBroker;
import de.kentoj.scrowlib.messaging.NatsMessageBroker;
import de.kentoj.scrowlib.player.NetworkPlayerImpl;
import de.kentoj.scrowlib.player.PlayerPrefixProvider;
import io.nats.client.Nats;
import io.nats.client.Options;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jdbi.v3.core.Jdbi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Properties;
@NoArgsConstructor(access = AccessLevel.NONE)
public class ScrowAPISurface {
private static final Logger log = LoggerFactory.getLogger(ScrowAPISurface.class);
public static void initScrowAPI(Plugin plugin, PlayerPrefixProvider prefixProvider) {
ScrowAPI.setCorePlugin(plugin);
var natsHost = System.getenv("HOST_NATS");
if (natsHost != null) {
try {
var options = Options.builder()
.server(natsHost)
.pedantic()
.build();
ScrowAPI.setMessageBroker(new NatsMessageBroker(Nats.connect(options)));
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
} else {
log.error("HOST_NATS not set -> Using in-memory dummy message broker");
ScrowAPI.setMessageBroker(new InMemoyMessageBroker());
}
var hostPostgres = System.getenv().get("HOST_POSTGRES");
if (hostPostgres != null) {
try {
Class.forName("org.postgresql.Driver", true, ScrowAPISurface.class.getClassLoader());
var props = new Properties();
props.setProperty("user", "postgres");
ScrowAPI.setJdbi(Jdbi.create(hostPostgres, props));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
log.error("HOST_POSTGRES not set -> Using in-memory database");
}
ScrowAPI.setPlayerFactory(playerId -> new NetworkPlayerImpl(ScrowAPI.messageBroker(), playerId));
ScrowAPI.setRegionManager(new RegionManagerImpl());
ScrowAPI.setCommands(new CommandAPI<>(plugin, CommandSender.class));
ScrowAPI.setPlayerCommands(new CommandAPI<>(plugin, Player.class));
ScrowAPI.setInstanceManager(new InstanceManagerImpl(ScrowAPI.messageBroker()));
ScrowAPI.setEconomyService(ScrowAPI.jdbi() != null ?
new PostgresEconomyService(ScrowAPI.jdbi()) : new InMemoryEconomyService());
ScrowAPI.setFriendRequestService(ScrowAPI.jdbi() != null ?
new PostgresFriendRequestService(ScrowAPI.jdbi()) : new InMemoryFriendRequestService());
ScrowAPI.setFriendshipService(ScrowAPI.jdbi() != null ?
new PostgresFriendshipService(ScrowAPI.jdbi()) : new InMemoryFriendshipService());
ScrowAPI.setPlayerPrefixProvider(prefixProvider);
new RegionTask(new RegionPlayerTracker()).runTaskTimer(plugin, 10, 10);
new NetworkPlayerWatchdog().listen();
}
public static void destroyScrowAPI() {
}
}

View file

@ -0,0 +1,45 @@
package de.kentoj.scrow.bukkit.command.economy;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.kencommandapi.api.platform.MessageStyle;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.Getter;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public final class CoinsCommand {
private final RootLiteral<Player> rootLiteral;
private final MessageStyle style;
public CoinsCommand() {
style = ScrowMessageStyle.GENERIC;
rootLiteral = Literal.<Player>builder("coins", "bal", "balance")
.withPermission("command.coins.get.self")
.withExecutor(this::execute)
.withLiteral(new CoinsSetLiteral(style).literal())
.withLiteral(new CoinsGetLiteral(style).literal())
.asRootLiteral(style);
}
public CompletableFuture<Result<Object, String>> execute(CommandContext<Player> ctx) {
var player = ctx.getSender();
return Tasks.supplyAsync(() ->
ScrowAPI.economyService().getCoins(player.getUniqueId()))
.<Result<Object, String>>mapSync(amount -> {
player.sendMessage(style.ok("You have " + amount + "$"));
return Results.success(new Object());
}).toFuture();
}
public RootLiteral<Player> rootLiteral() {
return rootLiteral;
}
}

View file

@ -0,0 +1,51 @@
package de.kentoj.scrow.bukkit.command.economy;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.platform.MessageStyle;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import lombok.Getter;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public final class CoinsGetLiteral {
private final Literal<Player> literal;
private final MessageStyle style;
private final CommandArgumentSpec<Player, OfflinePlayer> playerArg;
public CoinsGetLiteral(MessageStyle style) {
this.style = style;
playerArg = CommandArgumentSpec.builder("player", new OfflinePlayerArgumentType<Player>())
.withDefaultValue(CommandContext::getSender)
.build();
literal = Literal.<Player>builder("get")
.withPermission("command.coins.get.others")
.withArgument(playerArg)
.withExecutor(this::execute)
.build();
}
private CompletableFuture<Result<Object, String>> execute(CommandContext<Player> ctx) {
var player = ctx.getArg(playerArg);
return Tasks.supplyAsync(() ->
ScrowAPI.economyService().getCoins(player.getUniqueId()))
.<Result<Object, String>>mapSync(amount -> {
ctx.getSender().sendMessage(style.ok(player.getName() + " has " + amount + "$"));
return Results.success(new Object());
}).toFuture();
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,54 @@
package de.kentoj.scrow.bukkit.command.economy;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.argument.types.IntegerArgumentType;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.platform.MessageStyle;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public final class CoinsSetLiteral {
private final Literal<Player> literal;
private final MessageStyle style;
private final CommandArgumentSpec<Player, OfflinePlayer> playerArg;
private final CommandArgumentSpec<Player, Integer> amountArg;
CoinsSetLiteral(MessageStyle style) {
this.style = style;
playerArg = CommandArgumentSpec.builder("player", new OfflinePlayerArgumentType<Player>())
.withDefaultValue(CommandContext::getSender)
.build();
amountArg = CommandArgumentSpec.builder("amount", new IntegerArgumentType<Player>())
.build();
literal = Literal.<Player>builder("set")
.withPermission("command.coins.set")
.withArgument(playerArg)
.withArgument(amountArg)
.withExecutor(this::execute)
.build();
}
private CompletableFuture<Result<Object, String>> execute(CommandContext<Player> ctx) {
var player = ctx.getArg(playerArg);
var amount = ctx.getArg(amountArg);
return Tasks.runAsync(() ->
ScrowAPI.economyService().setCoins(player.getUniqueId(), amount))
.<Result<Object, String>>supplySync(() -> {
ctx.getSender().sendMessage(style.ok(player.getName() + " now has " + amount + "$"));
return Results.success(new Object());
}).toFuture();
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,123 @@
package de.kentoj.scrow.bukkit.command.friends;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Task;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import de.kentoj.scrowlib.friends.FriendRequestService;
import de.kentoj.scrowlib.friends.FriendshipService;
import de.kentoj.scrowlib.friends.friendship.FriendshipImpl;
import de.kentoj.scrowlib.friends.request.FriendRequestImpl;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.time.Instant;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public final class FriendAddLiteral {
private final Literal<Player> literal;
private final CommandArgumentSpec<Player, OfflinePlayer> playerArg;
private final ScrowMessageStyle style;
private final FriendRequestService requestService = ScrowAPI.friendRequestService();
private final FriendshipService friendsService = ScrowAPI.friendshipService();
FriendAddLiteral(ScrowMessageStyle style) {
this.style = style;
playerArg = CommandArgumentSpec.builder("player", new OfflinePlayerArgumentType<Player>())
.build();
literal = Literal.<Player>builder("add")
.withArgument(playerArg)
.withExecutor(this::execute)
.build();
}
private CompletableFuture<Result<Object, String>> execute(CommandContext<Player> ctx) {
var self = ctx.getSender();
var other = ctx.getArg(playerArg);
return Tasks.supplyAsync(() -> findAction(self, other)
.mapSuccess(action -> {
if (action == AddAction.SEND_REQUEST) sendRequest(self, other);
else if (action == AddAction.ACCEPT_REQUEST) acceptRequest(self, other);
return new Object();
}))
.toFuture();
}
private void sendRequest(Player self, OfflinePlayer other) {
Tasks.runAsync(() ->
requestService.saveFriendRequest(new FriendRequestImpl(self.getUniqueId(), other.getUniqueId())))
.runSync(() -> {
self.sendMessage(style.ok("Successfully sent " + other.getName() + " a friend request."));
var msg = Component.text(self.getName() + " sent you a friend request.")
.append(FriendCommand.friendRequestOptions(self.getName(), true));
ScrowAPI.getPlayer(other).sendMessage(style.ok(msg));
})
.await();
}
private void acceptRequest(Player self, OfflinePlayer other) {
Tasks.runAsync(() -> Tasks.awaitAll(
Tasks.runAsync(() -> requestService.deleteFriendRequest(self.getUniqueId(), other.getUniqueId())),
Tasks.runAsync(() -> requestService.deleteFriendRequest(other.getUniqueId(), self.getUniqueId())),
Tasks.runAsync(() -> friendsService.saveFriendship(new FriendshipImpl(self.getUniqueId(), other.getUniqueId(), Instant.now()))
)))
.runSync(() -> {
self.sendMessage(style.ok("You are now friends with " + other.getName() + "."));
ScrowAPI.getPlayer(other).sendMessage(style.ok("You are now friends with " + self.getName() + "."));
})
.await();
}
private Result<AddAction, String> findAction(Player self, OfflinePlayer other) {
if (self.getUniqueId() == other.getUniqueId())
return Results.failure("You cannot add yourself.");
var isAlreadyFriendTask = checkAlreadyFriends(self, other);
var isAlreadySent = checkAlreadySent(self.getUniqueId(), other.getUniqueId());
var isShouldAccept = checkShouldAccept(self, other);
Tasks.awaitAll(isAlreadyFriendTask, isAlreadyFriendTask, isShouldAccept);
if (isAlreadyFriendTask.await())
return Results.failure("You are already friends with " + other.getName() + ".");
if (isAlreadySent.await())
return Results.failure("You've already sent " + other.getName() + " a friend request.");
if (isShouldAccept.await())
return Results.success(AddAction.ACCEPT_REQUEST);
return Results.success(AddAction.SEND_REQUEST);
}
private Task<Boolean> checkShouldAccept(Player self, OfflinePlayer other) {
return Tasks.supplyAsync(() -> requestService.getFriendRequest(other.getUniqueId(), self.getUniqueId()))
.mapSync(Objects::nonNull);
}
private Task<Boolean> checkAlreadyFriends(Player self, OfflinePlayer other) {
return Tasks.supplyAsync(() -> friendsService.getFriendship(self.getUniqueId(), other.getUniqueId()))
.mapSync(Objects::nonNull);
}
private Task<Boolean> checkAlreadySent(UUID self, UUID other) {
return Tasks.supplyAsync(() -> requestService.getFriendRequest(self, other))
.mapSync(Objects::nonNull);
}
public Literal<Player> literal() {
return literal;
}
private enum AddAction {
SEND_REQUEST,
ACCEPT_REQUEST
}
}

View file

@ -0,0 +1,47 @@
package de.kentoj.scrow.bukkit.command.friends;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
public final class FriendCommand {
private final RootLiteral<Player> rootLiteral;
public FriendCommand() {
var style = new ScrowMessageStyle("Friends", NamedTextColor.AQUA);
rootLiteral = Literal.<Player>builder("f", "friend")
.withLiteral(new FriendListLiteral(style).literal())
.withLiteral(new FriendAddLiteral(style).literal())
.withLiteral(new FriendRemoveLiteral(style).literal())
.withLiteral(new FriendRequestDeclineLiteral(style).literal())
.withLiteral(new FriendRequestsLiteral(style).literal())
.asRootLiteral(style);
}
public static Component friendRequestOptions(String requestSenderName, boolean runInstantly) {
return Component.text(" ")
.append(Component.text("[accept]")
.hoverEvent(HoverEvent.showText(Component.text("click to accept")))
.clickEvent(ClickEvent.runCommand("/f add " + requestSenderName))
.color(NamedTextColor.GREEN))
.append(Component.text(" "))
.append(Component.text("[decline]")
.hoverEvent(HoverEvent.showText(Component.text("click to decline")))
.clickEvent(runInstantly
? ClickEvent.runCommand("/f decline " + requestSenderName)
: ClickEvent.suggestCommand("/f decline " + requestSenderName))
.color(NamedTextColor.RED));
}
public RootLiteral<Player> rootLiteral() {
return rootLiteral;
}
}

View file

@ -0,0 +1,75 @@
package de.kentoj.scrow.bukkit.command.friends;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static java.util.Objects.requireNonNull;
import static net.kyori.adventure.text.Component.text;
public final class FriendListLiteral {
private final Literal<Player> literal;
private final ScrowMessageStyle style;
FriendListLiteral(ScrowMessageStyle style) {
this.style = style;
literal = Literal.<Player>builder("list")
.withExecutor(this::displayFriendList)
.build();
}
private CompletableFuture<Result<Object, String>> displayFriendList(CommandContext<Player> ctx) {
var sender = ctx.getSender();
return Tasks.supplyAsync(() ->
ScrowAPI.friendshipService().getFriendships(sender.getUniqueId()))
.<Result<Object, String>>mapSync(friendships -> {
var friends = friendships.stream()
.map(friendship -> Bukkit.getOfflinePlayer(friendship.uuids().getOther(sender.getUniqueId())))
.sorted(this::compareByOnlineStatus)
.toList();
if (friends.isEmpty()) return Results.failure("You have no friends. Use /friend add <player>");
sender.sendMessage(formatList(friends));
return Results.success(new Object());
}).toFuture();
}
private int compareByOnlineStatus(OfflinePlayer p1, OfflinePlayer p2) {
if (p1.isOnline() && !p2.isOnline()) return 1;
if (!p1.isOnline() && p2.isOnline()) return -1;
return 0;
}
private Component formatList(List<OfflinePlayer> friends) {
var message = style.ok(MiniMessage.miniMessage().deserialize("Your friends:"));
for (var friend : friends) {
var status = MiniMessage.miniMessage().deserialize(friend.isOnline()
? " <gray>(<green>online<gray>)"
: " <gray>(<red>offline<gray>)");
message = message
.appendNewline()
.append(text("").color(style.prefixColor()))
.append(text(requireNonNull(friend.getName())).color(NamedTextColor.GRAY))
.append(status);
}
return message;
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,49 @@
package de.kentoj.scrow.bukkit.command.friends;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.Getter;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public final class FriendRemoveLiteral {
private final Literal<Player> literal;
private final CommandArgumentSpec<Player, OfflinePlayer> playerArg;
private final ScrowMessageStyle style;
FriendRemoveLiteral(ScrowMessageStyle style) {
this.style = style;
playerArg = CommandArgumentSpec.<Player, OfflinePlayer>builder("player", new OfflinePlayerArgumentType<>())
.build();
literal = Literal.<Player>builder("remove")
.withArgument(playerArg)
.withExecutor(this::removeFriend)
.build();
}
private CompletableFuture<Result<Object, String>> removeFriend(CommandContext<Player> ctx) {
var friend = ctx.getArg(playerArg);
return Tasks.supplyAsync(() ->
ScrowAPI.friendshipService().deleteFriendship(friend.getUniqueId(), ctx.getSender().getUniqueId()))
.<Result<Object, String>>mapSync(deleted -> {
if (!deleted)
return Results.failure("You are not friends with " + friend.getName() + ".");
ctx.getSender().sendMessage(style.ok("You have ended the friendship with " + friend.getName() + "."));
return Results.success(new Object());
}).toFuture();
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,54 @@
package de.kentoj.scrow.bukkit.command.friends;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import de.kentoj.scrowlib.friends.FriendRequestService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public final class FriendRequestDeclineLiteral {
private final Literal<Player> literal;
private final CommandArgumentSpec<Player, OfflinePlayer> playerArg;
private final ScrowMessageStyle style;
private final FriendRequestService requestService = ScrowAPI.friendRequestService();
FriendRequestDeclineLiteral(ScrowMessageStyle style) {
this.style = style;
playerArg = CommandArgumentSpec.builder("player", new OfflinePlayerArgumentType<Player>())
.build();
literal = Literal.<Player>builder("reject", "decline")
.withArgument(playerArg)
.withExecutor(this::declineFriendRequest)
.build();
}
private CompletableFuture<Result<Object, String>> declineFriendRequest(CommandContext<Player> ctx) {
var self = ctx.getSender();
var other = ctx.getArg(playerArg);
return Tasks.supplyAsync(() ->
requestService.deleteFriendRequest(other.getUniqueId(), self.getUniqueId()))
.<Result<Object, String>>mapSync(deleted -> {
if (!deleted)
return Results.failure("There's no incoming friend request from " + other.getName() + ".");
self.sendMessage(style.ok("Friend request declined."));
ScrowAPI.getPlayer(other).sendMessage(style.ok(self.getName() + " has declined your friend request."));
return Results.success(new Object());
}).toFuture();
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,66 @@
package de.kentoj.scrow.bukkit.command.friends;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import de.kentoj.scrowlib.friends.FriendRequest;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static net.kyori.adventure.text.Component.text;
public final class FriendRequestsLiteral {
private final Literal<Player> literal;
private final ScrowMessageStyle style;
FriendRequestsLiteral(ScrowMessageStyle style) {
this.style = style;
literal = Literal.<Player>builder("requests")
.withExecutor(this::displayFriendRequests)
.build();
}
private CompletableFuture<Result<Object, String>> displayFriendRequests(CommandContext<Player> ctx) {
return Tasks.supplyAsync(() ->
ScrowAPI.friendRequestService().getFriendRequestsTo(ctx.getSender().getUniqueId()))
.<Result<Object, String>>mapAsync(list -> {
if (list.isEmpty()) return Results.failure("You have no friend requests.");
var requesters = list.stream()
.sorted(this::compareByAge)
.map(fr -> Bukkit.getOfflinePlayer(fr.from()).getName());
ctx.getSender().sendMessage(formatList(requesters.toList()));
return Results.success(new Object());
}).toFuture();
}
private int compareByAge(FriendRequest a, FriendRequest b) {
return a.createdAt().isAfter(b.createdAt()) ? 1 : -1;
}
private Component formatList(List<String> requesters) {
var message = style.ok(MiniMessage.miniMessage().deserialize("Open friend requests:"));
for (String requester : requesters) {
message = message
.appendNewline()
.append(text("").color(style.prefixColor()))
.append(Component.text(requester).color(NamedTextColor.GRAY)
.append(FriendCommand.friendRequestOptions(requester, false)));
}
return message;
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,40 @@
package de.kentoj.scrow.bukkit.command.instance;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrowlib.instancemanager.ServerInstance;
import org.bson.Document;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class InstanceCache {
private final ConcurrentMap<String, ServerInstance> cache = new ConcurrentHashMap<>();
public InstanceCache() {
ScrowAPI.messageBroker().subscribe("event.instance.up", doc -> {
var inst = toInstance(doc);
cache.put(inst.handle(), inst);
});
ScrowAPI.messageBroker().subscribe("event.instance.down", doc ->
cache.remove(doc.getString("handle")));
}
public Collection<ServerInstance> getInstances() {
return cache.values();
}
public @Nullable ServerInstance getInstance(String handle) {
return cache.get(handle);
}
private ServerInstance toInstance(Document doc) {
return new ServerInstance(
doc.getString("handle"),
doc.getString("template"),
doc.getInteger("port")
);
}
}

View file

@ -0,0 +1,27 @@
package de.kentoj.scrow.bukkit.command.instance;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
public final class InstanceCommand {
private RootLiteral<CommandSender> rootLiteral;
public InstanceCommand(InstanceCache instanceCache) {
var style = new ScrowMessageStyle("server-manager", NamedTextColor.RED);
rootLiteral = Literal.<CommandSender>builder("instance", "server-manager")
.withLiteral(new InstanceListLiteral(style).literal())
.withLiteral(new InstanceDeployLiteral(style).literal())
.withLiteral(new InstanceDestroyLiteral(instanceCache, style).literal())
.asRootLiteral(style);
}
public RootLiteral<CommandSender> rootLiteral() {
return rootLiteral;
}
}

View file

@ -0,0 +1,48 @@
package de.kentoj.scrow.bukkit.command.instance;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.command.CommandSender;
import java.util.concurrent.CompletableFuture;
public final class InstanceDeployLiteral {
private final Literal<CommandSender> literal;
private final CommandArgumentSpec<CommandSender, String> templateArg;
private final ScrowMessageStyle style;
InstanceDeployLiteral(ScrowMessageStyle style) {
this.style = style;
templateArg = CommandArgumentSpec.builder("template", new StringArgumentType<CommandSender>())
.build();
literal = Literal.<CommandSender>builder("deploy")
.withArgument(templateArg)
.withExecutor(this::deployInstance)
.build();
}
private CompletableFuture<Result<Object, String>> deployInstance(CommandContext<CommandSender> ctx) {
var template = ctx.getArg(templateArg);
ctx.getSender().sendMessage(style.ok("Deploying instance... this may take a while."));
return Tasks.supplyAsync(() ->
ScrowAPI.instanceManager().deployInstance(template))
.<Result<Object, String>>mapSync(instance -> {
ctx.getSender().sendMessage(style.ok("Deployed instance of " + template + " with handle " + instance.handle() + "."));
// FIXME handle unknown server
return Results.success(new Object());
}).toFuture();
}
public Literal<CommandSender> literal() {
return literal;
}
}

View file

@ -0,0 +1,52 @@
package de.kentoj.scrow.bukkit.command.instance;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import de.kentoj.scrowlib.instancemanager.ServerInstance;
import org.bukkit.command.CommandSender;
import java.util.concurrent.CompletableFuture;
public final class InstanceDestroyLiteral {
private final Literal<CommandSender> literal;
private final CommandArgumentSpec<CommandSender, String> handleArg;
private final ScrowMessageStyle style;
InstanceDestroyLiteral(InstanceCache cache, ScrowMessageStyle style) {
this.style = style;
handleArg = CommandArgumentSpec.builder("handle", new StringArgumentType<CommandSender>())
.withSuggestionProvider(_ ->
cache.getInstances().stream()
.map(ServerInstance::handle)
.toList())
.build();
literal = Literal.<CommandSender>builder("destroy")
.withArgument(handleArg)
.withExecutor(this::destroyInstance)
.build();
}
private CompletableFuture<Result<Object, String>> destroyInstance(CommandContext<CommandSender> ctx) {
var handle = ctx.getArg(handleArg);
return Tasks.supplyAsync(() -> ScrowAPI.instanceManager().destroyInstance(handle))
.<Result<Object, String>>mapSync(found -> {
if (!found)
return Results.failure("No instance with that handle found.");
ctx.getSender().sendMessage(style.ok("Destroyed server with handle " + handle + "."));
return Results.success(new Object());
}).toFuture();
}
public Literal<CommandSender> literal() {
return literal;
}
}

View file

@ -0,0 +1,44 @@
package de.kentoj.scrow.bukkit.command.instance;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import java.util.concurrent.CompletableFuture;
public final class InstanceListLiteral {
private final Literal<CommandSender> literal;
private final ScrowMessageStyle style;
InstanceListLiteral(ScrowMessageStyle style) {
this.style = style;
this.literal = Literal.<CommandSender>builder("list")
.withExecutor(this::displayInstanceList)
.build();
}
private CompletableFuture<Result<Object, String>> displayInstanceList(CommandContext<CommandSender> ctx) {
return Tasks.supplyAsync(() ->
ScrowAPI.instanceManager().instances())
.<Result<Object, String>>mapSync(instances -> {
var msg = Component.text("Running instances:");
for (var inst : instances) {
msg = msg.appendNewline()
.append(Component.text(" - " + inst.handle() + " on port " + inst.port()));
}
ctx.getSender().sendMessage(style.ok(msg));
return Results.success(new Object());
}).toFuture();
}
public Literal<CommandSender> literal() {
return literal;
}
}

View file

@ -0,0 +1,32 @@
package de.kentoj.scrow.bukkit.command.instance;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public final class LobbyCommand {
private final RootLiteral<Player> rootLiteral;
public LobbyCommand() {
rootLiteral = Literal.<Player>builder("lobby", "l", "hub")
.withSyncExecutor(this::sendToLobby)
.asRootLiteral(ScrowMessageStyle.GENERIC);
}
private Result<Object, String> sendToLobby(CommandContext<Player> ctx) {
Bukkit.dispatchCommand(ctx.getSender(), "play lobby");
return Results.success(new Object());
}
public RootLiteral<Player> rootLiteral() {
return rootLiteral;
}
}

View file

@ -0,0 +1,52 @@
package de.kentoj.scrow.bukkit.command.instance;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.scheduler.Tasks;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public final class PlayCommand {
private final RootLiteral<Player> rootLiteral;
private final CommandArgumentSpec<Player, String> gamemodeArg;
public PlayCommand() {
var style = ScrowMessageStyle.GENERIC;
gamemodeArg = CommandArgumentSpec.builder("gamemode", new StringArgumentType<Player>()).build();
rootLiteral = Literal.<Player>builder("play", "queue")
.withArgument(gamemodeArg)
.withExecutor(this::execute)
.asRootLiteral(style);
}
private CompletableFuture<Result<Object, String>> execute(CommandContext<Player> ctx) {
var gamemode = ctx.getArg(gamemodeArg);
return Tasks.supplyAsync(() -> ScrowAPI.instanceManager().instances())
.mapAsync(instances -> {
var instance = instances.stream()
.filter(inst -> inst.template().equals(gamemode))
.findFirst()
.orElse(null);
if (instance == null)
return Results.failure("No instance for " + gamemode + " found.");
return Results.ofCallable(() -> {
ScrowAPI.getPlayer(ctx.getSender()).sendToInstance(instance.handle());
return new Object();
}).mapFailure(Exception::getMessage);
}).toFuture();
}
public RootLiteral<Player> rootLiteral() {
return rootLiteral;
}
}

View file

@ -0,0 +1,60 @@
package de.kentoj.scrow.bukkit.internal.player;
import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bson.Document;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import java.util.function.BiConsumer;
public class NetworkPlayerWatchdog {
private static final MiniMessage MM = MiniMessage.miniMessage();
public void listen() {
handle("player.sendMessage", (player, doc) -> {
var msg = MM.deserialize(doc.getString("message"));
player.sendMessage(msg);
});
// TODO test methods below
handle("player.sendActionBar", (player, doc) -> {
var msg = MM.deserialize(doc.getString("message"));
player.sendActionBar(msg);
});
handle("player.playSound", (player, doc) ->
player.playSound(toSound(doc)));
handle("player.playSound1", (player, doc) ->
player.playSound(toSound(doc), doc.getDouble("x"), doc.getDouble("y"), doc.getDouble("z")));
handle("player.stopSound", (player, doc) ->
player.stopSound(toSound(doc)));
}
private @NotNull Sound toSound(Document doc) {
@SuppressWarnings("PatternValidation")
var name = Key.key("namespace", doc.getString("name"));
var source = Sound.Source.valueOf(doc.getString("source"));
return Sound.sound(
name,
source,
doc.getDouble("volume").floatValue(),
doc.getDouble("pitch").floatValue());
}
private void handle(String subject, BiConsumer<Player, Document> handler) {
ScrowAPI.messageBroker().subscribe(subject, doc -> {
var player = Bukkit.getPlayer(doc.get("playerId", UUID.class));
if (player != null)
handler.accept(player, doc);
});
}
}

View file

@ -0,0 +1,37 @@
package de.kentoj.scrow.bukkit.internal.player.prefix;
import de.kentoj.scrowlib.player.PlayerPrefixProvider;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.util.*;
public class CachedPrefixProvider implements PlayerPrefixProvider {
private final Map<UUID, @Nullable Component> prefixes = new HashMap<>();
private final List<ChangeListener> listeners = new ArrayList<>();
@Override
public @Nullable Component getPrefix(UUID playerId) {
return prefixes.get(playerId);
}
@Override
public Closeable onChange(ChangeListener changeListener) {
listeners.add(changeListener);
return () -> listeners.remove(changeListener);
}
public void setPrefix(UUID playerId, @Nullable Component prefix) {
if (prefix == null) {
prefixes.remove(playerId);
} else {
var oldValue = prefixes.put(playerId, prefix);
var hasChanged = !Objects.equals(oldValue, prefix);
if (hasChanged) {
listeners.forEach(l -> l.onChange(playerId, prefix));
}
}
}
}

View file

@ -0,0 +1,68 @@
package de.kentoj.scrow.bukkit.internal.player.prefix;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class LuckPermsPrefixSetter implements Listener {
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LuckPerms luckPerms;
private final CachedPrefixProvider cache;
public LuckPermsPrefixSetter(LuckPerms luckPerms, CachedPrefixProvider cache) {
this.luckPerms = luckPerms;
this.cache = cache;
}
@SuppressWarnings("resource")
public void init(Plugin plugin) {
luckPerms.getEventBus().subscribe(UserDataRecalculateEvent.class, this::onDataChange);
Bukkit.getPluginManager().registerEvents(this, plugin);
}
private void onDataChange(UserDataRecalculateEvent ev) {
var playerId = ev.getUser().getUniqueId();
if (Bukkit.getPlayer(playerId) == null) return;
var prefix = ev.getData().getMetaData().getPrefix();
updatePrefix(playerId, prefix);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onPlayerJoin(PlayerJoinEvent ev) {
var playerId = ev.getPlayer().getUniqueId();
var user = luckPerms.getUserManager().getUser(playerId);
if (user == null) return;
var prefix = user.getCachedData().getMetaData().getPrefix();
updatePrefix(playerId, prefix);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent ev) {
var playerId = ev.getPlayer().getUniqueId();
cache.setPrefix(playerId, null);
}
private void updatePrefix(UUID playerId, @Nullable String rawPrefix) {
if (rawPrefix == null) {
cache.setPrefix(playerId, null);
return;
}
var prefixComponent = MM.deserialize(rawPrefix);
cache.setPrefix(playerId, prefixComponent);
}
}

View file

@ -0,0 +1,53 @@
package de.kentoj.scrow.bukkit.internal.region;
import de.kentoj.scrow.bukkit.region.ChunkPos;
import de.kentoj.scrow.bukkit.region.Region;
import de.kentoj.scrow.bukkit.region.RegionManager;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class RegionManagerImpl implements RegionManager {
private final Map<Long, List<UUID>> chunkIndex = new HashMap<>();
private final Map<UUID, Region> regions = new HashMap<>();
@Override
public List<Region> getRegions(ChunkPos chunkPos) {
long key = toKey(chunkPos.x(), chunkPos.z());
var uuids = chunkIndex.get(key);
if (uuids == null) return Collections.emptyList();
return uuids.stream().map(regions::get).toList();
}
@Override
public @Nullable Region getRegion(UUID regionUuid) {
return regions.get(regionUuid);
}
@Override
public void registerRegion(Region region) {
for (ChunkPos chunkPos : region.chunks()) {
var key = toKey(chunkPos.x(), chunkPos.z());
chunkIndex.computeIfAbsent(key, __ -> new ArrayList<>())
.add(region.uuid());
}
regions.put(region.uuid(), region);
}
@Override
public void unregisterRegion(Region region) {
for (ChunkPos chunkPos : region.chunks()) {
var key = toKey(chunkPos.x(), chunkPos.z());
var list = chunkIndex.get(key);
if (list == null) return;
list.remove(region.uuid());
}
regions.remove(region.uuid());
}
public long toKey(int x, int z) {
// chatgpt
return (((long) x) << 32) ^ (z & 0xffffffffL);
}
}

View file

@ -0,0 +1,39 @@
package de.kentoj.scrow.bukkit.internal.region;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.region.Region;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class RegionPlayerTracker {
private final Map<UUID, List<UUID>> playersInRegion = new HashMap<>();
public void registerInRegion(Region region, Player player) {
playersInRegion.computeIfAbsent(region.uuid(), __ -> new ArrayList<>())
.add(player.getUniqueId());
}
public void unregisterInRegion(Region region, Player player) {
var uuids = playersInRegion.get(region.uuid());
if (uuids == null) return;
uuids.remove(player.getUniqueId());
}
public Map<Region, Stream<Player>> getRegionsWithPlayers() {
return playersInRegion.entrySet().stream().collect(Collectors.toMap(
ent -> ScrowAPI.regionManager().getRegion(ent.getKey()),
ent -> ent.getValue().stream().map(Bukkit::getPlayer)
));
}
public boolean isInRegion(Region region, Player player) {
var uuids = playersInRegion.get(region.uuid());
if (uuids == null) return false;
return uuids.contains(player.getUniqueId());
}
}

View file

@ -0,0 +1,47 @@
package de.kentoj.scrow.bukkit.internal.region;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.region.ChunkPos;
import de.kentoj.scrow.bukkit.region.Region;
import de.kentoj.scrow.bukkit.region.event.RegionJoinEvent;
import de.kentoj.scrow.bukkit.region.event.RegionLeaveEvent;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
public class RegionTask extends BukkitRunnable {
private final RegionPlayerTracker playerTracker;
public RegionTask(RegionPlayerTracker playerTracker) {
this.playerTracker = playerTracker;
}
@Override
public void run() {
playerTracker.getRegionsWithPlayers().forEach((region, players) ->
players.forEach(player -> {
if (!region.isInRegion(player.getLocation())) {
var ev = new RegionLeaveEvent(region, player);
Bukkit.getPluginManager().callEvent(ev);
if (!ev.isCancelled())
playerTracker.unregisterInRegion(region, player);
}
})
);
for (var player : Bukkit.getOnlinePlayers()) {
var regions = ScrowAPI.regionManager().getRegions(ChunkPos.fromChunk(player.getLocation().getChunk()));
for (Region region : regions) {
var tracked = playerTracker.isInRegion(region, player);
var inside = region.isInRegion(player.getLocation());
if (!tracked && inside) {
var ev = new RegionJoinEvent(region, player);
Bukkit.getPluginManager().callEvent(ev);
if (!ev.isCancelled())
playerTracker.registerInRegion(region, player);
}
}
}
}
}

View file

@ -0,0 +1,25 @@
package de.kentoj.scrow.bukkit.style;
import de.kentoj.scrowlib.player.PlayerPrefixProvider;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public final class ChatStyleListener implements Listener {
private final PlayerPrefixProvider prefixProvider;
public ChatStyleListener(PlayerPrefixProvider prefixProvider) {
this.prefixProvider = prefixProvider;
}
@EventHandler
public void onChat(AsyncChatEvent ev) {
var prefix = prefixProvider.getPrefix(ev.getPlayer().getUniqueId());
Component formattedMessage = StyleHelper.formatMessage(ev.getPlayer().name(), prefix, ev.message());
ev.setCancelled(true);
Bukkit.broadcast(formattedMessage);
}
}

View file

@ -0,0 +1,28 @@
package de.kentoj.scrow.bukkit.style;
import de.kentoj.scrowlib.utils.ComponentUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.Nullable;
public class StyleHelper {
private StyleHelper() {}
public static Component formatName(Component playerName, @Nullable Component prefix) {
Component result = prefix != null
? prefix.append(Component.text("").color(NamedTextColor.DARK_GRAY))
: Component.empty();
return result.append(playerName.color(NamedTextColor.GRAY));
}
public static Component formatMessage(Component playerName, @Nullable Component prefix, Component message) {
Component result = prefix != null
? prefix.append(Component.text("", NamedTextColor.DARK_GRAY))
: Component.empty();
return result.append(playerName.color(NamedTextColor.GRAY).append(Component.text(": ")))
.append(ComponentUtils.resolveURLS(message).colorIfAbsent(NamedTextColor.GRAY));
}
}

View file

@ -0,0 +1,37 @@
package de.kentoj.scrow.bukkit.style;
import de.kentoj.scrowlib.player.PlayerPrefixProvider;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public final class TabStyleListener implements Listener {
private final PlayerPrefixProvider prefixProvider;
public TabStyleListener(PlayerPrefixProvider prefixProvider) {
this.prefixProvider = prefixProvider;
//noinspection resource
prefixProvider.onChange(this::onPrefixChange);
}
private void onPrefixChange(UUID playerID, @Nullable Component prefix) {
var player = Bukkit.getPlayer(playerID);
if (player == null) return;
Component formattedName = StyleHelper.formatName(player.name(), prefix);
player.playerListName(formattedName);
}
@EventHandler(ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent ev) {
var prefix = prefixProvider.getPrefix(ev.getPlayer().getUniqueId());
Component formattedName = StyleHelper.formatName(ev.getPlayer().name(), prefix);
ev.getPlayer().playerListName(formattedName);
}
}

View file

@ -0,0 +1,6 @@
name: "CoreBukkit"
version: "${version}"
depend: [ "LuckPerms" ]
main: "de.kentoj.scrow.bukkit.CoreImplPlugin"
api-version: "1.21"
author: "Kento2"