From e80c8a0fc3abea07e47d1c4c1cc98d6d007c3421 Mon Sep 17 00:00:00 2001 From: thomas Date: Sat, 2 May 2026 10:37:59 -0700 Subject: [PATCH 01/12] port to MC 26.1.2 (first unobfuscated release) Full port of craftedcore to Minecraft Java Edition 26.1.2 targeting Fabric API 0.147.0 and NeoForge 26.1.2.36-beta. See README for a complete breakdown of API renames and build changes. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 72 +++++++++++++++++ build.gradle.kts | 38 ++++++--- common/build.gradle.kts | 43 +++++++++-- .../dev/tocraft/craftedcore/CraftedCore.java | 11 ++- .../craftedcore/config/ConfigLoader.java | 7 +- .../data/PlayerDataSynchronizer.java | 4 +- .../data/SynchronizedJsonReloadListener.java | 33 ++++---- .../event/client/RenderEvents.java | 6 +- .../event/common/PlayerEvents.java | 2 +- .../craftedcore/gui/LongTextWidget.java | 16 ++-- .../tocraft/craftedcore/gui/TextureCache.java | 8 +- .../craftedcore/gui/TimerOverlayRenderer.java | 6 +- .../mixin/MultiPlayerGameModeMixin.java | 3 +- .../craftedcore/mixin/PlayerMixin.java | 3 +- .../craftedcore/network/ModernNetworking.java | 37 +++++---- .../network/ModernNetworkingService.java | 13 ++++ .../permission/PermissionChecker.java | 13 ++-- .../permission/PermissionCheckerService.java | 8 ++ .../craftedcore/platform/PlatformData.java | 32 ++++---- .../platform/PlatformDataService.java | 23 ++++++ .../craftedcore/platform/PlayerProfile.java | 6 +- .../registration/KeyBindingRegistry.java | 9 ++- .../KeyBindingRegistryService.java | 7 ++ .../registration/RegistryRegistry.java | 9 ++- .../registration/RegistryRegistryService.java | 8 ++ .../SynchronizedReloadListenerRegistry.java | 32 +++----- ...hronizedReloadListenerRegistryService.java | 8 ++ fabric/build.gradle.kts | 77 ++++++++++++------- .../fabric/CraftedCoreFabricEventHandler.java | 12 ++- .../CraftedCoreFabricEventHandlerClient.java | 5 +- .../fabric/mixin/client/GuiMixin.java | 18 ++--- .../mixin/client/MinecraftClientMixin.java | 4 +- .../network/fabric/ModernNetworkingImpl.java | 21 ++--- .../fabric/PermissionCheckerImpl.java | 16 ++-- .../platform/fabric/PlatformDataImpl.java | 27 ++++--- .../fabric/KeyBindingRegistryImpl.java | 10 ++- .../fabric/RegistryRegistryImpl.java | 8 +- ...ynchronizedReloadListenerRegistryImpl.java | 10 ++- ...raftedcore.network.ModernNetworkingService | 1 + ...edcore.permission.PermissionCheckerService | 1 + ...t.craftedcore.platform.PlatformDataService | 1 + ...ore.registration.KeyBindingRegistryService | 1 + ...dcore.registration.RegistryRegistryService | 1 + ....SynchronizedReloadListenerRegistryService | 1 + .../resources/craftedcore-fabric.mixins.json | 2 +- gradle.properties | 22 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- neoforge/build.gradle.kts | 72 +++++++++++------ .../CraftedCoreNeoForgeEventHandler.java | 13 +++- ...CraftedCoreNeoForgeEventHandlerClient.java | 2 +- .../neoforge/ModernNetworkingImpl.java | 17 ++-- .../neoforge/PermissionCheckerImpl.java | 30 +++----- .../platform/neoforge/PlatformDataImpl.java | 26 ++++--- .../neoforge/KeyBindingRegistryImpl.java | 6 +- .../neoforge/RegistryRegistryImpl.java | 6 +- ...ynchronizedReloadListenerRegistryImpl.java | 8 +- ...raftedcore.network.ModernNetworkingService | 1 + ...edcore.permission.PermissionCheckerService | 1 + ...t.craftedcore.platform.PlatformDataService | 1 + ...ore.registration.KeyBindingRegistryService | 1 + ...dcore.registration.RegistryRegistryService | 1 + ....SynchronizedReloadListenerRegistryService | 1 + settings.gradle.kts | 12 ++- 63 files changed, 581 insertions(+), 314 deletions(-) create mode 100644 common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworkingService.java create mode 100644 common/src/main/java/dev/tocraft/craftedcore/permission/PermissionCheckerService.java create mode 100644 common/src/main/java/dev/tocraft/craftedcore/platform/PlatformDataService.java create mode 100644 common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistryService.java create mode 100644 common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistryService.java create mode 100644 common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistryService.java create mode 100644 fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService create mode 100644 fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService create mode 100644 fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService create mode 100644 fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService create mode 100644 fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService create mode 100644 fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService create mode 100644 neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService create mode 100644 neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService create mode 100644 neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService create mode 100644 neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService create mode 100644 neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService create mode 100644 neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService diff --git a/README.md b/README.md index bfb9b55..e82a1c5 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,75 @@ forge: modApi "dev.tocraft:craftedcore-forge:1.20.2-2.0" ``` +## Building and installing locally + +This fork targets **Minecraft 26.1.2** and publishes to Maven Local (`~/.m2`). + +**Requirements:** JDK 21+, internet access for the first Gradle run (downloads MC assets). + +```bash +# Compile and publish all three artifacts (common, fabric, neoforge) to ~/.m2 +./gradlew publishToMavenLocal +``` + +To use the locally published artifacts in a dependent mod, add `mavenLocal()` to its repository list before any remote repos: + +```kotlin +repositories { + mavenLocal() + // ... other repos +} +``` + +Then depend on the artifacts as normal: + +```kotlin +// common +modApi("dev.tocraft:craftedcore:8.0") +// fabric +modApi("dev.tocraft:craftedcore-fabric:8.0") +// neoforge +modApi("dev.tocraft:craftedcore-neoforge:8.0") +``` + +To launch the Fabric test client: + +```bash +./gradlew :fabric:runClient +``` + +## Fork notes (MC 26.1.2) + +This is a fork of [ToCraft/craftedcore](https://github.com/ToCraft/craftedcore) updated to be compatible with **Minecraft Java Edition 26.1.2**, the first unobfuscated MC release (April 2026). The upstream library targets 1.21.x and below. + +### What changed + +**Minecraft API renames (global)** +- `net.minecraft.resources.ResourceLocation` → `net.minecraft.resources.Identifier` +- `net.minecraft.client.gui.GuiGraphics` → `net.minecraft.client.gui.GuiGraphicsExtractor` +- `GuiGraphics.renderItem()` → `GuiGraphicsExtractor.item()` +- `AbstractScrollArea` constructor requires a `ScrollbarSettings` argument; use `AbstractScrollArea.defaultSettings(width)` +- `MultiLineTextWidget.setColor()` removed; apply via `Component.copy().withStyle(s -> s.withColor(TextColor.fromRgb(...)))` +- `ServerLevel.getDayTime()` → `ServerLevel.getOverworldClockTime()` +- `player.hasPermissions(int)` → `player.permissions().hasPermission(Permissions.COMMANDS_GAMEMASTER)` +- `Player.interactOn(Entity, InteractionHand)` gained a `Vec3` parameter +- `MultiPlayerGameMode.interact(Player, Entity, InteractionHand)` gained an `EntityHitResult` parameter +- `Minecraft.disconnect(Screen, boolean)` now delegates to a new 3-param overload; the actual disconnect logic (including the `GameNarrator.clear()` call site) moved to `disconnect(Screen, boolean, boolean)` + +**Fabric API renames (fabric-api 0.147.0+26.1.2)** +- `KeyBindingHelper` / `registerKeyBinding` → `KeyMappingHelper` / `registerKeyMapping` (package `keymapping.v1`) +- `ResourceLoader.registerReloader` → `registerReloadListener` +- `HudRenderCallback` removed; replaced by `HudElementRegistry.addLast(id, HudElement)` with `extractRenderState(GuiGraphicsExtractor, DeltaTracker)` method +- `ServerWorldEvents` → `ServerLevelEvents` (now matches craftedcore's own class name; use FQN to disambiguate) +- `EntitySleepEvents.ALLOW_SLEEP_TIME` removed; replaced by `ALLOW_SLEEPING` with `(Player, BlockPos) → BedSleepingProblem` callback (return `null` to allow, `OTHER_PROBLEM` to deny) +- `PayloadTypeRegistry.playC2S()` / `playS2C()` → `serverboundPlay()` / `clientboundPlay()` +- `FabricRegistryBuilder.createSimple` → `FabricRegistryBuilder.create` + +**NeoForge API renames** +- `SleepFinishedTimeEvent.getNewTime()` / `setTimeAddition(long)` removed; replaced by `getAdjustment()` / `setAdjustment(ClockAdjustment)`. Use `new ClockAdjustment.Absolute(time)` — `ClockAdjustment` is not a functional interface so lambdas do not work. + +**Build** +- Added `maven-publish` plugin to all subprojects for `publishToMavenLocal` support +- `processResources` `expand()` blocks: capture `project.property(...)` into local vals before the `filesMatching` block (inside the block `this` is `FileCopyDetails`, not `Project`) +- Added `compileOnly("net.fabricmc:fabric-loader:...")` to the neoforge subproject so common `@Environment(EnvType.CLIENT)` annotations compile + diff --git a/build.gradle.kts b/build.gradle.kts index 16c10c5..429425e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,15 +1,35 @@ plugins { - id("dev.tocraft.modmaster.root") version ("single-1.9") + id("net.fabricmc.fabric-loom") version "1.15.5" apply false + id("net.neoforged.moddev") version "2.0.141" apply false } -val clothConfigVersion: String? = properties["cloth_config_version"] as String +allprojects { + group = property("maven_group") as String + version = property("mod_version") as String +} + +subprojects { + apply(plugin = "maven-publish") -ext { - val modMeta = mutableMapOf() - modMeta["minecraft"] = project.properties["minecraft"] as String - modMeta["version"] = version - if (clothConfigVersion != null) { - modMeta["clothConfig"] = clothConfigVersion + afterEvaluate { + configure { + publications { + create("mavenJava") { + from(components["java"]) + } + } + } + } + repositories { + mavenLocal() + mavenCentral() + maven("https://maven.fabricmc.net/") + maven("https://maven.neoforged.net/releases/") + maven("https://maven.terraformersmc.com/releases/") + maven("https://maven.shedaniel.me/") + maven { + name = "Minecraft Libraries" + url = uri("https://libraries.minecraft.net") + } } - set("mod_meta", modMeta) } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index a5defa6..7a6957b 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,21 +1,48 @@ plugins { - id("dev.tocraft.modmaster.common") + `java-library` + id("net.neoforged.moddev") } -val clothConfigVersion: String? = parent!!.properties["cloth_config_version"] as String +val javaVersion = (property("java") as String).toInt() + +java { + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) + withSourcesJar() +} + +neoForge { + neoFormVersion = property("neoform_version") as String +} + +// Expose common sources as consumable artifacts for loader subprojects +val commonJava: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} +val commonResources: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add("commonJava", sourceSets.main.get().java.sourceDirectories.singleFile) + add("commonResources", sourceSets.main.get().resources.sourceDirectories.singleFile) +} + +val clothConfigVersion = properties["cloth_config_version"] as String? repositories { - maven("https://maven.terraformersmc.com/releases/") maven("https://maven.shedaniel.me/") } dependencies { - // mixin extras - implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:${rootProject.properties["mixinextras_version"]}")!!) - // Cloth Config + compileOnly("org.spongepowered:mixin:0.8.5") + compileOnly("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}") + annotationProcessor("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}") + compileOnly("net.fabricmc:fabric-loader:${property("fabric_loader")}") if (clothConfigVersion != null) { - modCompileOnly("me.shedaniel.cloth:cloth-config:${clothConfigVersion}") { + compileOnly("me.shedaniel.cloth:cloth-config:${clothConfigVersion}") { exclude("net.fabricmc.fabric-api") } } -} \ No newline at end of file +} diff --git a/common/src/main/java/dev/tocraft/craftedcore/CraftedCore.java b/common/src/main/java/dev/tocraft/craftedcore/CraftedCore.java index 211b90a..7f29456 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/CraftedCore.java +++ b/common/src/main/java/dev/tocraft/craftedcore/CraftedCore.java @@ -9,9 +9,8 @@ import dev.tocraft.craftedcore.platform.PlayerProfile; import dev.tocraft.craftedcore.platform.VersionChecker; import dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistry; -import net.fabricmc.api.EnvType; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; @@ -28,7 +27,7 @@ public class CraftedCore { @ApiStatus.Internal public final static Path CACHE_DIR = PlatformData.getConfigPath().resolve(CraftedCore.MODID + File.separatorChar + "cache"); @ApiStatus.Internal - public static final ResourceLocation CLEAR_CACHE_PACKET = id("clear_cache"); + public static final Identifier CLEAR_CACHE_PACKET = id("clear_cache"); public static final String MODID = "craftedcore"; public void initialize() { @@ -60,7 +59,7 @@ public void initialize() { CraftedCoreCommand.initialize(); // register config screen - if (PlatformData.getEnv() == EnvType.CLIENT) { + if (PlatformData.getEnv() == PlatformData.Env.CLIENT) { PlatformData.registerConfigScreen(CraftedCoreConfig.INSTANCE.getName()); } } @@ -76,8 +75,8 @@ public static void reportMissingInternet(Throwable cause) { @Contract("_ -> new") @ApiStatus.Internal - public static @NotNull ResourceLocation id(String name) { - return ResourceLocation.fromNamespaceAndPath(MODID, name); + public static @NotNull Identifier id(String name) { + return Identifier.fromNamespaceAndPath(MODID, name); } @ApiStatus.Internal diff --git a/common/src/main/java/dev/tocraft/craftedcore/config/ConfigLoader.java b/common/src/main/java/dev/tocraft/craftedcore/config/ConfigLoader.java index 29adb6d..6b7091e 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/config/ConfigLoader.java +++ b/common/src/main/java/dev/tocraft/craftedcore/config/ConfigLoader.java @@ -13,9 +13,10 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -32,7 +33,7 @@ // heavily based on the work of Draylar - https://github.com/Draylar/omega-config/ and therefore this class is licensed under MIT public class ConfigLoader { - public static final ResourceLocation CONFIG_SYNC = CraftedCore.id("config_sync"); + public static final Identifier CONFIG_SYNC = CraftedCore.id("config_sync"); private static final Map LOADED_CONFIGS = new ConcurrentHashMap<>(); private static final List CLIENT_CONFIGS = new ArrayList<>(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().setStrictness(Strictness.LENIENT).create(); @@ -75,7 +76,7 @@ public static C register(String name, C @NotNull ... typeGett C config = read(name, clazz); - if (PlatformData.getEnv() == EnvType.CLIENT) { + if (PlatformData.getEnv() == PlatformData.Env.CLIENT) { PlatformData.registerConfigScreen(name); } diff --git a/common/src/main/java/dev/tocraft/craftedcore/data/PlayerDataSynchronizer.java b/common/src/main/java/dev/tocraft/craftedcore/data/PlayerDataSynchronizer.java index f1a02da..20b2946 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/data/PlayerDataSynchronizer.java +++ b/common/src/main/java/dev/tocraft/craftedcore/data/PlayerDataSynchronizer.java @@ -8,7 +8,7 @@ import dev.tocraft.craftedcore.registration.PlayerDataRegistry; import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.*; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public class PlayerDataSynchronizer { private static final String PLAYER_DATA_SYNC = "player_data_sync"; - public static final ResourceLocation PLAYER_DATA_SYNC_ID = CraftedCore.id(PLAYER_DATA_SYNC); + public static final Identifier PLAYER_DATA_SYNC_ID = CraftedCore.id(PLAYER_DATA_SYNC); public static void registerPacketHandler() { ModernNetworking.registerReceiver(ModernNetworking.Side.S2C, PLAYER_DATA_SYNC_ID, (context, tag) -> { diff --git a/common/src/main/java/dev/tocraft/craftedcore/data/SynchronizedJsonReloadListener.java b/common/src/main/java/dev/tocraft/craftedcore/data/SynchronizedJsonReloadListener.java index 70be284..3d7302b 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/data/SynchronizedJsonReloadListener.java +++ b/common/src/main/java/dev/tocraft/craftedcore/data/SynchronizedJsonReloadListener.java @@ -10,9 +10,10 @@ import dev.tocraft.craftedcore.platform.PlatformData; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; + import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.FileToIdConverter; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; @@ -31,11 +32,11 @@ */ @SuppressWarnings("unused") public abstract class SynchronizedJsonReloadListener extends - SimplePreparableReloadListener> { - public final ResourceLocation RELOAD_SYNC; + SimplePreparableReloadListener> { + public final Identifier RELOAD_SYNC; protected final String directory; protected final Gson gson; - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); public SynchronizedJsonReloadListener(Gson gson, String directory) { this.gson = gson; @@ -43,22 +44,22 @@ public SynchronizedJsonReloadListener(Gson gson, String directory) { this.RELOAD_SYNC = CraftedCore.id("data_sync_" + directory); } - protected @NotNull Map prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) { - Map map = new HashMap<>(); + protected @NotNull Map prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) { + Map map = new HashMap<>(); scanDirectory(resourceManager, map); return map; } @Override - protected void apply(Map map, ResourceManager resourceManager, ProfilerFiller profiler) { + protected void apply(Map map, ResourceManager resourceManager, ProfilerFiller profiler) { this.map.clear(); this.map.putAll(map); - if (PlatformData.getEnv() == EnvType.SERVER) { + if (PlatformData.getEnv() == PlatformData.Env.SERVER) { this.onApply(this.map); } } - protected abstract void onApply(Map map); + protected abstract void onApply(Map map); public void sendSyncPacket(ServerPlayer player) { // Serialize unlocked to tag @@ -71,11 +72,11 @@ public void sendSyncPacket(ServerPlayer player) { @Environment(EnvType.CLIENT) private void onPacketReceive(ModernNetworking.Context context, CompoundTag compound) { - Map map = new HashMap<>(); + Map map = new HashMap<>(); if (compound != null) { for (String key : compound.keySet()) { String json = compound.getString(key).orElseThrow(); - map.put(ResourceLocation.parse(key), JsonParser.parseString(json)); + map.put(Identifier.parse(key), JsonParser.parseString(json)); } } this.onApply(map); @@ -86,12 +87,12 @@ public void registerPacketReceiver() { ModernNetworking.registerReceiver(ModernNetworking.Side.S2C, RELOAD_SYNC, this::onPacketReceive); } - private void scanDirectory(ResourceManager resourceManager, Map map) { + private void scanDirectory(ResourceManager resourceManager, Map map) { FileToIdConverter var4 = FileToIdConverter.json(directory); - Iterable> entrySet = var4.listMatchingResources(resourceManager).entrySet(); - for (Map.Entry entry : entrySet) { - ResourceLocation var7 = entry.getKey(); - ResourceLocation var8 = var4.fileToId(var7); + Iterable> entrySet = var4.listMatchingResources(resourceManager).entrySet(); + for (Map.Entry entry : entrySet) { + Identifier var7 = entry.getKey(); + Identifier var8 = var4.fileToId(var7); try { BufferedReader reader = entry.getValue().openAsReader(); try { diff --git a/common/src/main/java/dev/tocraft/craftedcore/event/client/RenderEvents.java b/common/src/main/java/dev/tocraft/craftedcore/event/client/RenderEvents.java index 008332d..c1401cc 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/event/client/RenderEvents.java +++ b/common/src/main/java/dev/tocraft/craftedcore/event/client/RenderEvents.java @@ -5,7 +5,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.DeltaTracker; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.Nullable; @@ -22,12 +22,12 @@ public final class RenderEvents { @Environment(EnvType.CLIENT) @FunctionalInterface public interface HUDRendering { - void render(GuiGraphics graphics, DeltaTracker tickCounter); + void extractRenderState(GuiGraphicsExtractor graphics, DeltaTracker tickCounter); } @Environment(EnvType.CLIENT) @FunctionalInterface public interface OverlayRendering { - InteractionResult render(@Nullable GuiGraphics graphics, Player player); + InteractionResult render(@Nullable GuiGraphicsExtractor graphics, Player player); } } diff --git a/common/src/main/java/dev/tocraft/craftedcore/event/common/PlayerEvents.java b/common/src/main/java/dev/tocraft/craftedcore/event/common/PlayerEvents.java index 8153351..497fcd2 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/event/common/PlayerEvents.java +++ b/common/src/main/java/dev/tocraft/craftedcore/event/common/PlayerEvents.java @@ -25,7 +25,7 @@ public final class PlayerEvents { long newNewTime = 0; for (SleepFinishedTime callback : callbacks) { long newTimeIn = callback.setTimeAddition(level, newTime); - if (level.getDayTime() <= newTime) { + if (level.getOverworldClockTime() <= newTime) { newNewTime = newTimeIn; } } diff --git a/common/src/main/java/dev/tocraft/craftedcore/gui/LongTextWidget.java b/common/src/main/java/dev/tocraft/craftedcore/gui/LongTextWidget.java index 5ec9d49..90719cd 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/gui/LongTextWidget.java +++ b/common/src/main/java/dev/tocraft/craftedcore/gui/LongTextWidget.java @@ -2,7 +2,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.AbstractScrollArea; import net.minecraft.client.gui.components.MultiLineTextWidget; import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; @@ -13,6 +13,7 @@ import net.minecraft.client.sounds.SoundManager; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -25,7 +26,7 @@ public class LongTextWidget extends AbstractScrollArea { private final int row_width; public LongTextWidget(int x, int y, int width, int height, boolean separators, int row_width) { - super(x, y, width, height, Component.nullToEmpty("")); + super(x, y, width, height, Component.nullToEmpty(""), AbstractScrollArea.defaultSettings(SCROLLBAR_WIDTH)); this.separators = separators; this.row_width = row_width; } @@ -46,7 +47,8 @@ public void addText(Component text) { } public void addText(Component text, Font font, int color) { - this.text.add(new MultiLineTextWidget(text, font).setColor(color)); + Component styled = (color != -1) ? text.copy().withStyle(s -> s.withColor(TextColor.fromRgb(color & 0xFFFFFF))) : text; + this.text.add(new MultiLineTextWidget(styled, font)); } protected int textWidth() { @@ -64,7 +66,7 @@ protected int scrollBarX() { } @Override - protected void renderWidget(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) { + protected void extractWidgetRenderState(@NotNull GuiGraphicsExtractor guiGraphics, int mouseX, int mouseY, float delta) { guiGraphics.enableScissor(this.getX(), this.getY(), this.getRight(), this.getBottom()); int x = this.getX() + (getWidth() - textWidth()) / 2; @@ -72,20 +74,20 @@ protected void renderWidget(@NotNull GuiGraphics guiGraphics, int mouseX, int mo for (MultiLineTextWidget widget : this.text) { widget.setPosition(x, y); widget.setMaxWidth(textWidth()); - widget.render(guiGraphics, mouseX, mouseY, delta); + widget.extractRenderState(guiGraphics, mouseX, mouseY, delta); y += widget.getHeight(); } guiGraphics.disableScissor(); - renderScrollbar(guiGraphics, mouseX, mouseY); + extractScrollbar(guiGraphics, mouseX, mouseY); if (separators) { renderSeparators(guiGraphics); } } - protected void renderSeparators(@NotNull GuiGraphics guiGraphics) { + protected void renderSeparators(@NotNull GuiGraphicsExtractor guiGraphics) { guiGraphics.blit(RenderPipelines.GUI_TEXTURED, Screen.INWORLD_HEADER_SEPARATOR, this.getX(), this.getY() - 2, 0.0F, 0.0F, this.getWidth(), 2, 32, 2); guiGraphics.blit(RenderPipelines.GUI_TEXTURED, Screen.INWORLD_FOOTER_SEPARATOR, this.getX(), this.getBottom(), 0.0F, 0.0F, this.getWidth(), 2, 32, 2); } diff --git a/common/src/main/java/dev/tocraft/craftedcore/gui/TextureCache.java b/common/src/main/java/dev/tocraft/craftedcore/gui/TextureCache.java index 85ca6ba..c3b21e2 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/gui/TextureCache.java +++ b/common/src/main/java/dev/tocraft/craftedcore/gui/TextureCache.java @@ -6,7 +6,7 @@ import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; @@ -20,7 +20,7 @@ @SuppressWarnings("unused") @Environment(EnvType.CLIENT) public class TextureCache { - private static final Map> TEXTURE_CACHE = new ConcurrentHashMap<>(); + private static final Map> TEXTURE_CACHE = new ConcurrentHashMap<>(); /** * Converts a URL to a readable id @@ -33,9 +33,9 @@ public class TextureCache { * @return the id if the texture could be cached or null, if there was an exception */ @Nullable - public static ResourceLocation getTextureId(String namespace, String type, String prefix, String fileType, URL textureURL) { + public static Identifier getTextureId(String namespace, String type, String prefix, String fileType, URL textureURL) { return TEXTURE_CACHE.computeIfAbsent(String.valueOf(textureURL), key -> { - ResourceLocation id = ResourceLocation.fromNamespaceAndPath(namespace, "textures/" + type + "/" + prefix + key.hashCode() + "." + fileType); + Identifier id = Identifier.fromNamespaceAndPath(namespace, "textures/" + type + "/" + prefix + key.hashCode() + "." + fileType); try (InputStream is = textureURL.openStream()) { NativeImage image = NativeImage.read(new ByteArrayInputStream(is.readAllBytes())); DynamicTexture dynamicTexture = new DynamicTexture(id::toString, image); diff --git a/common/src/main/java/dev/tocraft/craftedcore/gui/TimerOverlayRenderer.java b/common/src/main/java/dev/tocraft/craftedcore/gui/TimerOverlayRenderer.java index e17c899..1c6c78d 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/gui/TimerOverlayRenderer.java +++ b/common/src/main/java/dev/tocraft/craftedcore/gui/TimerOverlayRenderer.java @@ -1,14 +1,14 @@ package dev.tocraft.craftedcore.gui; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.ChatScreen; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @SuppressWarnings("unused") public class TimerOverlayRenderer { - public static void register(GuiGraphics graphics, int currentCooldown, int maxCooldown, Item item) { + public static void register(GuiGraphicsExtractor graphics, int currentCooldown, int maxCooldown, Item item) { Minecraft client = Minecraft.getInstance(); if (client.screen instanceof ChatScreen || currentCooldown <= 0 || item == null) { @@ -32,7 +32,7 @@ public static void register(GuiGraphics graphics, int currentCooldown, int maxCo } ItemStack stack = new ItemStack(item); - graphics.renderItem(stack, (int) (width * .95f), (int) (height * .92f)); + graphics.item(stack, (int) (width * .95f), (int) (height * .92f)); if (cooldownScale != 1) { graphics.disableScissor(); diff --git a/common/src/main/java/dev/tocraft/craftedcore/mixin/MultiPlayerGameModeMixin.java b/common/src/main/java/dev/tocraft/craftedcore/mixin/MultiPlayerGameModeMixin.java index c76cf27..d8f1adb 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/mixin/MultiPlayerGameModeMixin.java +++ b/common/src/main/java/dev/tocraft/craftedcore/mixin/MultiPlayerGameModeMixin.java @@ -6,6 +6,7 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.EntityHitResult; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -17,7 +18,7 @@ public class MultiPlayerGameModeMixin { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V", shift = At.Shift.AFTER), cancellable = true) - private void entityInteract(Player player, Entity entity, InteractionHand interactionHand, CallbackInfoReturnable cir) { + private void entityInteract(Player player, Entity entity, EntityHitResult hitResult, InteractionHand interactionHand, CallbackInfoReturnable cir) { InteractionResult result = EntityEvents.INTERACT_WITH_PLAYER.invoke().interact(player, entity, interactionHand); if (result != InteractionResult.PASS) { cir.setReturnValue(result); diff --git a/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerMixin.java b/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerMixin.java index 052a2d8..d55fa2f 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerMixin.java +++ b/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerMixin.java @@ -9,6 +9,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -47,7 +48,7 @@ private void writeNbt(ValueOutput out, CallbackInfo ci) { target = "Lnet/minecraft/world/entity/player/Player;getItemInHand(Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/item/ItemStack;", ordinal = 0), cancellable = true) - private void onInteraction(Entity entity, InteractionHand interactionHand, CallbackInfoReturnable cir) { + private void onInteraction(Entity entity, InteractionHand interactionHand, Vec3 hitVec, CallbackInfoReturnable cir) { InteractionResult result = EntityEvents.INTERACT_WITH_PLAYER.invoke().interact((Player) (Object) this, entity, interactionHand); if (result != InteractionResult.PASS) { cir.setReturnValue(result); diff --git a/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworking.java b/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworking.java index ec34073..b4ae8b9 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworking.java +++ b/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworking.java @@ -1,6 +1,5 @@ package dev.tocraft.craftedcore.network; -import dev.architectury.injectables.annotations.ExpectPlatform; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; @@ -10,7 +9,7 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.ApiStatus; @@ -19,40 +18,41 @@ import java.util.HashMap; import java.util.Map; +import java.util.ServiceLoader; @SuppressWarnings("unused") public class ModernNetworking { - private static final Map> TYPES = new HashMap<>(); + private static final ModernNetworkingService SERVICE = + ServiceLoader.load(ModernNetworkingService.class).findFirst().orElseThrow(); + private static final Map> TYPES = new HashMap<>(); - @ExpectPlatform - public static void registerReceiver(Side side, ResourceLocation id, Receiver receiver) { - throw new AssertionError(); + public static void registerReceiver(Side side, Identifier id, Receiver receiver) { + SERVICE.registerReceiver(side, id, receiver); } - @ExpectPlatform - public static void registerType(ResourceLocation id) { - throw new AssertionError(); + public static void registerType(Identifier id) { + SERVICE.registerType(id); } - public static CustomPacketPayload.Type getType(ResourceLocation id) { + public static CustomPacketPayload.Type getType(Identifier id) { if (!TYPES.containsKey(id)) { TYPES.put(id, new CustomPacketPayload.Type<>(id)); } return TYPES.get(id); } - public static void sendToPlayer(@NotNull ServerPlayer player, ResourceLocation packetId, CompoundTag data) { + public static void sendToPlayer(@NotNull ServerPlayer player, Identifier packetId, CompoundTag data) { player.connection.send(toPacket(Side.S2C, new PacketPayload(packetId, data))); } - public static void sendToPlayers(@NotNull Iterable players, ResourceLocation packetId, CompoundTag data) { + public static void sendToPlayers(@NotNull Iterable players, Identifier packetId, CompoundTag data) { for (ServerPlayer player : players) { sendToPlayer(player, packetId, data); } } @Environment(EnvType.CLIENT) - public static void sendToServer(ResourceLocation packetId, CompoundTag data) { + public static void sendToServer(Identifier packetId, CompoundTag data) { ClientPacketListener connection = Minecraft.getInstance().getConnection(); if (connection != null) { @@ -60,10 +60,9 @@ public static void sendToServer(ResourceLocation packetId, CompoundTag data) { } } - @ExpectPlatform @ApiStatus.Internal public static Packet toPacket(ModernNetworking.Side side, CustomPacketPayload payload) { - throw new AssertionError(); + return SERVICE.toPacket(side, payload); } @FunctionalInterface @@ -88,16 +87,16 @@ public enum Env { } @ApiStatus.Internal - public record PacketPayload(ResourceLocation id, + public record PacketPayload(Identifier id, CompoundTag nbt) implements CustomPacketPayload { public void write(@NotNull RegistryFriendlyByteBuf buf) { - buf.writeResourceLocation(id); + buf.writeIdentifier(id); buf.writeNbt(nbt); } public PacketPayload(@NotNull RegistryFriendlyByteBuf buf) { - this(buf.readResourceLocation(), buf.readNbt()); + this(buf.readIdentifier(), buf.readNbt()); } @Override @@ -115,7 +114,7 @@ public PacketPayload(@NotNull RegistryFriendlyByteBuf buf) { @Override public void encode(@NotNull RegistryFriendlyByteBuf buf, @NotNull PacketPayload payload) { - buf.writeResourceLocation(payload.id); + buf.writeIdentifier(payload.id); buf.writeNbt(payload.nbt); } }; diff --git a/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworkingService.java b/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworkingService.java new file mode 100644 index 0000000..5ca359b --- /dev/null +++ b/common/src/main/java/dev/tocraft/craftedcore/network/ModernNetworkingService.java @@ -0,0 +1,13 @@ +package dev.tocraft.craftedcore.network; + +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; + +public interface ModernNetworkingService { + void registerReceiver(ModernNetworking.Side side, Identifier id, ModernNetworking.Receiver receiver); + + void registerType(Identifier id); + + Packet toPacket(ModernNetworking.Side side, CustomPacketPayload payload); +} diff --git a/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionChecker.java b/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionChecker.java index 27fac1b..13335d7 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionChecker.java +++ b/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionChecker.java @@ -1,16 +1,14 @@ package dev.tocraft.craftedcore.permission; -import dev.architectury.injectables.annotations.ExpectPlatform; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; -/** - * Cross-platform permission manager interface for ReMorphed mod. - * Implementations handle platform-specific permission checks. - * NOTE: Requires Fabric Permissions API on Fabric to work properly - */ +import java.util.ServiceLoader; + @SuppressWarnings("unused") public class PermissionChecker { + private static final PermissionCheckerService SERVICE = + ServiceLoader.load(PermissionCheckerService.class).findFirst().orElseThrow(); /** * Check if a player has a specific permission node @@ -20,8 +18,7 @@ public class PermissionChecker { * @param permission The permission node to check * @return true if the player has the permission */ - @ExpectPlatform public static boolean hasPermission(@NotNull ServerPlayer player, @NotNull String namespace, @NotNull String permission) { - throw new AssertionError(); + return SERVICE.hasPermission(player, namespace, permission); } } diff --git a/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionCheckerService.java b/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionCheckerService.java new file mode 100644 index 0000000..a169bd1 --- /dev/null +++ b/common/src/main/java/dev/tocraft/craftedcore/permission/PermissionCheckerService.java @@ -0,0 +1,8 @@ +package dev.tocraft.craftedcore.permission; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +public interface PermissionCheckerService { + boolean hasPermission(@NotNull ServerPlayer player, @NotNull String namespace, @NotNull String permission); +} diff --git a/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java b/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java index ba11afd..28dd27d 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java +++ b/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java @@ -1,6 +1,5 @@ package dev.tocraft.craftedcore.platform; -import dev.architectury.injectables.annotations.ExpectPlatform; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import org.jetbrains.annotations.ApiStatus; @@ -9,51 +8,52 @@ import java.lang.module.ModuleDescriptor.Version; import java.nio.file.Path; +import java.util.ServiceLoader; @SuppressWarnings({"unused", "Contract"}) public final class PlatformData { - @ExpectPlatform + private static final PlatformDataService SERVICE = + ServiceLoader.load(PlatformDataService.class).findFirst().orElseThrow(); + public static boolean isModLoaded(String modid) { - throw new AssertionError(); + return SERVICE.isModLoaded(modid); } - @ExpectPlatform @Nullable public static Version getModVersion(String modid) { - throw new AssertionError(); + return SERVICE.getModVersion(modid); } - @ExpectPlatform public static boolean isDevEnv() { - throw new AssertionError(); + return SERVICE.isDevEnv(); } @Contract(value = " -> !null", pure = true) - @ExpectPlatform - public static EnvType getEnv() { - throw new AssertionError(); + public static Env getEnv() { + return SERVICE.getEnv(); } - @ExpectPlatform @Contract(value = " -> !null", pure = true) public static Path getConfigPath() { - throw new AssertionError(); + return SERVICE.getConfigPath(); } - @ExpectPlatform @Contract(value = " -> !null", pure = true) public static ModLoader getModLoaderId() { - throw new AssertionError(); + return SERVICE.getModLoaderId(); } - @ExpectPlatform @ApiStatus.Internal @Environment(EnvType.CLIENT) public static void registerConfigScreen(String name) { - throw new AssertionError(); + SERVICE.registerConfigScreen(name); } public enum ModLoader { FABRIC, FORGE, NEOFORGE, OTHER } + + public enum Env { + CLIENT, SERVER + } } diff --git a/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformDataService.java b/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformDataService.java new file mode 100644 index 0000000..c6645e8 --- /dev/null +++ b/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformDataService.java @@ -0,0 +1,23 @@ +package dev.tocraft.craftedcore.platform; + +import org.jetbrains.annotations.Nullable; + +import java.lang.module.ModuleDescriptor.Version; +import java.nio.file.Path; + +public interface PlatformDataService { + boolean isModLoaded(String modid); + + @Nullable + Version getModVersion(String modid); + + boolean isDevEnv(); + + PlatformData.Env getEnv(); + + Path getConfigPath(); + + PlatformData.ModLoader getModLoaderId(); + + void registerConfigScreen(String name); +} diff --git a/common/src/main/java/dev/tocraft/craftedcore/platform/PlayerProfile.java b/common/src/main/java/dev/tocraft/craftedcore/platform/PlayerProfile.java index 55ace16..04be97a 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/platform/PlayerProfile.java +++ b/common/src/main/java/dev/tocraft/craftedcore/platform/PlayerProfile.java @@ -5,7 +5,7 @@ import dev.tocraft.craftedcore.CraftedCoreConfig; import dev.tocraft.craftedcore.gui.TextureCache; import dev.tocraft.craftedcore.util.NetUtils; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -213,7 +213,7 @@ private static PlayerProfile _ofId(@NotNull final UUID uuid) { } @Nullable - public ResourceLocation getSkinId() { + public Identifier getSkinId() { if (skin != null) { return TextureCache.getTextureId(CraftedCore.MODID, "entity", "custom_skin_", "png", skin); } else { @@ -222,7 +222,7 @@ public ResourceLocation getSkinId() { } @Nullable - public ResourceLocation getCapeId() { + public Identifier getCapeId() { if (cape != null) { return TextureCache.getTextureId(CraftedCore.MODID, "entity", "custom_cape_", "png", cape); } else { diff --git a/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistry.java b/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistry.java index ec238b1..a9c0c7b 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistry.java +++ b/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistry.java @@ -1,15 +1,18 @@ package dev.tocraft.craftedcore.registration; -import dev.architectury.injectables.annotations.ExpectPlatform; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.KeyMapping; +import java.util.ServiceLoader; + @SuppressWarnings("unused") @Environment(EnvType.CLIENT) public final class KeyBindingRegistry { - @ExpectPlatform + private static final KeyBindingRegistryService SERVICE = + ServiceLoader.load(KeyBindingRegistryService.class).findFirst().orElseThrow(); + public static void register(KeyMapping keyMapping) { - throw new AssertionError(); + SERVICE.register(keyMapping); } } diff --git a/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistryService.java b/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistryService.java new file mode 100644 index 0000000..296975d --- /dev/null +++ b/common/src/main/java/dev/tocraft/craftedcore/registration/KeyBindingRegistryService.java @@ -0,0 +1,7 @@ +package dev.tocraft.craftedcore.registration; + +import net.minecraft.client.KeyMapping; + +public interface KeyBindingRegistryService { + void register(KeyMapping keyMapping); +} diff --git a/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistry.java b/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistry.java index a0abc42..bd8e512 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistry.java +++ b/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistry.java @@ -1,14 +1,17 @@ package dev.tocraft.craftedcore.registration; -import dev.architectury.injectables.annotations.ExpectPlatform; import net.minecraft.core.Registry; import net.minecraft.resources.RegistryDataLoader; import net.minecraft.resources.ResourceKey; +import java.util.ServiceLoader; + public class RegistryRegistry { - @ExpectPlatform + private static final RegistryRegistryService SERVICE = + ServiceLoader.load(RegistryRegistryService.class).findFirst().orElseThrow(); + public static Registry createSimpleRegistry(ResourceKey> key) { - throw new AssertionError(); + return SERVICE.createSimpleRegistry(key); } public static void registerWorldgen(RegistryDataLoader.RegistryData registryData) { diff --git a/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistryService.java b/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistryService.java new file mode 100644 index 0000000..1ccea0f --- /dev/null +++ b/common/src/main/java/dev/tocraft/craftedcore/registration/RegistryRegistryService.java @@ -0,0 +1,8 @@ +package dev.tocraft.craftedcore.registration; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; + +public interface RegistryRegistryService { + Registry createSimpleRegistry(ResourceKey> key); +} diff --git a/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistry.java b/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistry.java index c6afc59..d507956 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistry.java +++ b/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistry.java @@ -1,12 +1,10 @@ package dev.tocraft.craftedcore.registration; -import dev.architectury.injectables.annotations.ExpectPlatform; import dev.tocraft.craftedcore.data.SynchronizedJsonReloadListener; import dev.tocraft.craftedcore.event.common.ResourceEvents; import dev.tocraft.craftedcore.network.ModernNetworking; import dev.tocraft.craftedcore.platform.PlatformData; -import net.fabricmc.api.EnvType; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; @@ -14,45 +12,35 @@ import java.util.HashMap; import java.util.Map; +import java.util.ServiceLoader; @SuppressWarnings("UnreachableCode") public class SynchronizedReloadListenerRegistry { - private static final Map listener = new HashMap<>(); + private static final SynchronizedReloadListenerRegistryService SERVICE = + ServiceLoader.load(SynchronizedReloadListenerRegistryService.class).findFirst().orElseThrow(); + private static final Map listener = new HashMap<>(); - /** - * Register a serverside synchronized json reload listener - */ @SuppressWarnings("unused") - public static void register(SynchronizedJsonReloadListener reloadListener, ResourceLocation id) { + public static void register(SynchronizedJsonReloadListener reloadListener, Identifier id) { listener.put(id, reloadListener); - // Register Data Packet receiver - if (PlatformData.getEnv() == EnvType.CLIENT) { + if (PlatformData.getEnv() == PlatformData.Env.CLIENT) { reloadListener.registerPacketReceiver(); } - - // register Network packet ModernNetworking.registerType(reloadListener.RELOAD_SYNC); - onRegister(reloadListener, id); - } - - @SuppressWarnings("unused") - @ApiStatus.Internal - @ExpectPlatform - private static void onRegister(SynchronizedJsonReloadListener reloadListener, ResourceLocation id) { - throw new AssertionError(); + SERVICE.onRegister(reloadListener, id); } @Contract(" -> new") @ApiStatus.Internal - public static @NotNull Map getAllListener() { + public static @NotNull Map getAllListener() { return new HashMap<>(listener); } @SuppressWarnings("unused") - public static SynchronizedJsonReloadListener get(ResourceLocation id) { + public static SynchronizedJsonReloadListener get(Identifier id) { return listener.get(id); } diff --git a/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistryService.java b/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistryService.java new file mode 100644 index 0000000..cb02778 --- /dev/null +++ b/common/src/main/java/dev/tocraft/craftedcore/registration/SynchronizedReloadListenerRegistryService.java @@ -0,0 +1,8 @@ +package dev.tocraft.craftedcore.registration; + +import dev.tocraft.craftedcore.data.SynchronizedJsonReloadListener; +import net.minecraft.resources.Identifier; + +public interface SynchronizedReloadListenerRegistryService { + void onRegister(SynchronizedJsonReloadListener reloadListener, Identifier id); +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 5719b9e..4dec975 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,42 +1,65 @@ plugins { - id("dev.tocraft.modmaster.fabric") + id("net.fabricmc.fabric-loom") + `java-library` } -tasks.withType { - @Suppress("UNCHECKED_CAST") val modMeta = parent!!.ext["mod_meta"]!! as Map - //inputs.properties.putAll(modMeta) +val javaVersion = (property("java") as String).toInt() - filesMatching("fabric.mod.json") { - expand(modMeta) - } - - outputs.upToDateWhen { false } +java { + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) + withSourcesJar() } -val modMenuVersion: String? = parent!!.properties["modmenu_version"] as String +// Resolve common sources from :common subproject +val commonJava: Configuration by configurations.creating { isCanBeResolved = true } +val commonResources: Configuration by configurations.creating { isCanBeResolved = true } + +dependencies { + minecraft("com.mojang:minecraft:${property("minecraft")}") + implementation("net.fabricmc:fabric-loader:${property("fabric_loader")}") + implementation("net.fabricmc.fabric-api:fabric-api:${property("fabric")}") -val clothConfigVersion: String? = parent!!.properties["cloth_config_version"] as String + commonJava(project(":common", "commonJava")) + commonResources(project(":common", "commonResources")) -repositories { - maven("https://maven.terraformersmc.com/releases/") - maven("https://maven.shedaniel.me/") -} + // MixinExtras bundled with the mod + include(implementation(annotationProcessor("io.github.llamalad7:mixinextras-fabric:${property("mixinextras_version")}")!!)!!) -dependencies { - // mixin extras - include(implementation(annotationProcessor("io.github.llamalad7:mixinextras-fabric:${rootProject.properties["mixinextras_version"]}")!!)!!) + val modMenuVersion = properties["modmenu_version"] as String? if (modMenuVersion != null) { - modCompileOnly("com.terraformersmc:modmenu:${modMenuVersion}") { - isTransitive = false - } - modRuntimeOnly("com.terraformersmc:modmenu:${modMenuVersion}") { - isTransitive = false - } + compileOnly("com.terraformersmc:modmenu:${modMenuVersion}") { isTransitive = false } + runtimeOnly("com.terraformersmc:modmenu:${modMenuVersion}") { isTransitive = false } } - // Cloth Config + val clothConfigVersion = properties["cloth_config_version"] as String? if (clothConfigVersion != null) { - modRuntimeOnly("me.shedaniel.cloth:cloth-config-fabric:${clothConfigVersion}") { - exclude("net.fabricmc.fabric-api") + compileOnly("me.shedaniel.cloth:cloth-config-fabric:${clothConfigVersion}") { + exclude(group = "net.fabricmc.fabric-api") + } + runtimeOnly("me.shedaniel.cloth:cloth-config-fabric:${clothConfigVersion}") { + exclude(group = "net.fabricmc.fabric-api") } } } + +// Include common sources in this compilation +tasks.compileJava { source(commonJava) } +tasks.javadoc { source(commonJava) } + +tasks.processResources { + from(commonResources) + val mcVersion = project.property("minecraft") + val clothVersion = project.property("cloth_config_version") + filesMatching("fabric.mod.json") { + expand(mapOf( + "version" to project.version, + "minecraft" to mcVersion, + "clothConfig" to clothVersion + )) + } + outputs.upToDateWhen { false } +} + +tasks.named("sourcesJar") { + from(commonJava) + from(commonResources) +} diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/CraftedCoreFabricEventHandler.java b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/CraftedCoreFabricEventHandler.java index 8d27b7b..75176e1 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/CraftedCoreFabricEventHandler.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/CraftedCoreFabricEventHandler.java @@ -5,16 +5,20 @@ import dev.tocraft.craftedcore.event.common.ServerLevelEvents; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal public class CraftedCoreFabricEventHandler { public static void initialize() { - EntitySleepEvents.ALLOW_SLEEP_TIME.register((player, sleepingPos, vanillaResult) -> PlayerEvents.ALLOW_SLEEP_TIME.invoke().allowSleepTime(player, sleepingPos, vanillaResult)); + EntitySleepEvents.ALLOW_SLEEPING.register((player, sleepingPos) -> { + InteractionResult result = PlayerEvents.ALLOW_SLEEP_TIME.invoke().allowSleepTime(player, sleepingPos, true); + return result == InteractionResult.FAIL ? Player.BedSleepingProblem.OTHER_PROBLEM : null; + }); CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> CommandEvents.REGISTRATION.invoke().register(dispatcher, registryAccess, environment)); - ServerWorldEvents.LOAD.register((server, world) -> ServerLevelEvents.LEVEL_LOAD.invoke().call(world)); - ServerWorldEvents.UNLOAD.register((server, world) -> ServerLevelEvents.LEVEL_UNLOAD.invoke().call(world)); + net.fabricmc.fabric.api.event.lifecycle.v1.ServerLevelEvents.LOAD.register((server, world) -> ServerLevelEvents.LEVEL_LOAD.invoke().call(world)); + net.fabricmc.fabric.api.event.lifecycle.v1.ServerLevelEvents.UNLOAD.register((server, world) -> ServerLevelEvents.LEVEL_UNLOAD.invoke().call(world)); } } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/client/CraftedCoreFabricEventHandlerClient.java b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/client/CraftedCoreFabricEventHandlerClient.java index 0c9cdf6..54a65e5 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/client/CraftedCoreFabricEventHandlerClient.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/client/CraftedCoreFabricEventHandlerClient.java @@ -1,10 +1,11 @@ package dev.tocraft.craftedcore.fabric.client; +import dev.tocraft.craftedcore.CraftedCore; import dev.tocraft.craftedcore.event.client.RenderEvents; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal @@ -12,7 +13,7 @@ public class CraftedCoreFabricEventHandlerClient { public static void initialize() { - HudRenderCallback.EVENT.register((graphics, tickCounter) -> RenderEvents.HUD_RENDERING.invoke().render(graphics, tickCounter)); + HudElementRegistry.addLast(CraftedCore.id("hud_render"), (graphics, tickCounter) -> RenderEvents.HUD_RENDERING.invoke().extractRenderState(graphics, tickCounter)); ClientTickEvents.START_CLIENT_TICK.register(instance -> dev.tocraft.craftedcore.event.client.ClientTickEvents.CLIENT_PRE.invoke().tick(instance)); ClientTickEvents.END_CLIENT_TICK.register(instance -> dev.tocraft.craftedcore.event.client.ClientTickEvents.CLIENT_POST.invoke().tick(instance)); } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/GuiMixin.java b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/GuiMixin.java index 405e305..13ec028 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/GuiMixin.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/GuiMixin.java @@ -4,7 +4,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.Gui; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.ApiStatus; @@ -23,32 +23,32 @@ public abstract class GuiMixin { @Shadow protected abstract Player getCameraPlayer(); - @Inject(method = "renderAirBubbles", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isEyeInFluid(Lnet/minecraft/tags/TagKey;)Z"), cancellable = true) - private void shouldRenderBreath(GuiGraphics guiGraphics, Player player, int i, int j, int k, CallbackInfo ci) { + @Inject(method = "extractAirBubbles", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isEyeInFluid(Lnet/minecraft/tags/TagKey;)Z"), cancellable = true) + private void shouldRenderBreath(GuiGraphicsExtractor guiGraphics, Player player, int i, int j, int k, CallbackInfo ci) { InteractionResult result = RenderEvents.RENDER_BREATH.invoke().render(guiGraphics, this.getCameraPlayer()); if (result == InteractionResult.FAIL) { ci.cancel(); } } - @Inject(method = "renderHearts", at = @At(value = "HEAD"), cancellable = true) - private void shouldRenderHealth(GuiGraphics guiGraphics, Player player, int x, int y, int height, int offsetHeartIndex, float maxHealth, int currentHealth, int displayHealth, int absorptionAmount, boolean renderHighlight, CallbackInfo ci) { + @Inject(method = "extractHearts", at = @At(value = "HEAD"), cancellable = true) + private void shouldRenderHealth(GuiGraphicsExtractor guiGraphics, Player player, int x, int y, int height, int offsetHeartIndex, float maxHealth, int currentHealth, int displayHealth, int absorptionAmount, boolean renderHighlight, CallbackInfo ci) { InteractionResult result = RenderEvents.RENDER_HEALTH.invoke().render(guiGraphics, player); if (result == InteractionResult.FAIL) { ci.cancel(); } } - @Inject(method = "renderFood", at = @At(value = "HEAD"), cancellable = true) - private void shouldRenderFood(GuiGraphics guiGraphics, Player player, int y, int x, CallbackInfo ci) { + @Inject(method = "extractFood", at = @At(value = "HEAD"), cancellable = true) + private void shouldRenderFood(GuiGraphicsExtractor guiGraphics, Player player, int y, int x, CallbackInfo ci) { InteractionResult result = RenderEvents.RENDER_FOOD.invoke().render(guiGraphics, player); if (result == InteractionResult.FAIL) { ci.cancel(); } } - @Inject(method = "renderVehicleHealth", at = @At(value = "HEAD"), cancellable = true) - private void shouldRenderMountHealth(GuiGraphics guiGraphics, CallbackInfo ci) { + @Inject(method = "extractVehicleHealth", at = @At(value = "HEAD"), cancellable = true) + private void shouldRenderMountHealth(GuiGraphicsExtractor guiGraphics, CallbackInfo ci) { InteractionResult result = RenderEvents.RENDER_MOUNT_HEALTH.invoke().render(guiGraphics, this.getCameraPlayer()); if (result == InteractionResult.FAIL) { ci.cancel(); diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/MinecraftClientMixin.java b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/MinecraftClientMixin.java index b1793c1..ee946f8 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/MinecraftClientMixin.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/fabric/mixin/client/MinecraftClientMixin.java @@ -16,9 +16,9 @@ @Environment(EnvType.CLIENT) @Mixin(Minecraft.class) public abstract class MinecraftClientMixin { - @Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", + @Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;ZZ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/GameNarrator;clear()V")) - private void onDisconnect(Screen screen, boolean retainDownloadedPacks, CallbackInfo ci) { + private void onDisconnect(Screen screen, boolean retainDownloadedPacks, boolean dropClientLevel, CallbackInfo ci) { ClientPlayerEvents.CLIENT_PLAYER_QUIT.invoke().quit(Minecraft.getInstance().player); } } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/network/fabric/ModernNetworkingImpl.java b/fabric/src/main/java/dev/tocraft/craftedcore/network/fabric/ModernNetworkingImpl.java index 34c59f0..f80b464 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/network/fabric/ModernNetworkingImpl.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/network/fabric/ModernNetworkingImpl.java @@ -2,6 +2,7 @@ import dev.tocraft.craftedcore.network.ModernNetworking; import dev.tocraft.craftedcore.network.ModernNetworking.PacketPayload; +import dev.tocraft.craftedcore.network.ModernNetworkingService; import net.fabricmc.api.EnvType; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; @@ -11,7 +12,7 @@ import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.ApiStatus; @@ -21,10 +22,11 @@ import static dev.tocraft.craftedcore.network.ModernNetworking.getType; @SuppressWarnings({"unused", "resource"}) -public class ModernNetworkingImpl { - public static void registerReceiver(ModernNetworking.Side side, ResourceLocation id, ModernNetworking.Receiver receiver) { +public class ModernNetworkingImpl implements ModernNetworkingService { + @Override + public void registerReceiver(ModernNetworking.Side side, Identifier id, ModernNetworking.Receiver receiver) { if (side == ModernNetworking.Side.C2S) { - PayloadTypeRegistry.playC2S().register(getType(id), PacketPayload.streamCodec()); + PayloadTypeRegistry.serverboundPlay().register(getType(id), PacketPayload.streamCodec()); ServerPlayNetworking.registerGlobalReceiver(getType(id), (payload, context) -> receiver.receive(new ModernNetworking.Context() { @Override public Player getPlayer() { @@ -45,7 +47,7 @@ public void queue(Runnable runnable) { } }, payload.nbt())); } else if (side == ModernNetworking.Side.S2C) { - PayloadTypeRegistry.playS2C().register(getType(id), PacketPayload.streamCodec()); + PayloadTypeRegistry.clientboundPlay().register(getType(id), PacketPayload.streamCodec()); ClientPlayNetworking.registerGlobalReceiver(getType(id), (payload, context) -> receiver.receive(new ModernNetworking.Context() { @Override public Player getPlayer() { @@ -65,16 +67,18 @@ public void queue(Runnable runnable) { } } - public static void registerType(ResourceLocation id) { + @Override + public void registerType(Identifier id) { if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { ModernNetworking.getType(id); - PayloadTypeRegistry.playS2C().register(getType(id), PacketPayload.streamCodec()); + PayloadTypeRegistry.clientboundPlay().register(getType(id), PacketPayload.streamCodec()); } } @Contract("_, _ -> new") + @Override @ApiStatus.Internal - public static @NotNull Packet toPacket(ModernNetworking.Side side, CustomPacketPayload payload) { + public @NotNull Packet toPacket(ModernNetworking.Side side, CustomPacketPayload payload) { if (side == ModernNetworking.Side.C2S) { return new ServerboundCustomPayloadPacket(payload); } else { @@ -82,4 +86,3 @@ public static void registerType(ResourceLocation id) { } } } - diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/permission/fabric/PermissionCheckerImpl.java b/fabric/src/main/java/dev/tocraft/craftedcore/permission/fabric/PermissionCheckerImpl.java index e4f4cfa..c3e0f09 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/permission/fabric/PermissionCheckerImpl.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/permission/fabric/PermissionCheckerImpl.java @@ -1,23 +1,21 @@ package dev.tocraft.craftedcore.permission.fabric; import dev.tocraft.craftedcore.CraftedCore; +import dev.tocraft.craftedcore.permission.PermissionCheckerService; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.permissions.Permissions; import net.minecraft.world.entity.Entity; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicBoolean; -/** - * Clean Fabric implementation of the PermissionManager using Fabric Permissions API - * Based on the clean approach used in OldSchoolJail - */ @SuppressWarnings("unused") -public class PermissionCheckerImpl { +public class PermissionCheckerImpl implements PermissionCheckerService { private static final AtomicBoolean CRASHED = new AtomicBoolean(false); - public static boolean hasPermission(@NotNull ServerPlayer player, @NotNull String namespace, @NotNull String permission) { - // Try to use Fabric Permissions API if available + @Override + public boolean hasPermission(@NotNull ServerPlayer player, @NotNull String namespace, @NotNull String permission) { try { if (!CRASHED.get()) { Class clazz = Class.forName("me.lucko.fabric.api.permissions.v0.Permissions"); @@ -28,8 +26,6 @@ public static boolean hasPermission(@NotNull ServerPlayer player, @NotNull Strin CraftedCore.LOGGER.error("Could not access Fabric-Permission-API-v0!", e); CRASHED.set(true); } - // Permissions API not available, fall back to OP level 2 - return player.hasPermissions(2); - + return player.permissions().hasPermission(Permissions.COMMANDS_GAMEMASTER); } } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/platform/fabric/PlatformDataImpl.java b/fabric/src/main/java/dev/tocraft/craftedcore/platform/fabric/PlatformDataImpl.java index 4bf590b..eab628a 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/platform/fabric/PlatformDataImpl.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/platform/fabric/PlatformDataImpl.java @@ -2,6 +2,7 @@ import dev.tocraft.craftedcore.fabric.client.CraftedCoreFabricClient; import dev.tocraft.craftedcore.platform.PlatformData; +import dev.tocraft.craftedcore.platform.PlatformDataService; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; @@ -12,35 +13,43 @@ import java.nio.file.Path; @SuppressWarnings({"unused", "SameReturnValue"}) -public final class PlatformDataImpl { - public static boolean isModLoaded(String modid) { +public final class PlatformDataImpl implements PlatformDataService { + @Override + public boolean isModLoaded(String modid) { return FabricLoader.getInstance().isModLoaded(modid); } + @Override @Nullable - public static Version getModVersion(String modid) { + public Version getModVersion(String modid) { return FabricLoader.getInstance().getModContainer(modid).map(modContainer -> Version.parse(modContainer.getMetadata().getVersion().getFriendlyString())).orElse(null); } - public static boolean isDevEnv() { + @Override + public boolean isDevEnv() { return FabricLoader.getInstance().isDevelopmentEnvironment(); } - public static EnvType getEnv() { - return FabricLoader.getInstance().getEnvironmentType(); + @Override + public PlatformData.Env getEnv() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT + ? PlatformData.Env.CLIENT : PlatformData.Env.SERVER; } - public static Path getConfigPath() { + @Override + public Path getConfigPath() { return FabricLoader.getInstance().getConfigDir(); } - public static PlatformData.ModLoader getModLoaderId() { + @Override + public PlatformData.ModLoader getModLoaderId() { return PlatformData.ModLoader.FABRIC; } + @Override @ApiStatus.Internal @Environment(EnvType.CLIENT) - public static void registerConfigScreen(String name) { + public void registerConfigScreen(String name) { CraftedCoreFabricClient.CONFIGS.add(name); } } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/KeyBindingRegistryImpl.java b/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/KeyBindingRegistryImpl.java index efd699d..afa734e 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/KeyBindingRegistryImpl.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/KeyBindingRegistryImpl.java @@ -1,14 +1,16 @@ package dev.tocraft.craftedcore.registration.fabric; +import dev.tocraft.craftedcore.registration.KeyBindingRegistryService; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; import net.minecraft.client.KeyMapping; @SuppressWarnings("unused") @Environment(EnvType.CLIENT) -public final class KeyBindingRegistryImpl { - public static void register(KeyMapping keyMapping) { - KeyBindingHelper.registerKeyBinding(keyMapping); +public final class KeyBindingRegistryImpl implements KeyBindingRegistryService { + @Override + public void register(KeyMapping keyMapping) { + KeyMappingHelper.registerKeyMapping(keyMapping); } } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/RegistryRegistryImpl.java b/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/RegistryRegistryImpl.java index 7e6b8b1..473733f 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/RegistryRegistryImpl.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/RegistryRegistryImpl.java @@ -1,12 +1,14 @@ package dev.tocraft.craftedcore.registration.fabric; +import dev.tocraft.craftedcore.registration.RegistryRegistryService; import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; @SuppressWarnings("unused") -public class RegistryRegistryImpl { - public static Registry createSimpleRegistry(ResourceKey> key) { - return FabricRegistryBuilder.createSimple(key).buildAndRegister(); +public class RegistryRegistryImpl implements RegistryRegistryService { + @Override + public Registry createSimpleRegistry(ResourceKey> key) { + return FabricRegistryBuilder.create(key).buildAndRegister(); } } diff --git a/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/SynchronizedReloadListenerRegistryImpl.java b/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/SynchronizedReloadListenerRegistryImpl.java index e2d2972..b694a4b 100644 --- a/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/SynchronizedReloadListenerRegistryImpl.java +++ b/fabric/src/main/java/dev/tocraft/craftedcore/registration/fabric/SynchronizedReloadListenerRegistryImpl.java @@ -1,15 +1,17 @@ package dev.tocraft.craftedcore.registration.fabric; import dev.tocraft.craftedcore.data.SynchronizedJsonReloadListener; +import dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService; import net.fabricmc.fabric.api.resource.v1.ResourceLoader; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.packs.PackType; import org.jetbrains.annotations.ApiStatus; @SuppressWarnings("unused") @ApiStatus.Internal -public class SynchronizedReloadListenerRegistryImpl { - public static void onRegister(SynchronizedJsonReloadListener reloadListener, ResourceLocation id) { - ResourceLoader.get(PackType.SERVER_DATA).registerReloader(id, reloadListener); +public class SynchronizedReloadListenerRegistryImpl implements SynchronizedReloadListenerRegistryService { + @Override + public void onRegister(SynchronizedJsonReloadListener reloadListener, Identifier id) { + ResourceLoader.get(PackType.SERVER_DATA).registerReloadListener(id, reloadListener); } } diff --git a/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService new file mode 100644 index 0000000..762c4f8 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.network.fabric.ModernNetworkingImpl diff --git a/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService new file mode 100644 index 0000000..b96dee3 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.permission.fabric.PermissionCheckerImpl diff --git a/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService new file mode 100644 index 0000000..6866e97 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.platform.fabric.PlatformDataImpl diff --git a/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService new file mode 100644 index 0000000..c8e7a6b --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.registration.fabric.KeyBindingRegistryImpl diff --git a/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService new file mode 100644 index 0000000..86ccc73 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.registration.fabric.RegistryRegistryImpl diff --git a/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService new file mode 100644 index 0000000..582974d --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.registration.fabric.SynchronizedReloadListenerRegistryImpl diff --git a/fabric/src/main/resources/craftedcore-fabric.mixins.json b/fabric/src/main/resources/craftedcore-fabric.mixins.json index 4da2716..760e966 100644 --- a/fabric/src/main/resources/craftedcore-fabric.mixins.json +++ b/fabric/src/main/resources/craftedcore-fabric.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "dev.tocraft.craftedcore.fabric.mixin", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "mixins": [ "LivingBreatheMixin", "LivingDeathMixin", diff --git a/gradle.properties b/gradle.properties index 71358a2..5a56f44 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,12 @@ org.gradle.jvmargs=-Xmx4G # Base Versions archives_base_name=craftedcore -mod_version=7.1 +mod_version=8.0 artifact_type=release maven_group=dev.tocraft # Loader Versions -fabric_loader=0.17.2 -mixinextras_version=0.5.0 +fabric_loader=0.18.4 +mixinextras_version=0.5.3 # Upload data curseforge_id=923377 modrinth_id=Dg7PHdkJ @@ -14,12 +14,12 @@ optional_dependencies=cloth-config # Discord Webhook ping_role=1247931121019261070 # <--- ModMaster ---> -minecraft=1.21.9 -supported_versions=1.21.9,1.21.10 -java=21 -mappings=2025.10.05 -fabric=0.134.0 -neoforge=21.9.16-beta +minecraft=26.1.2 +neoform_version=26.1.2-1 +java=25 +fabric=0.147.0+26.1.2 +fabric_loader=0.18.6 +neoforge=26.1.2.36-beta # Dependencies -modmenu_version=16.0.0-rc.1 -cloth_config_version=20.0.148 \ No newline at end of file +modmenu_version=18.0.0-alpha.8 +cloth_config_version=26.1.154 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e11132..dbc3ce4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 307a5af..4106d01 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -1,37 +1,59 @@ -import java.util.* - plugins { - id("dev.tocraft.modmaster.neoforge") + id("net.neoforged.moddev") + `java-library` } -tasks.withType { - @Suppress("UNCHECKED_CAST") val modMeta = parent!!.ext["mod_meta"]!! as Map - - filesMatching("META-INF/mods.toml") { - expand(modMeta) - } - - filesMatching("META-INF/neoforge.mods.toml") { - expand(modMeta) - } +val javaVersion = (property("java") as String).toInt() - - outputs.upToDateWhen { false } +java { + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) + withSourcesJar() } -val clothConfigVersion: String? = parent!!.properties["cloth_config_version"] as String +// Resolve common sources from :common subproject +val commonJava: Configuration by configurations.creating { isCanBeResolved = true } +val commonResources: Configuration by configurations.creating { isCanBeResolved = true } -repositories { - maven("https://maven.terraformersmc.com/releases/") - maven("https://maven.shedaniel.me/") +neoForge { + version = property("neoforge") as String } dependencies { - // mixin extras - compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${rootProject.properties["mixinextras_version"]}")!!) - implementation(include("io.github.llamalad7:mixinextras-neoforge:${rootProject.properties["mixinextras_version"]}")!!) - // Cloth Config + commonJava(project(":common", "commonJava")) + commonResources(project(":common", "commonResources")) + + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}")!!) + implementation(jarJar("io.github.llamalad7:mixinextras-neoforge:${property("mixinextras_version")}")!!) + + // Needed to compile common sources that use @Environment(EnvType.CLIENT) + compileOnly("net.fabricmc:fabric-loader:${property("fabric_loader")}") + + val clothConfigVersion = properties["cloth_config_version"] as String? if (clothConfigVersion != null) { - modRuntimeOnly("me.shedaniel.cloth:cloth-config-neoforge:${clothConfigVersion}") + compileOnly("me.shedaniel.cloth:cloth-config-neoforge:${clothConfigVersion}") + runtimeOnly("me.shedaniel.cloth:cloth-config-neoforge:${clothConfigVersion}") + } +} + +// Include common sources in this compilation +tasks.compileJava { source(commonJava) } +tasks.javadoc { source(commonJava) } + +tasks.processResources { + from(commonResources) + val mcVersion = project.property("minecraft") + val clothVersion = project.property("cloth_config_version") + filesMatching(listOf("META-INF/neoforge.mods.toml", "META-INF/mods.toml")) { + expand(mapOf( + "version" to project.version, + "minecraft" to mcVersion, + "clothConfig" to clothVersion + )) } -} \ No newline at end of file + outputs.upToDateWhen { false } +} + +tasks.named("sourcesJar") { + from(commonJava) + from(commonResources) +} diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java index 6ef2f97..93cb67a 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java @@ -6,7 +6,7 @@ import dev.tocraft.craftedcore.event.common.PlayerEvents; import dev.tocraft.craftedcore.event.common.ServerLevelEvents; import dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistry; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; @@ -18,6 +18,7 @@ import net.neoforged.neoforge.event.entity.player.CanContinueSleepingEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.level.LevelEvent; +import net.neoforged.neoforge.common.util.ClockAdjustment; import net.neoforged.neoforge.event.level.SleepFinishedTimeEvent; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -29,7 +30,7 @@ public class CraftedCoreNeoForgeEventHandler { @SubscribeEvent public void addReloadListenerEvent(AddServerReloadListenersEvent event) { - for (Map.Entry entry : SynchronizedReloadListenerRegistry.getAllListener().entrySet()) { + for (Map.Entry entry : SynchronizedReloadListenerRegistry.getAllListener().entrySet()) { event.addListener(entry.getKey(), entry.getValue()); } } @@ -57,8 +58,12 @@ public void allowSleepTime(@NotNull CanContinueSleepingEvent event) { @SubscribeEvent public void sleepFinishedTime(@NotNull SleepFinishedTimeEvent event) { - long newTimeIn = PlayerEvents.SLEEP_FINISHED_TIME.invoke().setTimeAddition((ServerLevel) event.getLevel(), event.getNewTime()); - event.setTimeAddition(newTimeIn); + ServerLevel level = (ServerLevel) event.getLevel(); + long currentTime = level.getOverworldClockTime(); + long newTimeIn = PlayerEvents.SLEEP_FINISHED_TIME.invoke().setTimeAddition(level, currentTime); + if (newTimeIn != currentTime) { + event.setAdjustment(new ClockAdjustment.Absolute(newTimeIn)); + } } @SubscribeEvent diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/client/CraftedCoreNeoForgeEventHandlerClient.java b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/client/CraftedCoreNeoForgeEventHandlerClient.java index c16018f..5263f2a 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/client/CraftedCoreNeoForgeEventHandlerClient.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/client/CraftedCoreNeoForgeEventHandlerClient.java @@ -21,7 +21,7 @@ public class CraftedCoreNeoForgeEventHandlerClient { @SubscribeEvent public void event(RenderGuiEvent.@NotNull Post event) { - RenderEvents.HUD_RENDERING.invoke().render(event.getGuiGraphics(), event.getPartialTick()); + RenderEvents.HUD_RENDERING.invoke().extractRenderState(event.getGuiGraphics(), event.getPartialTick()); } @SubscribeEvent diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/network/neoforge/ModernNetworkingImpl.java b/neoforge/src/main/java/dev/tocraft/craftedcore/network/neoforge/ModernNetworkingImpl.java index 5008263..a968640 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/network/neoforge/ModernNetworkingImpl.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/network/neoforge/ModernNetworkingImpl.java @@ -3,17 +3,16 @@ import dev.tocraft.craftedcore.neoforge.CraftedCoreNeoForge; import dev.tocraft.craftedcore.network.ModernNetworking; import dev.tocraft.craftedcore.network.ModernNetworking.PacketPayload; +import dev.tocraft.craftedcore.network.ModernNetworkingService; import net.minecraft.client.Minecraft; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.entity.player.Player; -import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.loading.FMLEnvironment; -import net.neoforged.fml.loading.FMLLoader; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; @@ -22,9 +21,9 @@ import static dev.tocraft.craftedcore.network.ModernNetworking.getType; @SuppressWarnings("unused") -public class ModernNetworkingImpl { - public static void registerReceiver(ModernNetworking.Side side, ResourceLocation id, ModernNetworking.Receiver - receiver) { +public class ModernNetworkingImpl implements ModernNetworkingService { + @Override + public void registerReceiver(ModernNetworking.Side side, Identifier id, ModernNetworking.Receiver receiver) { IEventBus eventBus = CraftedCoreNeoForge.getEventBus(); if (side == ModernNetworking.Side.C2S) { @@ -64,7 +63,8 @@ public void queue(Runnable runnable) { } } - public static void registerType(ResourceLocation id) { + @Override + public void registerType(Identifier id) { if (FMLEnvironment.getDist().isDedicatedServer()) { ModernNetworking.getType(id); registerReceiver(ModernNetworking.Side.S2C, id, (context, data) -> { @@ -73,8 +73,9 @@ public static void registerType(ResourceLocation id) { } @Contract("_, _ -> new") + @Override @ApiStatus.Internal - public static @NotNull Packet toPacket(ModernNetworking.Side side, CustomPacketPayload payload) { + public @NotNull Packet toPacket(ModernNetworking.Side side, CustomPacketPayload payload) { if (side == ModernNetworking.Side.C2S) { return new ServerboundCustomPayloadPacket(payload); } else { diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/permission/neoforge/PermissionCheckerImpl.java b/neoforge/src/main/java/dev/tocraft/craftedcore/permission/neoforge/PermissionCheckerImpl.java index 7dcef18..c2a0340 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/permission/neoforge/PermissionCheckerImpl.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/permission/neoforge/PermissionCheckerImpl.java @@ -1,7 +1,9 @@ package dev.tocraft.craftedcore.permission.neoforge; -import net.minecraft.resources.ResourceLocation; +import dev.tocraft.craftedcore.permission.PermissionCheckerService; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.permissions.Permissions; import net.neoforged.neoforge.server.permission.PermissionAPI; import net.neoforged.neoforge.server.permission.nodes.PermissionNode; import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; @@ -10,30 +12,20 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -/** - * NeoForge implementation of the PermissionManager using NeoForge PermissionAPI - */ @SuppressWarnings("unused") -public class PermissionCheckerImpl { +public class PermissionCheckerImpl implements PermissionCheckerService { + private static final ConcurrentMap> PERMISSION_NODES = new ConcurrentHashMap<>(); - // Cache for permission nodes to avoid recreating them - private static final ConcurrentMap> PERMISSION_NODES = new ConcurrentHashMap<>(); - - public static boolean hasPermission(@NotNull ServerPlayer player, @NotNull String namespace, @NotNull String permission) { - // Get or create permission node - ResourceLocation id = ResourceLocation.fromNamespaceAndPath(namespace, permission); + @Override + public boolean hasPermission(@NotNull ServerPlayer player, @NotNull String namespace, @NotNull String permission) { + Identifier id = Identifier.fromNamespaceAndPath(namespace, permission); PermissionNode node = PERMISSION_NODES.get(id); - - // Check permission using NeoForge PermissionAPI - return node != null ? PermissionAPI.getPermission(player, node) : player.hasPermissions(2); + return node != null ? PermissionAPI.getPermission(player, node) : player.permissions().hasPermission(Permissions.COMMANDS_GAMEMASTER); } - /** - * This MUST be called while registering the nodes! - */ public static PermissionNode createNode(@NotNull String namespace, @NotNull String permission) { - ResourceLocation id = ResourceLocation.fromNamespaceAndPath(namespace, permission); + Identifier id = Identifier.fromNamespaceAndPath(namespace, permission); return PERMISSION_NODES.put(id, new PermissionNode<>(id, PermissionTypes.BOOLEAN, - (player, playerUUID, context) -> player != null && player.hasPermissions(2))); + (player, playerUUID, context) -> player != null && player.permissions().hasPermission(Permissions.COMMANDS_GAMEMASTER))); } } diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/platform/neoforge/PlatformDataImpl.java b/neoforge/src/main/java/dev/tocraft/craftedcore/platform/neoforge/PlatformDataImpl.java index 5cfb16e..cf33678 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/platform/neoforge/PlatformDataImpl.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/platform/neoforge/PlatformDataImpl.java @@ -3,6 +3,7 @@ import dev.tocraft.craftedcore.config.Config; import dev.tocraft.craftedcore.config.ConfigLoader; import dev.tocraft.craftedcore.platform.PlatformData; +import dev.tocraft.craftedcore.platform.PlatformDataService; import net.minecraft.client.gui.screens.Screen; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; @@ -19,35 +20,42 @@ @SuppressWarnings({"unused", "SameReturnValue"}) @ApiStatus.Internal -public final class PlatformDataImpl { - public static boolean isModLoaded(String modid) { +public final class PlatformDataImpl implements PlatformDataService { + @Override + public boolean isModLoaded(String modid) { return ModList.get().isLoaded(modid); } + @Override @Nullable - public static Version getModVersion(String modid) { + public Version getModVersion(String modid) { return ModList.get().getModContainerById(modid).map(modContainer -> Version.parse(modContainer.getModInfo().getVersion().toString())).orElse(null); } - public static boolean isDevEnv() { + @Override + public boolean isDevEnv() { return !FMLLoader.getCurrent().isProduction(); } - public static Dist getEnv() { - return FMLEnvironment.getDist(); + @Override + public PlatformData.Env getEnv() { + return FMLEnvironment.getDist().isClient() ? PlatformData.Env.CLIENT : PlatformData.Env.SERVER; } - public static Path getConfigPath() { + @Override + public Path getConfigPath() { return FMLPaths.CONFIGDIR.get(); } - public static PlatformData.ModLoader getModLoaderId() { + @Override + public PlatformData.ModLoader getModLoaderId() { return PlatformData.ModLoader.NEOFORGE; } + @Override @ApiStatus.Internal @OnlyIn(Dist.CLIENT) - public static void registerConfigScreen(String name) { + public void registerConfigScreen(String name) { if (ModList.get().getModContainerById("cloth_config").isPresent()) { ModList.get().getModContainerById(name).ifPresent(mod -> mod.registerExtensionPoint(IConfigScreenFactory.class, (minecraft, parent) -> { Config c; diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/KeyBindingRegistryImpl.java b/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/KeyBindingRegistryImpl.java index 1b60e40..f8c3690 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/KeyBindingRegistryImpl.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/KeyBindingRegistryImpl.java @@ -1,6 +1,7 @@ package dev.tocraft.craftedcore.registration.neoforge; import com.mojang.logging.LogUtils; +import dev.tocraft.craftedcore.registration.KeyBindingRegistryService; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.Options; @@ -16,12 +17,13 @@ @SuppressWarnings("unused") @OnlyIn(Dist.CLIENT) -public final class KeyBindingRegistryImpl { +public final class KeyBindingRegistryImpl implements KeyBindingRegistryService { private static final Logger LOGGER = LogUtils.getLogger(); private static final List MAPPINGS = new ArrayList<>(); private static boolean eventCalled = false; - public static void register(KeyMapping mapping) { + @Override + public void register(KeyMapping mapping) { if (eventCalled) { Options options = Minecraft.getInstance().options; options.keyMappings = ArrayUtils.add(options.keyMappings, mapping); diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/RegistryRegistryImpl.java b/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/RegistryRegistryImpl.java index eb589b8..f2eefff 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/RegistryRegistryImpl.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/RegistryRegistryImpl.java @@ -1,5 +1,6 @@ package dev.tocraft.craftedcore.registration.neoforge; +import dev.tocraft.craftedcore.registration.RegistryRegistryService; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; import net.neoforged.neoforge.registries.RegistryBuilder; @@ -10,10 +11,11 @@ import java.util.ArrayList; import java.util.List; -public class RegistryRegistryImpl { +public class RegistryRegistryImpl implements RegistryRegistryService { private static final List> registries = new ArrayList<>(); - public static @NotNull Registry createSimpleRegistry(ResourceKey> key) { + @Override + public @NotNull Registry createSimpleRegistry(ResourceKey> key) { var registry = new RegistryBuilder<>(key).create(); registries.add(registry); return registry; diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/SynchronizedReloadListenerRegistryImpl.java b/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/SynchronizedReloadListenerRegistryImpl.java index c490ef2..a681af3 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/SynchronizedReloadListenerRegistryImpl.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/registration/neoforge/SynchronizedReloadListenerRegistryImpl.java @@ -1,13 +1,15 @@ package dev.tocraft.craftedcore.registration.neoforge; import dev.tocraft.craftedcore.data.SynchronizedJsonReloadListener; -import net.minecraft.resources.ResourceLocation; +import dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService; +import net.minecraft.resources.Identifier; import org.jetbrains.annotations.ApiStatus; @SuppressWarnings("unused") @ApiStatus.Internal -public class SynchronizedReloadListenerRegistryImpl { +public class SynchronizedReloadListenerRegistryImpl implements SynchronizedReloadListenerRegistryService { + @Override @SuppressWarnings("EmptyMethod") - public static void onRegister(SynchronizedJsonReloadListener reloadListener, ResourceLocation id) { + public void onRegister(SynchronizedJsonReloadListener reloadListener, Identifier id) { } } diff --git a/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService new file mode 100644 index 0000000..1118d32 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.network.ModernNetworkingService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.network.neoforge.ModernNetworkingImpl diff --git a/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService new file mode 100644 index 0000000..0fef267 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.permission.PermissionCheckerService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.permission.neoforge.PermissionCheckerImpl diff --git a/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService new file mode 100644 index 0000000..acde246 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.platform.PlatformDataService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.platform.neoforge.PlatformDataImpl diff --git a/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService new file mode 100644 index 0000000..f9b10f1 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.KeyBindingRegistryService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.registration.neoforge.KeyBindingRegistryImpl diff --git a/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService new file mode 100644 index 0000000..dcdc2d4 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.RegistryRegistryService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.registration.neoforge.RegistryRegistryImpl diff --git a/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService new file mode 100644 index 0000000..16475e6 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/dev.tocraft.craftedcore.registration.SynchronizedReloadListenerRegistryService @@ -0,0 +1 @@ +dev.tocraft.craftedcore.registration.neoforge.SynchronizedReloadListenerRegistryImpl diff --git a/settings.gradle.kts b/settings.gradle.kts index aa35f98..3401481 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,18 +1,16 @@ pluginManagement { repositories { maven("https://maven.fabricmc.net/") - maven("https://maven.architectury.dev/") - maven("https://maven.minecraftforge.net/") - maven("https://maven.parchmentmc.org") - maven("https://maven.tocraft.dev/public") + maven("https://maven.neoforged.net/releases/") gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + rootProject.name = "craftedcore" include("common") include("fabric") include("neoforge") -include("testmod-common") -include("testmod-fabric") -include("testmod-neoforge") \ No newline at end of file From 0be7686c1fe48edd9e86b2bc1d1733a56bec20c5 Mon Sep 17 00:00:00 2001 From: thomas Date: Sat, 2 May 2026 11:01:01 -0700 Subject: [PATCH 02/12] fix maven artifact IDs to match expected craftedcore/craftedcore-fabric/craftedcore-neoforge Co-Authored-By: Claude Sonnet 4.6 --- build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 429425e..ba630b1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,12 @@ subprojects { configure { publications { create("mavenJava") { + artifactId = when (project.name) { + "common" -> "craftedcore" + "fabric" -> "craftedcore-fabric" + "neoforge" -> "craftedcore-neoforge" + else -> project.name + } from(components["java"]) } } From 36f79676b3b0ff27e82add03b0f5f2e31f0362a3 Mon Sep 17 00:00:00 2001 From: thomas Date: Tue, 5 May 2026 14:39:36 -0700 Subject: [PATCH 03/12] Add CI checks, GitHub Releases workflow, and fix JAR naming - Add check.yml: PR builds for fabric and neoforge - Replace modmaster release action with GitHub Releases; tags on version bump - Name JARs craftedcore-fabric/neoforge-{version}.jar via archivesName - Bump version to 8.0-tgboyles_1 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/check.yml | 18 ++++++++++ .github/workflows/release.yml | 63 ++++++++++++++++++++++++----------- fabric/build.gradle.kts | 4 +++ gradle.properties | 2 +- neoforge/build.gradle.kts | 4 +++ 5 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..38122fb --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,18 @@ +name: Check + +on: + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '25' + distribution: 'temurin' + - uses: gradle/actions/setup-gradle@v4 + - name: Build + run: ./gradlew :fabric:build :neoforge:build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56d49cc..7b3ffa7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,31 +2,54 @@ name: Release on: push: - paths: - - '**.gradle' - - '**.gradle.kts' - - '**.properties' - - '**/src/**' - branches: - - "main" - - "master" - - "1.**" - workflow_dispatch: + branches: [main] permissions: contents: write - actions: write jobs: - build: + release: runs-on: ubuntu-latest - if: | - !contains(github.event.head_commit.message, '[ci skip]') steps: - - uses: ToCraft/modmaster-release-action@v1.1 + - uses: actions/checkout@v4 with: - java-version: '22' - maven-pass: ${{ secrets.MAVEN_PASS }} - curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} - modrinth-token: ${{ secrets.MODRINTH_TOKEN }} - webhook: ${{ secrets.DISCORD_WEB_HOOK }} \ No newline at end of file + fetch-depth: 0 + + - name: Read version + id: version + run: echo "version=$(grep '^mod_version=' gradle.properties | cut -d= -f2)" >> $GITHUB_OUTPUT + + - name: Check if tag exists + id: tag + run: | + if git ls-remote --tags origin "refs/tags/v${{ steps.version.outputs.version }}" | grep -q .; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - uses: actions/setup-java@v4 + if: steps.tag.outputs.exists == 'false' + with: + java-version: '25' + distribution: 'temurin' + + - uses: gradle/actions/setup-gradle@v4 + if: steps.tag.outputs.exists == 'false' + + - name: Build + if: steps.tag.outputs.exists == 'false' + run: ./gradlew :fabric:build :neoforge:build + + - name: Create GitHub Release + if: steps.tag.outputs.exists == 'false' + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.version }} + name: v${{ steps.version.outputs.version }} + generate_release_notes: true + files: | + fabric/build/libs/*.jar + !fabric/build/libs/*-sources.jar + neoforge/build/libs/*.jar + !neoforge/build/libs/*-sources.jar \ No newline at end of file diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 4dec975..d3d7374 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -3,6 +3,10 @@ plugins { `java-library` } +base { + archivesName = "${property("archives_base_name")}-fabric" +} + val javaVersion = (property("java") as String).toInt() java { diff --git a/gradle.properties b/gradle.properties index 5a56f44..319089b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx4G # Base Versions archives_base_name=craftedcore -mod_version=8.0 +mod_version=8.0-tgboyles_1 artifact_type=release maven_group=dev.tocraft # Loader Versions diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 4106d01..ae0c8d5 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -3,6 +3,10 @@ plugins { `java-library` } +base { + archivesName = "${property("archives_base_name")}-neoforge" +} + val javaVersion = (property("java") as String).toInt() java { From c1a399fff8cbb6157df821f35539b5aacaaeed90 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Thu, 7 May 2026 18:20:04 +0200 Subject: [PATCH 04/12] update gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 43705 -> 48966 bytes gradlew | 9 +++------ gradlew.bat | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975c742b298b441bfb90dbc124400a3751b9..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 40550 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
irPMAS;~ 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 diff --git a/gradlew b/gradlew index faf9300..0262dcb 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 63e479d4eeca6816ac4fe807f42f89bfd8df2bd0 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Thu, 7 May 2026 18:20:22 +0200 Subject: [PATCH 05/12] fix gradle.properties --- gradle.properties | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 319089b..93d5e8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,8 @@ mod_version=8.0-tgboyles_1 artifact_type=release maven_group=dev.tocraft # Loader Versions -fabric_loader=0.18.4 +fabric_loader=0.18.6 +neoform_version=26.1.2-1 mixinextras_version=0.5.3 # Upload data curseforge_id=923377 @@ -15,10 +16,10 @@ optional_dependencies=cloth-config ping_role=1247931121019261070 # <--- ModMaster ---> minecraft=26.1.2 -neoform_version=26.1.2-1 +supported_versions=1.21.9,1.21.10 java=25 +#mappings=2025.12.20 fabric=0.147.0+26.1.2 -fabric_loader=0.18.6 neoforge=26.1.2.36-beta # Dependencies modmenu_version=18.0.0-alpha.8 From abf66cba0e016e8080fd0abbc41ca1d58154d969 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Thu, 7 May 2026 18:20:36 +0200 Subject: [PATCH 06/12] add run tasks for neoforge --- neoforge/build.gradle.kts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index ae0c8d5..d4889d1 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -20,6 +20,22 @@ val commonResources: Configuration by configurations.creating { isCanBeResolved neoForge { version = property("neoforge") as String + + runs { + mods { + create("craftedcore") { // Replace with your actual mod ID + sourceSet(sourceSets.main.get()) + } + } + create("client") { + client() + } + + create("server") { + server() + programArgument("--nogui") + } + } } dependencies { From 2fdbd6880e60dbead05f55d7df5bd038d71ac642 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Sat, 9 May 2026 17:48:32 +0200 Subject: [PATCH 07/12] merge to newest modmaster, fix testmod, fix release task, cleanup workflows --- .github/workflows/check.yml | 18 ------ .github/workflows/release.yml | 63 ++++++------------- build.gradle.kts | 34 ++-------- common/build.gradle.kts | 38 +---------- .../craftedcore/platform/PlatformData.java | 6 +- fabric/build.gradle.kts | 29 +-------- gradle.properties | 4 +- neoforge/build.gradle.kts | 57 ++--------------- settings.gradle.kts | 14 ++--- .../tocraft/craftedcore/testmod/TestMod.java | 2 +- testmod-fabric/build.gradle.kts | 4 ++ 11 files changed, 53 insertions(+), 216 deletions(-) delete mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml deleted file mode 100644 index 38122fb..0000000 --- a/.github/workflows/check.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Check - -on: - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - java-version: '25' - distribution: 'temurin' - - uses: gradle/actions/setup-gradle@v4 - - name: Build - run: ./gradlew :fabric:build :neoforge:build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b3ffa7..56d49cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,54 +2,31 @@ name: Release on: push: - branches: [main] + paths: + - '**.gradle' + - '**.gradle.kts' + - '**.properties' + - '**/src/**' + branches: + - "main" + - "master" + - "1.**" + workflow_dispatch: permissions: contents: write + actions: write jobs: - release: + build: runs-on: ubuntu-latest + if: | + !contains(github.event.head_commit.message, '[ci skip]') steps: - - uses: actions/checkout@v4 + - uses: ToCraft/modmaster-release-action@v1.1 with: - fetch-depth: 0 - - - name: Read version - id: version - run: echo "version=$(grep '^mod_version=' gradle.properties | cut -d= -f2)" >> $GITHUB_OUTPUT - - - name: Check if tag exists - id: tag - run: | - if git ls-remote --tags origin "refs/tags/v${{ steps.version.outputs.version }}" | grep -q .; then - echo "exists=true" >> $GITHUB_OUTPUT - else - echo "exists=false" >> $GITHUB_OUTPUT - fi - - - uses: actions/setup-java@v4 - if: steps.tag.outputs.exists == 'false' - with: - java-version: '25' - distribution: 'temurin' - - - uses: gradle/actions/setup-gradle@v4 - if: steps.tag.outputs.exists == 'false' - - - name: Build - if: steps.tag.outputs.exists == 'false' - run: ./gradlew :fabric:build :neoforge:build - - - name: Create GitHub Release - if: steps.tag.outputs.exists == 'false' - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.version.outputs.version }} - name: v${{ steps.version.outputs.version }} - generate_release_notes: true - files: | - fabric/build/libs/*.jar - !fabric/build/libs/*-sources.jar - neoforge/build/libs/*.jar - !neoforge/build/libs/*-sources.jar \ No newline at end of file + java-version: '22' + maven-pass: ${{ secrets.MAVEN_PASS }} + curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + webhook: ${{ secrets.DISCORD_WEB_HOOK }} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ba630b1..5730a36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,38 +1,12 @@ plugins { - id("net.fabricmc.fabric-loom") version "1.15.5" apply false - id("net.neoforged.moddev") version "2.0.141" apply false -} - -allprojects { - group = property("maven_group") as String - version = property("mod_version") as String + id("dev.tocraft.modmaster.root") version ("2.0") } subprojects { - apply(plugin = "maven-publish") - - afterEvaluate { - configure { - publications { - create("mavenJava") { - artifactId = when (project.name) { - "common" -> "craftedcore" - "fabric" -> "craftedcore-fabric" - "neoforge" -> "craftedcore-neoforge" - else -> project.name - } - from(components["java"]) - } - } - } - } repositories { - mavenLocal() - mavenCentral() - maven("https://maven.fabricmc.net/") - maven("https://maven.neoforged.net/releases/") - maven("https://maven.terraformersmc.com/releases/") - maven("https://maven.shedaniel.me/") + maven("https://maven.fabricmc.net/") // fabric api + maven("https://maven.terraformersmc.com/releases/") // mod menu mod + maven("https://maven.shedaniel.me/") // cloth config maven { name = "Minecraft Libraries" url = uri("https://libraries.minecraft.net") diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 7a6957b..1afec61 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,48 +1,16 @@ plugins { - `java-library` - id("net.neoforged.moddev") + id("dev.tocraft.modmaster.common") } -val javaVersion = (property("java") as String).toInt() - -java { - toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) - withSourcesJar() -} - -neoForge { - neoFormVersion = property("neoform_version") as String -} - -// Expose common sources as consumable artifacts for loader subprojects -val commonJava: Configuration by configurations.creating { - isCanBeResolved = false - isCanBeConsumed = true -} -val commonResources: Configuration by configurations.creating { - isCanBeResolved = false - isCanBeConsumed = true -} - -artifacts { - add("commonJava", sourceSets.main.get().java.sourceDirectories.singleFile) - add("commonResources", sourceSets.main.get().resources.sourceDirectories.singleFile) -} - -val clothConfigVersion = properties["cloth_config_version"] as String? - -repositories { - maven("https://maven.shedaniel.me/") -} +val clothConfigVersion: String? = parent!!.properties["cloth_config_version"] as String? dependencies { compileOnly("org.spongepowered:mixin:0.8.5") compileOnly("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}") annotationProcessor("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}") - compileOnly("net.fabricmc:fabric-loader:${property("fabric_loader")}") if (clothConfigVersion != null) { compileOnly("me.shedaniel.cloth:cloth-config:${clothConfigVersion}") { exclude("net.fabricmc.fabric-api") } } -} +} \ No newline at end of file diff --git a/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java b/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java index 28dd27d..504c2ed 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java +++ b/common/src/main/java/dev/tocraft/craftedcore/platform/PlatformData.java @@ -54,6 +54,10 @@ public enum ModLoader { } public enum Env { - CLIENT, SERVER + CLIENT, SERVER; + + public boolean isClient() { + return this == CLIENT; + } } } diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index d3d7374..eb5c8d0 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,23 +1,7 @@ plugins { - id("net.fabricmc.fabric-loom") - `java-library` + id("dev.tocraft.modmaster.fabric") } -base { - archivesName = "${property("archives_base_name")}-fabric" -} - -val javaVersion = (property("java") as String).toInt() - -java { - toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) - withSourcesJar() -} - -// Resolve common sources from :common subproject -val commonJava: Configuration by configurations.creating { isCanBeResolved = true } -val commonResources: Configuration by configurations.creating { isCanBeResolved = true } - dependencies { minecraft("com.mojang:minecraft:${property("minecraft")}") implementation("net.fabricmc:fabric-loader:${property("fabric_loader")}") @@ -45,12 +29,8 @@ dependencies { } } -// Include common sources in this compilation -tasks.compileJava { source(commonJava) } -tasks.javadoc { source(commonJava) } - tasks.processResources { - from(commonResources) + from("commonResources") val mcVersion = project.property("minecraft") val clothVersion = project.property("cloth_config_version") filesMatching("fabric.mod.json") { @@ -62,8 +42,3 @@ tasks.processResources { } outputs.upToDateWhen { false } } - -tasks.named("sourcesJar") { - from(commonJava) - from(commonResources) -} diff --git a/gradle.properties b/gradle.properties index 93d5e8a..884142e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx4G # Base Versions -archives_base_name=craftedcore +modid=craftedcore mod_version=8.0-tgboyles_1 artifact_type=release maven_group=dev.tocraft @@ -20,7 +20,7 @@ supported_versions=1.21.9,1.21.10 java=25 #mappings=2025.12.20 fabric=0.147.0+26.1.2 -neoforge=26.1.2.36-beta +neoforge=26.1.2.43-beta # Dependencies modmenu_version=18.0.0-alpha.8 cloth_config_version=26.1.154 \ No newline at end of file diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index d4889d1..4c5fc04 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -1,53 +1,15 @@ -plugins { - id("net.neoforged.moddev") - `java-library` -} - -base { - archivesName = "${property("archives_base_name")}-neoforge" -} +import java.util.* -val javaVersion = (property("java") as String).toInt() - -java { - toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) - withSourcesJar() +plugins { + id("dev.tocraft.modmaster.neoforge") } -// Resolve common sources from :common subproject -val commonJava: Configuration by configurations.creating { isCanBeResolved = true } -val commonResources: Configuration by configurations.creating { isCanBeResolved = true } - -neoForge { - version = property("neoforge") as String - - runs { - mods { - create("craftedcore") { // Replace with your actual mod ID - sourceSet(sourceSets.main.get()) - } - } - create("client") { - client() - } - - create("server") { - server() - programArgument("--nogui") - } - } -} +val clothConfigVersion: String? = parent!!.properties["cloth_config_version"] as String? dependencies { - commonJava(project(":common", "commonJava")) - commonResources(project(":common", "commonResources")) - compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}")!!) implementation(jarJar("io.github.llamalad7:mixinextras-neoforge:${property("mixinextras_version")}")!!) - // Needed to compile common sources that use @Environment(EnvType.CLIENT) - compileOnly("net.fabricmc:fabric-loader:${property("fabric_loader")}") - val clothConfigVersion = properties["cloth_config_version"] as String? if (clothConfigVersion != null) { compileOnly("me.shedaniel.cloth:cloth-config-neoforge:${clothConfigVersion}") @@ -55,12 +17,8 @@ dependencies { } } -// Include common sources in this compilation -tasks.compileJava { source(commonJava) } -tasks.javadoc { source(commonJava) } - tasks.processResources { - from(commonResources) + from("commonResources") val mcVersion = project.property("minecraft") val clothVersion = project.property("cloth_config_version") filesMatching(listOf("META-INF/neoforge.mods.toml", "META-INF/mods.toml")) { @@ -72,8 +30,3 @@ tasks.processResources { } outputs.upToDateWhen { false } } - -tasks.named("sourcesJar") { - from(commonJava) - from(commonResources) -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 3401481..124d768 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,16 +1,16 @@ pluginManagement { repositories { - maven("https://maven.fabricmc.net/") - maven("https://maven.neoforged.net/releases/") - gradlePluginPortal() + maven("https://maven.fabricmc.net/") // fabric loom + maven("https://maven.neoforged.net/releases/") // neoforge mod dev + maven("https://maven.tocraft.dev/public") // mod master + gradlePluginPortal() // publishing plugins (CFGradle, Minotaur, Schoomp) } } -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" -} - rootProject.name = "craftedcore" include("common") include("fabric") include("neoforge") +include("testmod-common") +include("testmod-fabric") +include("testmod-neoforge") \ No newline at end of file diff --git a/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java b/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java index db15e6e..b4431d6 100644 --- a/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java +++ b/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java @@ -53,7 +53,7 @@ public static void initialize() { } }); - if (PlatformData.getEnv() == EnvType.CLIENT) { + if (PlatformData.getEnv() == PlatformData.Env.CLIENT) { TestModClient.initialize(); } } diff --git a/testmod-fabric/build.gradle.kts b/testmod-fabric/build.gradle.kts index 5a90726..ee394c4 100644 --- a/testmod-fabric/build.gradle.kts +++ b/testmod-fabric/build.gradle.kts @@ -1,3 +1,7 @@ plugins { id("dev.tocraft.modmaster.testmod-fabric") } + +dependencies { + implementation("net.fabricmc.fabric-api:fabric-api:${property("fabric")}") +} \ No newline at end of file From 38fca0e95caab1c1d580ff017f18e2cf2cf331ed Mon Sep 17 00:00:00 2001 From: To_Craft Date: Sat, 9 May 2026 17:56:29 +0200 Subject: [PATCH 08/12] remove redundand cloth config version line --- neoforge/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 4c5fc04..6a42a37 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -4,8 +4,6 @@ plugins { id("dev.tocraft.modmaster.neoforge") } -val clothConfigVersion: String? = parent!!.properties["cloth_config_version"] as String? - dependencies { compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${property("mixinextras_version")}")!!) implementation(jarJar("io.github.llamalad7:mixinextras-neoforge:${property("mixinextras_version")}")!!) From 2cbc53fcc70f8b5c5dbb4f634ec714defeac4586 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Sun, 10 May 2026 18:26:53 +0200 Subject: [PATCH 09/12] fix DESTROY_SPEED & REVOKE_ADVANCEMENT events --- build.gradle.kts | 2 +- .../tocraft/craftedcore/mixin/PlayerAdvancementsMixin.java | 4 +++- gradle.properties | 2 +- .../craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5730a36..4cfbcc6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("dev.tocraft.modmaster.root") version ("2.0") + id("dev.tocraft.modmaster.root") version ("2.1") } subprojects { diff --git a/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerAdvancementsMixin.java b/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerAdvancementsMixin.java index 9849c8d..7528e0b 100644 --- a/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerAdvancementsMixin.java +++ b/common/src/main/java/dev/tocraft/craftedcore/mixin/PlayerAdvancementsMixin.java @@ -22,6 +22,8 @@ private void afterAward(AdvancementHolder advancement, String criterionKey, Call @Inject(method = "revoke", at = @At(value = "RETURN")) private void afterRevoke(AdvancementHolder advancement, String criterionKey, CallbackInfoReturnable cir) { - PlayerEvents.REVOKE_ADVANCEMENT.invoke().revoke(this.player, advancement, criterionKey); + if (cir.getReturnValue()) { + PlayerEvents.REVOKE_ADVANCEMENT.invoke().revoke(this.player, advancement, criterionKey); + } } } diff --git a/gradle.properties b/gradle.properties index 884142e..a196c65 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ optional_dependencies=cloth-config ping_role=1247931121019261070 # <--- ModMaster ---> minecraft=26.1.2 -supported_versions=1.21.9,1.21.10 +supported_versions=26.1.2 java=25 #mappings=2025.12.20 fabric=0.147.0+26.1.2 diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java index 93cb67a..75f0afe 100644 --- a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/CraftedCoreNeoForgeEventHandler.java @@ -90,7 +90,9 @@ public void livingBreathe(@NotNull LivingBreatheEvent event) { event.setCanBreathe(EntityEvents.LIVING_BREATHE.invoke().breathe(event.getEntity(), event.canBreathe())); } + @SubscribeEvent private void destroySpeed(PlayerEvent.@NotNull BreakSpeed event) { - PlayerEvents.DESTROY_SPEED.invoke().setDestroySpeed(event.getEntity(), event.getNewSpeed()); + float speed = PlayerEvents.DESTROY_SPEED.invoke().setDestroySpeed(event.getEntity(), event.getNewSpeed()); + event.setNewSpeed(speed); } } From b67c65f92b78e58fb3add1c10e30d7f71d715800 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Sun, 10 May 2026 18:29:37 +0200 Subject: [PATCH 10/12] fix TestMod logging --- .../dev/tocraft/craftedcore/testmod/TestMod.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java b/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java index b4431d6..f0e769d 100644 --- a/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java +++ b/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java @@ -26,15 +26,15 @@ public class TestMod { public static void initialize() { PlayerEvents.PLAYER_JOIN.register(player -> LOGGER.debug("player {} joined.", player != null ? player.getName().getString() : "")); PlayerEvents.PLAYER_QUIT.register(player -> LOGGER.debug("player {} quit.", player != null ? player.getName().getString() : "")); - PlayerEvents.AWARD_ADVANCEMENT.register((player, advancement, criterionKey) -> LOGGER.debug("{} unlocked advancement {}", player != null ? player.getDisplayName() : "", criterionKey)); - PlayerEvents.REVOKE_ADVANCEMENT.register((player, advancement, criterionKey) -> LOGGER.debug("{} revoked advancement {}", player != null ? player.getDisplayName() : "", criterionKey)); + PlayerEvents.AWARD_ADVANCEMENT.register((player, advancement, criterionKey) -> LOGGER.debug("{} unlocked advancement {}", player != null ? player.getName().getString() : "", criterionKey)); + PlayerEvents.REVOKE_ADVANCEMENT.register((player, advancement, criterionKey) -> LOGGER.debug("{} revoked advancement {}", player != null ? player.getName().getString() : "", criterionKey)); EntityEvents.INTERACT_WITH_PLAYER.register((player, entity, hand) -> { - LOGGER.debug("player {}just interacted with {}", player != null ? player.getName().getString() : "", entity.getName().getString()); + LOGGER.debug("player {} just interacted with {}", player != null ? player.getName().getString() : "", entity.getName().getString()); return InteractionResult.PASS; }); EntityEvents.LIVING_DEATH.register((entity, source) -> { - LOGGER.debug("{}Oh, I just died in your arms tonight.", entity != null ? entity.getName().getString() : ""); + LOGGER.debug("{} Oh, I just died in your arms tonight.", entity != null ? entity.getName().getString() : ""); return InteractionResult.PASS; }); @@ -53,6 +53,12 @@ public static void initialize() { } }); + PlayerEvents.DESTROY_SPEED.register((player, oSpeed) -> { + float speed = oSpeed * 5; + LOGGER.debug("{} is mining with {} speed.", player.getName().getString(), speed); + return speed; + }); + if (PlatformData.getEnv() == PlatformData.Env.CLIENT) { TestModClient.initialize(); } From 60e675cf197f71bfdf0f7db1a82e0cbeb0c9e821 Mon Sep 17 00:00:00 2001 From: To_Craft Date: Sun, 10 May 2026 18:29:55 +0200 Subject: [PATCH 11/12] make LIVING_BREATHE logging visible until event is fixed --- .../main/java/dev/tocraft/craftedcore/testmod/TestMod.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java b/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java index f0e769d..98b446c 100644 --- a/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java +++ b/testmod-common/src/main/java/dev/tocraft/craftedcore/testmod/TestMod.java @@ -41,14 +41,14 @@ public static void initialize() { EntityEvents.LIVING_BREATHE.register((entity, canBreathe) -> { if (entity instanceof Player) { if (canBreathe) { - LOGGER.debug("In and out."); + LOGGER.info("In and out."); } else { - LOGGER.debug("I need air!"); + LOGGER.info("I need air!"); } // revert value, the players will need to breathe underwater now return !canBreathe; } else { - LOGGER.debug("something is breathing here..."); + LOGGER.info("something is breathing here..."); return canBreathe; } }); From 011f13bf666da72f20cb9d864b9475427e4e91ce Mon Sep 17 00:00:00 2001 From: To_Craft Date: Sun, 10 May 2026 18:45:27 +0200 Subject: [PATCH 12/12] fix Living Breathe event --- .../main/resources/craftedcore.mixins.json | 2 +- .../resources/craftedcore-fabric.mixins.json | 2 +- .../neoforge/mixin/LivingBreatheMixin.java | 21 +++++++++++++++++++ .../resources/META-INF/neoforge.mods.toml | 2 ++ .../craftedcore-neoforge.mixins.json | 14 +++++++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/mixin/LivingBreatheMixin.java create mode 100644 neoforge/src/main/resources/craftedcore-neoforge.mixins.json diff --git a/common/src/main/resources/craftedcore.mixins.json b/common/src/main/resources/craftedcore.mixins.json index c01e667..c0ca90e 100644 --- a/common/src/main/resources/craftedcore.mixins.json +++ b/common/src/main/resources/craftedcore.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "dev.tocraft.craftedcore.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "mixins": [ "CrashReportCategoryMixin", "CrashReportMixin", diff --git a/fabric/src/main/resources/craftedcore-fabric.mixins.json b/fabric/src/main/resources/craftedcore-fabric.mixins.json index 760e966..72d05d6 100644 --- a/fabric/src/main/resources/craftedcore-fabric.mixins.json +++ b/fabric/src/main/resources/craftedcore-fabric.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "dev.tocraft.craftedcore.fabric.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "mixins": [ "LivingBreatheMixin", "LivingDeathMixin", diff --git a/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/mixin/LivingBreatheMixin.java b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/mixin/LivingBreatheMixin.java new file mode 100644 index 0000000..f0735a2 --- /dev/null +++ b/neoforge/src/main/java/dev/tocraft/craftedcore/neoforge/mixin/LivingBreatheMixin.java @@ -0,0 +1,21 @@ +package dev.tocraft.craftedcore.neoforge.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.mojang.logging.LogUtils; +import dev.tocraft.craftedcore.event.common.EntityEvents; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.ApiStatus; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; + +// TODO: Remove once NeoForge got the LivingBreathe event working +@SuppressWarnings({"DataFlowIssue", "unused"}) +@ApiStatus.Internal +@Mixin(LivingEntity.class) +public class LivingBreatheMixin { + @ModifyExpressionValue(method = "baseTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isEyeInFluid(Lnet/minecraft/tags/TagKey;)Z"), require = 0) + private boolean canBreathe(boolean cantBreathe) { + return !EntityEvents.LIVING_BREATHE.invoke().breathe((LivingEntity) (Object) this, !cantBreathe); + } +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 32ae60c..21a5640 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -6,6 +6,8 @@ license = "LGPL-3.0" [[mixins]] config = "craftedcore.mixins.json" +[[mixins]] +config = "craftedcore-neoforge.mixins.json" [[mods]] modId = "craftedcore" diff --git a/neoforge/src/main/resources/craftedcore-neoforge.mixins.json b/neoforge/src/main/resources/craftedcore-neoforge.mixins.json new file mode 100644 index 0000000..295db9a --- /dev/null +++ b/neoforge/src/main/resources/craftedcore-neoforge.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.tocraft.craftedcore.neoforge.mixin", + "compatibilityLevel": "JAVA_25", + "mixins": [ + "LivingBreatheMixin" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +}