diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index bafc3aa60..1160f02c1 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -17,9 +17,9 @@ jobs:
cache: 'gradle'
distribution: 'adopt'
- name: Set up server
- run: ./gradlew -PskipBuild=zombies-mapeditor :phantazm-server:setupServer
+ run: ./gradlew -PskipBuild=zombies-mapeditor,zombies-timer :phantazm-server:setupServer
- name: Build & copy jar
- run: ./gradlew -PskipBuild=zombies-mapeditor :phantazm-server:copyJar
+ run: ./gradlew -PskipBuild=zombies-mapeditor,zombies-timer :phantazm-server:copyJar
- name: Zip server files
run: |
cd ./run/server-1
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9eb1cce62..e98bdf0fa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,22 +1,24 @@
name: Test PhantazmServer
on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
jobs:
- build:
- runs-on: ubuntu-latest
+ build:
+ runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Set up JDK 17
- uses: actions/setup-java@v3
- with:
- java-version: 17
- cache: 'gradle'
- distribution: 'adopt'
- - name: Run tests
- run: ./gradlew test
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: 'recursive'
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: 17
+ cache: 'gradle'
+ distribution: 'adopt'
+ - name: Run tests
+ run: ./gradlew test
diff --git a/.run/Run Velocity.run.xml b/.run/Run Velocity.run.xml
index cbb04a6e3..f3c46f1ae 100644
--- a/.run/Run Velocity.run.xml
+++ b/.run/Run Velocity.run.xml
@@ -1,15 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/Run server.run.xml b/.run/Run server.run.xml
index e6b3ba9c2..694ab74e5 100644
--- a/.run/Run server.run.xml
+++ b/.run/Run server.run.xml
@@ -1,12 +1,12 @@
-
+
-
+
\ No newline at end of file
diff --git a/commons/src/main/java/org/phantazm/commons/MathUtils.java b/commons/src/main/java/org/phantazm/commons/MathUtils.java
index 5a0881b88..f05727765 100644
--- a/commons/src/main/java/org/phantazm/commons/MathUtils.java
+++ b/commons/src/main/java/org/phantazm/commons/MathUtils.java
@@ -12,6 +12,10 @@ private MathUtils() {
throw new UnsupportedOperationException();
}
+ public static boolean fuzzyEquals(double a, double b, double epsilon) {
+ return Math.abs(a - b) < epsilon;
+ }
+
public static long randomInterval(long min, long max) {
return (long)(Math.random() * (min - max)) + min;
}
diff --git a/commons/src/main/java/org/phantazm/commons/TickableTask.java b/commons/src/main/java/org/phantazm/commons/TickableTask.java
index 419265ab4..ab58a2afa 100644
--- a/commons/src/main/java/org/phantazm/commons/TickableTask.java
+++ b/commons/src/main/java/org/phantazm/commons/TickableTask.java
@@ -1,9 +1,38 @@
package org.phantazm.commons;
+import org.jetbrains.annotations.NotNull;
+
public interface TickableTask extends Tickable {
boolean isFinished();
default void end() {
}
+
+ static @NotNull TickableTask afterTicks(long ticks, @NotNull Runnable action, @NotNull Runnable endAction) {
+ long start = System.currentTimeMillis();
+ return new TickableTask() {
+ private boolean finished;
+
+ @Override
+ public boolean isFinished() {
+ return finished;
+ }
+
+ @Override
+ public void tick(long time) {
+ if ((time - start) / 50 >= ticks) {
+ finished = true;
+ action.run();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (!finished) {
+ endAction.run();
+ }
+ }
+ };
+ }
}
diff --git a/commons/src/main/java/org/phantazm/commons/chat/ChatDestination.java b/commons/src/main/java/org/phantazm/commons/chat/ChatDestination.java
new file mode 100644
index 000000000..f291a223e
--- /dev/null
+++ b/commons/src/main/java/org/phantazm/commons/chat/ChatDestination.java
@@ -0,0 +1,8 @@
+package org.phantazm.commons.chat;
+
+public enum ChatDestination {
+ TITLE,
+ SUBTITLE,
+ CHAT,
+ ACTION_BAR
+}
diff --git a/commons/src/main/java/org/phantazm/commons/chat/MessageWithDestination.java b/commons/src/main/java/org/phantazm/commons/chat/MessageWithDestination.java
new file mode 100644
index 000000000..bb45a2083
--- /dev/null
+++ b/commons/src/main/java/org/phantazm/commons/chat/MessageWithDestination.java
@@ -0,0 +1,8 @@
+package org.phantazm.commons.chat;
+
+import net.kyori.adventure.text.Component;
+import org.jetbrains.annotations.NotNull;
+
+public record MessageWithDestination(@NotNull Component component, @NotNull ChatDestination destination) {
+
+}
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index d4506a242..03247f58b 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -4,6 +4,7 @@ plugins {
dependencies {
api(projects.phantazmCommons)
+ api(projects.phantazmMessaging)
api(libs.ethylene.core)
api(libs.ethylene.mapper)
api(libs.commons.lang3)
diff --git a/core/src/main/java/org/phantazm/core/BasicClientBlockHandlerSource.java b/core/src/main/java/org/phantazm/core/BasicClientBlockHandlerSource.java
index 37f048efb..7564772ad 100644
--- a/core/src/main/java/org/phantazm/core/BasicClientBlockHandlerSource.java
+++ b/core/src/main/java/org/phantazm/core/BasicClientBlockHandlerSource.java
@@ -1,35 +1,32 @@
package org.phantazm.core;
import net.minestom.server.event.Event;
+import net.minestom.server.event.EventFilter;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.instance.InstanceUnregisterEvent;
+import net.minestom.server.event.trait.InstanceEvent;
import net.minestom.server.instance.Instance;
+import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
/**
* Standard implementation of {@link ClientBlockHandlerSource}.
*/
public class BasicClientBlockHandlerSource implements ClientBlockHandlerSource {
- private final Function super Instance, ? extends ClientBlockHandler> blockHandlerFunction;
- private final Map map;
-
- /**
- * Creates a new instance of this class given the provided {@link ClientBlockHandler}-producing function. This
- * function must never return null.
- *
- * @param handlerFunction the handler function, which should never return null
- */
- public BasicClientBlockHandlerSource(
- @NotNull Function super Instance, ? extends ClientBlockHandler> handlerFunction,
- @NotNull EventNode globalNode) {
- this.blockHandlerFunction = Objects.requireNonNull(handlerFunction, "handlerFunction");
+ private final EventNode rootNode;
+ private final Map map;
+
+ private record Node(@NotNull ClientBlockHandler handler, @NotNull EventNode node) {
+ }
+
+ public BasicClientBlockHandlerSource(@NotNull EventNode rootNode) {
+ this.rootNode = rootNode;
this.map = new ConcurrentHashMap<>();
- globalNode.addListener(InstanceUnregisterEvent.class, this::onInstanceUnregister);
+ rootNode.addListener(InstanceUnregisterEvent.class, this::onInstanceUnregister);
}
@Override
@@ -39,11 +36,24 @@ public BasicClientBlockHandlerSource(
throw new IllegalArgumentException("Cannot hold an unregistered instance");
}
- return Objects.requireNonNull(this.blockHandlerFunction.apply(instance), "handler");
- });
+ EventNode instanceNode =
+ EventNode.type("client_block_handler_" + instance.getUniqueId(), EventFilter.INSTANCE,
+ (e, v) -> v == instance);
+ DimensionType type = instance.getDimensionType();
+ Node node =
+ new Node(new InstanceClientBlockHandler(instance, type.getMinY(), type.getHeight(), instanceNode),
+ instanceNode);
+
+ rootNode.addChild(instanceNode);
+
+ return node;
+ }).handler;
}
private void onInstanceUnregister(InstanceUnregisterEvent event) {
- map.remove(event.getInstance().getUniqueId());
+ Node node = map.remove(event.getInstance().getUniqueId());
+ if (node != null) {
+ rootNode.removeChild(node.node);
+ }
}
}
diff --git a/core/src/main/java/org/phantazm/core/ComponentUtils.java b/core/src/main/java/org/phantazm/core/ComponentUtils.java
deleted file mode 100644
index 18a136183..000000000
--- a/core/src/main/java/org/phantazm/core/ComponentUtils.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.phantazm.core;
-
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.minimessage.MiniMessage;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.IllegalFormatException;
-import java.util.Objects;
-
-public final class ComponentUtils {
- private ComponentUtils() {
- }
-
- public static @NotNull Component tryFormat(@NotNull String formatString, Object... objects) {
- Objects.requireNonNull(formatString, "formatString");
-
- String message;
- try {
- message = String.format(formatString, objects);
- }
- catch (IllegalFormatException ignored) {
- message = formatString;
- }
-
- return MiniMessage.miniMessage().deserialize(message);
- }
-}
diff --git a/core/src/main/java/org/phantazm/core/InstanceClientBlockHandler.java b/core/src/main/java/org/phantazm/core/InstanceClientBlockHandler.java
index 28d6a73eb..3a0a7f6df 100644
--- a/core/src/main/java/org/phantazm/core/InstanceClientBlockHandler.java
+++ b/core/src/main/java/org/phantazm/core/InstanceClientBlockHandler.java
@@ -44,18 +44,18 @@ public class InstanceClientBlockHandler implements ClientBlockHandler {
* @param instance the instance this handler is bound to
* @param chunkFloor the minimum y-coordinate of chunks in this instance
*/
- public InstanceClientBlockHandler(@NotNull Instance instance, int chunkFloor, int chunkHeight) {
+ public InstanceClientBlockHandler(@NotNull Instance instance, int chunkFloor, int chunkHeight,
+ @NotNull EventNode instanceNode) {
this.instance = Objects.requireNonNull(instance, "instance");
this.clientData = new Long2ObjectOpenHashMap<>();
this.chunkFloor = chunkFloor;
this.chunkHeight = chunkHeight;
- EventNode node = instance.eventNode();
- node.addListener(PreBlockChangeEvent.class, this::onPreBlockChange);
- node.addListener(PlayerBlockBreakEvent.class, this::onPlayerBlockBreak);
- node.addListener(PrePlayerStartDiggingEvent.class, this::onPrePlayerStartDigging);
- node.addListener(PreSendChunkEvent.class, this::onPreSendChunk);
- node.addListener(InstanceChunkUnloadEvent.class, this::onChunkUnload);
+ instanceNode.addListener(PreBlockChangeEvent.class, this::onPreBlockChange);
+ instanceNode.addListener(PlayerBlockBreakEvent.class, this::onPlayerBlockBreak);
+ instanceNode.addListener(PrePlayerStartDiggingEvent.class, this::onPrePlayerStartDigging);
+ instanceNode.addListener(PreSendChunkEvent.class, this::onPreSendChunk);
+ instanceNode.addListener(InstanceChunkUnloadEvent.class, this::onChunkUnload);
}
@Override
diff --git a/core/src/main/java/org/phantazm/core/ItemStackUtils.java b/core/src/main/java/org/phantazm/core/ItemStackUtils.java
new file mode 100644
index 000000000..5b1daae24
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/ItemStackUtils.java
@@ -0,0 +1,44 @@
+package org.phantazm.core;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jglrxavpok.hephaistos.nbt.NBTCompound;
+import org.jglrxavpok.hephaistos.nbt.NBTException;
+import org.jglrxavpok.hephaistos.parser.SNBTParser;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class ItemStackUtils {
+ public static @NotNull ItemStack buildItem(@NotNull Material material, @Nullable String tag,
+ @Nullable String displayName, @Nullable List lore, @NotNull TagResolver @NotNull ... tags) {
+ ItemStack.Builder builder = ItemStack.builder(material);
+ if (tag != null) {
+ try {
+ builder.meta((NBTCompound)new SNBTParser(new StringReader(tag)).parse());
+ }
+ catch (NBTException ignored) {
+ }
+ }
+
+ if (displayName != null) {
+ builder.displayName(MiniMessage.miniMessage().deserialize(displayName, tags));
+ }
+
+ if (lore != null) {
+ List components = new ArrayList<>(lore.size());
+ for (String format : lore) {
+ components.add(MiniMessage.miniMessage().deserialize(format, tags));
+ }
+ builder.lore(components);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/core/src/main/java/org/phantazm/core/Tags.java b/core/src/main/java/org/phantazm/core/Tags.java
new file mode 100644
index 000000000..8a7e9f98e
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/Tags.java
@@ -0,0 +1,9 @@
+package org.phantazm.core;
+
+import net.minestom.server.tag.Tag;
+
+import java.util.UUID;
+
+public final class Tags {
+ public static final Tag LAST_MELEE_HIT_TAG = Tag.UUID("last_melee_hit");
+}
diff --git a/core/src/main/java/org/phantazm/core/chat/BasicChatChannel.java b/core/src/main/java/org/phantazm/core/chat/BasicChatChannel.java
index 4d54b6d32..e13a2e128 100644
--- a/core/src/main/java/org/phantazm/core/chat/BasicChatChannel.java
+++ b/core/src/main/java/org/phantazm/core/chat/BasicChatChannel.java
@@ -5,6 +5,9 @@
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerChatEvent;
import org.jetbrains.annotations.NotNull;
@@ -24,13 +27,20 @@ public abstract class BasicChatChannel implements ChatChannel {
private final PlayerViewProvider viewProvider;
+ private final MiniMessage miniMessage;
+
+ private final String chatFormat;
+
/**
* Creates a {@link BasicChatChannel}.
*
* @param viewProvider The {@link BasicChatChannel}'s {@link PlayerViewProvider}
*/
- public BasicChatChannel(@NotNull PlayerViewProvider viewProvider) {
+ public BasicChatChannel(@NotNull PlayerViewProvider viewProvider, @NotNull MiniMessage miniMessage,
+ @NotNull String chatFormat) {
this.viewProvider = Objects.requireNonNull(viewProvider, "viewProvider");
+ this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage");
+ this.chatFormat = Objects.requireNonNull(chatFormat, "chatFormat");
}
@Override
@@ -55,13 +65,17 @@ public void findAudience(@NotNull UUID channelMember, @NotNull Consumer chatFormatter = chatEvent.getChatFormatFunction();
- if (chatFormatter != null) {
- return chatFormatter.apply(chatEvent);
- }
+ public @NotNull Component formatMessage(@NotNull Player player, @NotNull String message) {
+ Optional extends Component> displayNameOptional =
+ getViewProvider().fromPlayer(player).getDisplayNameIfCached();
+ Component displayName = displayNameOptional.isPresent()
+ ? displayNameOptional.get()
+ : Component.text(player.getUsername());
- return chatEvent.getDefaultChatFormat().get();
+ TagResolver senderPlaceholder = Placeholder.component("sender", displayName);
+ TagResolver messagePlaceholder = Placeholder.unparsed("message", message);
+
+ return miniMessage.deserialize(chatFormat, senderPlaceholder, messagePlaceholder);
}
/**
@@ -75,4 +89,7 @@ public void findAudience(@NotNull UUID channelMember, @NotNull Consumer> getAudience(@NotNull Player player);
+ protected @NotNull PlayerViewProvider getViewProvider() {
+ return viewProvider;
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/chat/ChatChannel.java b/core/src/main/java/org/phantazm/core/chat/ChatChannel.java
index ee7092920..a7cff0a48 100644
--- a/core/src/main/java/org/phantazm/core/chat/ChatChannel.java
+++ b/core/src/main/java/org/phantazm/core/chat/ChatChannel.java
@@ -3,6 +3,7 @@
import it.unimi.dsi.fastutil.objects.ObjectBooleanPair;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
+import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerChatEvent;
import org.jetbrains.annotations.NotNull;
@@ -28,9 +29,8 @@ void findAudience(@NotNull UUID channelMember, @NotNull Consumer onSuc
/**
* Formats a message. Channels may add custom style or formatting.
*
- * @param chatEvent The {@link PlayerChatEvent} that is being formatted
* @return The formatted {@link Component} message
*/
- @NotNull Component formatMessage(@NotNull PlayerChatEvent chatEvent);
+ @NotNull Component formatMessage(@NotNull Player player, @NotNull String message);
}
diff --git a/core/src/main/java/org/phantazm/core/chat/ChatConfig.java b/core/src/main/java/org/phantazm/core/chat/ChatConfig.java
new file mode 100644
index 000000000..a87f98c36
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/chat/ChatConfig.java
@@ -0,0 +1,12 @@
+package org.phantazm.core.chat;
+
+import org.jetbrains.annotations.NotNull;
+import org.phantazm.core.guild.party.PartyChatChannel;
+
+import java.util.Map;
+
+public record ChatConfig(@NotNull Map chatFormats) {
+
+ public static final ChatConfig DEFAULT = new ChatConfig(Map.of("all", "", PartyChatChannel.CHANNEL_NAME, ""));
+
+}
diff --git a/core/src/main/java/org/phantazm/core/chat/InstanceChatChannel.java b/core/src/main/java/org/phantazm/core/chat/InstanceChatChannel.java
index 569bfc10b..71e36e936 100644
--- a/core/src/main/java/org/phantazm/core/chat/InstanceChatChannel.java
+++ b/core/src/main/java/org/phantazm/core/chat/InstanceChatChannel.java
@@ -6,6 +6,7 @@
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.minimessage.MiniMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
@@ -23,8 +24,9 @@ public class InstanceChatChannel extends BasicChatChannel {
*
* @param viewProvider The {@link InstanceChatChannel}'s {@link PlayerViewProvider}
*/
- public InstanceChatChannel(@NotNull PlayerViewProvider viewProvider) {
- super(viewProvider);
+ public InstanceChatChannel(@NotNull PlayerViewProvider viewProvider, @NotNull MiniMessage miniMessage,
+ @NotNull String chatFormat) {
+ super(viewProvider, miniMessage, chatFormat);
}
@Override
diff --git a/core/src/main/java/org/phantazm/core/chat/SelfChatChannel.java b/core/src/main/java/org/phantazm/core/chat/SelfChatChannel.java
deleted file mode 100644
index 55edbf3c4..000000000
--- a/core/src/main/java/org/phantazm/core/chat/SelfChatChannel.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.phantazm.core.chat;
-
-import it.unimi.dsi.fastutil.Pair;
-import it.unimi.dsi.fastutil.objects.ObjectBooleanPair;
-import net.kyori.adventure.audience.Audience;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.JoinConfiguration;
-import net.kyori.adventure.text.format.NamedTextColor;
-import net.minestom.server.entity.Player;
-import net.minestom.server.event.player.PlayerChatEvent;
-import org.jetbrains.annotations.NotNull;
-import org.phantazm.core.player.PlayerViewProvider;
-
-/**
- * A {@link ChatChannel} that only sends message to the message's sender.
- */
-public class SelfChatChannel extends BasicChatChannel {
-
- public static final String CHANNEL_NAME = "self";
-
- /**
- * Creates a {@link SelfChatChannel}.
- *
- * @param viewProvider The {@link SelfChatChannel}'s {@link PlayerViewProvider}
- */
- public SelfChatChannel(@NotNull PlayerViewProvider viewProvider) {
- super(viewProvider);
- }
-
- @Override
- public @NotNull Component formatMessage(@NotNull PlayerChatEvent chatEvent) {
- JoinConfiguration joinConfiguration = JoinConfiguration.separator(Component.space());
- return Component.join(joinConfiguration, Component.text("self"), Component.text(">", NamedTextColor.GRAY),
- super.formatMessage(chatEvent));
- }
-
- @Override
- protected @NotNull Pair> getAudience(@NotNull Player player) {
- return Pair.of(player, null);
- }
-}
diff --git a/core/src/main/java/org/phantazm/core/chat/command/ChatChannelSendCommand.java b/core/src/main/java/org/phantazm/core/chat/command/ChatChannelSendCommand.java
new file mode 100644
index 000000000..a04756d96
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/chat/command/ChatChannelSendCommand.java
@@ -0,0 +1,48 @@
+package org.phantazm.core.chat.command;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.minestom.server.command.builder.Command;
+import net.minestom.server.command.builder.arguments.Argument;
+import net.minestom.server.command.builder.arguments.ArgumentType;
+import net.minestom.server.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.phantazm.core.chat.ChatChannel;
+
+import java.util.Objects;
+
+public class ChatChannelSendCommand {
+
+ private ChatChannelSendCommand() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static @NotNull Command chatChannelSend(@NotNull String commandName, @NotNull ChatChannel channel) {
+ Objects.requireNonNull(channel, "channel");
+ Command command = new Command(commandName);
+
+ Argument message = ArgumentType.StringArray("message");
+ command.addConditionalSyntax((sender, commandString) -> {
+ if (commandString == null) {
+ return sender instanceof Player;
+ }
+
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(Component.text("You have to be a player to use that command!", NamedTextColor.RED));
+ }
+
+ return true;
+ }, (sender, context) -> {
+ Player player = (Player)sender;
+ channel.findAudience(player.getUuid(), audience -> {
+ Component formatted = channel.formatMessage(player, String.join(" ", context.get(message)));
+ audience.sendMessage(formatted);
+ }, (failure) -> {
+ player.sendMessage(failure.left());
+ });
+ }, message);
+
+ return command;
+ }
+
+}
diff --git a/core/src/main/java/org/phantazm/core/datapack/Datapack.java b/core/src/main/java/org/phantazm/core/datapack/Datapack.java
new file mode 100644
index 000000000..d42ab692f
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/datapack/Datapack.java
@@ -0,0 +1,11 @@
+package org.phantazm.core.datapack;
+
+import net.minestom.server.utils.NamespaceID;
+import net.minestom.server.world.biomes.Biome;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+
+public record Datapack(@NotNull Map biomes) {
+
+}
diff --git a/core/src/main/java/org/phantazm/core/datapack/DatapackLoader.java b/core/src/main/java/org/phantazm/core/datapack/DatapackLoader.java
new file mode 100644
index 000000000..85be22309
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/datapack/DatapackLoader.java
@@ -0,0 +1,127 @@
+package org.phantazm.core.datapack;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import net.minestom.server.utils.NamespaceID;
+import net.minestom.server.world.biomes.Biome;
+import net.minestom.server.world.biomes.BiomeEffects;
+import net.minestom.server.world.biomes.BiomeParticle;
+import org.jetbrains.annotations.NotNull;
+import org.jglrxavpok.hephaistos.json.NBTGsonReader;
+import org.jglrxavpok.hephaistos.nbt.NBTCompound;
+import org.jglrxavpok.hephaistos.nbt.NBTType;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class DatapackLoader {
+
+ public @NotNull Datapack loadDatapack(@NotNull Path datapackPath) throws IOException {
+ Path data = datapackPath.resolve("data");
+ Map biomes = new TreeMap<>();
+ try (DirectoryStream namespaceRoots = Files.newDirectoryStream(data, Files::isDirectory)) {
+ for (Path namespaceRoot : namespaceRoots) {
+ String namespace = namespaceRoot.getFileName().toString();
+ biomes.putAll(loadBiomes(namespace, namespaceRoot));
+ }
+
+ return new Datapack(biomes);
+ }
+ }
+
+ private Map loadBiomes(String namespace, Path root) throws IOException {
+ Map biomes = new TreeMap<>();
+ Path biomeRoot = root.resolve("worldgen/biome");
+ PathMatcher matcher = root.getFileSystem().getPathMatcher("glob:**/*.json");
+ DirectoryStream.Filter filter = entry -> matcher.matches(entry) && Files.isRegularFile(entry);
+ try (DirectoryStream biomePaths = Files.newDirectoryStream(biomeRoot, filter)) {
+ for (Path biomePath : biomePaths) {
+ String fileName = biomePath.getFileName().toString();
+ NamespaceID name = NamespaceID.from(namespace, fileName.substring(0, fileName.length() - ".json".length()));
+ Biome biome = loadBiome(name, biomePath);
+ biomes.put(name, biome);
+ }
+ }
+
+ return biomes;
+ }
+
+ private Biome loadBiome(NamespaceID name, Path path) throws IOException {
+ JsonObject rootJson = JsonParser.parseReader(Files.newBufferedReader(path)).getAsJsonObject();
+ Biome.Builder biomeBuilder = Biome.builder()
+ .name(name)
+ .temperature(rootJson.get("temperature").getAsFloat())
+ .downfall(rootJson.get("downfall").getAsFloat())
+ .hasPrecipitation(rootJson.get("has_precipitation").getAsBoolean());
+ if (rootJson.has("temperature_modifier")) {
+ biomeBuilder.temperatureModifier(Biome.TemperatureModifier.valueOf(rootJson.get("temperature_modifier").getAsString().toUpperCase()));
+ }
+
+ JsonObject effectsJson = rootJson.getAsJsonObject("effects");
+ BiomeEffects.Builder effectsBuilder = BiomeEffects.builder()
+ .fogColor(effectsJson.get("fog_color").getAsInt())
+ .skyColor(effectsJson.get("sky_color").getAsInt())
+ .waterColor(effectsJson.get("water_color").getAsInt())
+ .waterFogColor(effectsJson.get("water_fog_color").getAsInt());
+ if (effectsJson.has("foliage_color")) {
+ effectsBuilder.foliageColor(effectsJson.get("foliage_color").getAsInt());
+ }
+ if (effectsJson.has("grass_color")) {
+ effectsBuilder.grassColor(effectsJson.get("grass_color").getAsInt());
+ }
+ if (effectsJson.has("grass_color_modifier")) {
+ effectsBuilder.grassColorModifier(BiomeEffects.GrassColorModifier.valueOf(effectsJson.get(
+ "grass_color_modifier").getAsString().toUpperCase()));
+ }
+ if (effectsJson.has("particle")) {
+ JsonObject particleJson = effectsJson.getAsJsonObject("particle");
+ float probability = particleJson.get("probability").getAsFloat();
+ String particleSNBT = particleJson.get("options").toString();
+
+ try (NBTGsonReader nbtReader = new NBTGsonReader(new StringReader(particleSNBT))) {
+ NBTCompound particleNBT = (NBTCompound) nbtReader.read(NBTType.TAG_Compound);
+ BiomeParticle.Option option = () -> particleNBT;
+ BiomeParticle particle = new BiomeParticle(probability, option);
+ effectsBuilder.biomeParticle(particle);
+ }
+ }
+ if (effectsJson.has("ambient_sound")) {
+ effectsBuilder.ambientSound(NamespaceID.from(effectsJson.get("ambient_sound").getAsString()));
+ }
+ if (effectsJson.has("mood_sound")) {
+ JsonObject moodSoundJson = effectsJson.getAsJsonObject("mood_sound");
+ NamespaceID sound = NamespaceID.from(moodSoundJson.get("sound").getAsString());
+ int tickDelay = moodSoundJson.get("tick_delay").getAsInt();
+ int blockSearchExtent = moodSoundJson.get("block_search_extent").getAsInt();
+ double offset = moodSoundJson.get("offset").getAsDouble();
+ BiomeEffects.MoodSound moodSound = new BiomeEffects.MoodSound(sound, tickDelay, blockSearchExtent, offset);
+ effectsBuilder.moodSound(moodSound);
+ }
+ if (effectsJson.has("additions_sound")) {
+ JsonObject additionsSoundJson = effectsJson.getAsJsonObject("additions_sound");
+ NamespaceID sound = NamespaceID.from(additionsSoundJson.get("sound").getAsString());
+ double tickChance = additionsSoundJson.get("tick_chance").getAsDouble();
+ BiomeEffects.AdditionsSound additionsSound = new BiomeEffects.AdditionsSound(sound, tickChance);
+ effectsBuilder.additionsSound(additionsSound);
+ }
+ if (effectsJson.has("music")) {
+ JsonObject musicJson = effectsJson.getAsJsonObject("music");
+ NamespaceID sound = NamespaceID.from(musicJson.get("sound").getAsString());
+ int minDelay = musicJson.get("min_delay").getAsInt();
+ int maxDelay = musicJson.get("max_delay").getAsInt();
+ boolean replaceCurrentMusic = musicJson.get("replace_current_music").getAsBoolean();
+ BiomeEffects.Music music = new BiomeEffects.Music(sound, minDelay, maxDelay, replaceCurrentMusic);
+ effectsBuilder.music(music);
+ }
+
+ biomeBuilder.effects(effectsBuilder.build());
+ return biomeBuilder.build();
+ }
+
+}
diff --git a/core/src/main/java/org/phantazm/core/equipment/EquipmentHandler.java b/core/src/main/java/org/phantazm/core/equipment/EquipmentHandler.java
index 104fd19cf..544d01e83 100644
--- a/core/src/main/java/org/phantazm/core/equipment/EquipmentHandler.java
+++ b/core/src/main/java/org/phantazm/core/equipment/EquipmentHandler.java
@@ -3,11 +3,14 @@
import com.github.steanky.toolkit.collection.Wrapper;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.kyori.adventure.key.Key;
+import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.phantazm.core.inventory.*;
import java.util.*;
+import java.util.function.Supplier;
public class EquipmentHandler {
private final InventoryAccessRegistry accessRegistry;
@@ -103,4 +106,91 @@ public void refreshGroup(@NotNull Key groupKey) {
}
});
}
+
+ public @NotNull Result addOrReplaceEquipment(Key groupKey, Key equipmentKey, boolean allowReplace, int specificSlot,
+ boolean allowDuplicate, @NotNull Supplier extends Optional extends Equipment>> equipmentSupplier,
+ @Nullable Player player) {
+ if (!allowDuplicate && hasEquipment(groupKey, equipmentKey)) {
+ return Result.DUPLICATE;
+ }
+
+ boolean addEquipment;
+ if (!(addEquipment = canAddEquipment(groupKey)) && !allowReplace) {
+ return Result.FAILED;
+ }
+
+ InventoryAccessRegistry accessRegistry = accessRegistry();
+ Optional inventoryAccessOptional = accessRegistry.getCurrentAccess();
+ if (inventoryAccessOptional.isEmpty()) {
+ return Result.FAILED;
+ }
+
+ InventoryAccess inventoryAccess = inventoryAccessOptional.get();
+ if (addEquipment) {
+ Optional extends Equipment> equipmentOptional = equipmentSupplier.get();
+ if (equipmentOptional.isEmpty()) {
+ return Result.FAILED;
+ }
+
+ Equipment equipment = equipmentOptional.get();
+ if (specificSlot < 0) {
+ addEquipment(equipment, groupKey);
+ return Result.ADDED;
+ }
+
+ InventoryObjectGroup group = inventoryAccess.groups().get(groupKey);
+ if (invalidGroupSlot(group, specificSlot)) {
+ return Result.FAILED;
+ }
+
+ InventoryProfile profile = group.getProfile();
+ if (!profile.hasInventoryObject(specificSlot)) {
+ accessRegistry.replaceObject(specificSlot, equipment);
+ return Result.ADDED;
+ }
+
+ InventoryObject currentObject = profile.getInventoryObject(specificSlot);
+ if (allowReplace || group.defaultObject() == currentObject) {
+ InventoryObject old = accessRegistry.replaceObject(specificSlot, equipment);
+ return old == null || old == group.defaultObject() ? Result.ADDED : Result.REPLACED;
+ }
+
+ return Result.FAILED;
+ }
+
+ if (player == null) {
+ return Result.FAILED;
+ }
+
+ int targetSlot = specificSlot < 0 ? player.getHeldSlot() : specificSlot;
+
+ InventoryObjectGroup group = inventoryAccess.groups().get(groupKey);
+ if (invalidGroupSlot(group, targetSlot)) {
+ return Result.FAILED;
+ }
+
+ Optional extends Equipment> equipmentOptional = equipmentSupplier.get();
+ if (equipmentOptional.isEmpty()) {
+ return Result.FAILED;
+ }
+
+ Equipment equipment = equipmentOptional.get();
+ InventoryObject old = accessRegistry.replaceObject(targetSlot, equipment);
+ return old == null || old == group.defaultObject() ? Result.ADDED : Result.REPLACED;
+ }
+
+ private boolean invalidGroupSlot(InventoryObjectGroup group, int slot) {
+ return group == null || !group.getSlots().contains(slot);
+ }
+
+ public enum Result {
+ ADDED,
+ REPLACED,
+ DUPLICATE,
+ FAILED;
+
+ public boolean success() {
+ return this == ADDED || this == REPLACED;
+ }
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/equipment/LinearUpgradePath.java b/core/src/main/java/org/phantazm/core/equipment/LinearUpgradePath.java
index 9801881a7..98ec44bed 100644
--- a/core/src/main/java/org/phantazm/core/equipment/LinearUpgradePath.java
+++ b/core/src/main/java/org/phantazm/core/equipment/LinearUpgradePath.java
@@ -1,5 +1,6 @@
package org.phantazm.core.equipment;
+import com.github.steanky.element.core.annotation.Cache;
import com.github.steanky.element.core.annotation.DataObject;
import com.github.steanky.element.core.annotation.FactoryMethod;
import com.github.steanky.element.core.annotation.Model;
@@ -12,6 +13,7 @@
import java.util.Optional;
@Model("zombies.map.shop.upgrade_path.linear")
+@Cache
public class LinearUpgradePath implements UpgradePath {
private final Data data;
diff --git a/core/src/main/java/org/phantazm/core/event/PlayerJoinLobbyEvent.java b/core/src/main/java/org/phantazm/core/event/PlayerJoinLobbyEvent.java
new file mode 100644
index 000000000..7433e6afb
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/event/PlayerJoinLobbyEvent.java
@@ -0,0 +1,18 @@
+package org.phantazm.core.event;
+
+import net.minestom.server.entity.Player;
+import net.minestom.server.event.trait.PlayerEvent;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Objects;
+
+public record PlayerJoinLobbyEvent(Player player) implements PlayerEvent {
+ public PlayerJoinLobbyEvent(@NotNull Player player) {
+ this.player = Objects.requireNonNull(player, "player");
+ }
+
+ @Override
+ public @NotNull Player getPlayer() {
+ return player;
+ }
+}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/InstanceScene.java b/core/src/main/java/org/phantazm/core/game/scene/InstanceScene.java
index ad27c3618..199372d58 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/InstanceScene.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/InstanceScene.java
@@ -1,12 +1,17 @@
package org.phantazm.core.game.scene;
import com.github.steanky.toolkit.collection.Wrapper;
+import net.minestom.server.MinecraftServer;
+import net.minestom.server.coordinate.Point;
+import net.minestom.server.entity.GameMode;
+import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.game.scene.fallback.SceneFallback;
import org.phantazm.core.player.PlayerView;
import java.util.*;
+import java.util.concurrent.locks.StampedLock;
/**
* Basic Scene which corresponds to a single {@link Instance} and {@link SceneFallback} to route players.
@@ -17,13 +22,21 @@ public abstract class InstanceScene implement
private final UUID uuid;
protected final Instance instance;
protected final SceneFallback fallback;
+ protected final Point spawnPoint;
- private boolean shutdown = false;
+ private final Set ghosts;
+ private final StampedLock ghostLock;
- public InstanceScene(@NotNull UUID uuid, @NotNull Instance instance, @NotNull SceneFallback fallback) {
+ protected volatile boolean shutdown = false;
+
+ public InstanceScene(@NotNull UUID uuid, @NotNull Instance instance, @NotNull SceneFallback fallback,
+ @NotNull Point spawnPoint) {
this.uuid = Objects.requireNonNull(uuid, "uuid");
this.instance = Objects.requireNonNull(instance, "instance");
this.fallback = Objects.requireNonNull(fallback, "fallback");
+ this.spawnPoint = Objects.requireNonNull(spawnPoint, "spawnPoint");
+ this.ghosts = Collections.newSetFromMap(new WeakHashMap<>());
+ this.ghostLock = new StampedLock();
}
@Override
@@ -58,10 +71,88 @@ public boolean isShutdown() {
@Override
public void shutdown() {
shutdown = true;
+ MinecraftServer.getInstanceManager().forceUnregisterInstance(instance);
}
@Override
public @NotNull SceneFallback getFallback() {
return fallback;
}
+
+ @Override
+ public boolean acceptGhost(@NotNull PlayerView playerView) {
+ Optional playerOptional = playerView.getPlayer();
+ if (playerOptional.isEmpty()) {
+ return false;
+ }
+
+ Player player = playerOptional.get();
+ Instance oldInstance;
+ if ((oldInstance = player.getInstance()) == instance) {
+ return false;
+ }
+
+ long writeStamp = ghostLock.writeLock();
+ try {
+ ghosts.add(player);
+ ghosts.removeIf(this::invalidGhost);
+ }
+ finally {
+ ghostLock.unlockWrite(writeStamp);
+ }
+
+ player.setInstanceAddCallback(
+ () -> Utils.handleInstanceTransfer(oldInstance, instance, player, newInstancePlayer -> true,
+ this::hasGhost));
+ player.setGameMode(GameMode.SPECTATOR);
+ player.setInstance(instance, spawnPoint);
+ return true;
+ }
+
+ @Override
+ public boolean hasGhost(@NotNull Player player) {
+ long optimisticRead = ghostLock.tryOptimisticRead();
+ if (ghostLock.validate(optimisticRead)) {
+ boolean result = ghosts.contains(player);
+ if (ghostLock.validate(optimisticRead)) {
+ if (result && invalidGhost(player)) {
+ cleanGhosts();
+ return false;
+ }
+
+ return result;
+ }
+ }
+
+ long readStamp = ghostLock.readLock();
+ try {
+ if (!ghosts.contains(player)) {
+ return false;
+ }
+ }
+ finally {
+ ghostLock.unlockRead(readStamp);
+ }
+
+ if (invalidGhost(player)) {
+ cleanGhosts();
+ return false;
+ }
+
+ return true;
+ }
+
+ private void cleanGhosts() {
+ long writeStamp = ghostLock.writeLock();
+ try {
+ ghosts.removeIf(this::invalidGhost);
+ }
+ finally {
+ ghostLock.unlockWrite(writeStamp);
+ }
+ }
+
+ private boolean invalidGhost(Player player) {
+ return !player.isOnline() || player.getInstance() != instance;
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/Scene.java b/core/src/main/java/org/phantazm/core/game/scene/Scene.java
index 352c305a0..f15cd3804 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/Scene.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/Scene.java
@@ -1,5 +1,6 @@
package org.phantazm.core.game.scene;
+import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
import org.phantazm.commons.Tickable;
@@ -79,4 +80,7 @@ public interface Scene extends Tickable {
boolean isQuittable();
+ boolean acceptGhost(@NotNull PlayerView playerView);
+
+ boolean hasGhost(@NotNull Player player);
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/SceneProvider.java b/core/src/main/java/org/phantazm/core/game/scene/SceneProvider.java
index c9be2f678..12ba8f1de 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/SceneProvider.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/SceneProvider.java
@@ -6,6 +6,7 @@
import java.util.Collection;
import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
/**
* A provider to create new {@link Scene}s.
@@ -20,7 +21,7 @@ public interface SceneProvider, TRequest extends
* @param request The request used to provide an appropriate scene
* @return An {@link Optional} of a {@link Scene}. The scene may be newly created or from the provider's store.
*/
- @NotNull Optional provideScene(@NotNull TRequest request);
+ @NotNull CompletableFuture> provideScene(@NotNull TRequest request);
/**
* Gets the {@link Scene}s currently stored by the {@link Scene} provider.
diff --git a/core/src/main/java/org/phantazm/core/game/scene/SceneProviderAbstract.java b/core/src/main/java/org/phantazm/core/game/scene/SceneProviderAbstract.java
index c193d2ba7..f2a19a369 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/SceneProviderAbstract.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/SceneProviderAbstract.java
@@ -7,14 +7,20 @@
import org.phantazm.core.game.scene.event.SceneShutdownEvent;
import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.StampedLock;
/**
* An abstract base for {@link SceneProvider}s.
*/
public abstract class SceneProviderAbstract, TRequest extends SceneJoinRequest>
implements SceneProvider {
- private final Collection scenes = new ArrayList<>();
+ private final List scenes = new CopyOnWriteArrayList<>();
private final Collection unmodifiableScenes = Collections.unmodifiableCollection(scenes);
+ private final StampedLock lock = new StampedLock();
+ private final Executor executor;
private final int maximumScenes;
/**
@@ -22,22 +28,46 @@ public abstract class SceneProviderAbstract, TReq
*
* @param maximumScenes The maximum number of {@link Scene}s in the provider.
*/
- public SceneProviderAbstract(int maximumScenes) {
+ public SceneProviderAbstract(@NotNull Executor executor, int maximumScenes) {
+ this.executor = Objects.requireNonNull(executor, "executor");
this.maximumScenes = maximumScenes;
}
@Override
- public @NotNull Optional provideScene(@NotNull TRequest request) {
- return Optional.ofNullable(chooseScene(request).orElseGet(() -> {
- if (scenes.size() >= maximumScenes) {
- return null;
+ public @NotNull CompletableFuture> provideScene(@NotNull TRequest request) {
+ return CompletableFuture.supplyAsync(() -> {
+ long optimisticReadStamp = lock.tryOptimisticRead();
+ if (lock.validate(optimisticReadStamp)) {
+ Optional sceneOptional = chooseScene(request);
+
+ if (lock.validate(optimisticReadStamp) && sceneOptional.isPresent()) {
+ return sceneOptional;
+ }
}
- TScene newScene = createScene(request);
- scenes.add(newScene);
+ long writeStamp = lock.writeLock();
+ try {
+ Optional sceneOptional = chooseScene(request);
+ if (sceneOptional.isPresent()) {
+ return sceneOptional;
+ }
- return newScene;
- }));
+ if (scenes.size() >= maximumScenes) {
+ return Optional.empty();
+ }
+
+ TScene scene = createScene(request).join();
+ if (scene != null) {
+ scenes.add(scene);
+ return Optional.of(scene);
+ }
+
+ return Optional.empty();
+ }
+ finally {
+ lock.unlockWrite(writeStamp);
+ }
+ }, executor);
}
@Override
@@ -56,14 +86,12 @@ public void forceShutdown() {
@Override
public void tick(long time) {
- Iterator iterator = scenes.iterator();
-
- while (iterator.hasNext()) {
- TScene scene = iterator.next();
+ for (int i = scenes.size() - 1; i >= 0; --i) {
+ TScene scene = scenes.get(i);
if (scene.isShutdown()) {
cleanupScene(scene);
- iterator.remove();
+ scenes.remove(i);
ServerProcess process = MinecraftServer.process();
if (process != null) {
@@ -90,7 +118,7 @@ public void tick(long time) {
* @param request The join request which triggered the creation of the {@link Scene}
* @return The new {@link Scene}
*/
- protected abstract @NotNull TScene createScene(@NotNull TRequest request);
+ protected abstract @NotNull CompletableFuture createScene(@NotNull TRequest request);
protected abstract void cleanupScene(@NotNull TScene scene);
diff --git a/core/src/main/java/org/phantazm/core/game/scene/SceneRouter.java b/core/src/main/java/org/phantazm/core/game/scene/SceneRouter.java
index 88c32ea42..5b7fc4c33 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/SceneRouter.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/SceneRouter.java
@@ -1,15 +1,17 @@
package org.phantazm.core.game.scene;
+import net.kyori.adventure.key.Keyed;
import org.jetbrains.annotations.NotNull;
import org.phantazm.commons.Tickable;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
-public interface SceneRouter, TRequest extends SceneJoinRequest> extends Tickable {
+public interface SceneRouter, TRequest extends SceneJoinRequest> extends Tickable, Keyed {
- @NotNull RouteResult findScene(@NotNull TRequest joinRequest);
+ @NotNull CompletableFuture> findScene(@NotNull TRequest joinRequest);
@NotNull Collection getScenes();
diff --git a/core/src/main/java/org/phantazm/core/game/scene/SceneTransferHelper.java b/core/src/main/java/org/phantazm/core/game/scene/SceneTransferHelper.java
index f68f815ac..f73a04b8f 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/SceneTransferHelper.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/SceneTransferHelper.java
@@ -1,5 +1,6 @@
package org.phantazm.core.game.scene;
+import it.unimi.dsi.fastutil.booleans.BooleanObjectPair;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;
@@ -17,6 +18,35 @@ public SceneTransferHelper(@NotNull RouterStore routerStore) {
public void transfer(@NotNull Scene to,
@NotNull TJoinRequest joinRequest, @NotNull Collection leavers, @NotNull PlayerView leader) {
+ BooleanObjectPair> result = leaveOldScenes(leavers, to);
+
+ if (result.firstBoolean()) {
+ leader.getPlayer().ifPresent(leaderPlayer -> {
+ leaderPlayer.sendMessage(
+ Component.text("Failed to join because not all players could leave their " + "previous scenes.",
+ NamedTextColor.RED));
+ });
+ return;
+ }
+
+ for (Runnable leaveExecutor : result.second()) {
+ leaveExecutor.run();
+ }
+
+ try (TransferResult joinResult = to.join(joinRequest)) {
+ if (joinResult.executor().isPresent()) {
+ joinResult.executor().get().run();
+ }
+ else if (joinResult.message().isPresent()) {
+ leader.getPlayer().ifPresent(leaderPlayer -> {
+ leaderPlayer.sendMessage(joinResult.message().get());
+ });
+ }
+ }
+ }
+
+ private BooleanObjectPair> leaveOldScenes(Collection extends PlayerView> leavers,
+ Scene> to) {
boolean anyFailures = false;
Collection leaveExecutors = new ArrayList<>(leavers.size());
for (PlayerView leaver : leavers) {
@@ -30,40 +60,37 @@ public void transfer(@NotNull Scene {
- leaver.getPlayer().ifPresent(player -> player.sendMessage(message));
- });
+ try (TransferResult leaveResult = oldScene.leave(Collections.singleton(leaver.getUUID()))) {
+ if (leaveResult.executor().isPresent()) {
+ leaveExecutors.add(leaveResult.executor().get());
+ }
+ else {
+ anyFailures = true;
+ leaveResult.message().ifPresent(message -> {
+ leaver.getPlayer().ifPresent(player -> player.sendMessage(message));
+ });
+ }
}
}
- if (anyFailures) {
- leader.getPlayer().ifPresent(leaderPlayer -> {
- leaderPlayer.sendMessage(
+ return BooleanObjectPair.of(anyFailures, leaveExecutors);
+ }
+
+ public void ghost(@NotNull Scene> target, @NotNull PlayerView ghoster) {
+ BooleanObjectPair> result = leaveOldScenes(List.of(ghoster), null);
+ if (result.firstBoolean()) {
+ ghoster.getPlayer().ifPresent(ghosterPlayer -> {
+ ghosterPlayer.sendMessage(
Component.text("Failed to join because not all players could leave their " + "previous scenes.",
NamedTextColor.RED));
});
return;
}
- for (Runnable leaveExecutor : leaveExecutors) {
- leaveExecutor.run();
- }
-
- TransferResult joinResult = to.join(joinRequest);
- if (joinResult.executor().isPresent()) {
- joinResult.executor().get().run();
- }
- else if (joinResult.message().isPresent()) {
- leader.getPlayer().ifPresent(leaderPlayer -> {
- leaderPlayer.sendMessage(joinResult.message().get());
- });
+ if (target.acceptGhost(ghoster)) {
+ for (Runnable leaveExecutor : result.second()) {
+ leaveExecutor.run();
+ }
}
}
-
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/TransferResult.java b/core/src/main/java/org/phantazm/core/game/scene/TransferResult.java
index 52d276736..1de50e17f 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/TransferResult.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/TransferResult.java
@@ -4,13 +4,16 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.io.Closeable;
import java.util.Objects;
import java.util.Optional;
/**
* Represents the result of transferring a player to or from a {@link Scene}.
*/
-public record TransferResult(@NotNull Optional executor, @NotNull Optional message) {
+public record TransferResult(@NotNull Optional executor,
+ @NotNull Runnable end,
+ @NotNull Optional message) implements Closeable {
/**
* Creates a transfer result.
@@ -23,11 +26,25 @@ public record TransferResult(@NotNull Optional executor, @NotNull Opti
}
public static @NotNull TransferResult success(@NotNull Runnable executor) {
- return new TransferResult(Optional.of(executor), Optional.empty());
+ return new TransferResult(Optional.of(executor), () -> {
+ }, Optional.empty());
+ }
+
+ public static @NotNull TransferResult success(@NotNull Runnable executor, @NotNull Runnable end) {
+ return new TransferResult(Optional.of(executor), end, Optional.empty());
}
public static @NotNull TransferResult failure(@Nullable Component message) {
- return new TransferResult(Optional.empty(), Optional.ofNullable(message));
+ return new TransferResult(Optional.empty(), () -> {
+ }, Optional.ofNullable(message));
}
+ public static @NotNull TransferResult failure(@Nullable Component message, @NotNull Runnable end) {
+ return new TransferResult(Optional.empty(), end, Optional.ofNullable(message));
+ }
+
+ @Override
+ public void close() {
+ end.run();
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/Utils.java b/core/src/main/java/org/phantazm/core/game/scene/Utils.java
index c225b4ea3..682e5ad4c 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/Utils.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/Utils.java
@@ -1,24 +1,31 @@
package org.phantazm.core.game.scene;
-import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.EntityTracker;
import net.minestom.server.instance.Instance;
import net.minestom.server.network.packet.server.ServerPacket;
-import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
-import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
public class Utils {
/**
- * Handles player transfer between instances, sending list packets.
+ * Handles player transfer between instances, sending list packets. Will optionally add this player to the tab
+ * list of other players in the instance. This method must be called before spawn packets are sent to players
+ * in the target instance.
*
- * @param oldInstance the old instance; is {@code null} if the player is logging in for the first time
- * @param player the player
+ * @param oldInstance the old instance; is {@code null} if the player is logging in for the first time
+ * @param newInstance the new instance, if this is the same object as oldInstance, this method will do nothing
+ * @param player the player
+ * @param transferPlayerCanSeeInstancePlayer a predicate to test whether a player in {@code newInstance} should be
+ * visible by the transferring player {@code player}
+ * @param instancePlayerCanSeeTransferPlayer a predicate to test whether a player in {@code newInstance} should receive a tablist packet
*/
- public static void handleInstanceTransfer(@NotNull Instance oldInstance, @NotNull Player player) {
- Instance newInstance = Objects.requireNonNull(player.getInstance(), "player instance");
+ public static void handleInstanceTransfer(@Nullable Instance oldInstance, @NotNull Instance newInstance,
+ @NotNull Player player, @NotNull Predicate super Player> transferPlayerCanSeeInstancePlayer,
+ @NotNull Predicate super Player> instancePlayerCanSeeTransferPlayer) {
if (newInstance == oldInstance) {
return;
}
@@ -26,23 +33,45 @@ public static void handleInstanceTransfer(@NotNull Instance oldInstance, @NotNul
ServerPacket playerRemove = player.getRemovePlayerToList();
ServerPacket playerAdd = player.getAddPlayerToList();
- for (Player oldPlayer : oldInstance.getEntityTracker().entities(EntityTracker.Target.PLAYERS)) {
- oldPlayer.sendPacket(playerRemove);
- player.sendPacket(oldPlayer.getRemovePlayerToList());
+ if (oldInstance != null) {
+ for (Player oldPlayer : oldInstance.getEntityTracker().entities(EntityTracker.Target.PLAYERS)) {
+ if (oldPlayer == player) {
+ continue;
+ }
+
+ oldPlayer.sendPacket(playerRemove);
+ player.sendPacket(oldPlayer.getRemovePlayerToList());
+ }
}
- for (Player newInstancePlayer : newInstance.getEntityTracker().entities(EntityTracker.Target.PLAYERS)) {
+ Set instancePlayers = newInstance.getEntityTracker().entities(EntityTracker.Target.PLAYERS);
+ for (Player newInstancePlayer : instancePlayers) {
if (newInstancePlayer == player) {
continue;
}
- player.sendPacket(newInstancePlayer.getAddPlayerToList());
- newInstancePlayer.sendPacket(playerAdd);
+ if (transferPlayerCanSeeInstancePlayer.test(newInstancePlayer)) {
+ player.sendPacket(newInstancePlayer.getAddPlayerToList());
+ }
- MinecraftServer.getSchedulerManager().buildTask(() -> {
- player.updateNewViewer(newInstancePlayer);
- newInstancePlayer.updateNewViewer(player);
- }).delay(20, TimeUnit.SERVER_TICK).schedule();
+ if (instancePlayerCanSeeTransferPlayer.test(newInstancePlayer)) {
+ newInstancePlayer.sendPacket(playerAdd);
+ }
}
}
+
+ /**
+ * Handles player transfer between instances, sending list packets. Will also notify players in the new instance
+ * (the player's current instance).
+ *
+ * @param oldInstance the old instance; is {@code null} if the player is logging in for the first time
+ * @param player the player
+ * @param transferPlayerCanSeeInstancePlayer a predicate to test whether a player in {@code newInstance} should be
+ * visible by the transferring player {@code player}
+ */
+ public static void handleInstanceTransfer(@Nullable Instance oldInstance, @NotNull Instance newInstance,
+ @NotNull Player player, @NotNull Predicate super Player> transferPlayerCanSeeInstancePlayer) {
+ handleInstanceTransfer(oldInstance, newInstance, player, transferPlayerCanSeeInstancePlayer,
+ newInstancePlayer -> true);
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/command/QuitCommand.java b/core/src/main/java/org/phantazm/core/game/scene/command/QuitCommand.java
index aad743c74..4144bc6b5 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/command/QuitCommand.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/command/QuitCommand.java
@@ -6,20 +6,27 @@
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.game.scene.RouterStore;
+import org.phantazm.core.game.scene.Scene;
import org.phantazm.core.game.scene.TransferResult;
+import org.phantazm.core.game.scene.fallback.SceneFallback;
import org.phantazm.core.player.PlayerViewProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Objects;
+import java.util.Optional;
public final class QuitCommand {
+ private static final Logger LOGGER = LoggerFactory.getLogger(QuitCommand.class);
+
private QuitCommand() {
throw new UnsupportedOperationException();
}
public static @NotNull Command quitCommand(@NotNull RouterStore routerStore,
- @NotNull PlayerViewProvider viewProvider) {
+ @NotNull PlayerViewProvider viewProvider, @NotNull SceneFallback defaultFallback) {
Objects.requireNonNull(routerStore, "routerStore");
Objects.requireNonNull(viewProvider, "viewProvider");
@@ -37,7 +44,9 @@ private QuitCommand() {
return true;
}, (sender, context) -> {
Player player = (Player)sender;
- routerStore.getCurrentScene(player.getUuid()).ifPresent(scene -> {
+ Optional extends Scene>> sceneOptional = routerStore.getCurrentScene(player.getUuid());
+ if (sceneOptional.isPresent()) {
+ Scene> scene = sceneOptional.get();
if (!scene.isQuittable()) {
sender.sendMessage(Component.text("You can't quit this scene.", NamedTextColor.RED));
return;
@@ -46,12 +55,24 @@ private QuitCommand() {
TransferResult result = scene.leave(Collections.singleton(player.getUuid()));
if (result.executor().isPresent()) {
result.executor().get().run();
- scene.getFallback().fallback(viewProvider.fromPlayer(player));
+ scene.getFallback().fallback(viewProvider.fromPlayer(player))
+ .whenComplete((fallbackResult, throwable) -> {
+ if (throwable != null) {
+ LOGGER.warn("Failed to fallback", throwable);
+ }
+ });
}
else {
result.message().ifPresent(sender::sendMessage);
}
- });
+ }
+ else {
+ defaultFallback.fallback(viewProvider.fromPlayer(player)).whenComplete((fallbackResult, throwable) -> {
+ if (throwable != null) {
+ LOGGER.warn("Failed to fallback", throwable);
+ }
+ });
+ }
});
return command;
diff --git a/core/src/main/java/org/phantazm/core/game/scene/fallback/CompositeFallback.java b/core/src/main/java/org/phantazm/core/game/scene/fallback/CompositeFallback.java
index 27ed7db7e..fe08dd861 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/fallback/CompositeFallback.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/fallback/CompositeFallback.java
@@ -3,7 +3,9 @@
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.player.PlayerView;
+import java.util.Iterator;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* A {@link SceneFallback} which delegates to multiple sub-{@link SceneFallback}s.
@@ -23,14 +25,25 @@ public CompositeFallback(@NotNull Iterable fallbacks) {
}
@Override
- public boolean fallback(@NotNull PlayerView player) {
- for (SceneFallback fallback : fallbacks) {
- if (fallback.fallback(player)) {
- return true;
- }
+ public CompletableFuture fallback(@NotNull PlayerView player) {
+ Iterator iterator = fallbacks.iterator();
+ if (!iterator.hasNext()) {
+ return CompletableFuture.completedFuture(false);
}
- return false;
+ CompletableFuture future = iterator.next().fallback(player);
+ while (iterator.hasNext()) {
+ SceneFallback fallback = iterator.next();
+ future = future.thenCompose(result -> {
+ if (result) {
+ return CompletableFuture.completedFuture(true);
+ }
+
+ return fallback.fallback(player);
+ });
+ }
+
+ return future;
}
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/fallback/KickFallback.java b/core/src/main/java/org/phantazm/core/game/scene/fallback/KickFallback.java
index 8873fe9b5..8afe8f7d8 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/fallback/KickFallback.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/fallback/KickFallback.java
@@ -7,6 +7,7 @@
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
/**
* A {@link SceneFallback} which kicks {@link Player}s.
@@ -25,14 +26,14 @@ public KickFallback(@NotNull Component kickMessage) {
}
@Override
- public boolean fallback(@NotNull PlayerView playerView) {
+ public CompletableFuture fallback(@NotNull PlayerView playerView) {
Optional playerOptional = playerView.getPlayer();
if (playerOptional.isPresent()) {
playerOptional.get().kick(kickMessage);
- return true;
+ return CompletableFuture.completedFuture(true);
}
- return false;
+ return CompletableFuture.completedFuture(false);
}
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/fallback/SceneFallback.java b/core/src/main/java/org/phantazm/core/game/scene/fallback/SceneFallback.java
index 066283e07..a1060105c 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/fallback/SceneFallback.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/fallback/SceneFallback.java
@@ -3,6 +3,8 @@
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.player.PlayerView;
+import java.util.concurrent.CompletableFuture;
+
/**
* A fallback for scenes to route players to when necessary.
*/
@@ -15,6 +17,6 @@ public interface SceneFallback {
* @param player The {@link PlayerView} to handle
* @return Whether the fallback was successful
*/
- boolean fallback(@NotNull PlayerView player);
+ CompletableFuture fallback(@NotNull PlayerView player);
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyJoinRequest.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyJoinRequest.java
index 52a8cf99d..a31f50232 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyJoinRequest.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyJoinRequest.java
@@ -1,10 +1,7 @@
package org.phantazm.core.game.scene.lobby;
-import it.unimi.dsi.fastutil.Pair;
import net.minestom.server.entity.GameMode;
-import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
-import net.minestom.server.network.ConnectionManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
import org.phantazm.core.config.InstanceConfig;
@@ -22,10 +19,6 @@
*/
public class BasicLobbyJoinRequest implements LobbyJoinRequest {
- private static final CompletableFuture>[] EMPTY_COMPLETABLE_FUTURE_ARRAY = new CompletableFuture[0];
-
- private final ConnectionManager connectionManager;
-
private final Collection players;
/**
@@ -33,9 +26,7 @@ public class BasicLobbyJoinRequest implements LobbyJoinRequest {
*
* @param players The players in the request
*/
- public BasicLobbyJoinRequest(@NotNull ConnectionManager connectionManager,
- @NotNull Collection players) {
- this.connectionManager = Objects.requireNonNull(connectionManager, "connectionManager");
+ public BasicLobbyJoinRequest(@NotNull Collection players) {
this.players = Objects.requireNonNull(players, "players");
}
@@ -45,33 +36,25 @@ public BasicLobbyJoinRequest(@NotNull ConnectionManager connectionManager,
}
@Override
- public void handleJoin(@NotNull Instance instance, @NotNull InstanceConfig instanceConfig) {
- List> teleportedPlayers = new ArrayList<>(players.size());
+ public void handleJoin(@NotNull Lobby lobby, @NotNull Instance instance, @NotNull InstanceConfig instanceConfig) {
List> futures = new ArrayList<>(players.size());
for (PlayerView view : players) {
view.getPlayer().ifPresent(player -> {
player.setGameMode(GameMode.ADVENTURE);
- teleportedPlayers.add(Pair.of(player, player.getInstance()));
if (player.getInstance() == instance) {
futures.add(player.teleport(instanceConfig.spawnPoint()));
}
else {
+ Instance oldInstance = player.getInstance();
+ player.setInstanceAddCallback(() -> Utils.handleInstanceTransfer(oldInstance, instance, player,
+ newInstancePlayer -> !lobby.hasGhost(newInstancePlayer)));
futures.add(player.setInstance(instance, instanceConfig.spawnPoint()));
}
});
}
- CompletableFuture.allOf(futures.toArray(EMPTY_COMPLETABLE_FUTURE_ARRAY)).thenRun(() -> {
- for (int i = 0; i < futures.size(); ++i) {
- Pair pair = teleportedPlayers.get(i);
-
- Player teleportedPlayer = pair.first();
- Instance oldInstance = pair.second();
-
- Utils.handleInstanceTransfer(oldInstance, teleportedPlayer);
- }
- }).join();
+ CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();
}
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyProvider.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyProvider.java
index c5071e0a5..4d0ed96b2 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyProvider.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/BasicLobbyProvider.java
@@ -5,6 +5,10 @@
import com.github.steanky.element.core.context.ElementContext;
import com.github.steanky.ethylene.core.ConfigElement;
import com.github.steanky.ethylene.core.collection.ConfigList;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.minestom.server.entity.Player;
+import net.minestom.server.event.Event;
+import net.minestom.server.event.EventFilter;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.event.item.ItemDropEvent;
@@ -13,10 +17,14 @@
import net.minestom.server.event.player.*;
import net.minestom.server.event.trait.InstanceEvent;
import net.minestom.server.instance.Instance;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import net.minestom.server.network.packet.server.play.OpenBookPacket;
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.ElementUtils;
import org.phantazm.core.config.InstanceConfig;
import org.phantazm.core.game.scene.SceneProviderAbstract;
+import org.phantazm.core.game.scene.TransferResult;
import org.phantazm.core.game.scene.fallback.SceneFallback;
import org.phantazm.core.instance.InstanceLoader;
import org.phantazm.core.npc.NPC;
@@ -25,6 +33,8 @@
import org.slf4j.LoggerFactory;
import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -39,7 +49,11 @@ public class BasicLobbyProvider extends LobbyProviderAbstract {
private final SceneFallback fallback;
private final InstanceConfig instanceConfig;
private final List npcContexts;
+ private final Collection defaultItems;
+ private final MiniMessage miniMessage;
+ private final String lobbyJoinFormat;
private final boolean quittable;
+ private final EventNode rootNode;
/**
* Creates a basic implementation of a {@link SceneProviderAbstract}.
@@ -52,10 +66,13 @@ public class BasicLobbyProvider extends LobbyProviderAbstract {
* @param fallback A {@link SceneFallback} for the created {@link Lobby}s
* @param instanceConfig The {@link InstanceConfig} for the {@link Lobby}s
*/
- public BasicLobbyProvider(int maximumLobbies, int newLobbyThreshold, @NotNull InstanceLoader instanceLoader,
- @NotNull List lobbyPaths, @NotNull SceneFallback fallback, @NotNull InstanceConfig instanceConfig,
- @NotNull ContextManager contextManager, @NotNull ConfigList npcConfigs, boolean quittable) {
- super(maximumLobbies, newLobbyThreshold);
+ public BasicLobbyProvider(@NotNull Executor executor, int maximumLobbies, int newLobbyThreshold,
+ @NotNull InstanceLoader instanceLoader, @NotNull List lobbyPaths, @NotNull SceneFallback fallback,
+ @NotNull InstanceConfig instanceConfig, @NotNull ContextManager contextManager,
+ @NotNull ConfigList npcConfigs, @NotNull Collection defaultItems,
+ @NotNull MiniMessage miniMessage, @NotNull String lobbyJoinFormat, boolean quittable,
+ @NotNull EventNode rootNode) {
+ super(executor, maximumLobbies, newLobbyThreshold);
this.instanceLoader = Objects.requireNonNull(instanceLoader, "instanceLoader");
this.lobbyPaths = List.copyOf(Objects.requireNonNull(lobbyPaths, "lobbyPaths"));
@@ -71,49 +88,67 @@ public BasicLobbyProvider(int maximumLobbies, int newLobbyThreshold, @NotNull In
}
this.npcContexts = List.copyOf(npcContexts);
+ this.defaultItems = Objects.requireNonNull(defaultItems, "defaultItems");
+ this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage");
+ this.lobbyJoinFormat = Objects.requireNonNull(lobbyJoinFormat, "lobbyJoinFormat");
this.quittable = quittable;
+ this.rootNode = rootNode;
}
@Override
- protected @NotNull Lobby createScene(@NotNull LobbyJoinRequest request) {
- Instance instance = instanceLoader.loadInstance(lobbyPaths);
- instance.setTime(instanceConfig.time());
- instance.setTimeRate(instanceConfig.timeRate());
+ protected @NotNull CompletableFuture createScene(@NotNull LobbyJoinRequest request) {
+ return instanceLoader.loadInstance(lobbyPaths).thenApply(instance -> {
+ instance.setTime(instanceConfig.time());
+ instance.setTimeRate(instanceConfig.timeRate());
- EventNode super InstanceEvent> eventNode = instance.eventNode();
- eventNode.addListener(PlayerSwapItemEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(ItemDropEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(InventoryPreClickEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(PlayerPreEatEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(PickupItemEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(PickupExperienceEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(PrePlayerStartDiggingEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(PlayerBlockPlaceEvent.class, event -> event.setCancelled(true));
- eventNode.addListener(PlayerBlockInteractEvent.class, event -> {
- event.setCancelled(true);
- event.setBlockingItemUse(true);
- });
- eventNode.addListener(PlayerBlockBreakEvent.class, event -> event.setCancelled(true));
+ EventNode instanceNode =
+ EventNode.type("instance_npc_node_" + instance.getUniqueId(), EventFilter.INSTANCE,
+ (e, v) -> v == instance);
- List npcs = new ArrayList<>(npcContexts.size());
- for (ElementContext context : npcContexts) {
- NPC npc = context.provide(HANDLER, () -> null);
- if (npc != null) {
- npcs.add(npc);
- }
- }
+ instanceNode.addListener(PlayerSwapItemEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(ItemDropEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(InventoryPreClickEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PlayerPreEatEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PickupItemEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PickupExperienceEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PrePlayerStartDiggingEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PlayerBlockPlaceEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PlayerBlockInteractEvent.class, event -> {
+ event.setCancelled(true);
+ event.setBlockingItemUse(true);
+ });
+ instanceNode.addListener(PlayerBlockBreakEvent.class, event -> event.setCancelled(true));
+ instanceNode.addListener(PlayerUseItemEvent.class, event -> {
+ if (event.getPlayer().getItemInMainHand().material().equals(Material.WRITTEN_BOOK)) {
+ event.getPlayer().sendPacket(new OpenBookPacket(Player.Hand.MAIN));
+ }
+ });
- Lobby lobby = new Lobby(UUID.randomUUID(), instance, instanceConfig, fallback,
- new NPCHandler(List.copyOf(npcs), instance), quittable);
- eventNode.addListener(PlayerDisconnectEvent.class,
- event -> lobby.leave(Collections.singleton(event.getPlayer().getUuid())).executor()
- .ifPresent(Runnable::run));
+ List npcs = new ArrayList<>(npcContexts.size());
+ for (ElementContext context : npcContexts) {
+ NPC npc = context.provide(HANDLER, () -> null);
+ if (npc != null) {
+ npcs.add(npc);
+ }
+ }
- return lobby;
+ Lobby lobby = new Lobby(UUID.randomUUID(), instance, instanceConfig, fallback,
+ new NPCHandler(List.copyOf(npcs), instance, instanceNode), defaultItems, miniMessage,
+ lobbyJoinFormat, quittable);
+ instanceNode.addListener(PlayerDisconnectEvent.class, event -> {
+ try (TransferResult result = lobby.leave(Collections.singleton(event.getPlayer().getUuid()))) {
+ result.executor().ifPresent(Runnable::run);
+ }
+ });
+ rootNode.addChild(instanceNode);
+ return lobby;
+ });
}
@Override
protected void cleanupScene(@NotNull Lobby scene) {
- scene.cleanup();
+ NPCHandler handler = scene.handler();
+ handler.end();
+ rootNode.removeChild(handler.instanceNode());
}
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/Lobby.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/Lobby.java
index 8459c419b..24b9aa962 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/Lobby.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/Lobby.java
@@ -2,16 +2,24 @@
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minestom.server.entity.Player;
+import net.minestom.server.event.EventDispatcher;
import net.minestom.server.instance.Instance;
+import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
import org.phantazm.core.config.InstanceConfig;
+import org.phantazm.core.event.PlayerJoinLobbyEvent;
import org.phantazm.core.game.scene.InstanceScene;
import org.phantazm.core.game.scene.TransferResult;
import org.phantazm.core.game.scene.fallback.SceneFallback;
import org.phantazm.core.npc.NPCHandler;
import org.phantazm.core.player.PlayerView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.*;
@@ -19,9 +27,13 @@
* Represents a lobby. Most basic scene which contains {@link Player}s.
*/
public class Lobby extends InstanceScene {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Lobby.class);
private final InstanceConfig instanceConfig;
private final Map players;
private final NPCHandler npcHandler;
+ private final Collection defaultItems;
+ private final MiniMessage miniMessage;
+ private final String lobbyJoinFormat;
private final boolean quittable;
private boolean joinable = true;
@@ -34,12 +46,17 @@ public class Lobby extends InstanceScene {
* @param fallback A fallback for the lobby
*/
public Lobby(@NotNull UUID uuid, @NotNull Instance instance, @NotNull InstanceConfig instanceConfig,
- @NotNull SceneFallback fallback, @NotNull NPCHandler npcHandler, boolean quittable) {
- super(uuid, instance, fallback);
+ @NotNull SceneFallback fallback, @NotNull NPCHandler npcHandler,
+ @NotNull Collection defaultItems, @NotNull MiniMessage miniMessage,
+ @NotNull String lobbyJoinFormat, boolean quittable) {
+ super(uuid, instance, fallback, instanceConfig.spawnPoint());
this.instanceConfig = Objects.requireNonNull(instanceConfig, "instanceConfig");
this.players = new HashMap<>();
this.npcHandler = Objects.requireNonNull(npcHandler, "npcHandler");
this.npcHandler.spawnAll();
+ this.defaultItems = Objects.requireNonNull(defaultItems, "defaultItems");
+ this.miniMessage = Objects.requireNonNull(miniMessage, "miniMessage");
+ this.lobbyJoinFormat = Objects.requireNonNull(lobbyJoinFormat, "lobbyJoinFormat");
this.quittable = quittable;
}
@@ -71,7 +88,20 @@ public Lobby(@NotNull UUID uuid, @NotNull Instance instance, @NotNull InstanceCo
players.put(player.first().getUUID(), player.first());
}
- joinRequest.handleJoin(instance, instanceConfig);
+ joinRequest.handleJoin(this, instance, instanceConfig);
+ for (Pair player : joiners) {
+ player.left().getDisplayName().thenAccept(joiner -> {
+ TagResolver joinerPlaceholder = Placeholder.component("joiner", joiner);
+ Component message = miniMessage.deserialize(lobbyJoinFormat, joinerPlaceholder);
+ instance.sendMessage(message);
+ });
+
+ for (ItemStack stack : defaultItems) {
+ player.right().getInventory().addItemStack(stack);
+ }
+
+ EventDispatcher.call(new PlayerJoinLobbyEvent(player.right()));
+ }
});
}
@@ -119,18 +149,22 @@ public boolean isQuittable() {
@Override
public void shutdown() {
for (PlayerView player : players.values()) {
- fallback.fallback(player);
+ fallback.fallback(player).whenComplete((fallbackResult, throwable) -> {
+ if (throwable != null) {
+ LOGGER.warn("Failed to fallback {}", player.getUUID(), throwable);
+ }
+ });
}
super.shutdown();
}
- public void cleanup() {
- this.npcHandler.end();
- }
-
@Override
public void tick(long time) {
this.npcHandler.tick(time);
}
+
+ public @NotNull NPCHandler handler() {
+ return this.npcHandler;
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyJoinRequest.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyJoinRequest.java
index 827c12a95..8adc58ccf 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyJoinRequest.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyJoinRequest.java
@@ -26,7 +26,7 @@ public interface LobbyJoinRequest extends SceneJoinRequest {
* @param instance The {@link Instance} the players are joining
* @param instanceConfig Configuration for the {@link Instance}
*/
- void handleJoin(@NotNull Instance instance, @NotNull InstanceConfig instanceConfig);
+ void handleJoin(@NotNull Lobby lobby, @NotNull Instance instance, @NotNull InstanceConfig instanceConfig);
@Override
default int getRequestWeight() {
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyProviderAbstract.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyProviderAbstract.java
index 127309a43..9dc2ce212 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyProviderAbstract.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyProviderAbstract.java
@@ -6,6 +6,7 @@
import org.phantazm.core.player.PlayerView;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* An abstract {@link Lobby} {@link SceneProvider}.
@@ -21,8 +22,8 @@ public abstract class LobbyProviderAbstract extends SceneProviderAbstract chooseScene(@NotNull LobbyJoinRequest request) {
Lobby maximumLobby = null;
- int maximumWeighting = Integer.MIN_VALUE;
+ int maximumWeighting = Integer.MAX_VALUE;
sceneLoop:
for (Lobby lobby : getScenes()) {
@@ -42,7 +43,7 @@ public LobbyProviderAbstract(int maximumLobbies, int newLobbyThreshold) {
int joinWeight = lobby.getJoinWeight(request);
- if (joinWeight > maximumWeighting) {
+ if (joinWeight < maximumWeighting) {
maximumLobby = lobby;
maximumWeighting = joinWeight;
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouter.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouter.java
index cdb50a422..c5cca5356 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouter.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouter.java
@@ -1,18 +1,23 @@
package org.phantazm.core.game.scene.lobby;
+import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;
+import org.phantazm.commons.Namespaces;
import org.phantazm.core.game.scene.RouteResult;
import org.phantazm.core.game.scene.Scene;
import org.phantazm.core.game.scene.SceneProvider;
import org.phantazm.core.game.scene.SceneRouter;
import java.util.*;
+import java.util.concurrent.CompletableFuture;
/**
* {@link Scene} router for {@link Lobby}s.
*/
public class LobbyRouter implements SceneRouter {
+ public static final Key KEY = Key.key(Namespaces.PHANTAZM, "lobby");
private final Map> lobbyProviders;
@@ -58,22 +63,27 @@ public LobbyRouter(@NotNull Map>
}
@Override
- public @NotNull RouteResult findScene(@NotNull LobbyRouteRequest routeRequest) {
+ public @NotNull CompletableFuture> findScene(@NotNull LobbyRouteRequest routeRequest) {
if (isShutdown()) {
- return RouteResult.failure(Component.text("The router is shutdown."));
+ return CompletableFuture.completedFuture(
+ RouteResult.failure(Component.text("This game has shut down.", NamedTextColor.RED)));
}
if (!isJoinable()) {
- return RouteResult.failure(Component.text("The router is not joinable."));
+ return CompletableFuture.completedFuture(
+ RouteResult.failure(Component.text("This game is not joinable.", NamedTextColor.RED)));
}
SceneProvider lobbyProvider = lobbyProviders.get(routeRequest.targetLobbyName());
if (lobbyProvider == null) {
- return RouteResult.failure(
- Component.text("No lobbies exist under the name " + routeRequest.targetLobbyName() + "."));
+ return CompletableFuture.completedFuture(RouteResult.failure(
+ Component.text("No lobbies exist under the name " + routeRequest.targetLobbyName() + ".",
+ NamedTextColor.RED)));
}
- return lobbyProvider.provideScene(routeRequest.joinRequest()).map(RouteResult::success)
- .orElseGet(() -> RouteResult.failure(Component.text("No lobbies are joinable.")));
+ return lobbyProvider.provideScene(routeRequest.joinRequest()).thenApply(sceneOptional -> {
+ return sceneOptional.map(RouteResult::success).orElseGet(
+ () -> RouteResult.failure(Component.text("No lobbies are joinable.", NamedTextColor.RED)));
+ });
}
@Override
@@ -123,4 +133,8 @@ public void tick(long time) {
}
}
+ @Override
+ public @NotNull Key key() {
+ return KEY;
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouterFallback.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouterFallback.java
index 3a67dd5e0..91cd56a9f 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouterFallback.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/LobbyRouterFallback.java
@@ -1,6 +1,5 @@
package org.phantazm.core.game.scene.lobby;
-import net.minestom.server.network.ConnectionManager;
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.game.scene.TransferResult;
import org.phantazm.core.game.scene.fallback.SceneFallback;
@@ -8,15 +7,13 @@
import java.util.Collections;
import java.util.Objects;
-import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
/**
* A {@link SceneFallback} which routes to a lobby.
*/
public class LobbyRouterFallback implements SceneFallback {
- private final ConnectionManager connectionManager;
-
private final LobbyRouter lobbyRouter;
private final String lobbyName;
@@ -27,30 +24,30 @@ public class LobbyRouterFallback implements SceneFallback {
* @param lobby The {@link LobbyRouter} to fallback to
* @param lobbyName The name of the {@link Lobby} to fallback to
*/
- public LobbyRouterFallback(@NotNull ConnectionManager connectionManager, @NotNull LobbyRouter lobby,
- @NotNull String lobbyName) {
- this.connectionManager = Objects.requireNonNull(connectionManager, "connectionManager");
+ public LobbyRouterFallback(@NotNull LobbyRouter lobby, @NotNull String lobbyName) {
this.lobbyRouter = Objects.requireNonNull(lobby, "lobbyRouter");
this.lobbyName = Objects.requireNonNull(lobbyName, "lobbyName");
}
@Override
- public boolean fallback(@NotNull PlayerView player) {
- LobbyJoinRequest joinRequest = new BasicLobbyJoinRequest(connectionManager, Collections.singleton(player));
+ public CompletableFuture fallback(@NotNull PlayerView player) {
+ LobbyJoinRequest joinRequest = new BasicLobbyJoinRequest(Collections.singleton(player));
LobbyRouteRequest routeRequest = new LobbyRouteRequest(lobbyName, joinRequest);
- Optional lobbyOptional = lobbyRouter.findScene(routeRequest).scene();
- if (lobbyOptional.isEmpty()) {
- return false;
- }
-
- Lobby lobby = lobbyOptional.get();
- TransferResult result = lobby.join(joinRequest);
- if (result.executor().isEmpty()) {
- return false;
- }
-
- result.executor().get().run();
- return true;
+ return lobbyRouter.findScene(routeRequest).thenApply(routeResult -> {
+ if (routeResult.scene().isEmpty()) {
+ return false;
+ }
+
+ Lobby lobby = routeResult.scene().get();
+ try (TransferResult result = lobby.join(joinRequest)) {
+ if (result.executor().isEmpty()) {
+ return false;
+ }
+
+ result.executor().get().run();
+ return true;
+ }
+ });
}
}
diff --git a/core/src/main/java/org/phantazm/core/game/scene/lobby/LoginLobbyJoinRequest.java b/core/src/main/java/org/phantazm/core/game/scene/lobby/LoginLobbyJoinRequest.java
index f661db0b6..962f84395 100644
--- a/core/src/main/java/org/phantazm/core/game/scene/lobby/LoginLobbyJoinRequest.java
+++ b/core/src/main/java/org/phantazm/core/game/scene/lobby/LoginLobbyJoinRequest.java
@@ -1,15 +1,12 @@
package org.phantazm.core.game.scene.lobby;
import net.minestom.server.entity.GameMode;
-import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerLoginEvent;
-import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.Instance;
-import net.minestom.server.network.ConnectionManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
import org.phantazm.core.config.InstanceConfig;
-import org.phantazm.core.game.scene.Utils;
+
import org.phantazm.core.player.PlayerView;
import org.phantazm.core.player.PlayerViewProvider;
@@ -43,7 +40,7 @@ public LoginLobbyJoinRequest(@NotNull PlayerLoginEvent event, @NotNull PlayerVie
}
@Override
- public void handleJoin(@NotNull Instance instance, @NotNull InstanceConfig instanceConfig) {
+ public void handleJoin(@NotNull Lobby lobby, @NotNull Instance instance, @NotNull InstanceConfig instanceConfig) {
event.setSpawningInstance(instance);
event.getPlayer().setRespawnPoint(instanceConfig.spawnPoint());
event.getPlayer().setGameMode(GameMode.ADVENTURE);
diff --git a/core/src/main/java/org/phantazm/core/gui/Gui.java b/core/src/main/java/org/phantazm/core/gui/Gui.java
index 16af241a7..feb91502a 100644
--- a/core/src/main/java/org/phantazm/core/gui/Gui.java
+++ b/core/src/main/java/org/phantazm/core/gui/Gui.java
@@ -12,6 +12,8 @@
import java.util.*;
import java.util.function.BiPredicate;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
/**
* Extension of {@link Inventory} designed to ease the creation of graphical user interfaces. May or may not be
@@ -121,27 +123,27 @@ public void removeItem(int slot) {
@Override
public boolean leftClick(@NotNull Player player, int slot) {
- return handleClick(player, slot, super::leftClick, GuiItem.ClickType.LEFT_CLICK);
+ return handleClick(player, slot, () -> super.leftClick(player, slot), GuiItem.ClickType.LEFT_CLICK);
}
@Override
public boolean rightClick(@NotNull Player player, int slot) {
- return handleClick(player, slot, super::rightClick, GuiItem.ClickType.RIGHT_CLICK);
+ return handleClick(player, slot, () -> super.rightClick(player, slot), GuiItem.ClickType.RIGHT_CLICK);
}
@Override
public boolean shiftClick(@NotNull Player player, int slot) {
- return handleClick(player, slot, super::shiftClick, GuiItem.ClickType.SHIFT_CLICK);
+ return handleClick(player, slot, () -> super.shiftClick(player, slot), GuiItem.ClickType.SHIFT_CLICK);
}
@Override
public boolean middleClick(@NotNull Player player, int slot) {
- return handleClick(player, slot, super::middleClick, GuiItem.ClickType.MIDDLE_CLICK);
+ return handleClick(player, slot, () -> super.middleClick(player, slot), GuiItem.ClickType.MIDDLE_CLICK);
}
@Override
- public boolean doubleClick(@NotNull Player player, int slot) {
- return handleClick(player, slot, super::doubleClick, GuiItem.ClickType.DOUBLE_CLICK);
+ public boolean doubleClick(@NotNull Player player, int slot, int button) {
+ return handleClick(player, slot, () -> super.doubleClick(player, slot, button), GuiItem.ClickType.DOUBLE_CLICK);
}
@Override
@@ -183,8 +185,7 @@ private SlottedItem[] getTickItems() {
return tickItems;
}
- private boolean handleClick(Player player, int slot, BiPredicate super Player, Integer> superFunction,
- GuiItem.ClickType clickType) {
+ private boolean handleClick(Player player, int slot, BooleanSupplier superFunction, GuiItem.ClickType clickType) {
if (slot < getInventoryType().getSize()) {
GuiItem item = items.get(slot);
if (item != null) {
@@ -195,7 +196,7 @@ private boolean handleClick(Player player, int slot, BiPredicate super Player,
}
}
- return superFunction.test(player, slot);
+ return superFunction.getAsBoolean();
}
/**
diff --git a/core/src/main/java/org/phantazm/core/guild/invite/InvitationManager.java b/core/src/main/java/org/phantazm/core/guild/invite/InvitationManager.java
index 668b32be8..b11a5f407 100644
--- a/core/src/main/java/org/phantazm/core/guild/invite/InvitationManager.java
+++ b/core/src/main/java/org/phantazm/core/guild/invite/InvitationManager.java
@@ -1,7 +1,7 @@
package org.phantazm.core.guild.invite;
-import it.unimi.dsi.fastutil.objects.Object2LongArrayMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import org.jetbrains.annotations.NotNull;
import org.phantazm.commons.Tickable;
import org.phantazm.core.guild.GuildMember;
@@ -13,7 +13,7 @@
public class InvitationManager implements Tickable {
- private final Object2LongMap latestInviteTimes = new Object2LongArrayMap<>();
+ private final Object2LongMap latestInviteTimes = new Object2LongOpenHashMap<>();
private final Queue invitations = new LinkedList<>();
diff --git a/core/src/main/java/org/phantazm/core/guild/party/PartyChatChannel.java b/core/src/main/java/org/phantazm/core/guild/party/PartyChatChannel.java
index b0531b9e8..c5640e452 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/PartyChatChannel.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/PartyChatChannel.java
@@ -5,6 +5,9 @@
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerChatEvent;
import org.jetbrains.annotations.NotNull;
@@ -14,6 +17,7 @@
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
@@ -24,8 +28,8 @@ public class PartyChatChannel extends BasicChatChannel {
private final Map super UUID, ? extends Party> parties;
public PartyChatChannel(@NotNull Map super UUID, ? extends Party> parties,
- @NotNull PlayerViewProvider viewProvider) {
- super(viewProvider);
+ @NotNull PlayerViewProvider viewProvider, @NotNull MiniMessage miniMessage, @NotNull String chatFormat) {
+ super(viewProvider, miniMessage, chatFormat);
this.parties = Objects.requireNonNull(parties, "parties");
}
@@ -40,9 +44,4 @@ public PartyChatChannel(@NotNull Map super UUID, ? extends Party> parties,
return Pair.of(party.getAudience(), null);
}
- @Override
- public @NotNull Component formatMessage(@NotNull PlayerChatEvent chatEvent) {
- return Component.textOfChildren(Component.text("Party", NamedTextColor.BLUE),
- Component.text(" > ", NamedTextColor.DARK_GRAY), super.formatMessage(chatEvent));
- }
}
diff --git a/core/src/main/java/org/phantazm/core/guild/party/PartyConfig.java b/core/src/main/java/org/phantazm/core/guild/party/PartyConfig.java
index 7457c5d61..405e4d06d 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/PartyConfig.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/PartyConfig.java
@@ -1,10 +1,21 @@
package org.phantazm.core.guild.party;
+import net.minestom.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import org.phantazm.core.guild.party.command.PartyCommandConfig;
import org.phantazm.core.guild.party.notification.PartyNotificationConfig;
public record PartyConfig(@NotNull PartyNotificationConfig notificationConfig,
- @NotNull PartyCommandConfig commandConfig, int creatorRank, int defaultRank, long invitationDuration, int minimumKickRank, int minimumInviteRank,
+ @NotNull PartyCommandConfig commandConfig,
+ int creatorRank,
+ int defaultRank,
+ long invitationDuration,
+ int minimumKickRank,
+ int minimumInviteRank,
int minimumJoinRank) {
+
+ public static final PartyConfig DEFAULT =
+ new PartyConfig(PartyNotificationConfig.DEFAULT, PartyCommandConfig.DEFAULT, 1, 0,
+ 60L * MinecraftServer.TICK_PER_SECOND, 1, 1, 1);
+
}
diff --git a/core/src/main/java/org/phantazm/core/guild/party/PartyCreator.java b/core/src/main/java/org/phantazm/core/guild/party/PartyCreator.java
index d09b2d440..8d05c02b1 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/PartyCreator.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/PartyCreator.java
@@ -85,8 +85,7 @@ private PartyMember createMember(PlayerView playerView) {
public static class Builder {
- private PartyNotificationConfig notificationConfig =
- new PartyNotificationConfig("", "", "", "", "", "", "", "", "", "", "", "");
+ private PartyNotificationConfig notificationConfig = PartyNotificationConfig.DEFAULT;
private TickFormatter tickFormatter = new AnalogTickFormatter(new AnalogTickFormatter.Data(false));
diff --git a/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommand.java b/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommand.java
index 04a0a9379..1b3b98eab 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommand.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommand.java
@@ -18,12 +18,12 @@ private PartyCommand() {
public static @NotNull Command partyCommand(@NotNull PartyCommandConfig config, @NotNull MiniMessage miniMessage,
@NotNull GuildHolder partyHolder, @NotNull PlayerViewProvider viewProvider,
- @NotNull PartyCreator partyCreator, @NotNull Random random) {
+ @NotNull PartyCreator partyCreator, @NotNull Random random, int creatorRank) {
Command command = new Command("party", "p");
command.addSubcommand(PartyCreateCommand.createCommand(config, partyHolder, viewProvider, partyCreator));
command.addSubcommand(
PartyJoinCommand.joinCommand(config, miniMessage, partyHolder.uuidToGuild(), viewProvider));
- command.addSubcommand(PartyLeaveCommand.leaveCommand(config, partyHolder.uuidToGuild(), random));
+ command.addSubcommand(PartyLeaveCommand.leaveCommand(config, partyHolder.uuidToGuild(), random, creatorRank));
command.addSubcommand(PartyKickCommand.kickCommand(config, miniMessage, partyHolder.uuidToGuild(),
viewProvider));
command.addSubcommand(
diff --git a/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommandConfig.java b/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommandConfig.java
index c86e2e115..12ff86c00 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommandConfig.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/command/PartyCommandConfig.java
@@ -22,4 +22,11 @@ public record PartyCommandConfig(@NotNull Component notInParty,
@NotNull String listFormat,
@NotNull String onlineMemberFormat,
@NotNull String offlineMemberFormat) {
+
+ public static final PartyCommandConfig DEFAULT =
+ new PartyCommandConfig(Component.empty(), Component.empty(), Component.empty(), "",
+ "", Component.empty(), Component.empty(), Component.empty(), Component.empty(),
+ "", "", Component.empty(), Component.empty(), "",
+ Component.empty(), "", "", "", "");
+
}
diff --git a/core/src/main/java/org/phantazm/core/guild/party/command/PartyLeaveCommand.java b/core/src/main/java/org/phantazm/core/guild/party/command/PartyLeaveCommand.java
index 9c4ccf5a4..426df4246 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/command/PartyLeaveCommand.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/command/PartyLeaveCommand.java
@@ -1,11 +1,8 @@
package org.phantazm.core.guild.party.command;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
-import org.phantazm.core.guild.GuildHolder;
import org.phantazm.core.guild.party.Party;
import org.phantazm.core.guild.party.PartyMember;
@@ -18,7 +15,7 @@ private PartyLeaveCommand() {
}
public static @NotNull Command leaveCommand(@NotNull PartyCommandConfig config,
- @NotNull Map super UUID, ? extends Party> partyMap, @NotNull Random random) {
+ @NotNull Map super UUID, ? extends Party> partyMap, @NotNull Random random, int creatorRank) {
Objects.requireNonNull(config, "config");
Objects.requireNonNull(partyMap, "partyMap");
Objects.requireNonNull(random, "random");
@@ -72,6 +69,7 @@ private PartyLeaveCommand() {
}
party.getOwner().set(newOwner);
+ newOwner.setRank(creatorRank);
party.getNotification().notifyTransfer(oldMember, newOwner);
}
}
diff --git a/core/src/main/java/org/phantazm/core/guild/party/notification/PartyNotificationConfig.java b/core/src/main/java/org/phantazm/core/guild/party/notification/PartyNotificationConfig.java
index b2f3bdd0d..a62123cf2 100644
--- a/core/src/main/java/org/phantazm/core/guild/party/notification/PartyNotificationConfig.java
+++ b/core/src/main/java/org/phantazm/core/guild/party/notification/PartyNotificationConfig.java
@@ -16,4 +16,8 @@ public record PartyNotificationConfig(
@NotNull String kickToKickedFormat,
@NotNull String transferFormat
) {
+
+ public static final PartyNotificationConfig DEFAULT = new PartyNotificationConfig("", "", "", "", "", "", "", "",
+ "", "", "", "");
+
}
diff --git a/core/src/main/java/org/phantazm/core/instance/AnvilFileSystemInstanceLoader.java b/core/src/main/java/org/phantazm/core/instance/AnvilFileSystemInstanceLoader.java
index 5b0f3197e..af413f4a2 100644
--- a/core/src/main/java/org/phantazm/core/instance/AnvilFileSystemInstanceLoader.java
+++ b/core/src/main/java/org/phantazm/core/instance/AnvilFileSystemInstanceLoader.java
@@ -8,6 +8,7 @@
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
+import java.util.concurrent.Executor;
/**
* A {@link FileSystemInstanceLoader} that loads {@link Instance}s using {@link AnvilLoader}s.
@@ -21,8 +22,8 @@ public class AnvilFileSystemInstanceLoader extends FileSystemInstanceLoader {
* @param chunkSupplier the {@link ChunkSupplier} used to create chunks
*/
public AnvilFileSystemInstanceLoader(@NotNull InstanceManager instanceManager, @NotNull Path rootPath,
- @NotNull ChunkSupplier chunkSupplier) {
- super(instanceManager, rootPath, chunkSupplier);
+ @NotNull ChunkSupplier chunkSupplier, @NotNull Executor executor) {
+ super(instanceManager, rootPath, chunkSupplier, executor);
}
@Override
diff --git a/core/src/main/java/org/phantazm/core/instance/FileSystemInstanceLoader.java b/core/src/main/java/org/phantazm/core/instance/FileSystemInstanceLoader.java
index 8a78b0a86..5f975c395 100644
--- a/core/src/main/java/org/phantazm/core/instance/FileSystemInstanceLoader.java
+++ b/core/src/main/java/org/phantazm/core/instance/FileSystemInstanceLoader.java
@@ -7,6 +7,7 @@
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.utils.chunk.ChunkSupplier;
import net.minestom.server.utils.chunk.ChunkUtils;
+import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
@@ -14,8 +15,8 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Phaser;
+import java.util.UUID;
+import java.util.concurrent.*;
/**
* Implements an {@link InstanceLoader} using the file system.
@@ -31,6 +32,8 @@ public abstract class FileSystemInstanceLoader implements InstanceLoader {
private final Map instanceSources;
+ private final Executor executor;
+
/**
* Creates an {@link InstanceLoader} based on a file system.
*
@@ -38,16 +41,17 @@ public abstract class FileSystemInstanceLoader implements InstanceLoader {
* @param chunkSupplier The {@link ChunkSupplier} used to define the chunk implementation used
*/
public FileSystemInstanceLoader(@NotNull InstanceManager instanceManager, @NotNull Path rootPath,
- @NotNull ChunkSupplier chunkSupplier) {
+ @NotNull ChunkSupplier chunkSupplier, @NotNull Executor executor) {
this.instanceManager = Objects.requireNonNull(instanceManager, "instanceManager");
this.rootPath = Objects.requireNonNull(rootPath, "rootPath");
this.chunkSupplier = Objects.requireNonNull(chunkSupplier, "chunkSupplier");
this.instanceSources = new ConcurrentHashMap<>();
+ this.executor = Objects.requireNonNull(executor, "executorService");
}
// TODO: what if there are distinct spawnPos invocations?
@Override
- public @NotNull Instance loadInstance(@UnmodifiableView @NotNull List subPaths) {
+ public @NotNull CompletableFuture loadInstance(@UnmodifiableView @NotNull List subPaths) {
Path path = rootPath;
for (String subPath : subPaths) {
path = path.resolve(subPath);
@@ -58,11 +62,13 @@ public FileSystemInstanceLoader(@NotNull InstanceManager instanceManager, @NotNu
throw new IllegalArgumentException("Instance at " + path + " has not been preloaded");
}
- InstanceContainer container = source.copy();
- container.setChunkSupplier(chunkSupplier);
- instanceManager.registerInstance(container);
+ return CompletableFuture.supplyAsync(source::copy, executor).thenApply(container -> {
+ container.enableAutoChunkLoad(false);
+ container.setChunkSupplier(chunkSupplier);
+ instanceManager.registerInstance(container);
- return container;
+ return container;
+ });
}
@Override
@@ -81,7 +87,9 @@ public void preload(@UnmodifiableView @NotNull List subPaths, @NotNull P
@SuppressWarnings("UnstableApiUsage")
private InstanceContainer createTemplateContainer(InstanceManager instanceManager, Path path, Point spawnPoint,
int chunkViewDistance) {
- InstanceContainer container = instanceManager.createInstanceContainer(createChunkLoader(path));
+ InstanceContainer container =
+ new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD, createChunkLoader(path));
+ container.enableAutoChunkLoad(false);
container.setChunkSupplier(chunkSupplier);
awaitChunkLoadSync(container, spawnPoint, chunkViewDistance);
@@ -93,7 +101,7 @@ private void awaitChunkLoadSync(Instance instance, Point spawnPoint, int chunkVi
Phaser phaser = new Phaser(1);
ChunkUtils.forChunksInRange(spawnPoint, chunkViewDistance, (chunkX, chunkZ) -> {
phaser.register();
- instance.loadOptionalChunk(chunkX, chunkZ).whenComplete((chunk, throwable) -> phaser.arriveAndDeregister());
+ instance.loadChunk(chunkX, chunkZ).whenComplete((chunk, throwable) -> phaser.arriveAndDeregister());
});
phaser.arriveAndAwaitAdvance();
}
diff --git a/core/src/main/java/org/phantazm/core/instance/InstanceLoader.java b/core/src/main/java/org/phantazm/core/instance/InstanceLoader.java
index 18f392c54..7b19601ec 100644
--- a/core/src/main/java/org/phantazm/core/instance/InstanceLoader.java
+++ b/core/src/main/java/org/phantazm/core/instance/InstanceLoader.java
@@ -6,6 +6,7 @@
import org.jetbrains.annotations.UnmodifiableView;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
/**
* Loads usable {@link Instance}s.
@@ -18,7 +19,7 @@ public interface InstanceLoader {
* @param subPaths Paths used to identify the {@link Instance}
* @return A new {@link Instance}
*/
- @NotNull Instance loadInstance(@UnmodifiableView @NotNull List subPaths);
+ @NotNull CompletableFuture loadInstance(@UnmodifiableView @NotNull List subPaths);
void preload(@UnmodifiableView @NotNull List subPaths, @NotNull Point spawnPos, int chunkViewDistance);
diff --git a/core/src/main/java/org/phantazm/core/inventory/BasicInventoryAccessRegistry.java b/core/src/main/java/org/phantazm/core/inventory/BasicInventoryAccessRegistry.java
index 7c0923008..f58ab290e 100644
--- a/core/src/main/java/org/phantazm/core/inventory/BasicInventoryAccessRegistry.java
+++ b/core/src/main/java/org/phantazm/core/inventory/BasicInventoryAccessRegistry.java
@@ -1,6 +1,7 @@
package org.phantazm.core.inventory;
import net.kyori.adventure.key.Key;
+import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.phantazm.core.equipment.Equipment;
@@ -35,20 +36,20 @@ public void switchAccess(@Nullable Key key) {
if (key == null) {
applyTo(null);
currentAccess = null;
+ return;
}
- else {
- InventoryAccess access = accessMap.get(key);
- if (access == null) {
- throw new IllegalArgumentException("No matching inventory access found");
- }
- if (access == currentAccess) {
- return;
- }
+ InventoryAccess access = accessMap.get(key);
+ if (access == null) {
+ throw new IllegalArgumentException("No matching inventory access found");
+ }
- currentAccess = access;
- applyTo(access);
+ if (access == currentAccess) {
+ return;
}
+
+ applyTo(access);
+ currentAccess = access;
}
}
@@ -67,51 +68,63 @@ public void registerAccess(@NotNull Key key, @NotNull InventoryAccess profile) {
}
@Override
- public void unregisterAccess(@NotNull Key key) {
- Objects.requireNonNull(key, "key");
+ public boolean canPushTo(@NotNull InventoryAccess currentAccess, @NotNull Key groupKey) {
+ InventoryObjectGroup group = currentAccess.groups().get(groupKey);
+ return group != null && !group.isFull();
+ }
- synchronized (sync) {
- if (!accessMap.containsKey(key)) {
- throw new IllegalArgumentException("Inventory profile not yet registered");
- }
+ @Override
+ public @Nullable InventoryObject replaceObject(@NotNull InventoryAccess access, int slot,
+ @NotNull InventoryObject newObject) {
+ InventoryProfile profile = access.profile();
+ InventoryObject old = null;
+ if (profile.hasInventoryObject(slot)) {
+ old = profile.removeInventoryObject(slot);
- accessMap.remove(key);
+ if (access == currentAccess) {
+ old.end();
+ }
}
- }
- @Override
- public boolean canPushTo(@NotNull Key groupKey) {
- Optional accessOptional = getCurrentAccess();
- if (accessOptional.isPresent()) {
- InventoryAccess inventoryAccess = accessOptional.get();
- InventoryObjectGroup group = inventoryAccess.groups().get(groupKey);
- return group != null && !group.isFull();
+ profile.setInventoryObject(slot, newObject);
+
+ if (access == currentAccess) {
+ onAdd(slot, newObject);
}
- return false;
+ return old;
}
@Override
- public void replaceObject(int slot, @NotNull InventoryObject newObject) {
- InventoryAccess access = getAccess();
-
+ public @Nullable InventoryObject removeObject(@NotNull InventoryAccess access, int slot) {
InventoryProfile profile = access.profile();
+
+ InventoryObject old = null;
if (profile.hasInventoryObject(slot)) {
- InventoryObject old = profile.removeInventoryObject(slot);
- old.end();
+ old = profile.removeInventoryObject(slot);
+
+ if (access == this.currentAccess) {
+ old.end();
+ playerView.getPlayer().ifPresent(player -> player.getInventory().setItemStack(slot, ItemStack.AIR));
+ }
}
-
- profile.setInventoryObject(slot, newObject);
- onAdd(slot, newObject);
+
+ return old;
}
@Override
- public void pushObject(@NotNull Key groupKey, @NotNull InventoryObject object) {
- InventoryAccess access = getAccess();
+ public void pushObject(@NotNull InventoryAccess access, @NotNull Key groupKey, @NotNull InventoryObject object) {
InventoryObjectGroup group = getGroup(access, groupKey);
int slot = group.pushInventoryObject(object);
- onAdd(slot, object);
+ if (access == currentAccess) {
+ onAdd(slot, object);
+ }
+ }
+
+ @Override
+ public @NotNull InventoryAccess getAccess(@NotNull Key profileKey) {
+ return Objects.requireNonNull(accessMap.get(profileKey), "no access named " + profileKey);
}
private void onAdd(int slot, InventoryObject object) {
@@ -123,15 +136,6 @@ private void onAdd(int slot, InventoryObject object) {
});
}
- private InventoryAccess getAccess() {
- Optional accessOptional = getCurrentAccess();
- if (accessOptional.isEmpty()) {
- throw new IllegalArgumentException("No current access");
- }
-
- return accessOptional.get();
- }
-
private InventoryObjectGroup getGroup(InventoryAccess access, Key groupKey) {
InventoryObjectGroup group = access.groups().get(groupKey);
if (group == null) {
@@ -145,18 +149,22 @@ private void applyTo(InventoryAccess newAccess) {
playerView.getPlayer().ifPresent(player -> {
player.getInventory().clear();
- InventoryProfile oldProfile = this.currentAccess.profile();
- for (int slot = 0; slot < oldProfile.getSlotCount(); slot++) {
- if (!oldProfile.hasInventoryObject(slot)) {
- continue;
- }
+ InventoryAccess oldAccess = this.currentAccess;
- InventoryObject object = oldProfile.getInventoryObject(slot);
- if (slot == player.getHeldSlot() && object instanceof Equipment equipment) {
- equipment.setSelected(false);
- }
+ if (oldAccess != null) {
+ InventoryProfile oldProfile = oldAccess.profile();
+ for (int slot = 0; slot < oldProfile.getSlotCount(); slot++) {
+ if (!oldProfile.hasInventoryObject(slot)) {
+ continue;
+ }
+
+ InventoryObject object = oldProfile.getInventoryObject(slot);
+ if (slot == player.getHeldSlot() && object instanceof Equipment equipment) {
+ equipment.setSelected(false);
+ }
- object.end();
+ object.end();
+ }
}
if (newAccess != null) {
diff --git a/core/src/main/java/org/phantazm/core/inventory/InventoryAccessRegistry.java b/core/src/main/java/org/phantazm/core/inventory/InventoryAccessRegistry.java
index 8d2147b13..067f71c75 100644
--- a/core/src/main/java/org/phantazm/core/inventory/InventoryAccessRegistry.java
+++ b/core/src/main/java/org/phantazm/core/inventory/InventoryAccessRegistry.java
@@ -35,17 +35,31 @@ public interface InventoryAccessRegistry {
*/
void registerAccess(@NotNull Key key, @NotNull InventoryAccess profile);
- /**
- * Unregisters a {@link InventoryProfile} from the view.
- *
- * @param key The {@link Key} of the {@link InventoryProfile} to unregister
- * @throws IllegalArgumentException If no {@link InventoryProfile} is registered with the {@link Key}
- */
- void unregisterAccess(@NotNull Key key);
+ default boolean canPushTo(@NotNull Key groupKey) {
+ return getCurrentAccess().filter(access -> canPushTo(access, groupKey)).isPresent();
+ }
+
+ boolean canPushTo(@NotNull InventoryAccess currentAccess, @NotNull Key groupKey);
+
+ default @Nullable InventoryObject replaceObject(int slot, @NotNull InventoryObject newObject) {
+ return getCurrentAccess().map(access -> replaceObject(access, slot, newObject)).orElse(null);
+
+ }
+
+ @Nullable InventoryObject replaceObject(@NotNull InventoryAccess currentAccess, int slot,
+ @NotNull InventoryObject newObject);
+
+ default @Nullable InventoryObject removeObject(int slot) {
+ return getCurrentAccess().map(access -> removeObject(access, slot)).orElse(null);
+ }
+
+ @Nullable InventoryObject removeObject(@NotNull InventoryAccess currentAccess, int slot);
- boolean canPushTo(@NotNull Key groupKey);
+ default void pushObject(@NotNull Key groupKey, @NotNull InventoryObject object) {
+ getCurrentAccess().ifPresent(access -> pushObject(access, groupKey, object));
+ }
- void replaceObject(int slot, @NotNull InventoryObject newObject);
+ void pushObject(@NotNull InventoryAccess currentAccess, @NotNull Key groupKey, @NotNull InventoryObject object);
- void pushObject(@NotNull Key groupKey, @NotNull InventoryObject object);
+ @NotNull InventoryAccess getAccess(@NotNull Key profileKey);
}
diff --git a/core/src/main/java/org/phantazm/core/item/StaticUpdatingItem.java b/core/src/main/java/org/phantazm/core/item/StaticUpdatingItem.java
index 69da81be0..b87e7454c 100644
--- a/core/src/main/java/org/phantazm/core/item/StaticUpdatingItem.java
+++ b/core/src/main/java/org/phantazm/core/item/StaticUpdatingItem.java
@@ -1,5 +1,6 @@
package org.phantazm.core.item;
+import com.github.steanky.element.core.annotation.Cache;
import com.github.steanky.element.core.annotation.DataObject;
import com.github.steanky.element.core.annotation.FactoryMethod;
import com.github.steanky.element.core.annotation.Model;
@@ -9,6 +10,7 @@
import java.util.Objects;
@Model("item.updating.static")
+@Cache
public class StaticUpdatingItem implements UpdatingItem {
private final Data data;
diff --git a/core/src/main/java/org/phantazm/core/npc/NPCHandler.java b/core/src/main/java/org/phantazm/core/npc/NPCHandler.java
index 589350e2c..12257d1cf 100644
--- a/core/src/main/java/org/phantazm/core/npc/NPCHandler.java
+++ b/core/src/main/java/org/phantazm/core/npc/NPCHandler.java
@@ -1,7 +1,7 @@
package org.phantazm.core.npc;
import net.minestom.server.entity.Entity;
-import net.minestom.server.event.EventFilter;
+import net.minestom.server.entity.Player;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import net.minestom.server.event.trait.InstanceEvent;
@@ -13,24 +13,24 @@
import java.util.Objects;
public class NPCHandler implements Tickable {
- private final Instance instance;
private final List npcs;
- private final EventNode instanceEventNode;
- private final EventNode handlerNode;
+ private final Instance instance;
+ private final EventNode instanceNode;
- public NPCHandler(@NotNull List npcs, @NotNull Instance instance) {
- this.instance = Objects.requireNonNull(instance, "instance");
+ public NPCHandler(@NotNull List npcs, @NotNull Instance instance,
+ @NotNull EventNode instanceNode) {
this.npcs = List.copyOf(npcs);
- this.instanceEventNode = instance.eventNode();
-
- this.handlerNode = EventNode.event("npc_handler_{" + instance.getUniqueId() + "}",
- EventFilter.from(InstanceEvent.class, Instance.class, InstanceEvent::getInstance), event -> true);
+ this.instance = Objects.requireNonNull(instance, "instance");
+ this.instanceNode = Objects.requireNonNull(instanceNode, "instanceNode");
- handlerNode.addListener(PlayerEntityInteractEvent.class, this::entityInteractEvent);
- instanceEventNode.addChild(handlerNode);
+ instanceNode.addListener(PlayerEntityInteractEvent.class, this::entityInteractEvent);
}
private void entityInteractEvent(PlayerEntityInteractEvent event) {
+ if (event.getHand() != Player.Hand.MAIN) {
+ return;
+ }
+
if (event.getInstance() != instance) {
return;
}
@@ -53,8 +53,6 @@ public void end() {
for (NPC npc : npcs) {
npc.despawn();
}
-
- instanceEventNode.removeChild(handlerNode);
}
@Override
@@ -63,4 +61,8 @@ public void tick(long time) {
npc.tick(time);
}
}
+
+ public @NotNull EventNode instanceNode() {
+ return instanceNode;
+ }
}
diff --git a/core/src/main/java/org/phantazm/core/npc/interactor/MessageInteractor.java b/core/src/main/java/org/phantazm/core/npc/interactor/MessageInteractor.java
new file mode 100644
index 000000000..0656850de
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/npc/interactor/MessageInteractor.java
@@ -0,0 +1,48 @@
+package org.phantazm.core.npc.interactor;
+
+import com.github.steanky.element.core.annotation.Cache;
+import com.github.steanky.element.core.annotation.DataObject;
+import com.github.steanky.element.core.annotation.FactoryMethod;
+import com.github.steanky.element.core.annotation.Model;
+import com.github.steanky.ethylene.core.ConfigElement;
+import com.github.steanky.ethylene.core.ConfigPrimitive;
+import com.github.steanky.ethylene.mapper.annotation.Default;
+import net.kyori.adventure.text.Component;
+import net.minestom.server.entity.Player;
+import net.minestom.server.instance.Instance;
+import org.jetbrains.annotations.NotNull;
+
+@Model("npc.interactor.message")
+@Cache
+public class MessageInteractor implements Interactor {
+ private final Data data;
+
+ @FactoryMethod
+ public MessageInteractor(@NotNull Data data) {
+ this.data = data;
+ }
+
+ @Override
+ public void interact(@NotNull Player player) {
+ if (data.broadcast) {
+ Instance instance = player.getInstance();
+ if (instance != null) {
+ instance.sendMessage(data.message);
+ }
+ else {
+ player.sendMessage(data.message);
+ }
+ }
+ else {
+ player.sendMessage(data.message);
+ }
+ }
+
+ @DataObject
+ public record Data(@NotNull Component message, boolean broadcast) {
+ @Default("broadcast")
+ public static @NotNull ConfigElement broadcastDefault() {
+ return ConfigPrimitive.of(false);
+ }
+ }
+}
diff --git a/core/src/main/java/org/phantazm/core/npc/interactor/ShowGuiInteractor.java b/core/src/main/java/org/phantazm/core/npc/interactor/ShowGuiInteractor.java
new file mode 100644
index 000000000..9e89892ee
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/npc/interactor/ShowGuiInteractor.java
@@ -0,0 +1,48 @@
+package org.phantazm.core.npc.interactor;
+
+import com.github.steanky.element.core.annotation.*;
+import com.github.steanky.ethylene.core.ConfigElement;
+import com.github.steanky.ethylene.core.ConfigPrimitive;
+import com.github.steanky.ethylene.mapper.annotation.Default;
+import net.minestom.server.entity.Player;
+import net.minestom.server.inventory.InventoryType;
+import org.jetbrains.annotations.NotNull;
+import org.phantazm.core.gui.BasicSlotDistributor;
+import org.phantazm.core.gui.Gui;
+import org.phantazm.core.gui.GuiItem;
+
+import java.util.List;
+
+@Model("npc.interactor.show_gui")
+@Cache(false)
+public class ShowGuiInteractor implements Interactor {
+ private final Data data;
+ private final List guiItems;
+
+ @FactoryMethod
+ public ShowGuiInteractor(@NotNull Data data, @NotNull @Child("gui_items") List guiItems) {
+ this.data = data;
+ this.guiItems = guiItems;
+ }
+
+ @Override
+ public void interact(@NotNull Player player) {
+ player.openInventory(
+ Gui.builder(data.inventoryType, new BasicSlotDistributor(data.padding)).withItems(guiItems).build());
+ }
+
+ @DataObject
+ public record Data(@NotNull InventoryType inventoryType,
+ int padding,
+ @NotNull @ChildPath("gui_items") List guiItems) {
+ @Default("inventoryType")
+ public static @NotNull ConfigElement defaultInventoryType() {
+ return ConfigPrimitive.of("CHEST_3_ROW");
+ }
+
+ @Default("padding")
+ public static @NotNull ConfigElement defaultPadding() {
+ return ConfigPrimitive.of(1);
+ }
+ }
+}
diff --git a/core/src/main/java/org/phantazm/core/npc/interactor/item/InteractorDelegatingItem.java b/core/src/main/java/org/phantazm/core/npc/interactor/item/InteractorDelegatingItem.java
new file mode 100644
index 000000000..fb569fb94
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/npc/interactor/item/InteractorDelegatingItem.java
@@ -0,0 +1,66 @@
+package org.phantazm.core.npc.interactor.item;
+
+import com.github.steanky.element.core.annotation.*;
+import com.github.steanky.ethylene.core.ConfigElement;
+import com.github.steanky.ethylene.core.ConfigPrimitive;
+import com.github.steanky.ethylene.mapper.annotation.Default;
+import net.minestom.server.entity.Player;
+import net.minestom.server.item.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.phantazm.core.gui.Gui;
+import org.phantazm.core.gui.GuiItem;
+import org.phantazm.core.npc.interactor.Interactor;
+
+@Model("npc.interactor.gui.item")
+@Cache(false)
+public class InteractorDelegatingItem implements GuiItem {
+ private final Data data;
+ private final Interactor interactor;
+
+ @FactoryMethod
+ public InteractorDelegatingItem(@NotNull Data data, @NotNull @Child("interactor") Interactor interactor) {
+ this.data = data;
+ this.interactor = interactor;
+ }
+
+ @Override
+ public void handleClick(@NotNull Gui owner, @NotNull Player player, int slot, @NotNull ClickType clickType) {
+ if (clickType == ClickType.LEFT_CLICK) {
+ interactor.interact(player);
+
+ if (data.closeOnClick) {
+ player.closeInventory();
+ }
+ }
+ }
+
+ @Override
+ public void onRemove(@NotNull Gui owner, int slot) {
+
+ }
+
+ @Override
+ public void onReplace(@NotNull Gui owner, @NotNull GuiItem newItem, int slot) {
+
+ }
+
+ @Override
+ public @NotNull ItemStack getItemStack() {
+ return data.itemStack;
+ }
+
+ @Override
+ public boolean shouldRedraw() {
+ return false;
+ }
+
+ @DataObject
+ public record Data(@NotNull ItemStack itemStack,
+ boolean closeOnClick,
+ @NotNull @ChildPath("interactor") String interactor) {
+ @Default("closeOnClick")
+ public static @NotNull ConfigElement defaultCloseOnClick() {
+ return ConfigPrimitive.of(true);
+ }
+ }
+}
diff --git a/core/src/main/java/org/phantazm/core/npc/supplier/PlayerEntitySupplier.java b/core/src/main/java/org/phantazm/core/npc/supplier/PlayerEntitySupplier.java
index cb32f3b1b..7e82064fe 100644
--- a/core/src/main/java/org/phantazm/core/npc/supplier/PlayerEntitySupplier.java
+++ b/core/src/main/java/org/phantazm/core/npc/supplier/PlayerEntitySupplier.java
@@ -11,6 +11,7 @@
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.PlayerSkin;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.phantazm.core.entity.fakeplayer.MinimalFakePlayer;
import java.util.function.Supplier;
@@ -27,15 +28,29 @@ public PlayerEntitySupplier(@NotNull Data data) {
@Override
public Entity get() {
- return new MinimalFakePlayer(MinecraftServer.getSchedulerManager(), data.playerName,
- PlayerSkin.fromUuid(data.skinUUID.replace("-", "")));
+ if (data.skinUUID != null) {
+ return new MinimalFakePlayer(MinecraftServer.getSchedulerManager(), data.playerName,
+ PlayerSkin.fromUuid(data.skinUUID.replace("-", "")));
+ }
+
+ return new MinimalFakePlayer(MinecraftServer.getSchedulerManager(), data.playerName, data.playerSkin);
}
@DataObject
- public record Data(@NotNull String playerName, @NotNull String skinUUID) {
+ public record Data(@NotNull String playerName, @Nullable String skinUUID, @Nullable PlayerSkin playerSkin) {
@Default("playerName")
public static ConfigElement defaultPlayerName() {
return ConfigPrimitive.of("");
}
+
+ @Default("skinUUID")
+ public static ConfigElement defaultSkinUUID() {
+ return ConfigPrimitive.NULL;
+ }
+
+ @Default("playerSkin")
+ public static ConfigElement defaultPlayerSkin() {
+ return ConfigPrimitive.NULL;
+ }
}
}
diff --git a/server/src/main/java/org/phantazm/server/packet/BinaryDataReader.java b/core/src/main/java/org/phantazm/core/packet/BinaryDataReader.java
similarity index 96%
rename from server/src/main/java/org/phantazm/server/packet/BinaryDataReader.java
rename to core/src/main/java/org/phantazm/core/packet/BinaryDataReader.java
index 048db64a5..d45641bf8 100644
--- a/server/src/main/java/org/phantazm/server/packet/BinaryDataReader.java
+++ b/core/src/main/java/org/phantazm/core/packet/BinaryDataReader.java
@@ -1,4 +1,4 @@
-package org.phantazm.server.packet;
+package org.phantazm.core.packet;
import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter;
diff --git a/server/src/main/java/org/phantazm/server/packet/BinaryDataWriter.java b/core/src/main/java/org/phantazm/core/packet/BinaryDataWriter.java
similarity index 76%
rename from server/src/main/java/org/phantazm/server/packet/BinaryDataWriter.java
rename to core/src/main/java/org/phantazm/core/packet/BinaryDataWriter.java
index ceee74737..46b667b0d 100644
--- a/server/src/main/java/org/phantazm/server/packet/BinaryDataWriter.java
+++ b/core/src/main/java/org/phantazm/core/packet/BinaryDataWriter.java
@@ -1,5 +1,6 @@
-package org.phantazm.server.packet;
+package org.phantazm.core.packet;
+import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
import org.phantazm.messaging.serialization.DataReader;
@@ -23,6 +24,10 @@ public BinaryDataWriter(@NotNull BinaryWriter binaryWriter) {
this.binaryWriter = Objects.requireNonNull(binaryWriter, "binaryWriter");
}
+ public static @NotNull BinaryDataWriter fromNetworkBuffer(@NotNull NetworkBuffer networkBuffer) {
+ return new BinaryDataWriter(new BinaryWriter(Objects.requireNonNull(networkBuffer, "networkBuffer")));
+ }
+
@Override
public void writeByte(byte data) {
binaryWriter.writeByte(data);
diff --git a/core/src/main/java/org/phantazm/core/packet/MinestomPacketUtils.java b/core/src/main/java/org/phantazm/core/packet/MinestomPacketUtils.java
new file mode 100644
index 000000000..3aec8f816
--- /dev/null
+++ b/core/src/main/java/org/phantazm/core/packet/MinestomPacketUtils.java
@@ -0,0 +1,15 @@
+package org.phantazm.core.packet;
+
+import net.minestom.server.network.NetworkBuffer;
+import org.jetbrains.annotations.NotNull;
+import org.phantazm.messaging.packet.Packet;
+
+public class MinestomPacketUtils {
+
+ public static byte @NotNull[] serialize(@NotNull Packet packet) {
+ return NetworkBuffer.makeArray(buffer -> {
+ packet.write(BinaryDataWriter.fromNetworkBuffer(buffer));
+ });
+ }
+
+}
diff --git a/core/src/main/java/org/phantazm/core/player/BasicPlayerView.java b/core/src/main/java/org/phantazm/core/player/BasicPlayerView.java
index 073964ca8..29ed51d36 100644
--- a/core/src/main/java/org/phantazm/core/player/BasicPlayerView.java
+++ b/core/src/main/java/org/phantazm/core/player/BasicPlayerView.java
@@ -126,6 +126,17 @@ private CompletableFuture getUsernameRequest() {
return getUsername().thenApply(Component::text);
}
+ @Override
+ public @NotNull Component getDisplayNameIfPresent() {
+ Optional extends Component> cached = getPlayer().map(Player::getDisplayName);
+ if (cached.isPresent()) {
+ return cached.get();
+ }
+
+ return getUsernameIfCached().map(Component::text).orElseGet(() -> Component.text(getUUID().toString()));
+
+ }
+
@Override
public @NotNull Optional extends Component> getDisplayNameIfCached() {
return getPlayer().map(Player::getDisplayName).or(() -> getUsernameIfCached().map(Component::text));
diff --git a/core/src/main/java/org/phantazm/core/player/PlayerView.java b/core/src/main/java/org/phantazm/core/player/PlayerView.java
index 01cc88855..9f7b33c33 100644
--- a/core/src/main/java/org/phantazm/core/player/PlayerView.java
+++ b/core/src/main/java/org/phantazm/core/player/PlayerView.java
@@ -41,6 +41,8 @@ public interface PlayerView {
@NotNull CompletableFuture extends Component> getDisplayName();
+ @NotNull Component getDisplayNameIfPresent();
+
@NotNull Optional extends Component> getDisplayNameIfCached();
/**
diff --git a/core/src/main/java/org/phantazm/core/sound/BasicSongPlayer.java b/core/src/main/java/org/phantazm/core/sound/BasicSongPlayer.java
index 9a9a3e385..eda517ba1 100644
--- a/core/src/main/java/org/phantazm/core/sound/BasicSongPlayer.java
+++ b/core/src/main/java/org/phantazm/core/sound/BasicSongPlayer.java
@@ -3,6 +3,8 @@
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.MinecraftServer;
+import net.minestom.server.coordinate.Point;
+import net.minestom.server.coordinate.Vec;
import org.jetbrains.annotations.NotNull;
import java.util.Deque;
@@ -23,9 +25,29 @@ public void tick(long time) {
}
@Override
- public @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Emitter emitter, @NotNull List notes,
- boolean loop) {
- SongImpl song = new SongImpl(audience, emitter, notes, loop);
+ public @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, @NotNull Sound.Emitter emitter,
+ @NotNull List notes, float volume, boolean loop) {
+ Objects.requireNonNull(audience, "audience");
+ Objects.requireNonNull(source, "source");
+ Objects.requireNonNull(emitter, "emitter");
+ Objects.requireNonNull(notes, "notes");
+
+ SongImpl song = new SongImpl(audience, emitter, source, null, notes, volume, loop);
+ if (song.nextNote != null) {
+ songDeque.add(song);
+ }
+
+ return song;
+ }
+
+ @Override
+ public @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, double x, double y, double z,
+ @NotNull List notes, float volume, boolean loop) {
+ Objects.requireNonNull(audience, "audience");
+ Objects.requireNonNull(source, "source");
+ Objects.requireNonNull(notes, "notes");
+
+ SongImpl song = new SongImpl(audience, null, source, new Vec(x, y, z), notes, volume, loop);
if (song.nextNote != null) {
songDeque.add(song);
}
@@ -36,7 +58,10 @@ public void tick(long time) {
private static class SongImpl implements Song {
private final Audience audience;
private final Sound.Emitter emitter;
+ private final Sound.Source source;
+ private final Point location;
private final List notes;
+ private final float volume;
private final boolean loop;
private boolean stopped;
@@ -46,10 +71,14 @@ private static class SongImpl implements Song {
private Note nextNote;
private long lastNoteTime;
- private SongImpl(Audience audience, Sound.Emitter emitter, List notes, boolean loop) {
+ private SongImpl(Audience audience, Sound.Emitter emitter, Sound.Source source, Point location,
+ List notes, float volume, boolean loop) {
this.audience = Objects.requireNonNull(audience, "audience");
- this.emitter = Objects.requireNonNull(emitter, "emitter");
+ this.emitter = emitter;
+ this.source = source;
+ this.location = location;
this.notes = List.copyOf(notes);
+ this.volume = volume;
this.loop = loop;
this.noteIndex = 0;
@@ -97,7 +126,16 @@ private boolean tick(long time) {
this.lastNoteTime = time;
do {
- this.audience.playSound(this.currentSound = nextNote.sound(), this.emitter);
+ if (this.emitter != null) {
+ this.audience.playSound(
+ this.currentSound = Sound.sound(nextNote.soundType(), source, volume, nextNote.pitch()),
+ this.emitter);
+ }
+ else {
+ this.audience.playSound(
+ this.currentSound = Sound.sound(nextNote.soundType(), source, volume, nextNote.pitch()),
+ location.x(), location.y(), location.z());
+ }
nextNoteIndex = ++this.noteIndex;
if (nextNoteIndex < this.notes.size()) {
diff --git a/core/src/main/java/org/phantazm/core/sound/NBSSongLoader.java b/core/src/main/java/org/phantazm/core/sound/NBSSongLoader.java
index b29eb3e0e..a8744c346 100644
--- a/core/src/main/java/org/phantazm/core/sound/NBSSongLoader.java
+++ b/core/src/main/java/org/phantazm/core/sound/NBSSongLoader.java
@@ -3,7 +3,6 @@
import com.github.steanky.element.core.key.Constants;
import com.github.steanky.element.core.key.KeyParser;
import net.kyori.adventure.key.Key;
-import net.kyori.adventure.sound.Sound;
import org.apache.commons.lang3.StringUtils;
import org.intellij.lang.annotations.Subst;
import org.jetbrains.annotations.NotNull;
@@ -267,8 +266,7 @@ private record NBSNote(int delayTick, int instrumentIndex, float normalizedPitch
instrumentKey = customInstrumentKeys[index];
}
- Sound sound = Sound.sound(instrumentKey, Sound.Source.MUSIC, 10, nbsNote.normalizedPitch);
- actualNotes.add(new SongPlayer.Note(sound, nbsNote.delayTick));
+ actualNotes.add(new SongPlayer.Note(instrumentKey, nbsNote.normalizedPitch, nbsNote.delayTick));
}
return Optional.of(List.copyOf(actualNotes));
@@ -282,7 +280,7 @@ private record NBSNote(int delayTick, int instrumentIndex, float normalizedPitch
}
private static float normalizeKey(int key, float detune) {
- float uses = (key + detune) - MIN_KEY;
+ double uses = (key + (double)detune) - MIN_KEY;
return (float)Math.pow(2, (uses - 12) / 12D);
}
diff --git a/core/src/main/java/org/phantazm/core/sound/SongPlayer.java b/core/src/main/java/org/phantazm/core/sound/SongPlayer.java
index 3755d220f..f3c4b517e 100644
--- a/core/src/main/java/org/phantazm/core/sound/SongPlayer.java
+++ b/core/src/main/java/org/phantazm/core/sound/SongPlayer.java
@@ -1,18 +1,39 @@
package org.phantazm.core.sound;
import net.kyori.adventure.audience.Audience;
+import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
+import net.minestom.server.coordinate.Point;
import org.jetbrains.annotations.NotNull;
import org.phantazm.commons.Tickable;
import java.util.List;
public interface SongPlayer extends Tickable {
- @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Emitter emitter, @NotNull List notes,
- boolean loop);
+ @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, @NotNull Sound.Emitter emitter,
+ @NotNull List notes, float volume, boolean loop);
- default @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Emitter emitter, @NotNull List notes) {
- return play(audience, emitter, notes, false);
+ @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, double x, double y, double z,
+ @NotNull List notes, float volume, boolean loop);
+
+ default @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, double x, double y, double z,
+ @NotNull List notes, float volume) {
+ return play(audience, source, x, y, z, notes, volume, false);
+ }
+
+ default @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, @NotNull Point point,
+ @NotNull List notes, float volume) {
+ return play(audience, source, point.x(), point.y(), point.z(), notes, volume);
+ }
+
+ default @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, @NotNull Sound.Emitter emitter,
+ @NotNull List notes, float volume) {
+ return play(audience, source, emitter, notes, volume, false);
+ }
+
+ default @NotNull Song play(@NotNull Audience audience, @NotNull Sound.Source source, @NotNull Point point,
+ @NotNull List notes, float volume, boolean loop) {
+ return play(audience, source, point.x(), point.y(), point.z(), notes, volume, loop);
}
interface Song {
@@ -21,6 +42,6 @@ interface Song {
boolean isFinished();
}
- record Note(@NotNull Sound sound, int ticks) {
+ record Note(@NotNull Key soundType, float pitch, int ticks) {
}
}
diff --git a/core/src/main/java/org/phantazm/core/tracker/BoundedTrackerImpl.java b/core/src/main/java/org/phantazm/core/tracker/BoundedTrackerImpl.java
index 6465efb04..298ac122b 100644
--- a/core/src/main/java/org/phantazm/core/tracker/BoundedTrackerImpl.java
+++ b/core/src/main/java/org/phantazm/core/tracker/BoundedTrackerImpl.java
@@ -62,11 +62,11 @@ private static Long2ObjectMap