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
88 changes: 88 additions & 0 deletions joinpoints/build.gradle
Original file line number Diff line number Diff line change
@@ -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')
Comment on lines +5 to +9
Copy link
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'))
}

// 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
}
}
}
}
103 changes: 103 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,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
Copy link
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
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
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();

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