.
This commit is contained in:
parent
c60f19cafc
commit
2ea2eb5d1e
114 changed files with 671 additions and 609 deletions
43
core-bukkit/BUILD
Normal file
43
core-bukkit/BUILD
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
load("@rules_jvm_external//:defs.bzl", "artifact")
|
||||
load("@rules_java//java:java_library.bzl", "java_library")
|
||||
load("@rules_java//java:java_binary.bzl", "java_binary")
|
||||
|
||||
java_library(
|
||||
name = "api",
|
||||
srcs = glob(["api/main/**/*.java"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//core-lib:core",
|
||||
artifact("io.papermc.paper:paper-api"),
|
||||
artifact("org.projectlombok:lombok"),
|
||||
artifact("de.kentoj.scrow:kencommandapi-bukkit"),
|
||||
],
|
||||
exports = [
|
||||
"//core-lib:core",
|
||||
artifact("de.kentoj.scrow:kencommandapi-bukkit"),
|
||||
artifact("org.mongodb:bson"),
|
||||
],
|
||||
)
|
||||
|
||||
java_binary(
|
||||
name = "plugin_bin",
|
||||
srcs = glob(["plugin/main/**/*.java"]),
|
||||
create_executable = False,
|
||||
resources = glob(["plugin/main/resources/**"]),
|
||||
resource_strip_prefix = "core-bukkit/plugin/main/resources",
|
||||
deps = [
|
||||
":api",
|
||||
artifact("net.luckperms:api"),
|
||||
artifact("io.papermc.paper:paper-api"),
|
||||
artifact("io.nats:jnats"),
|
||||
artifact("net.kyori:adventure-key:5.2.0"),
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "plugin",
|
||||
srcs = [":plugin_bin_deploy.jar"],
|
||||
outs = ["plugin.jar"],
|
||||
cmd = "cp $< $@",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
170
core-bukkit/api/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java
Normal file
170
core-bukkit/api/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package de.kentoj.scrow.bukkit;
|
||||
|
||||
import de.kentoj.kencommandapi.CommandAPI;
|
||||
import de.kentoj.scrow.bukkit.minigame.MinigameManager;
|
||||
import de.kentoj.scrowlib.friends.FriendRequestService;
|
||||
import de.kentoj.scrowlib.friends.FriendshipService;
|
||||
import de.kentoj.scrowlib.economy.EconomyService;
|
||||
import de.kentoj.scrowlib.player.NetworkPlayer;
|
||||
import de.kentoj.scrowlib.player.NetworkPlayerFactory;
|
||||
import de.kentoj.scrow.bukkit.region.RegionManager;
|
||||
import de.kentoj.scrowlib.instancemanager.InstanceManager;
|
||||
import de.kentoj.scrowlib.messaging.MessageBroker;
|
||||
import de.kentoj.scrowlib.player.PlayerPrefixProvider;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ScrowAPI {
|
||||
|
||||
public ScrowAPI() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Might be null in testing environments
|
||||
*/
|
||||
private static @Nullable Jdbi jdbi;
|
||||
@ApiStatus.Internal
|
||||
public static Plugin plugin;
|
||||
private static EconomyService economyService;
|
||||
private static FriendshipService friendshipService;
|
||||
private static FriendRequestService friendRequestService;
|
||||
private static MessageBroker messageBroker;
|
||||
private static InstanceManager instanceManager;
|
||||
private static RegionManager regionManager;
|
||||
private static CommandAPI<Player> playerCommands;
|
||||
private static CommandAPI<CommandSender> commands;
|
||||
private static PlayerPrefixProvider playerPrefixProvider;
|
||||
private static final MinigameManager minigameManager = new MinigameManager();
|
||||
private static NetworkPlayerFactory playerFactory;
|
||||
|
||||
public static NetworkPlayer getPlayer(OfflinePlayer player) {
|
||||
return getPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
public static NetworkPlayer getPlayer(UUID uuid) {
|
||||
return playerFactory.create(uuid);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setEconomyService(EconomyService economyService) {
|
||||
ScrowAPI.economyService = economyService;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setCorePlugin(Plugin plugin) {
|
||||
ScrowAPI.plugin = plugin;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setFriendshipService(FriendshipService friendshipService) {
|
||||
ScrowAPI.friendshipService = friendshipService;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setFriendRequestService(FriendRequestService friendRequestService) {
|
||||
ScrowAPI.friendRequestService = friendRequestService;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setMessageBroker(MessageBroker messageBroker) {
|
||||
ScrowAPI.messageBroker = messageBroker;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setInstanceManager(InstanceManager instanceManager) {
|
||||
ScrowAPI.instanceManager = instanceManager;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setJdbi(@Nullable Jdbi jdbi) {
|
||||
ScrowAPI.jdbi = jdbi;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setRegionManager(RegionManager regionManager) {
|
||||
ScrowAPI.regionManager = regionManager;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setPlayerFactory(NetworkPlayerFactory playerFactory) {
|
||||
ScrowAPI.playerFactory = playerFactory;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setPlayerPrefixProvider(PlayerPrefixProvider playerPrefixProvider) {
|
||||
ScrowAPI.playerPrefixProvider = playerPrefixProvider;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setPlayerCommands(CommandAPI<Player> playerCommands) {
|
||||
ScrowAPI.playerCommands = playerCommands;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setCommands(CommandAPI<CommandSender> commands) {
|
||||
ScrowAPI.commands = commands;
|
||||
}
|
||||
|
||||
public static @Nullable Jdbi jdbi() {
|
||||
return jdbi;
|
||||
}
|
||||
|
||||
public static Plugin plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public static EconomyService economyService() {
|
||||
return economyService;
|
||||
}
|
||||
|
||||
public static FriendshipService friendshipService() {
|
||||
return friendshipService;
|
||||
}
|
||||
|
||||
public static FriendRequestService friendRequestService() {
|
||||
return friendRequestService;
|
||||
}
|
||||
|
||||
public static MessageBroker messageBroker() {
|
||||
return messageBroker;
|
||||
}
|
||||
|
||||
public static InstanceManager instanceManager() {
|
||||
return instanceManager;
|
||||
}
|
||||
|
||||
public static RegionManager regionManager() {
|
||||
return regionManager;
|
||||
}
|
||||
|
||||
public static CommandAPI<Player> playerCommands() {
|
||||
return playerCommands;
|
||||
}
|
||||
|
||||
public static CommandAPI<CommandSender> commands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public static PlayerPrefixProvider playerPrefixProvider() {
|
||||
return playerPrefixProvider;
|
||||
}
|
||||
|
||||
public static NetworkPlayerFactory playerFactory() {
|
||||
return playerFactory;
|
||||
}
|
||||
|
||||
public static MinigameManager minigameManager() {
|
||||
return minigameManager;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.kentoj.scrow.bukkit.gameserver;
|
||||
|
||||
public interface GameServer {
|
||||
|
||||
void destroy();
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package de.kentoj.scrow.bukkit.gameserver;
|
||||
|
||||
public interface GameServerPool {
|
||||
|
||||
GameServer prepareServer(String gameName);
|
||||
|
||||
void returnServer(String name);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package de.kentoj.scrow.bukkit.minigame;
|
||||
|
||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface Minigame {
|
||||
|
||||
ScrowMessageStyle messageStyle();
|
||||
|
||||
/**
|
||||
* Required participant count
|
||||
*/
|
||||
int minParticipants();
|
||||
|
||||
/**
|
||||
* Maximally allowed participant count
|
||||
*/
|
||||
int maxParticipants();
|
||||
|
||||
PlayerManager playerManager();
|
||||
|
||||
CompletableFuture<Void> start();
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package de.kentoj.scrow.bukkit.minigame;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MinigameManager {
|
||||
|
||||
private final Map<UUID, Minigame> playerToMinigameMap = new HashMap<>();
|
||||
|
||||
public @Nullable Minigame getMinigameByPlayer(OfflinePlayer player) {
|
||||
return playerToMinigameMap.get(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void setMinigame(OfflinePlayer player, @Nullable Minigame minigame) {
|
||||
if (minigame == null)
|
||||
playerToMinigameMap.remove(player.getUniqueId());
|
||||
else
|
||||
playerToMinigameMap.put(player.getUniqueId(), minigame);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package de.kentoj.scrow.bukkit.minigame;
|
||||
|
||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class PlayerManager {
|
||||
|
||||
private final Set<UUID> participants = new HashSet<>();
|
||||
private final Set<UUID> spectators = new HashSet<>();
|
||||
|
||||
private final MinigameManager minigameManager = ScrowAPI.minigameManager();
|
||||
private final Minigame minigame;
|
||||
|
||||
public PlayerManager(Minigame minigame) {
|
||||
this.minigame = minigame;
|
||||
}
|
||||
|
||||
public Stream<Player> getParticipants() {
|
||||
return participants.stream().map(Bukkit::getPlayer);
|
||||
}
|
||||
|
||||
public Stream<Player> getSpectators() {
|
||||
return spectators.stream().map(Bukkit::getPlayer);
|
||||
}
|
||||
|
||||
public int getParticipantCount() {
|
||||
return participants.size();
|
||||
}
|
||||
|
||||
public void addParticipant(Player player) {
|
||||
participants.add(player.getUniqueId());
|
||||
minigameManager.setMinigame(player, minigame);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the player was a participant
|
||||
*/
|
||||
public boolean removeParticipant(Player player) {
|
||||
minigameManager.setMinigame(player, null);
|
||||
return participants.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public boolean isParticipant(Player player) {
|
||||
return participants.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void addSpectator(Player player) {
|
||||
spectators.add(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the player was in a spectator
|
||||
*/
|
||||
public boolean removeSpectator(Player player) {
|
||||
return spectators.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public Stream<Player> getAllPlayers() {
|
||||
return Stream.concat(getParticipants(), getSpectators());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.lobby;
|
||||
|
||||
import de.kentoj.scrow.bukkit.minigame.Minigame;
|
||||
import de.kentoj.scrow.bukkit.minigame.phaseflow.LinearPhaseFlow;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.CompositePhase;
|
||||
import de.kentoj.scrow.bukkit.minigame.phaseflow.PhaseContext;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockExplodeEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
|
||||
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
|
||||
public class LobbyPhase<T extends Minigame> extends CompositePhase<T> {
|
||||
|
||||
private final LinearPhaseFlow subPhaseFlow = new LinearPhaseFlow("lobby");
|
||||
|
||||
public LobbyPhase(PhaseContext<T> ctx) {
|
||||
super(ctx);
|
||||
PhaseContext<T> subCtx = new PhaseContext<>(ctx.game(), subPhaseFlow);
|
||||
subPhaseFlow.add(new WaitForMinimumPlayersPhase<>(subCtx));
|
||||
subPhaseFlow.add(new WaitForMaximumPlayersPhase<>(subCtx));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
ctx.listeners().on(PlayerJoinEvent.class, EventPriority.LOWEST, this::onJoin);
|
||||
ctx.listeners().on(PlayerQuitEvent.class, EventPriority.LOWEST, this::onQuit);
|
||||
|
||||
//noinspection unchecked
|
||||
ctx.listeners().cancel(
|
||||
HangingBreakByEntityEvent.class,
|
||||
PlayerInteractAtEntityEvent.class,
|
||||
PlayerInteractEntityEvent.class,
|
||||
PlayerDropItemEvent.class,
|
||||
BlockExplodeEvent.class,
|
||||
EntityExplodeEvent.class,
|
||||
EntityDamageEvent.class,
|
||||
FoodLevelChangeEvent.class,
|
||||
BlockBreakEvent.class,
|
||||
BlockPlaceEvent.class,
|
||||
EntityTargetLivingEntityEvent.class
|
||||
);
|
||||
}
|
||||
|
||||
private void onJoin(PlayerJoinEvent ev) {
|
||||
ctx.players().addParticipant(ev.getPlayer());
|
||||
ctx.players().getAllPlayers().forEach(p ->
|
||||
p.sendMessage("§8[§a+§8] §7" + ev.getPlayer().getName()));
|
||||
}
|
||||
|
||||
private void onQuit(PlayerQuitEvent ev) {
|
||||
if (ctx.players().removeParticipant(ev.getPlayer()))
|
||||
ctx.players().getAllPlayers().forEach(p ->
|
||||
p.sendMessage("§8[§c-§8] §7" + ev.getPlayer().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinearPhaseFlow subPhaseFlow() {
|
||||
return subPhaseFlow;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.lobby;
|
||||
|
||||
import de.kentoj.scrow.bukkit.minigame.Minigame;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.Phase;
|
||||
import de.kentoj.scrow.bukkit.minigame.phaseflow.PhaseContext;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
class WaitForMaximumPlayersPhase<T extends Minigame> extends Phase<T> {
|
||||
|
||||
private int secsLeft;
|
||||
|
||||
public WaitForMaximumPlayersPhase(PhaseContext<T> ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
this.secsLeft = 5;
|
||||
ctx.listeners().on(PlayerQuitEvent.class, this::onQuit);
|
||||
}
|
||||
|
||||
private void onQuit(PlayerQuitEvent ev) {
|
||||
var isEnough = ctx.players().getParticipantCount() >= ctx.game().minParticipants();
|
||||
if (!isEnough) {
|
||||
//deployedServer
|
||||
ctx.phases().rewindPhase();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTick(int tick) {
|
||||
if (tick % 20 == 0) {
|
||||
ctx.players().getAllPlayers().forEach(player ->
|
||||
player.sendTitle("Game starting in " + secsLeft + " seconds", "have fun", 5, 20, 10)
|
||||
);
|
||||
secsLeft--;
|
||||
if (secsLeft == 0) ctx.advancePhase();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel() {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.lobby;
|
||||
|
||||
import de.kentoj.scrow.bukkit.minigame.Minigame;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.Phase;
|
||||
import de.kentoj.scrow.bukkit.minigame.phaseflow.PhaseContext;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
class WaitForMinimumPlayersPhase<T extends Minigame> extends Phase<T> {
|
||||
|
||||
public WaitForMinimumPlayersPhase(PhaseContext<T> ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
ctx.listeners().on(PlayerJoinEvent.class, this::onJoin);
|
||||
}
|
||||
|
||||
private void onJoin(PlayerJoinEvent ev) {
|
||||
boolean isEnoughParticipants = ctx.players().getParticipantCount() >= ctx.game().minParticipants();
|
||||
if (isEnoughParticipants) ctx.advancePhase();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel() {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase;
|
||||
|
||||
import de.kentoj.scrow.bukkit.minigame.Minigame;
|
||||
import de.kentoj.scrow.bukkit.minigame.phaseflow.PhaseContext;
|
||||
|
||||
public abstract class CompositePhase<T extends Minigame> extends Phase<T> {
|
||||
|
||||
public CompositePhase(PhaseContext<T> ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public abstract PhaseFlow subPhaseFlow();
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
subPhaseFlow().start().thenRun(super::advancePhase);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel() {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase;
|
||||
|
||||
public interface IPhase {
|
||||
|
||||
void start();
|
||||
|
||||
void end();
|
||||
|
||||
void cancel();
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase;
|
||||
|
||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
||||
import de.kentoj.scrow.bukkit.minigame.Minigame;
|
||||
import de.kentoj.scrow.bukkit.minigame.phaseflow.PhaseContext;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public abstract class Phase<T extends Minigame> implements IPhase {
|
||||
|
||||
protected final PhaseContext<T> ctx;
|
||||
|
||||
private boolean isActive = false;
|
||||
|
||||
protected Phase(PhaseContext<T> ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shall call {@link PhaseFlow#advancePhase()}
|
||||
*/
|
||||
protected abstract void onStart();
|
||||
|
||||
/**
|
||||
* Called together with {@link Phase#onEnd()} but only called when the phase was cancelled
|
||||
* due to an error
|
||||
*/
|
||||
protected abstract void onCancel();
|
||||
|
||||
protected void onEnd() {
|
||||
}
|
||||
|
||||
protected void onTick(int tick) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
end();
|
||||
onCancel();
|
||||
}
|
||||
|
||||
public final void start() {
|
||||
if (isActive) return;
|
||||
isActive = true;
|
||||
onStart();
|
||||
runTicker();
|
||||
}
|
||||
|
||||
public final void end() {
|
||||
if (!isActive) return;
|
||||
ctx.listeners().unregisterAll();
|
||||
onEnd();
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* end the current phase and advance to the next.
|
||||
* same as {@link PhaseFlow#advancePhase()}.
|
||||
*/
|
||||
public final void advancePhase() {
|
||||
ctx.phases().advancePhase();
|
||||
}
|
||||
|
||||
private void runTicker() {
|
||||
new BukkitRunnable() {
|
||||
int tick = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isActive) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
onTick(tick++);
|
||||
}
|
||||
}.runTaskTimer(ScrowAPI.plugin, 1, 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface PhaseFlow {
|
||||
|
||||
String name();
|
||||
|
||||
/**
|
||||
* end the current phase, go to the next phase and start it
|
||||
*/
|
||||
void advancePhase();
|
||||
|
||||
/**
|
||||
* end the current phase, go to the previous phase and start it
|
||||
*/
|
||||
void rewindPhase();
|
||||
|
||||
void handlePhaseError();
|
||||
|
||||
/**
|
||||
* CompletableFuture is completed when the game finishes
|
||||
*/
|
||||
CompletableFuture<Void> start();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase.event;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EventHandler<E> {
|
||||
|
||||
void handle(E ev);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventPriority;
|
||||
|
||||
public interface PhaseListeners {
|
||||
|
||||
<E extends Event> void on(Class<E> eventClass, EventPriority priority, EventHandler<E> handler);
|
||||
|
||||
default <E extends Event> void on(Class<E> eventClass, EventHandler<E> handler) {
|
||||
this.on(eventClass, EventPriority.NORMAL, handler);
|
||||
}
|
||||
|
||||
default void cancel(Class<? extends Event>... classes) {
|
||||
for (var clazz : classes) {
|
||||
Preconditions.checkArgument(Cancellable.class.isAssignableFrom(clazz),
|
||||
clazz.getCanonicalName() + " does not implement Cancellable");
|
||||
on(clazz, ev -> ((Cancellable) ev).setCancelled(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all listeners bound to this phase.
|
||||
* Note: this is automatically called on phase end.
|
||||
*/
|
||||
void unregisterAll();
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phase.event;
|
||||
|
||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class PhaseListenersImpl implements PhaseListeners, Listener {
|
||||
|
||||
@Override
|
||||
public final <E extends Event> void on(Class<E> clazz, EventPriority priority, EventHandler<E> handler) {
|
||||
Bukkit.getPluginManager().registerEvent(clazz, this, priority, (__, ev) -> {
|
||||
//noinspection unchecked
|
||||
handler.handle((E) ev);
|
||||
}, ScrowAPI.plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAll() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phaseflow;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.IPhase;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.PhaseFlow;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class LinearPhaseFlow implements PhaseFlow {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(LinearPhaseFlow.class);
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<IPhase> phases = new ArrayList<>();
|
||||
private CompletableFuture<Void> onEnd = null;
|
||||
private int curInd = -1;
|
||||
|
||||
public LinearPhaseFlow(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void add(IPhase phase) {
|
||||
this.phases.add(phase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advancePhase() {
|
||||
IPhase cur;
|
||||
cur = current();
|
||||
if (cur != null) {
|
||||
cur.cancel();
|
||||
}
|
||||
|
||||
curInd++;
|
||||
cur = current();
|
||||
if (cur == null) {
|
||||
onEnd.complete(null);
|
||||
log.info("phase flow {} ended", name());
|
||||
return;
|
||||
}
|
||||
startPhase(cur);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rewindPhase() {
|
||||
var cur = current();
|
||||
Preconditions.checkNotNull(cur, "trying to rewind phase but no phase is active");
|
||||
cur.cancel();
|
||||
|
||||
curInd--;
|
||||
cur = current();
|
||||
if (cur != null)
|
||||
startPhase(cur);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePhaseError() {
|
||||
var cur = current();
|
||||
Preconditions.checkNotNull(cur, "trying to rewind phase but no phase is active");
|
||||
cur.cancel();
|
||||
|
||||
if (curInd == 0) {
|
||||
startPhase(cur, false);
|
||||
return;
|
||||
}
|
||||
|
||||
curInd--;
|
||||
cur = current();
|
||||
startPhase(cur);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> start() {
|
||||
Preconditions.checkState(onEnd == null, "attempting to start game that is already active");
|
||||
onEnd = new CompletableFuture<>();
|
||||
onEnd.thenRun(() -> onEnd = null);
|
||||
log.info("starting phase flow {}", name());
|
||||
advancePhase();
|
||||
return onEnd;
|
||||
}
|
||||
|
||||
private void startPhase(IPhase phase) {
|
||||
this.startPhase(phase, true);
|
||||
}
|
||||
|
||||
private void startPhase(IPhase phase, boolean recoverOnError) {
|
||||
if (!recoverOnError) {
|
||||
phase.start();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("starting phase {}", phase.getClass().getCanonicalName());
|
||||
phase.start();
|
||||
} catch (Exception e) {
|
||||
log.error("failed starting phase {}: rewinding phase", phase.getClass().getCanonicalName(), e);
|
||||
handlePhaseError();
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable IPhase current() {
|
||||
if (curInd < 0 || curInd >= phases.size()) return null;
|
||||
return phases.get(curInd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package de.kentoj.scrow.bukkit.minigame.phaseflow;
|
||||
|
||||
import de.kentoj.kencommandapi.api.platform.MessageStyle;
|
||||
import de.kentoj.scrow.bukkit.minigame.Minigame;
|
||||
import de.kentoj.scrow.bukkit.minigame.PlayerManager;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.PhaseFlow;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.event.PhaseListeners;
|
||||
import de.kentoj.scrow.bukkit.minigame.phase.event.PhaseListenersImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public class PhaseContext<T extends Minigame> {
|
||||
private final T game;
|
||||
private final PhaseFlow phaseFlow;
|
||||
private final PhaseListeners listeners = new PhaseListenersImpl();
|
||||
|
||||
public PhaseContext(T game, PhaseFlow phaseFlow) {
|
||||
this.game = game;
|
||||
this.phaseFlow = phaseFlow;
|
||||
}
|
||||
|
||||
public T game() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public PhaseFlow phases() {
|
||||
return phaseFlow;
|
||||
}
|
||||
|
||||
public PlayerManager players() {
|
||||
return game.playerManager();
|
||||
}
|
||||
|
||||
public MessageStyle style() {
|
||||
return game.messageStyle();
|
||||
}
|
||||
|
||||
public PhaseListeners listeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void advancePhase() {
|
||||
phaseFlow.advancePhase();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractRegion implements Region {
|
||||
|
||||
private final UUID uuid = UUID.randomUUID();
|
||||
|
||||
public UUID uuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import org.bukkit.Chunk;
|
||||
|
||||
public record ChunkPos(int x, int z) {
|
||||
public static ChunkPos fromChunk(Chunk chunk) {
|
||||
return new ChunkPos(chunk.getX(), chunk.getZ());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* region consisting of multiple regions
|
||||
*/
|
||||
public class CompositeRegion extends AbstractRegion {
|
||||
|
||||
private final Region[] regions;
|
||||
|
||||
public CompositeRegion(Region[] regions) {
|
||||
this.regions = regions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInRegion(Location location) {
|
||||
for (Region region : regions) {
|
||||
if (region.isInRegion(location)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPos> chunks() {
|
||||
return Arrays.stream(regions)
|
||||
.map(Region::chunks)
|
||||
.reduce(new ArrayList<>(), (list, chunks) -> {
|
||||
list.addAll(chunks);
|
||||
return list;
|
||||
})
|
||||
.stream()
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CuboidRegion extends AbstractRegion {
|
||||
|
||||
private final Range xRange;
|
||||
private final Range yRange;
|
||||
private final Range zRange;
|
||||
|
||||
public CuboidRegion(Location loc1, Location loc2) {
|
||||
xRange = Range.of(loc1.getX(), loc2.getX());
|
||||
yRange = Range.of(loc1.getY(), loc2.getY());
|
||||
zRange = Range.of(loc1.getY(), loc2.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInRegion(Location location) {
|
||||
return xRange.contains(location.getX())
|
||||
&& yRange.contains(location.getY())
|
||||
&& zRange.contains(location.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPos> chunks() {
|
||||
List<ChunkPos> res = new ArrayList<>();
|
||||
|
||||
int minChunkX = (int) Math.floor(xRange.min()) << 4;
|
||||
int maxChunkX = (int) Math.ceil(xRange.max()) << 4;
|
||||
for (int chunkX = minChunkX; chunkX < maxChunkX; chunkX++) {
|
||||
int minChunkZ = (int) Math.floor(zRange.min()) << 4;
|
||||
int maxChunkZ = (int) Math.ceil(zRange.max()) << 4;
|
||||
for (int chunkZ = minChunkZ; chunkZ < maxChunkZ; chunkZ++) {
|
||||
res.add(new ChunkPos(chunkX, chunkZ));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CylinderRegion extends AbstractRegion {
|
||||
|
||||
private final Location center;
|
||||
private final double height;
|
||||
private final double radius;
|
||||
|
||||
public CylinderRegion(Location center, double height, double radius) {
|
||||
this.center = center;
|
||||
this.height = height;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInRegion(Location location) {
|
||||
if (location.getY() < center.getY()) return false;
|
||||
if (location.getY() > center.getY() + height) return false;
|
||||
double diffX = location.getX() - center.getX();
|
||||
double diffZ = location.getZ() - center.getZ();
|
||||
return diffX * diffX + diffZ * diffZ <= radius * radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPos> chunks() {
|
||||
List<ChunkPos> res = new ArrayList<>();
|
||||
int a = (int) Math.ceil(radius / 16);
|
||||
|
||||
var centerChunkX = center.getBlockX() >> 4;
|
||||
var centerChunkZ = center.getBlockZ() >> 4;
|
||||
|
||||
for (int x = -a; x <= a; x++) {
|
||||
for (int z = -a; z <= a; z++) {
|
||||
res.add(new ChunkPos(centerChunkX + x, centerChunkZ + z));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
public record Range(
|
||||
double min,
|
||||
double max
|
||||
) {
|
||||
public boolean contains(double num) {
|
||||
return min <= num && num <= max;
|
||||
}
|
||||
|
||||
public double getLength() {
|
||||
return max - min;
|
||||
}
|
||||
|
||||
public static Range of(double a, double b) {
|
||||
return new Range(Math.min(a, b), Math.max(a, b));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Region {
|
||||
|
||||
/**
|
||||
* A random uuid assigned to the Region at creation
|
||||
*/
|
||||
UUID uuid();
|
||||
|
||||
/**
|
||||
* only called if location is in {@link Region#chunks()}
|
||||
*/
|
||||
boolean isInRegion(Location location);
|
||||
|
||||
/**
|
||||
* Used for optimization: isInRegion is only called if the location is in one of these chunks
|
||||
* @return Chunks that the region touches
|
||||
*/
|
||||
List<ChunkPos> chunks();
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package de.kentoj.scrow.bukkit.region;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RegionManager {
|
||||
List<Region> getRegions(ChunkPos chunkPos);
|
||||
|
||||
@Nullable Region getRegion(UUID regionUuid);
|
||||
|
||||
void registerRegion(Region region);
|
||||
|
||||
void unregisterRegion(Region region);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package de.kentoj.scrow.bukkit.region.event;
|
||||
|
||||
import de.kentoj.scrow.bukkit.region.Region;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
|
||||
public abstract class RegionEvent extends PlayerEvent {
|
||||
private final Region region;
|
||||
|
||||
protected RegionEvent(Region region, Player player) {
|
||||
super(player);
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
public Region getRegion() {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package de.kentoj.scrow.bukkit.region.event;
|
||||
|
||||
import de.kentoj.scrow.bukkit.region.Region;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class RegionJoinEvent extends RegionEvent implements Cancellable {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private boolean isCancelled;
|
||||
|
||||
public RegionJoinEvent(Region region, Player player) {
|
||||
super(region, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.isCancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean isCancelled) {
|
||||
this.isCancelled = isCancelled;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package de.kentoj.scrow.bukkit.region.event;
|
||||
|
||||
import de.kentoj.scrow.bukkit.region.Region;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class RegionLeaveEvent extends RegionEvent implements Cancellable {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private boolean isCancelled;
|
||||
|
||||
public RegionLeaveEvent(Region region, Player player) {
|
||||
super(region, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.isCancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean isCancelled) {
|
||||
this.isCancelled = isCancelled;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package de.kentoj.scrow.bukkit.scheduler;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface Task<T> {
|
||||
|
||||
CompletableFuture<T> toFuture();
|
||||
|
||||
default Task<?> runSync(Runnable task) {
|
||||
return mapSync(__ -> {
|
||||
task.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
default <S> Task<S> supplySync(Supplier<S> supplier) {
|
||||
return mapSync(_ -> supplier.get());
|
||||
}
|
||||
|
||||
default Task<?> thenSync(Consumer<T> consumer) {
|
||||
return mapSync(previous -> {
|
||||
consumer.accept(previous);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
<S> Task<S> mapSync(Function<T, S> mapper);
|
||||
|
||||
default Task<?> runAsync(Runnable task) {
|
||||
return mapAsync(_ -> {
|
||||
task.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
default <S> Task<S> supplyAsync(Supplier<S> supplier) {
|
||||
return mapAsync(_ -> supplier.get());
|
||||
}
|
||||
|
||||
default Task<?> thenAsync(Consumer<T> consumer) {
|
||||
return mapAsync(previous -> {
|
||||
consumer.accept(previous);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
<S> Task<S> mapAsync(Function<T, S> mapper);
|
||||
|
||||
Task<T> yieldIf(Predicate<T> precondition);
|
||||
|
||||
default T await() {
|
||||
try {
|
||||
return toFuture().get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package de.kentoj.scrow.bukkit.scheduler;
|
||||
|
||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A Task runs entirely async, when sync is used, a subtask is run on the main thread and the async thread
|
||||
* blocks until the subtask returns.
|
||||
*
|
||||
* @param <T> type of computed result
|
||||
*/
|
||||
public class TaskImpl<T> implements Task<T> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TaskImpl.class);
|
||||
private static final ExecutorService EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
|
||||
|
||||
private final Supplier<TaskState<T>> compute;
|
||||
private volatile TaskState<T> cachedResult = null;
|
||||
|
||||
TaskImpl(Supplier<TaskState<T>> compute) {
|
||||
this.compute = compute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> toFuture() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return state().value();
|
||||
} catch (Exception ex) {
|
||||
log.error("failed completing future", ex);
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the last result on the main thread
|
||||
*/
|
||||
public <S> Task<S> mapSync(Function<T, S> mapper) {
|
||||
return new TaskImpl<>(() -> {
|
||||
var s = state();
|
||||
if (s.yielded()) return yielded(s);
|
||||
|
||||
try {
|
||||
return TaskState.completed(Bukkit.getScheduler()
|
||||
.callSyncMethod(ScrowAPI.plugin, () -> mapper.apply(s.value()))
|
||||
.get());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the last result async
|
||||
*/
|
||||
public <S> Task<S> mapAsync(Function<T, S> mapper) {
|
||||
return new TaskImpl<>(() -> {
|
||||
var s = state();
|
||||
if (s.yielded()) return yielded(s);
|
||||
return TaskState.completed(mapper.apply(state().value()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<T> yieldIf(Predicate<T> precondition) {
|
||||
var s = state();
|
||||
if (!s.yielded() && precondition.test(s.value())) {
|
||||
return new TaskImpl<>(() -> TaskState.yielded(s.value()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> TaskState<S> yielded(TaskState<T> state) {
|
||||
try {
|
||||
return (TaskState<S>) state;
|
||||
} catch (ClassCastException ex) {
|
||||
log.error("cannot yield when type of next task differs", ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private TaskState<T> state() {
|
||||
synchronized (this) {
|
||||
if (cachedResult == null)
|
||||
cachedResult = compute.get();
|
||||
return cachedResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.kentoj.scrow.bukkit.scheduler;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record TaskState<T>(@Nullable T value, boolean yielded) {
|
||||
public static <T> TaskState<T> yielded(@Nullable T value) {
|
||||
return new TaskState<>(value, true);
|
||||
}
|
||||
|
||||
public static <T> TaskState<T> completed(@Nullable T value) {
|
||||
return new TaskState<>(value, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package de.kentoj.scrow.bukkit.scheduler;
|
||||
|
||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Tasks {
|
||||
|
||||
private Tasks() {
|
||||
}
|
||||
|
||||
public static <T> Task<T> supplyAsync(Supplier<T> supplier) {
|
||||
return new TaskImpl<>(() -> TaskState.completed(supplier.get()));
|
||||
}
|
||||
|
||||
public static Task<?> runAsync(Runnable task) {
|
||||
return supplyAsync(() -> {
|
||||
task.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> Task<T> supplySync(Supplier<T> supplier) {
|
||||
return new TaskImpl<>(() -> {
|
||||
try {
|
||||
return TaskState.completed(Bukkit.getScheduler()
|
||||
.callSyncMethod(ScrowAPI.plugin, supplier::get)
|
||||
.get());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<?> runSync(Runnable task) {
|
||||
return supplySync(() -> {
|
||||
task.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static void awaitAll(Task<?>... tasks) {
|
||||
var cfs = Arrays.stream(tasks)
|
||||
.map(Task::toFuture)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(cfs).join();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
6
core-bukkit/plugin/main/resources/plugin.yml
Normal file
6
core-bukkit/plugin/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
name: "CoreBukkit"
|
||||
version: "${version}"
|
||||
depend: [ "LuckPerms" ]
|
||||
main: "de.kentoj.scrow.bukkit.CoreImplPlugin"
|
||||
api-version: "1.21"
|
||||
author: "Kento2"
|
||||
Loading…
Add table
Add a link
Reference in a new issue