diff --git a/.run/Minecraft Client.run.xml b/.run/Minecraft Client.run.xml
new file mode 100644
index 00000000..46b7a191
--- /dev/null
+++ b/.run/Minecraft Client.run.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/Minecraft Server.run.xml b/.run/Minecraft Server.run.xml
new file mode 100644
index 00000000..73ac751e
--- /dev/null
+++ b/.run/Minecraft Server.run.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/joinpoints server.run.xml b/.run/joinpoints server.run.xml
new file mode 100644
index 00000000..7f805263
--- /dev/null
+++ b/.run/joinpoints server.run.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a924c397..083c5fd3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,9 @@ plugins {
build {
dependsOn ':ec-core:build'
+ // Don't build joinpoints by default since it requires EC jars to exist first
+ // Build joinpoints separately with: ./gradlew :joinpoints:build
+ // Or for testing: ./gradlew :joinpoints:runServer (which auto-builds EC jars)
}
ext.env = loadenv()
@@ -49,8 +52,11 @@ allprojects {
testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
subprojects.each {
- implementation project(path: ":${it.name}", configuration: "namedElements")
- include project("${it.name}:")
+ // Skip joinpoints - it's a separate mod that depends on EC, not a library to include in EC
+ if (it.name != 'joinpoints') {
+ implementation project(path: ":${it.name}", configuration: "namedElements")
+ include project("${it.name}:")
+ }
}
}
@@ -79,7 +85,7 @@ allprojects {
repositories {
gradlePluginPortal()
mavenCentral()
-// mavenLocal()
+ mavenLocal()
maven { name 'Fabric'; url 'https://maven.fabricmc.net/' }
// maven { name 'TerraformersMC'; url 'https://maven.terraformersmc.com/' }
// Add repositories to retrieve artifacts from in here.
@@ -123,17 +129,16 @@ dependencies {
include "org.yaml:snakeyaml:${project.snakeyaml_version}"
modImplementation "io.github.ladysnake:PlayerAbilityLib:${pal_version}"
include "io.github.ladysnake:PlayerAbilityLib:${pal_version}"
-
- // SQLite for joinpoints storage
- implementation "org.xerial:sqlite-jdbc:3.47.1.0"
- include "org.xerial:sqlite-jdbc:3.47.1.0"
// mod compatibility
modCompileOnly "maven.modrinth:vanish:${project.vanish_version}"
subprojects.each {
- implementation project(path: ":${it.name}", configuration: "namedElements")
- include project("${it.name}:")
+ // Skip joinpoints - it's a separate mod that depends on EC, not a library to include in EC
+ if (it.name != 'joinpoints') {
+ implementation project(path: ":${it.name}", configuration: "namedElements")
+ include project("${it.name}:")
+ }
}
}
diff --git a/joinpoints/build.gradle b/joinpoints/build.gradle
new file mode 100644
index 00000000..d17a8fde
--- /dev/null
+++ b/joinpoints/build.gradle
@@ -0,0 +1,88 @@
+plugins {
+ id 'maven-publish'
+}
+
+// Ensure EC projects are evaluated and built before joinpoints configuration
+// This is needed because modLocalRuntime needs the jars to exist
+evaluationDependsOnChildren()
+project.evaluationDependsOn(':')
+project.evaluationDependsOn(':ec-core')
+
+base.archivesName = "joinpoints"
+
+dependencies {
+ // Essential Commands dependency (joinpoints hooks into EC)
+ // Reference the compiled classes directories directly
+ compileOnly files(rootProject.sourceSets.main.output.classesDirs)
+ compileOnly files(project(':ec-core').sourceSets.main.output.classesDirs)
+
+ // Make EC available as a mod at runtime for testing
+ // Only add if jars exist (handles clean builds where jars don't exist yet)
+ def ecJar = rootProject.file("build/libs/essential_commands-${rootProject.version}.jar")
+ def ecCoreJar = project(':ec-core').file("build/libs/ec-core-${project(':ec-core').version}.jar")
+ if (ecJar.exists() && ecCoreJar.exists()) {
+ modLocalRuntime(project(':')) { transitive = false }
+ modLocalRuntime(project(':ec-core')) { transitive = false }
+ }
+
+ // EC's transitive dependencies for runtime
+ modRuntimeOnly "eu.pb4:placeholder-api:${project.placeholder_api_version}"
+ modRuntimeOnly "org.yaml:snakeyaml:${project.snakeyaml_version}"
+ modRuntimeOnly "io.github.ladysnake:PlayerAbilityLib:${project.pal_version}"
+
+ // Fabric API - needed for command registration
+ modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
+
+ // Permissions API - used by joinpoints
+ modImplementation "me.lucko:fabric-permissions-api:${project.permissions_api_version}"
+
+ // SQLite for joinpoints storage
+ implementation "org.xerial:sqlite-jdbc:3.47.1.0"
+ include "org.xerial:sqlite-jdbc:3.47.1.0"
+
+ testImplementation platform("org.junit:junit-bom:${project.junit_bom_version}")
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+ testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
+}
+
+// Ensure EC is built before joinpoints compiles
+tasks.named('compileJava').configure {
+ dependsOn(rootProject.tasks.named('classes'))
+ dependsOn(project(':ec-core').tasks.named('classes'))
+}
+
+// Task to clean Loom cache for EC jars when EC code changes
+// Run this manually if EC changes aren't being picked up: ./gradlew :joinpoints:cleanECCache
+tasks.register('cleanECCache') {
+ group = 'fabric'
+ description = 'Clears cached EC jars from Loom to pick up latest changes'
+ doLast {
+ // Only delete EC-related cached jars, not all remapped mods
+ delete rootProject.fileTree('.gradle/loom-cache/remapped_mods') {
+ include '**/essential_commands-*/**'
+ include '**/ec-core-*/**'
+ }
+ logger.lifecycle('Cleared EC jars from Loom cache')
+ }
+}
+
+// Ensure EC jars are rebuilt before running the server
+tasks.matching { it.name.startsWith('run') }.configureEach {
+ dependsOn(rootProject.tasks.named('remapJar'))
+ dependsOn(project(':ec-core').tasks.named('remapJar'))
+}
+
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ artifactId 'joinpoints'
+ groupId project.maven_group
+ version project.version
+ artifact(remapJar) { builtBy remapJar }
+ artifact(sourcesJar) {
+ builtBy remapSourcesJar
+ }
+ }
+ }
+}
diff --git a/joinpoints/src/main/java/com/fibermc/joinpoints/Joinpoints.java b/joinpoints/src/main/java/com/fibermc/joinpoints/Joinpoints.java
new file mode 100644
index 00000000..26d08731
--- /dev/null
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/Joinpoints.java
@@ -0,0 +1,103 @@
+package com.fibermc.joinpoints;
+
+import java.nio.file.Path;
+
+import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
+import com.fibermc.essentialcommands.events.NicknameChangeCallback;
+import com.fibermc.essentialcommands.events.PlayerConnectCallback;
+import com.fibermc.essentialcommands.text.ECText;
+import com.fibermc.joinpoints.config.JoinpointsConfig;
+import com.fibermc.joinpoints.database.JoinpointDatabase;
+import com.google.gson.JsonElement;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.mojang.serialization.JsonOps;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.text.TextCodecs;
+
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
+
+public class Joinpoints implements ModInitializer {
+ public static final String MOD_ID = "joinpoints";
+ public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
+
+ private static final JoinpointsConfig config = new JoinpointsConfig(
+ Path.of("./config/joinpoints.properties"),
+ "Joinpoints Configuration",
+ "https://github.com/John-Paul-R/Essential-Commands" // TODO: Update with joinpoints docs link
+ );
+ private static JoinpointDatabase database;
+ private static MinecraftServer server;
+
+ @Override
+ public void onInitialize() {
+ LOGGER.info("Initializing Joinpoints mod");
+
+ // Initialize config early
+ config.loadOrCreateProperties();
+
+ // Register joinpoints lang file with ECText
+ ECText.registerAdditionalLangPath("/assets/joinpoints/lang/%s.json");
+
+ // Register commands
+ JoinpointsCommandRegistry.register();
+
+ // Register player connect callback to update player cache
+ PlayerConnectCallback.EVENT.register((connection, player) -> updatePlayerCache(player));
+
+ // Register nickname change callback to update player cache
+ NicknameChangeCallback.EVENT.register(Joinpoints::updatePlayerCache);
+
+ ServerLifecycleEvents.SERVER_STARTING.register(this::onServerStarting);
+ ServerLifecycleEvents.SERVER_STOPPED.register(this::onServerStopped);
+ }
+
+ private void onServerStarting(MinecraftServer server) {
+ Joinpoints.server = server;
+
+ // Initialize database
+ database = new JoinpointDatabase(server.getSavePath(net.minecraft.util.WorldSavePath.ROOT).toFile());
+
+ LOGGER.info("Joinpoints mod initialized successfully");
+ }
+
+ private void onServerStopped(MinecraftServer server) {
+ Joinpoints.server = null;
+ }
+
+ public static JoinpointsConfig getConfig() {
+ return config;
+ }
+
+ public static JoinpointDatabase getDatabase() {
+ return database;
+ }
+
+ public static MinecraftServer getServer() {
+ return server;
+ }
+
+ private static void updatePlayerCache(ServerPlayerEntity player) {
+ try {
+ var playerData = ((ServerPlayerEntityAccess) player).ec$getPlayerData();
+
+ String nicknameJson = playerData.getNickname()
+ .map(text -> TextCodecs.CODEC.encodeStart(JsonOps.INSTANCE, text).getOrThrow())
+ .map(JsonElement::toString)
+ .orElse(null);
+ database
+ .updatePlayerCacheAsync(player.getUuid(), player.getName().getString(), nicknameJson)
+ .exceptionally(err -> {
+ LOGGER.error(err);
+ return null;
+ });
+ } catch (Exception e) {
+ // Log but don't crash on cache update failure - joinpoint database might not be initialized yet
+ LOGGER.error(e);
+ }
+ }
+}
diff --git a/joinpoints/src/main/java/com/fibermc/joinpoints/JoinpointsCommandRegistry.java b/joinpoints/src/main/java/com/fibermc/joinpoints/JoinpointsCommandRegistry.java
new file mode 100644
index 00000000..635b501a
--- /dev/null
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/JoinpointsCommandRegistry.java
@@ -0,0 +1,95 @@
+package com.fibermc.joinpoints;
+
+import com.fibermc.joinpoints.commands.*;
+import com.mojang.brigadier.arguments.BoolArgumentType;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.tree.LiteralCommandNode;
+import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
+import net.minecraft.command.argument.EntityArgumentType;
+import net.minecraft.server.command.CommandManager;
+import net.minecraft.server.command.ServerCommandSource;
+
+import static net.minecraft.server.command.CommandManager.argument;
+
+public final class JoinpointsCommandRegistry {
+ private JoinpointsCommandRegistry() {}
+
+ public static void register() {
+ CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
+ if (!Joinpoints.getConfig().ENABLE_JOINPOINT.getValue()) {
+ return;
+ }
+
+ var joinpointBuilder = CommandManager.literal("joinpoint");
+ var joinpointSetBuilder = CommandManager.literal("set");
+ var joinpointTpBuilder = CommandManager.literal("tp");
+ var joinpointDeleteBuilder = CommandManager.literal("delete");
+ var joinpointOverwriteBuilder = CommandManager.literal("overwrite");
+ var joinpointShareBuilder = CommandManager.literal("share");
+ var joinpointListBuilder = CommandManager.literal("list");
+
+ joinpointSetBuilder
+ .requires(JoinpointsPerms.require(JoinpointsPerms.Registry.joinpoint_set, 0))
+ .then(argument("joinpoint_name", StringArgumentType.word())
+ .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.SET))
+ .then(argument("global", BoolArgumentType.bool())
+ .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.SET))));
+
+ joinpointTpBuilder
+ .requires(JoinpointsPerms.require(JoinpointsPerms.Registry.joinpoint_tp, 0))
+ .then(argument(JoinpointTpCommand.OWNER_PLAYER_ARG, StringArgumentType.word())
+ .suggests(JoinpointTpCommand.Suggestion.OWNERS_OF_ACCESSIBLE_JOINPOINTS)
+ .then(argument("joinpoint_name", StringArgumentType.word())
+ .suggests(JoinpointTpCommand.Suggestion.ACCESSIBLE_TARGET_PLAYER_JOINPOINTS)
+ .executes(new JoinpointTpCommand())));
+
+ joinpointDeleteBuilder
+ .requires(JoinpointsPerms.require(JoinpointsPerms.Registry.joinpoint_delete, 0))
+ .then(argument("joinpoint_name", StringArgumentType.word())
+ .suggests(JoinpointTpCommand.Suggestion.OWNED_JOINPOINTS)
+ .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.DELETE)));
+
+ joinpointOverwriteBuilder
+ .requires(JoinpointsPerms.require(JoinpointsPerms.Registry.joinpoint_set, 0))
+ .then(argument("joinpoint_name", StringArgumentType.word())
+ .suggests(JoinpointTpCommand.Suggestion.OWNED_JOINPOINTS)
+ .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.OVERWRITE))
+ .then(argument("global", BoolArgumentType.bool())
+ .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.OVERWRITE))));
+
+ joinpointShareBuilder
+ .requires(JoinpointsPerms.require(JoinpointsPerms.Registry.joinpoint_set, 0))
+ .then(argument("joinpoint_name", StringArgumentType.word())
+ .suggests(JoinpointTpCommand.Suggestion.OWNED_JOINPOINTS)
+ .then(CommandManager.literal("add")
+ .then(argument("target_players", EntityArgumentType.players())
+ .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.ADD))))
+ .then(CommandManager.literal("remove")
+ .then(argument("target_players", EntityArgumentType.players())
+ .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.REMOVE))))
+ .then(CommandManager.literal("list")
+ .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.LIST)))
+ .then(CommandManager.literal("clear")
+ .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.CLEAR))));
+
+ joinpointListBuilder
+ .requires(JoinpointsPerms.require(JoinpointsPerms.Registry.joinpoint_tp, 0))
+ .executes(new JoinpointListCommand()::runDefault)
+ .then(argument("filter", StringArgumentType.word())
+ .suggests(JoinpointListCommand.Suggestion.FILTER_TYPES)
+ .executes(new JoinpointListCommand()));
+
+ LiteralCommandNode joinpointNode = joinpointBuilder
+ .requires(JoinpointsPerms.requireAny(JoinpointsPerms.Registry.Group.joinpoint_group, 0))
+ .build();
+ joinpointNode.addChild(joinpointSetBuilder.build());
+ joinpointNode.addChild(joinpointTpBuilder.build());
+ joinpointNode.addChild(joinpointDeleteBuilder.build());
+ joinpointNode.addChild(joinpointOverwriteBuilder.build());
+ joinpointNode.addChild(joinpointShareBuilder.build());
+ joinpointNode.addChild(joinpointListBuilder.build());
+
+ dispatcher.getRoot().addChild(joinpointNode);
+ });
+ }
+}
diff --git a/joinpoints/src/main/java/com/fibermc/joinpoints/JoinpointsPerms.java b/joinpoints/src/main/java/com/fibermc/joinpoints/JoinpointsPerms.java
new file mode 100644
index 00000000..9c730a25
--- /dev/null
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/JoinpointsPerms.java
@@ -0,0 +1,58 @@
+package com.fibermc.joinpoints;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import com.fibermc.joinpoints.types.JoinpointLimit;
+import me.lucko.fabric.api.permissions.v0.Permissions;
+import org.jetbrains.annotations.NotNull;
+
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.server.network.ServerPlayerEntity;
+
+public final class JoinpointsPerms {
+ private JoinpointsPerms() {}
+
+ @SuppressWarnings({"checkstyle:constantname", "checkstyle:staticvariablename"})
+ public static final class Registry {
+ public static final String joinpoint_set = "joinpoints.set";
+ public static final String joinpoint_tp = "joinpoints.tp";
+ public static final String joinpoint_delete = "joinpoints.delete";
+
+ public static final class Group {
+ public static final String[] joinpoint_group = {joinpoint_set, joinpoint_tp, joinpoint_delete};
+ public static Map joinpoint_limit_groups = new HashMap<>();
+ }
+ }
+
+ public static Predicate require(String permission, int opLevel) {
+ return Permissions.require(permission, opLevel);
+ }
+
+ public static Predicate requireAny(String[] permissions, int opLevel) {
+ return src -> Arrays.stream(permissions).anyMatch(perm -> Permissions.check(src, perm, opLevel));
+ }
+
+ public static String[] makeNumericPermissionGroup(String basePermission, @NotNull Collection limits) {
+ return limits.stream()
+ .map(limit -> basePermission + "." + limit)
+ .toArray(String[]::new);
+ }
+
+ public static int getMaximumNumericalPermission(ServerPlayerEntity player, String basePermission, String[] validPermissions) {
+ return Stream.of(validPermissions)
+ .filter(perm -> Permissions.check(player, basePermission + "." + perm))
+ .map(Integer::parseInt)
+ .max(Integer::compare)
+ .orElse(0);
+ }
+
+ // Delegate to ECPerms for the shared permission logic
+ public static int getHighestNumericPermission(@NotNull ServerCommandSource source, @NotNull String[] permissionGroup) {
+ return com.fibermc.essentialcommands.ECPerms.getHighestNumericPermission(source, permissionGroup);
+ }
+}
diff --git a/joinpoints/src/main/java/com/fibermc/joinpoints/codec/JoinpointCodecs.java b/joinpoints/src/main/java/com/fibermc/joinpoints/codec/JoinpointCodecs.java
new file mode 100644
index 00000000..87e8a8bb
--- /dev/null
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/codec/JoinpointCodecs.java
@@ -0,0 +1,42 @@
+package com.fibermc.joinpoints.codec;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.UUID;
+
+import com.fibermc.essentialcommands.types.MinecraftLocation;
+import com.fibermc.joinpoints.types.JoinpointLocation;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.registry.RegistryKey;
+import net.minecraft.registry.RegistryKeys;
+import net.minecraft.world.World;
+
+public final class JoinpointCodecs {
+ private JoinpointCodecs() {}
+
+ public static final Codec> WORLD_KEY = RegistryKey.createCodec(RegistryKeys.WORLD);
+
+ public static final Codec JOINPOINT_LOCATION = RecordCodecBuilder.create(instance ->
+ instance.group(
+ // Inherit all fields from NamedMinecraftLocation
+ WORLD_KEY.fieldOf("WorldRegistryKey").forGetter(JoinpointLocation::dim),
+ Codec.DOUBLE.fieldOf("x").forGetter(MinecraftLocation::x),
+ Codec.DOUBLE.fieldOf("y").forGetter(MinecraftLocation::y),
+ Codec.DOUBLE.fieldOf("z").forGetter(MinecraftLocation::z),
+ Codec.FLOAT.optionalFieldOf("headYaw", 0.0f).forGetter(JoinpointLocation::headYaw),
+ Codec.FLOAT.optionalFieldOf("pitch", 0.0f).forGetter(JoinpointLocation::pitch),
+ // loaded from the map
+ Codec.STRING.optionalFieldOf("name").forGetter(joinpoint -> Optional.of(((JoinpointLocation)joinpoint).getName())),
+
+ // Joinpoint-specific fields
+ Codec.STRING.xmap(UUID::fromString, UUID::toString).fieldOf("owner").forGetter(JoinpointLocation::getOwner),
+ Codec.BOOL.optionalFieldOf("isGlobal", false).forGetter(JoinpointLocation::isGlobal),
+ Codec.STRING.xmap(UUID::fromString, UUID::toString).listOf().optionalFieldOf("sharedWith", java.util.List.of())
+ .xmap(HashSet::new, java.util.List::copyOf)
+ .forGetter(JoinpointLocation::getSharedWith)
+
+ ).apply(instance, JoinpointLocation::new)
+ );
+}
diff --git a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/Async.java b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/Async.java
similarity index 97%
rename from src/main/java/com/fibermc/essentialcommands/commands/joinpoints/Async.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/commands/Async.java
index a30cd638..254fd1ae 100644
--- a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/Async.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/Async.java
@@ -1,4 +1,4 @@
-package com.fibermc.essentialcommands.commands.joinpoints;
+package com.fibermc.joinpoints.commands;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
diff --git a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointException.java b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointException.java
similarity index 98%
rename from src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointException.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointException.java
index 375b4bee..f9cb5d8e 100644
--- a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointException.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointException.java
@@ -1,8 +1,8 @@
-package com.fibermc.essentialcommands.commands.joinpoints;
+package com.fibermc.joinpoints.commands;
import com.fibermc.essentialcommands.text.ECText;
import com.fibermc.essentialcommands.text.TextFormatType;
-import com.fibermc.essentialcommands.types.JoinpointLimit;
+import com.fibermc.joinpoints.types.JoinpointLimit;
import net.minecraft.text.Text;
diff --git a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointListCommand.java b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointListCommand.java
similarity index 92%
rename from src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointListCommand.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointListCommand.java
index d13af50d..7f65a032 100644
--- a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointListCommand.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointListCommand.java
@@ -1,15 +1,15 @@
-package com.fibermc.essentialcommands.commands.joinpoints;
+package com.fibermc.joinpoints.commands;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
-import com.fibermc.essentialcommands.ManagerLocator;
import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
-import com.fibermc.essentialcommands.database.JoinpointDatabase;
import com.fibermc.essentialcommands.playerdata.PlayerData;
import com.fibermc.essentialcommands.text.ECText;
-import com.fibermc.essentialcommands.types.JoinpointLocation;
+import com.fibermc.joinpoints.Joinpoints;
+import com.fibermc.joinpoints.database.JoinpointDatabase;
+import com.fibermc.joinpoints.types.JoinpointLocation;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
@@ -67,7 +67,7 @@ private int exec(ServerPlayerEntity senderPlayer, FilterType filter) {
Async.runCommand(() -> {
var senderPlayerId = senderPlayer.getUuid();
PlayerData playerData = ((ServerPlayerEntityAccess) senderPlayer).ec$getPlayerData();
- JoinpointDatabase database = ManagerLocator.getInstance().getJoinpointDatabase();
+ JoinpointDatabase database = Joinpoints.getDatabase();
var joinpoints = database.getAccessibleJoinpointsWithNamesAsync(senderPlayer).join();
// List ownedJoinpoints = database.getOwnedJoinpointsAsync(senderPlayer.getUuid()).join();
@@ -127,15 +127,14 @@ private int exec(ServerPlayerEntity senderPlayer, FilterType filter) {
message.append(" ").append(Text.literal("[Private+]").formatted(Formatting.BLUE));
}
- // This causes an exception on chat packet sending due to a null text component
-// message
-// .append(" ")
-// .append(Text.literal("by ").formatted(Formatting.GRAY))
-// .append(
-// entry.isOwned
-// ? ecText.literal("You")
-// : entry.ownerDisplayName
-// );
+ message
+ .append(" ")
+ .append(Text.literal("by ").formatted(Formatting.GRAY))
+ .append(
+ entry.isOwned
+ ? ecText.literal("You")
+ : entry.ownerDisplayName
+ );
senderPlayer.sendMessage(message);
diff --git a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointSetCommand.java b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointSetCommand.java
similarity index 90%
rename from src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointSetCommand.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointSetCommand.java
index c0359765..baa664ec 100644
--- a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointSetCommand.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointSetCommand.java
@@ -1,4 +1,4 @@
-package com.fibermc.essentialcommands.commands.joinpoints;
+package com.fibermc.joinpoints.commands;
import java.util.HashSet;
import java.util.Set;
@@ -6,15 +6,15 @@
import java.util.function.Consumer;
import java.util.function.Function;
-import com.fibermc.essentialcommands.ECPerms;
-import com.fibermc.essentialcommands.ManagerLocator;
+import com.fibermc.joinpoints.JoinpointsPerms;
+import com.fibermc.joinpoints.Joinpoints;
import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
-import com.fibermc.essentialcommands.database.JoinpointDatabase;
+import com.fibermc.joinpoints.database.JoinpointDatabase;
import com.fibermc.essentialcommands.playerdata.PlayerData;
import com.fibermc.essentialcommands.text.ChatConfirmationPrompt;
import com.fibermc.essentialcommands.text.ECText;
-import com.fibermc.essentialcommands.types.JoinpointLimit;
-import com.fibermc.essentialcommands.types.JoinpointLocation;
+import com.fibermc.joinpoints.types.JoinpointLimit;
+import com.fibermc.joinpoints.types.JoinpointLocation;
import com.fibermc.essentialcommands.types.MinecraftLocation;
import com.mojang.brigadier.Command;
@@ -75,7 +75,7 @@ private int exec(CommandContext context, String joinpointNa
Async.runCommand(() -> {
PlayerData playerData = ((ServerPlayerEntityAccess) senderPlayer).ec$getPlayerData();
- JoinpointDatabase database = ManagerLocator.getInstance().getJoinpointDatabase();
+ JoinpointDatabase database = Joinpoints.getDatabase();
return switch (action) {
case SET -> handleSetAsync(finalIsGlobal, joinpointName, senderPlayer, playerData, database);
@@ -119,11 +119,11 @@ private Void handleSetAsync(
? JoinpointLimit.JoinpointType.GLOBAL
: JoinpointLimit.JoinpointType.SHARED;
- var targetTypePerms = ECPerms.Registry.Group.joinpoint_limit_groups.get(joinpointType);
- var anyTypePerms = ECPerms.Registry.Group.joinpoint_limit_groups.get(JoinpointLimit.JoinpointType.ANY);
+ var targetTypePerms = JoinpointsPerms.Registry.Group.joinpoint_limit_groups.get(joinpointType);
+ var anyTypePerms = JoinpointsPerms.Registry.Group.joinpoint_limit_groups.get(JoinpointLimit.JoinpointType.ANY);
- int playerAllowedOfAnyType = anyTypePerms.length == 0 ? -1 : ECPerms.getHighestNumericPermission(senderPlayer.getCommandSource(), anyTypePerms);
- int playerAllowedCountOfTargetType = targetTypePerms.length == 0 ? -1 : ECPerms.getHighestNumericPermission(senderPlayer.getCommandSource(), targetTypePerms);
+ int playerAllowedOfAnyType = anyTypePerms.length == 0 ? -1 : JoinpointsPerms.getHighestNumericPermission(senderPlayer.getCommandSource(), anyTypePerms);
+ int playerAllowedCountOfTargetType = targetTypePerms.length == 0 ? -1 : JoinpointsPerms.getHighestNumericPermission(senderPlayer.getCommandSource(), targetTypePerms);
// any(5) -> up to 5 shared or global, any combindation
// any(5),shared(3) -> no more then 3 shared. Could have 5 global:0 shared to 2 global:3 shared
diff --git a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointShareCommand.java b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointShareCommand.java
similarity index 96%
rename from src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointShareCommand.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointShareCommand.java
index 517ba67f..81476561 100644
--- a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointShareCommand.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointShareCommand.java
@@ -1,4 +1,4 @@
-package com.fibermc.essentialcommands.commands.joinpoints;
+package com.fibermc.joinpoints.commands;
import java.util.Collection;
import java.util.HashSet;
@@ -7,12 +7,12 @@
import java.util.function.Consumer;
import java.util.function.Function;
-import com.fibermc.essentialcommands.ManagerLocator;
+import com.fibermc.joinpoints.Joinpoints;
import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
-import com.fibermc.essentialcommands.database.JoinpointDatabase;
+import com.fibermc.joinpoints.database.JoinpointDatabase;
import com.fibermc.essentialcommands.playerdata.PlayerData;
import com.fibermc.essentialcommands.text.ECText;
-import com.fibermc.essentialcommands.types.JoinpointLocation;
+import com.fibermc.joinpoints.types.JoinpointLocation;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
@@ -67,7 +67,7 @@ private int exec(CommandContext context, String joinpointNa
Async.runCommand(() -> {
PlayerData playerData = ((ServerPlayerEntityAccess) senderPlayer).ec$getPlayerData();
- JoinpointDatabase database = ManagerLocator.getInstance().getJoinpointDatabase();
+ JoinpointDatabase database = Joinpoints.getDatabase();
return switch (action) {
case ADD -> handleAddAsync(finalTargetPlayers, joinpointName, senderPlayer, playerData, database);
diff --git a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointTpCommand.java b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointTpCommand.java
similarity index 91%
rename from src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointTpCommand.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointTpCommand.java
index d7302e54..e59915d1 100644
--- a/src/main/java/com/fibermc/essentialcommands/commands/joinpoints/JoinpointTpCommand.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/commands/JoinpointTpCommand.java
@@ -1,4 +1,4 @@
-package com.fibermc.essentialcommands.commands.joinpoints;
+package com.fibermc.joinpoints.commands;
import java.util.List;
import java.util.UUID;
@@ -8,14 +8,14 @@
import java.util.stream.Collectors;
import com.fibermc.essentialcommands.EssentialCommands;
-import com.fibermc.essentialcommands.ManagerLocator;
+import com.fibermc.joinpoints.Joinpoints;
import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
-import com.fibermc.essentialcommands.database.JoinpointDatabase;
+import com.fibermc.joinpoints.database.JoinpointDatabase;
import com.fibermc.essentialcommands.playerdata.PlayerData;
import com.fibermc.essentialcommands.teleportation.PlayerTeleporter;
import com.fibermc.essentialcommands.text.ECText;
import com.fibermc.essentialcommands.text.TextFormatType;
-import com.fibermc.essentialcommands.types.JoinpointLocation;
+import com.fibermc.joinpoints.types.JoinpointLocation;
import com.fibermc.essentialcommands.types.NamedMinecraftLocation;
import org.apache.commons.lang3.function.TriFunction;
import org.apache.logging.log4j.message.ParameterizedMessage;
@@ -69,20 +69,27 @@ private Consumer> sendErrorToPlayer(ServerPlayerEntity se
*/
private @Nullable UUID getJoinpointOwnerIdAsync(ServerPlayerEntity senderPlayer, String ownerName)
{
+ var server = senderPlayer.getEntityWorld().getServer();
+ var apis = server.getApiServices();
+ var userCache = apis.nameToIdCache();
+
+ // player to owned joinpoints
+ // player to accessible joinpoints
+
// If owner is self, use sender's UUID
if (ownerName.equals(senderPlayer.getName().getString())) {
return senderPlayer.getUuid();
}
{ // Try to find owner by name from online players first
- var ownerPlayer = senderPlayer.getServer().getPlayerManager().getPlayer(ownerName);
+ var ownerPlayer = senderPlayer.getEntityWorld().getServer().getPlayerManager().getPlayer(ownerName);
if (ownerPlayer != null) {
return ownerPlayer.getUuid();
}
}
{ // Go to our cached list of all player names as a last resort (particularly for offline players)
- JoinpointDatabase database = ManagerLocator.getInstance().getJoinpointDatabase();
+ JoinpointDatabase database = Joinpoints.getDatabase();
var ownerUuid = database.getOwnerPlayerIdByNameAsync(ownerName, senderPlayer.getUuid()).join();
if (ownerUuid != null) {
return ownerUuid;
@@ -96,7 +103,7 @@ private int exec(ServerPlayerEntity senderPlayer, String ownerName, String joinp
{
Async.runCommand(() -> {
PlayerData senderPlayerData = ((ServerPlayerEntityAccess) senderPlayer).ec$getPlayerData();
- JoinpointDatabase database = ManagerLocator.getInstance().getJoinpointDatabase();
+ JoinpointDatabase database = Joinpoints.getDatabase();
var ownerUuid = getJoinpointOwnerIdAsync(senderPlayer, ownerName);
@@ -143,7 +150,7 @@ private static SuggestionProvider suggestFromDb(
return (context, builder) -> {
try {
ServerPlayerEntity player = context.getSource().getPlayerOrThrow();
- JoinpointDatabase database = ManagerLocator.getInstance().getJoinpointDatabase();
+ JoinpointDatabase database = Joinpoints.getDatabase();
return getSuggestionsAsync.apply(database, player, context)
.thenApply(items -> {
for (String item : items) {
diff --git a/joinpoints/src/main/java/com/fibermc/joinpoints/config/JoinpointsConfig.java b/joinpoints/src/main/java/com/fibermc/joinpoints/config/JoinpointsConfig.java
new file mode 100644
index 00000000..24f9b1dc
--- /dev/null
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/config/JoinpointsConfig.java
@@ -0,0 +1,44 @@
+package com.fibermc.joinpoints.config;
+
+import java.nio.file.Path;
+
+import com.fibermc.joinpoints.JoinpointsPerms;
+import com.fibermc.joinpoints.types.JoinpointLimit;
+import dev.jpcode.eccore.config.Config;
+import dev.jpcode.eccore.config.ConfigOption;
+import dev.jpcode.eccore.config.Option;
+
+@SuppressWarnings("checkstyle:all")
+public final class JoinpointsConfig extends Config {
+
+ @ConfigOption
+ public final Option ENABLE_JOINPOINT = new Option<>("enable_joinpoint", true, Boolean::parseBoolean);
+
+ @ConfigOption
+ public final Option JOINPOINT_LIMIT = new Option<>(
+ "joinpoint_limit",
+ JoinpointLimit.any(3, 5, 10),
+ JoinpointLimit::parse,
+ JoinpointLimit::serialize
+ );
+
+ public JoinpointsConfig(Path savePath, String displayName, String documentationLink) {
+ super(savePath, displayName, documentationLink);
+
+ JOINPOINT_LIMIT.changeEvent.register(joinpointLimit -> {
+ JoinpointsPerms.Registry.Group.joinpoint_limit_groups.clear();
+ for (var limitGroup : joinpointLimit.getLimits().entrySet()) {
+ var key = limitGroup.getKey();
+ var limitNums = limitGroup.getValue();
+
+ JoinpointsPerms.Registry.Group.joinpoint_limit_groups.put(
+ key,
+ JoinpointsPerms.makeNumericPermissionGroup(
+ "joinpoints.limit." + key.name().toLowerCase(),
+ limitNums
+ )
+ );
+ }
+ });
+ }
+}
diff --git a/joinpoints/src/main/java/com/fibermc/joinpoints/database/DatabaseHelper.java b/joinpoints/src/main/java/com/fibermc/joinpoints/database/DatabaseHelper.java
new file mode 100644
index 00000000..817732c7
--- /dev/null
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/database/DatabaseHelper.java
@@ -0,0 +1,31 @@
+package com.fibermc.joinpoints.database;
+
+import java.sql.SQLException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+import com.fibermc.joinpoints.Joinpoints;
+
+import net.minecraft.util.Util;
+
+public final class DatabaseHelper {
+ private DatabaseHelper() {}
+
+ private static final Executor EXECUTOR = Util.getIoWorkerExecutor();
+
+ public static CompletableFuture async(SqlSupplier supplier) {
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ return supplier.get();
+ } catch (SQLException e) {
+ Joinpoints.LOGGER.error("Database error", e);
+ throw new RuntimeException("Database error", e);
+ }
+ }, EXECUTOR);
+ }
+
+ @FunctionalInterface
+ public interface SqlSupplier {
+ T get() throws SQLException;
+ }
+}
diff --git a/src/main/java/com/fibermc/essentialcommands/database/JoinpointDatabase.java b/joinpoints/src/main/java/com/fibermc/joinpoints/database/JoinpointDatabase.java
similarity index 99%
rename from src/main/java/com/fibermc/essentialcommands/database/JoinpointDatabase.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/database/JoinpointDatabase.java
index a6fd48ca..e8043192 100644
--- a/src/main/java/com/fibermc/essentialcommands/database/JoinpointDatabase.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/database/JoinpointDatabase.java
@@ -1,4 +1,4 @@
-package com.fibermc.essentialcommands.database;
+package com.fibermc.joinpoints.database;
import java.io.File;
import java.sql.*;
@@ -7,7 +7,7 @@
import com.fibermc.essentialcommands.EssentialCommands;
import com.fibermc.essentialcommands.playerdata.PlayerDataManager;
-import com.fibermc.essentialcommands.types.JoinpointLocation;
+import com.fibermc.joinpoints.types.JoinpointLocation;
import com.fibermc.essentialcommands.types.MinecraftLocation;
import org.apache.logging.log4j.Level;
import org.jetbrains.annotations.NotNull;
diff --git a/src/main/java/com/fibermc/essentialcommands/types/JoinpointLimit.java b/joinpoints/src/main/java/com/fibermc/joinpoints/types/JoinpointLimit.java
similarity index 99%
rename from src/main/java/com/fibermc/essentialcommands/types/JoinpointLimit.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/types/JoinpointLimit.java
index b4ad574d..c50b4b9f 100644
--- a/src/main/java/com/fibermc/essentialcommands/types/JoinpointLimit.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/types/JoinpointLimit.java
@@ -1,4 +1,4 @@
-package com.fibermc.essentialcommands.types;
+package com.fibermc.joinpoints.types;
import java.util.HashMap;
import java.util.List;
diff --git a/src/main/java/com/fibermc/essentialcommands/types/JoinpointLocation.java b/joinpoints/src/main/java/com/fibermc/joinpoints/types/JoinpointLocation.java
similarity index 90%
rename from src/main/java/com/fibermc/essentialcommands/types/JoinpointLocation.java
rename to joinpoints/src/main/java/com/fibermc/joinpoints/types/JoinpointLocation.java
index e4e49150..5e789690 100644
--- a/src/main/java/com/fibermc/essentialcommands/types/JoinpointLocation.java
+++ b/joinpoints/src/main/java/com/fibermc/joinpoints/types/JoinpointLocation.java
@@ -1,11 +1,13 @@
-package com.fibermc.essentialcommands.types;
+package com.fibermc.joinpoints.types;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
-import com.fibermc.essentialcommands.codec.Codecs;
+import com.fibermc.essentialcommands.types.MinecraftLocation;
+import com.fibermc.essentialcommands.types.NamedMinecraftLocation;
+import com.fibermc.joinpoints.codec.JoinpointCodecs;
import com.mojang.serialization.Codec;
@@ -16,7 +18,7 @@
import net.minecraft.world.World;
public class JoinpointLocation extends NamedMinecraftLocation {
- public static final Codec CODEC = Codecs.JOINPOINT_LOCATION;
+ public static final Codec CODEC = JoinpointCodecs.JOINPOINT_LOCATION;
private final UUID owner;
private final boolean isGlobal;
diff --git a/joinpoints/src/main/resources/assets/joinpoints/icon.jpg b/joinpoints/src/main/resources/assets/joinpoints/icon.jpg
new file mode 100644
index 00000000..ec6f5f01
Binary files /dev/null and b/joinpoints/src/main/resources/assets/joinpoints/icon.jpg differ
diff --git a/joinpoints/src/main/resources/assets/joinpoints/lang/en_us.json b/joinpoints/src/main/resources/assets/joinpoints/lang/en_us.json
new file mode 100644
index 00000000..53ddb661
--- /dev/null
+++ b/joinpoints/src/main/resources/assets/joinpoints/lang/en_us.json
@@ -0,0 +1,39 @@
+{
+ "cmd.joinpoint.tp.error.not_found": "No joinpoint with the name '${0}' could be found for owner '${1}'.",
+ "cmd.joinpoint.tp.error.owner_not_found": "Player '${0}' not found or has no accessible joinpoints.",
+ "cmd.joinpoint.tp.error.no_access": "You don't have access to joinpoint '${0}' owned by '${1}'.",
+ "cmd.joinpoint.location_name": "joinpoint: ${0}",
+ "cmd.joinpoint.location_name_with_owner": "joinpoint: ${0} (owned by ${1})",
+ "cmd.joinpoint.delete.feedback": "Joinpoint '${0}' has been deleted.",
+ "cmd.joinpoint.delete.error": "Joinpoint '${0}' could not be deleted.",
+ "cmd.joinpoint.set.feedback": "Joinpoint '${0}' set.",
+ "cmd.joinpoint.set.feedback.global": "Global joinpoint '${0}' set.",
+ "cmd.joinpoint.set.feedback.shared": "Shared joinpoint '${0}' set.",
+ "cmd.joinpoint.set.error.limit": "Joinpoint '${0}' could not be set. Joinpoint limit (${1}) for joinpoint type '${2}' reached.",
+ "cmd.joinpoint.set.error.exists": "Joinpoint '${0}' could not be set. A joinpoint with the specified name already exists.",
+ "cmd.joinpoint.set.overwrite": "Joinpoint '${0}' already exists.\nWould you like to overwrite the existing joinpoint?",
+ "cmd.joinpoint.overwrite.feedback": "Joinpoint '${0}' moved to current location.",
+ "cmd.joinpoint.overwrite.feedback.global": "Global joinpoint '${0}' moved to current location.",
+ "cmd.joinpoint.overwrite.feedback.shared": "Shared joinpoint '${0}' moved to current location.",
+ "cmd.joinpoint.list.header.all": "All accessible joinpoints (${0}):",
+ "cmd.joinpoint.list.header.owned": "Your owned joinpoints (${0}):",
+ "cmd.joinpoint.list.header.shared_with": "Joinpoints shared with you (${0}):",
+ "cmd.joinpoint.list.header.global": "Global joinpoints (${0}):",
+ "cmd.joinpoint.list.empty.all": "You have no accessible joinpoints.",
+ "cmd.joinpoint.list.empty.owned": "You don't own any joinpoints.",
+ "cmd.joinpoint.list.empty.shared_with": "No joinpoints have been shared with you.",
+ "cmd.joinpoint.list.empty.global": "No global joinpoints are available.",
+ "cmd.joinpoint.error.database": "Database error: ${0}",
+ "cmd.joinpoint.error.unknown": "Joinpoint action failed - an unknown error occurred: ${0}",
+ "cmd.joinpoint.error.not_found": "Joinpoint '${0}' not found.",
+ "cmd.joinpoint.share.error.already_global": "Joinpoint '${0}' is already global and accessible to all players.",
+ "cmd.joinpoint.share.error.no_new_players": "No new players to share with.",
+ "cmd.joinpoint.share.error.players_not_shared": "None of the specified players have access to this joinpoint.",
+ "cmd.joinpoint.share.error.cannot_clear_global": "Cannot clear sharing for global joinpoint '${0}'. Use '/joinpoint set' to make it private first.",
+ "cmd.joinpoint.share.add.feedback": "Shared joinpoint '${0}' with: ${1}",
+ "cmd.joinpoint.share.remove.feedback": "Removed access to joinpoint '${0}' from: ${1}",
+ "cmd.joinpoint.share.list.global": "Joinpoint '${0}' is global (accessible to all players).",
+ "cmd.joinpoint.share.list.private": "Joinpoint '${0}' is private (only accessible to you).",
+ "cmd.joinpoint.share.list.shared": "Joinpoint '${0}' is shared with: ${1}",
+ "cmd.joinpoint.share.clear.feedback": "Cleared all sharing for joinpoint '${0}' (now private)."
+}
diff --git a/joinpoints/src/main/resources/fabric.mod.json b/joinpoints/src/main/resources/fabric.mod.json
new file mode 100644
index 00000000..38a4ebf4
--- /dev/null
+++ b/joinpoints/src/main/resources/fabric.mod.json
@@ -0,0 +1,38 @@
+{
+ "schemaVersion": 1,
+ "id": "joinpoints",
+ "version": "${version}",
+
+ "name": "Joinpoints",
+ "description": "Adds joinpoint functionality (player-created teleportation waypoints) to Essential Commands.",
+ "authors": [
+ "John Paul R. (JP79194)"
+ ],
+ "contact": {
+ "homepage": "https://www.jpcode.dev",
+ "issues": "https://github.com/John-Paul-R/Essential-Commands/issues",
+ "sources": "https://github.com/John-Paul-R/Essential-Commands",
+ "discord": "https://discord.jpcode.dev"
+ },
+
+ "license": "MIT",
+ "icon": "assets/joinpoints/icon.jpg",
+
+ "environment": "*",
+ "entrypoints": {
+ "main": [
+ "com.fibermc.joinpoints.Joinpoints"
+ ]
+ },
+ "mixins": [
+ ],
+
+ "depends": {
+ "fabricloader": ">=0.11.3",
+ "fabric": "*",
+ "essential_commands": "*"
+ },
+ "suggests": {
+ },
+ "jars": []
+}
diff --git a/settings.gradle b/settings.gradle
index 37e0875c..dfabe142 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -12,3 +12,4 @@ pluginManagement {
}
rootProject.name = 'essential_commands'
include 'ec-core'
+include 'joinpoints'
diff --git a/src/main/java/com/fibermc/essentialcommands/ECPerms.java b/src/main/java/com/fibermc/essentialcommands/ECPerms.java
index dc3e1a66..119f1cb8 100644
--- a/src/main/java/com/fibermc/essentialcommands/ECPerms.java
+++ b/src/main/java/com/fibermc/essentialcommands/ECPerms.java
@@ -2,12 +2,9 @@
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
-import com.fibermc.essentialcommands.types.JoinpointLimit;
import me.lucko.fabric.api.permissions.v0.Permissions;
import org.jetbrains.annotations.NotNull;
@@ -36,9 +33,6 @@ public static final class Registry {
public static final String warp_delete = "essentialcommands.warp.delete";
public static final String warp_tp_named = "essentialcommands.warp.tp_named";
public static final String warp_tp_others = "essentialcommands.warp_tp_others";
- public static final String joinpoint_set = "essentialcommands.joinpoint.set";
- public static final String joinpoint_tp = "essentialcommands.joinpoint.tp";
- public static final String joinpoint_delete = "essentialcommands.joinpoint.delete";
public static final String back = "essentialcommands.back";
public static final String spawn_tp = "essentialcommands.spawn.tp";
public static final String spawn_set = "essentialcommands.spawn.set";
@@ -94,14 +88,12 @@ public static final class Group {
public static final String[] tpa_group = {tpa, tpahere, tpaccept, tpdeny};
public static final String[] home_group = {home_set, home_tp, home_delete};
public static final String[] warp_group = {warp_set, warp_tp, warp_delete};
- public static final String[] joinpoint_group = {joinpoint_set, joinpoint_tp, joinpoint_delete};
public static final String[] spawn_group = {spawn_tp, spawn_set};
public static final String[] nickname_group = {nickname_self, nickname_others, nickname_reveal};
public static final String[] fly_group = {fly_self, fly_others};
public static final String[] invuln_group = {invuln_self, invuln_others};
public static final String[] config_group = {config_reload};
public static String[] home_limit_group;
- public static Map joinpoint_limit_groups = new HashMap<>();
public static final String[] stateful_player_abilities = {fly_self, fly_others, invuln_self, invuln_others};
}
diff --git a/src/main/java/com/fibermc/essentialcommands/EssentialCommandRegistry.java b/src/main/java/com/fibermc/essentialcommands/EssentialCommandRegistry.java
index 460554ca..6ecadf62 100644
--- a/src/main/java/com/fibermc/essentialcommands/EssentialCommandRegistry.java
+++ b/src/main/java/com/fibermc/essentialcommands/EssentialCommandRegistry.java
@@ -8,10 +8,6 @@
import com.fibermc.essentialcommands.commands.*;
import com.fibermc.essentialcommands.commands.bench.*;
-import com.fibermc.essentialcommands.commands.joinpoints.JoinpointListCommand;
-import com.fibermc.essentialcommands.commands.joinpoints.JoinpointSetCommand;
-import com.fibermc.essentialcommands.commands.joinpoints.JoinpointShareCommand;
-import com.fibermc.essentialcommands.commands.joinpoints.JoinpointTpCommand;
import com.fibermc.essentialcommands.commands.suggestions.ListSuggestion;
import com.fibermc.essentialcommands.commands.suggestions.NicknamePlayersSuggestion;
import com.fibermc.essentialcommands.commands.suggestions.TeleportResponseSuggestion;
@@ -195,83 +191,6 @@ public static void register(
essentialCommandsRootNode.addChild(homeOverwriteBuilder.build());
}
- if (CONFIG.ENABLE_JOINPOINT) {
- LiteralArgumentBuilder joinpointBuilder = CommandManager.literal("joinpoint");
- LiteralArgumentBuilder joinpointSetBuilder = CommandManager.literal("set");
- LiteralArgumentBuilder joinpointTpBuilder = CommandManager.literal("tp");
- LiteralArgumentBuilder joinpointDeleteBuilder = CommandManager.literal("delete");
- LiteralArgumentBuilder joinpointOverwriteBuilder = CommandManager.literal("overwrite");
- LiteralArgumentBuilder joinpointShareBuilder = CommandManager.literal("share");
- LiteralArgumentBuilder joinpointListBuilder = CommandManager.literal("list");
-
- joinpointSetBuilder
- .requires(ECPerms.require(ECPerms.Registry.joinpoint_set, 0))
- .then(argument("joinpoint_name", StringArgumentType.word())
- .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.SET))
- .then(argument("global", BoolArgumentType.bool())
- .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.SET))));
-
- joinpointTpBuilder
- .requires(ECPerms.require(ECPerms.Registry.joinpoint_tp, 0))
- .then(argument(JoinpointTpCommand.OWNER_PLAYER_ARG, StringArgumentType.word())
- .suggests(JoinpointTpCommand.Suggestion.OWNERS_OF_ACCESSIBLE_JOINPOINTS)
- .then(argument("joinpoint_name", StringArgumentType.word())
- .suggests(JoinpointTpCommand.Suggestion.ACCESSIBLE_TARGET_PLAYER_JOINPOINTS)
- .executes(new JoinpointTpCommand())));
-
- // this is interfering with the more-important playername suggestions
-// .then(argument("joinpoint_name", StringArgumentType.word())
-// .suggests(JoinpointTpCommand.Suggestion.ACCESSIBLE_JOINPOINTS)
-// .executes(new JoinpointTpCommand()::runOwnJoinpoint))
-
- joinpointDeleteBuilder
- .requires(ECPerms.require(ECPerms.Registry.joinpoint_delete, 0))
- .then(argument("joinpoint_name", StringArgumentType.word())
- .suggests(JoinpointTpCommand.Suggestion.OWNED_JOINPOINTS)
- .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.DELETE)));
-
- joinpointOverwriteBuilder
- .requires(ECPerms.require(ECPerms.Registry.joinpoint_set, 0))
- .then(argument("joinpoint_name", StringArgumentType.word())
- .suggests(JoinpointTpCommand.Suggestion.OWNED_JOINPOINTS)
- .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.OVERWRITE))
- .then(argument("global", BoolArgumentType.bool())
- .executes(new JoinpointSetCommand(JoinpointSetCommand.Action.OVERWRITE))));
-
- joinpointShareBuilder
- .requires(ECPerms.require(ECPerms.Registry.joinpoint_set, 0))
- .then(argument("joinpoint_name", StringArgumentType.word())
- .suggests(JoinpointTpCommand.Suggestion.OWNED_JOINPOINTS)
- .then(CommandManager.literal("add")
- .then(argument("target_players", EntityArgumentType.players())
- .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.ADD))))
- .then(CommandManager.literal("remove")
- .then(argument("target_players", EntityArgumentType.players())
- .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.REMOVE))))
- .then(CommandManager.literal("list")
- .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.LIST)))
- .then(CommandManager.literal("clear")
- .executes(new JoinpointShareCommand(JoinpointShareCommand.Action.CLEAR))));
-
- joinpointListBuilder
- .requires(ECPerms.require(ECPerms.Registry.joinpoint_tp, 0))
- .executes(new JoinpointListCommand()::runDefault)
- .then(argument("filter", StringArgumentType.word())
- .suggests(JoinpointListCommand.Suggestion.FILTER_TYPES)
- .executes(new JoinpointListCommand()));
-
- LiteralCommandNode joinpointNode = joinpointBuilder
- .requires(ECPerms.requireAny(ECPerms.Registry.Group.joinpoint_group, 0))
- .build();
- joinpointNode.addChild(joinpointSetBuilder.build());
- joinpointNode.addChild(joinpointTpBuilder.build());
- joinpointNode.addChild(joinpointDeleteBuilder.build());
- joinpointNode.addChild(joinpointOverwriteBuilder.build());
- joinpointNode.addChild(joinpointShareBuilder.build());
- joinpointNode.addChild(joinpointListBuilder.build());
-
- registerNode.accept(joinpointNode);
- }
//Back
if (CONFIG.ENABLE_BACK) {
diff --git a/src/main/java/com/fibermc/essentialcommands/ManagerLocator.java b/src/main/java/com/fibermc/essentialcommands/ManagerLocator.java
index 83a6378d..e5b5fa20 100644
--- a/src/main/java/com/fibermc/essentialcommands/ManagerLocator.java
+++ b/src/main/java/com/fibermc/essentialcommands/ManagerLocator.java
@@ -4,7 +4,6 @@
import java.util.function.Consumer;
import com.fibermc.essentialcommands.commands.suggestions.OfflinePlayerRepo;
-import com.fibermc.essentialcommands.database.JoinpointDatabase;
import com.fibermc.essentialcommands.playerdata.PlayerDataManager;
import com.fibermc.essentialcommands.teleportation.TeleportManager;
@@ -19,7 +18,6 @@ public final class ManagerLocator {
private TeleportManager tpManager;
private WorldDataManager worldDataManager;
private OfflinePlayerRepo offlinePlayerRepo;
- private JoinpointDatabase joinpointDatabase;
private final HashMap> serverStartActions = new HashMap<>();
public static ManagerLocator instance;
@@ -46,7 +44,6 @@ public void onServerStart(MinecraftServer server) {
this.tpManager = TeleportManager.getInstance();
this.worldDataManager = WorldDataManager.createForServer(server);
this.offlinePlayerRepo = new OfflinePlayerRepo(server);
- this.joinpointDatabase = new JoinpointDatabase(server.getSavePath(net.minecraft.util.WorldSavePath.ROOT).toFile());
ServerLifecycleEvents.SERVER_STARTED.register(server1 -> {
serverStartActions.values().forEach(a -> a.accept(server));
serverStarted = true;
@@ -69,10 +66,6 @@ public OfflinePlayerRepo getOfflinePlayerRepo() {
return offlinePlayerRepo;
}
- public JoinpointDatabase getJoinpointDatabase() {
- return joinpointDatabase;
- }
-
public void runAndQueue(String key, Consumer action) {
serverStartActions.putIfAbsent(key, action);
diff --git a/src/main/java/com/fibermc/essentialcommands/codec/Codecs.java b/src/main/java/com/fibermc/essentialcommands/codec/Codecs.java
index 250312e2..99936e4a 100644
--- a/src/main/java/com/fibermc/essentialcommands/codec/Codecs.java
+++ b/src/main/java/com/fibermc/essentialcommands/codec/Codecs.java
@@ -1,9 +1,7 @@
package com.fibermc.essentialcommands.codec;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Optional;
-import java.util.UUID;
import com.fibermc.essentialcommands.WorldData;
import com.fibermc.essentialcommands.types.*;
@@ -63,28 +61,6 @@ private Codecs() {}
).apply(instance, WarpLocation::new)
);
- public static final Codec JOINPOINT_LOCATION = RecordCodecBuilder.create(instance ->
- instance.group(
- // Inherit all fields from NamedMinecraftLocation
- WORLD_KEY.fieldOf("WorldRegistryKey").forGetter(JoinpointLocation::dim),
- Codec.DOUBLE.fieldOf("x").forGetter(MinecraftLocation::x),
- Codec.DOUBLE.fieldOf("y").forGetter(MinecraftLocation::y),
- Codec.DOUBLE.fieldOf("z").forGetter(MinecraftLocation::z),
- Codec.FLOAT.optionalFieldOf("headYaw", 0.0f).forGetter(JoinpointLocation::headYaw),
- Codec.FLOAT.optionalFieldOf("pitch", 0.0f).forGetter(JoinpointLocation::pitch),
- // loaded from the map
- Codec.STRING.optionalFieldOf("name").forGetter(joinpoint -> Optional.of(((JoinpointLocation)joinpoint).getName())),
-
- // Joinpoint-specific fields
- Codec.STRING.xmap(UUID::fromString, UUID::toString).fieldOf("owner").forGetter(JoinpointLocation::getOwner),
- Codec.BOOL.optionalFieldOf("isGlobal", false).forGetter(JoinpointLocation::isGlobal),
- Codec.STRING.xmap(UUID::fromString, UUID::toString).listOf().optionalFieldOf("sharedWith", java.util.List.of())
- .xmap(HashSet::new, java.util.List::copyOf)
- .forGetter(JoinpointLocation::getSharedWith)
-
- ).apply(instance, JoinpointLocation::new)
- );
-
public static final Codec NAMED_LOCATION_STORAGE =
Codec.unboundedMap(Codec.STRING, NAMED_MINECRAFT_LOCATION)
.xmap(
diff --git a/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfig.java b/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfig.java
index 49235b25..ebd30217 100644
--- a/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfig.java
+++ b/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfig.java
@@ -11,7 +11,6 @@
import com.fibermc.essentialcommands.EssentialCommands;
import com.fibermc.essentialcommands.ManagerLocator;
import com.fibermc.essentialcommands.playerdata.PlayerDataManager;
-import com.fibermc.essentialcommands.types.JoinpointLimit;
import com.fibermc.essentialcommands.types.RespawnCondition;
import com.fibermc.essentialcommands.types.RtpCenter;
import org.jetbrains.annotations.NotNull;
@@ -48,7 +47,6 @@ public final class EssentialCommandsConfig extends Config ENABLE_SPAWN = new Option<>("enable_spawn", true, Boolean::parseBoolean);
@ConfigOption public final Option ENABLE_TPA = new Option<>("enable_tpa", true, Boolean::parseBoolean);
@ConfigOption public final Option ENABLE_WARP = new Option<>("enable_warp", true, Boolean::parseBoolean);
- @ConfigOption public final Option ENABLE_JOINPOINT = new Option<>("enable_joinpoint", false, Boolean::parseBoolean);
@ConfigOption public final Option ENABLE_NICK = new Option<>("enable_nick", true, Boolean::parseBoolean);
@ConfigOption public final Option ENABLE_RTP = new Option<>("enable_rtp", true, Boolean::parseBoolean);
@ConfigOption public final Option ENABLE_FLY = new Option<>("enable_fly", true, Boolean::parseBoolean);
@@ -75,7 +73,6 @@ public final class EssentialCommandsConfig extends Config ENABLE_SLEEP = new Option<>("enable_sleep", false, Boolean::parseBoolean);
@ConfigOption public final Option ENABLE_DELETE_ALL_PLAYER_DATA = new Option<>("enable_delete_all_player_data", true, Boolean::parseBoolean);
@ConfigOption public final Option> HOME_LIMIT = new Option<>("home_limit", List.of(1, 2, 5), arrayParser(ConfigUtil::parseInt));
- @ConfigOption public final Option JOINPOINT_LIMIT = new Option<>("joinpoint_limit", JoinpointLimit.any(3, 5, 10), JoinpointLimit::parse, JoinpointLimit::serialize);
@ConfigOption public final Option TELEPORT_COOLDOWN = new Option<>("teleport_cooldown", 1.0, ConfigUtil::parseDouble);
@ConfigOption public final Option TELEPORT_DELAY = new Option<>("teleport_delay", 0.0, ConfigUtil::parseDouble);
@ConfigOption public final Option ALLOW_BACK_ON_DEATH = new Option<>("allow_back_on_death", false, Boolean::parseBoolean);
@@ -125,21 +122,6 @@ public EssentialCommandsConfig(Path savePath, String displayName, String documen
HOME_LIMIT.changeEvent.register(newValue ->
ECPerms.Registry.Group.home_limit_group = ECPerms.makeNumericPermissionGroup("essentialcommands.home.limit", newValue)
);
- JOINPOINT_LIMIT.changeEvent.register(joinpointLimit -> {
- ECPerms.Registry.Group.joinpoint_limit_groups.clear();
- for (var limitGroup : joinpointLimit.getLimits().entrySet()) {
- var key = limitGroup.getKey();
- var limitNums = limitGroup.getValue();
-
- ECPerms.Registry.Group.joinpoint_limit_groups.put(
- key,
- ECPerms.makeNumericPermissionGroup(
- "essentialcommands.joinpoint_limit." + key.name().toLowerCase(),
- limitNums
- )
- );
- }
- });
// This value is only sent on server start/player connect and, so, cannot be updated for all
// players immediately via the config reload command without a fair bit of hackery.
// NICKNAME_ABOVE_HEAD.changeEvent.register(ign -> {
diff --git a/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfigSnapshot.java b/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfigSnapshot.java
index 4f9d1a7f..b920356a 100644
--- a/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfigSnapshot.java
+++ b/src/main/java/com/fibermc/essentialcommands/config/EssentialCommandsConfigSnapshot.java
@@ -26,7 +26,6 @@ public final class EssentialCommandsConfigSnapshot {
public final boolean ENABLE_SPAWN;
public final boolean ENABLE_TPA;
public final boolean ENABLE_WARP;
- public final boolean ENABLE_JOINPOINT;
public final boolean ENABLE_NICK;
public final boolean ENABLE_RTP;
public final boolean ENABLE_FLY;
@@ -106,7 +105,6 @@ private EssentialCommandsConfigSnapshot(EssentialCommandsConfig config) {
this.ENABLE_SPAWN = config.ENABLE_SPAWN.getValue();
this.ENABLE_TPA = config.ENABLE_TPA.getValue();
this.ENABLE_WARP = config.ENABLE_WARP.getValue();
- this.ENABLE_JOINPOINT = config.ENABLE_JOINPOINT.getValue();
this.ENABLE_NICK = config.ENABLE_NICK.getValue();
this.ENABLE_RTP = config.ENABLE_RTP.getValue();
this.ENABLE_FLY = config.ENABLE_FLY.getValue();
diff --git a/src/main/java/com/fibermc/essentialcommands/events/NicknameChangeCallback.java b/src/main/java/com/fibermc/essentialcommands/events/NicknameChangeCallback.java
new file mode 100644
index 00000000..2512a981
--- /dev/null
+++ b/src/main/java/com/fibermc/essentialcommands/events/NicknameChangeCallback.java
@@ -0,0 +1,18 @@
+package com.fibermc.essentialcommands.events;
+
+import net.minecraft.server.network.ServerPlayerEntity;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+public interface NicknameChangeCallback {
+ Event EVENT = EventFactory.createArrayBacked(
+ NicknameChangeCallback.class,
+ (listeners) -> (player) -> {
+ for (NicknameChangeCallback event : listeners) {
+ event.onNicknameChange(player);
+ }
+ });
+
+ void onNicknameChange(ServerPlayerEntity player);
+}
diff --git a/src/main/java/com/fibermc/essentialcommands/playerdata/PlayerDataManager.java b/src/main/java/com/fibermc/essentialcommands/playerdata/PlayerDataManager.java
index 546d03c8..5f275db5 100644
--- a/src/main/java/com/fibermc/essentialcommands/playerdata/PlayerDataManager.java
+++ b/src/main/java/com/fibermc/essentialcommands/playerdata/PlayerDataManager.java
@@ -6,22 +6,15 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import com.fibermc.essentialcommands.EssentialCommands;
import com.fibermc.essentialcommands.ManagerLocator;
import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
import com.fibermc.essentialcommands.commands.MotdCommand;
-import com.fibermc.essentialcommands.events.PlayerConnectCallback;
-import com.fibermc.essentialcommands.events.PlayerDataManagerTickCallback;
-import com.fibermc.essentialcommands.events.PlayerDeathCallback;
-import com.fibermc.essentialcommands.events.PlayerLeaveCallback;
+import com.fibermc.essentialcommands.events.*;
import com.fibermc.essentialcommands.types.MinecraftLocation;
import com.fibermc.essentialcommands.types.RespawnCondition;
-import com.google.gson.JsonElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import com.mojang.serialization.JsonOps;
-
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
@@ -29,7 +22,6 @@
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.text.TextCodecs;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@@ -73,9 +65,6 @@ public static void init() {
ServerPlayConnectionEvents.JOIN.register(
PlayerDataManager::handleSendMotdForGameJoin
);
- ServerPlayConnectionEvents.JOIN.register(
- (handler, sender, server) -> updatePlayerCache(handler.player)
- );
}
public static final Event TICK_EVENT =
@@ -101,28 +90,6 @@ private static void handleSendMotdForGameJoin(
}
}
- private static void updatePlayerCache(
- ServerPlayerEntity player
- ) {
- try {
- var playerData = ((ServerPlayerEntityAccess) player).ec$getPlayerData();
- var database = ManagerLocator.getInstance().getJoinpointDatabase();
-
- String nicknameJson = playerData.getNickname()
- .map(text -> TextCodecs.CODEC.encodeStart(JsonOps.INSTANCE, text).getOrThrow())
- .map(JsonElement::toString)
- .orElse(null);
- database
- .updatePlayerCacheAsync(player.getUuid(), player.getName().getString(), nicknameJson)
- .exceptionally(err -> {
- EssentialCommands.LOGGER.error(err);
- return null;
- });
- } catch (Exception e) {
- // Log but don't crash on cache update failure - joinpoint database might not be initialized yet
- EssentialCommands.LOGGER.error(e);
- }
- }
public static boolean exists() {
return instance != null;
@@ -189,7 +156,7 @@ public void tick(MinecraftServer server) {
changedNicknames.forEach(playerData -> {
playerData.save();
- updatePlayerCache(playerData.getPlayer());
+ NicknameChangeCallback.EVENT.invoker().onNicknameChange(playerData.getPlayer());
});
this.changedNicknames.clear();
diff --git a/src/main/java/com/fibermc/essentialcommands/text/ECText.java b/src/main/java/com/fibermc/essentialcommands/text/ECText.java
index 339cfa7d..64ca388a 100644
--- a/src/main/java/com/fibermc/essentialcommands/text/ECText.java
+++ b/src/main/java/com/fibermc/essentialcommands/text/ECText.java
@@ -13,7 +13,6 @@
import com.fibermc.essentialcommands.playerdata.PlayerProfile;
import com.fibermc.essentialcommands.types.IStyleProvider;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
@@ -32,10 +31,10 @@
import static com.fibermc.essentialcommands.EssentialCommands.*;
public abstract class ECText {
- protected final Map stringMap;
+ protected final Map stringMap = new java.util.HashMap<>();
protected ECText(Map stringMap) {
- this.stringMap = stringMap;
+ this.stringMap.putAll(stringMap);
}
private static final Gson GSON = new Gson();
@@ -49,12 +48,49 @@ protected ECText(Map stringMap) {
BACKING_CONFIG.LANGUAGE.changeEvent.register((langId) -> instance = create(langId));
}
+ /**
+ * Register an additional lang file path to be loaded alongside Essential Commands lang files.
+ * @param resourcePathFormat Format string for the resource path (e.g., "/assets/mymod/lang/%s.json")
+ */
+ public static void registerAdditionalLangPath(String resourcePathFormat) {
+ loadLangFile(resourcePathFormat, CONFIG.LANGUAGE, instance.stringMap::put);
+ }
+
public static void init(MinecraftServer server) {
ECText.server = server;
}
+ private static void loadLangFile(String resourcePathFormat, String langId, BiConsumer entryConsumer) {
+ final String resourceLocation = String.format(resourcePathFormat, langId);
+ try {
+ InputStream inputStream = ECText.class.getResourceAsStream(resourceLocation);
+ if (inputStream == null) {
+ // Try default language if specified language not found
+ inputStream = ECText.class.getResourceAsStream(String.format(resourcePathFormat, DEFAULT_LANGUAGE_SPEC));
+ }
+
+ if (inputStream != null) {
+ try {
+ load(inputStream, entryConsumer);
+ } catch (Throwable loadEx) {
+ try {
+ inputStream.close();
+ } catch (Throwable closeEx) {
+ loadEx.addSuppressed(closeEx);
+ }
+ throw loadEx;
+ }
+ inputStream.close();
+ }
+ } catch (JsonParseException | IOException ex) {
+ LOGGER.error("Couldn't read strings from {}", resourceLocation, ex);
+ }
+ }
+
private static ECText create(String langId) {
- ImmutableMap.Builder builder = ImmutableMap.builder();
+ Map map = new java.util.HashMap<>();
+
+ // Load Essential Commands lang file
final String resourceFString = "/assets/essential_commands/lang/%s.json";
final String resourceLocation = String.format(resourceFString, langId);
try {
@@ -65,7 +101,7 @@ private static ECText create(String langId) {
}
try {
- load(inputStream, builder::put);
+ load(inputStream, map::put);
} catch (Throwable loadEx) {
if (inputStream != null) {
try {
@@ -85,7 +121,6 @@ private static ECText create(String langId) {
LOGGER.error("Couldn't read strings from {}", resourceLocation, ex);
}
- final Map map = builder.build();
return instance = server == null
? new ECTextImpl(map, ParserContext.of())
: ECTextImpl.forServer(map, server);
diff --git a/src/main/resources/assets/essential_commands/lang/en_us.json b/src/main/resources/assets/essential_commands/lang/en_us.json
index d8a2b5df..17ba07fa 100644
--- a/src/main/resources/assets/essential_commands/lang/en_us.json
+++ b/src/main/resources/assets/essential_commands/lang/en_us.json
@@ -20,43 +20,6 @@
"cmd.home.set.overwrite": "Home '${0}' already exists.\nWould you like to overwrite the existing home?",
"cmd.home.list.start": "Your current homes are: ",
"cmd.overwritehome.feedback": "Home '${0}' moved to current location.",
- "cmd.joinpoint.tp.error.not_found": "No joinpoint with the name '${0}' could be found for owner '${1}'.",
- "cmd.joinpoint.tp.error.owner_not_found": "Player '${0}' not found or has no accessible joinpoints.",
- "cmd.joinpoint.tp.error.no_access": "You don't have access to joinpoint '${0}' owned by '${1}'.",
- "cmd.joinpoint.location_name": "joinpoint: ${0}",
- "cmd.joinpoint.location_name_with_owner": "joinpoint: ${0} (owned by ${1})",
- "cmd.joinpoint.delete.feedback": "Joinpoint '${0}' has been deleted.",
- "cmd.joinpoint.delete.error": "Joinpoint '${0}' could not be deleted.",
- "cmd.joinpoint.set.feedback": "Joinpoint '${0}' set.",
- "cmd.joinpoint.set.feedback.global": "Global joinpoint '${0}' set.",
- "cmd.joinpoint.set.feedback.shared": "Shared joinpoint '${0}' set.",
- "cmd.joinpoint.set.error.limit": "Joinpoint '${0}' could not be set. Joinpoint limit (${1}) for joinpoint type '${2}' reached.",
- "cmd.joinpoint.set.error.exists": "Joinpoint '${0}' could not be set. A joinpoint with the specified name already exists.",
- "cmd.joinpoint.set.overwrite": "Joinpoint '${0}' already exists.\nWould you like to overwrite the existing joinpoint?",
- "cmd.joinpoint.overwrite.feedback": "Joinpoint '${0}' moved to current location.",
- "cmd.joinpoint.overwrite.feedback.global": "Global joinpoint '${0}' moved to current location.",
- "cmd.joinpoint.overwrite.feedback.shared": "Shared joinpoint '${0}' moved to current location.",
- "cmd.joinpoint.list.header.all": "All accessible joinpoints (${0}):",
- "cmd.joinpoint.list.header.owned": "Your owned joinpoints (${0}):",
- "cmd.joinpoint.list.header.shared_with": "Joinpoints shared with you (${0}):",
- "cmd.joinpoint.list.header.global": "Global joinpoints (${0}):",
- "cmd.joinpoint.list.empty.all": "You have no accessible joinpoints.",
- "cmd.joinpoint.list.empty.owned": "You don't own any joinpoints.",
- "cmd.joinpoint.list.empty.shared_with": "No joinpoints have been shared with you.",
- "cmd.joinpoint.list.empty.global": "No global joinpoints are available.",
- "cmd.joinpoint.error.database": "Database error: ${0}",
- "cmd.joinpoint.error.unknown": "Joinpoint action failed - an unknown error occurred: ${0}",
- "cmd.joinpoint.error.not_found": "Joinpoint '${0}' not found.",
- "cmd.joinpoint.share.error.already_global": "Joinpoint '${0}' is already global and accessible to all players.",
- "cmd.joinpoint.share.error.no_new_players": "No new players to share with.",
- "cmd.joinpoint.share.error.players_not_shared": "None of the specified players have access to this joinpoint.",
- "cmd.joinpoint.share.error.cannot_clear_global": "Cannot clear sharing for global joinpoint '${0}'. Use '/joinpoint set' to make it private first.",
- "cmd.joinpoint.share.add.feedback": "Shared joinpoint '${0}' with: ${1}",
- "cmd.joinpoint.share.remove.feedback": "Removed access to joinpoint '${0}' from: ${1}",
- "cmd.joinpoint.share.list.global": "Joinpoint '${0}' is global (accessible to all players).",
- "cmd.joinpoint.share.list.private": "Joinpoint '${0}' is private (only accessible to you).",
- "cmd.joinpoint.share.list.shared": "Joinpoint '${0}' is shared with: ${1}",
- "cmd.joinpoint.share.clear.feedback": "Cleared all sharing for joinpoint '${0}' (now private).",
"cmd.nickname.set.feedback": "Nickname set to '${0}'.",
"cmd.nickname.set.error": "Nickname could not be set to '${0}'. Reason: ${1}",
"cmd.nickname.set.error.perms": "Player has insufficient permissions for specified nickname.",