Skip to content
13 changes: 13 additions & 0 deletions .run/Minecraft Client.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Minecraft Client" type="Application" factoryName="Application">
<option name="ALTERNATIVE_JRE_PATH" value="jbr-21" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="net.fabricmc.devlaunchinjector.Main" />
<module name="essential_commands.main" />
<option name="VM_PARAMETERS" value="-XX:+ShowCodeDetailsInExceptionMessages -XX:+AllowEnhancedClassRedefinition -Dfabric.dli.config=$PROJECT_DIR$/.gradle/loom-cache/launch.cfg -Dfabric.dli.env=client -Dfabric.dli.main=net.fabricmc.loader.launch.knot.KnotServer" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/run/" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
34 changes: 34 additions & 0 deletions .run/Minecraft Server.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Minecraft Server" type="Application" factoryName="Application">
<option name="ALTERNATIVE_JRE_PATH" value="jbr-21" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<classpathModifications>
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-logging/commons-logging/1.3.4/b9fc14968d63a8b8a8a2c1885fe3e90564239708/commons-logging-1.3.4.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jcraft/jorbis/0.0.17/8872d22b293e8f5d7d56ff92be966e6dc28ebdc6/jorbis-0.0.17.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-tinyfd/3.3.3/82d755ca94b102e9ca77283b9e2dc46d1b15fbe5/lwjgl-tinyfd-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-codec/commons-codec/1.17.1/973638b7149d333563584137ebf13a691bb60579/commons-codec-1.17.1.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-glfw/3.3.3/efa1eb78c5ccd840e9f329717109b5e892d72f8e/lwjgl-glfw-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpclient/4.5.14/1194890e6f56ec29177673f2f12d0b8e627dec98/httpclient-4.5.14.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-compress/1.27.1/a19151084758e2fbb6b41eddaa88e7b8ff4e6599/commons-compress-1.27.1.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-jemalloc/3.3.3/b543467b7ff3c6920539a88ee602d34098628be5/lwjgl-jemalloc-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.mojang/text2speech/1.18.11/e853a12cdd6ba4f4836e8f4bf3b37844a13482b6/text2speech-1.18.11.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl/3.3.3/29589b5f87ed335a6c7e7ee6a5775f81f97ecb84/lwjgl-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-openal/3.3.3/daada81ceb5fc0c291fbfdd4433cb8d9423577f2/lwjgl-openal-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpcore/4.4.16/51cf043c87253c9f58b539c9f7e44c8894223850/httpcore-4.4.16.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.mojang/blocklist/1.0.10/5c685c5ffa94c4cd39496c7184c1d122e515ecef/blocklist-1.0.10.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.mojang/patchy/2.2.10/da05971b07cbb379d002cf7eaec6a2048211fefc/patchy-2.2.10.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-freetype/3.3.3/a0db6c84a8becc8ca05f9dbfa985edc348a824c7/lwjgl-freetype-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-stb/3.3.3/25dd6161988d7e65f71d5065c99902402ee32746/lwjgl-stb-3.3.3.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.ibm.icu/icu4j/76.1/215f3a8e936d4069344bd75f2b1368fd58112894/icu4j-76.1.jar" />
<entry exclude="true" path="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-opengl/3.3.3/2f6b0147078396a58979125a4c947664e98293a/lwjgl-opengl-3.3.3.jar" />
</classpathModifications>
<option name="MAIN_CLASS_NAME" value="net.fabricmc.devlaunchinjector.Main" />
<module name="essential_commands.main" />
<option name="PROGRAM_PARAMETERS" value="nogui" />
<option name="VM_PARAMETERS" value="-XX:+ShowCodeDetailsInExceptionMessages -Dfabric.dli.config=$PROJECT_DIR$/.gradle/loom-cache/launch.cfg -Dfabric.dli.env=server -Dfabric.dli.main=net.fabricmc.loader.impl.launch.knot.KnotServer" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/run/" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
13 changes: 13 additions & 0 deletions .run/joinpoints server.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="joinpoints server" type="Application" factoryName="Application">
<option name="ALTERNATIVE_JRE_PATH" value="/usr/lib/jvm/java-21-jetbrains" />
<option name="MAIN_CLASS_NAME" value="net.fabricmc.devlaunchinjector.Main" />
<module name="essential_commands.joinpoints.main" />
<option name="PROGRAM_PARAMETERS" value="nogui" />
<option name="VM_PARAMETERS" value="-XX:+ShowCodeDetailsInExceptionMessages -Dfabric.dli.config=$PROJECT_DIR$/.gradle/loom-cache/launch.cfg -Dfabric.dli.env=server -Dfabric.dli.main=net.fabricmc.loader.impl.launch.knot.KnotServer" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/joinpoints/run" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
23 changes: 14 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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}:")
}
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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}:")
}
}
}

Expand Down
73 changes: 73 additions & 0 deletions joinpoints/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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')
Comment on lines +5 to +9
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure this sorcery is still necessary, I think the sorcery in dependencies is doing the heavy lifting rn


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'))
}

// 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
}
}
}
}
69 changes: 69 additions & 0 deletions joinpoints/src/main/java/com/fibermc/joinpoints/Joinpoints.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.fibermc.joinpoints;

import java.nio.file.Path;

import com.fibermc.essentialcommands.text.ECText;
import com.fibermc.joinpoints.config.JoinpointsConfig;
import com.fibermc.joinpoints.database.JoinpointDatabase;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.minecraft.server.MinecraftServer;

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
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(though consider merging configs)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(though, this being "part of ec" may be a distinctly "me" thing -- whatever's decided, I should be consistent in the messaging: Is this a plugin for EC, or an entirely separate mod [that has a hard dependency]? -- is my ideal world one where the shared bits are a lib? how much really needs to be shared?)

);
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();

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;
}
}
Original file line number Diff line number Diff line change
@@ -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) -> {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should consider registering on the EC root node, and also supporting excluded_top_level_commands

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<ServerCommandSource> 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);
});
}
}
Loading