diff --git a/.gitignore b/.gitignore
index d18fc84..ef48ebd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ hs_err_pid*
# Common working directory
run
/repo/
+/.kotlin/
diff --git a/build.gradle b/build.gradle
index 5de1e9f..8a4a2a1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -42,7 +42,6 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(21)
kotlin.jvmToolchain(21)
neoForge {
- // Specify the version of NeoForge to use.
version = project.neo_version
parchment {
@@ -50,16 +49,10 @@ neoForge {
minecraftVersion = project.parchment_minecraft_version
}
- // This line is optional. Access Transformers are automatically detected
- // accessTransformers.add('src/main/resources/META-INF/accesstransformer.cfg')
-
- // Default run configurations.
- // These can be tweaked, removed, or duplicated as needed.
runs {
client {
client()
- // Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
@@ -69,9 +62,6 @@ neoForge {
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
- // This run config launches GameTestServer and runs all registered gametests, then exits.
- // By default, the server will crash when no gametests are provided.
- // The gametest system is also enabled by default for other run configs under the /test command.
gameTestServer {
type = "gameTestServer"
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
@@ -80,41 +70,23 @@ neoForge {
data {
data()
- // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it
- // gameDirectory = project.file('run-data')
-
- // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath()
}
- // applies to all the run configs above
configureEach {
- // Recommended logging data for a userdev environment
- // The markers can be added/remove as needed separated by commas.
- // "SCAN": For mods scan.
- // "REGISTRIES": For firing of registry events.
- // "REGISTRYDUMP": For getting the contents of all registries.
systemProperty 'forge.logging.markers', 'REGISTRIES'
- // Recommended logging level for the console
- // You can set various levels here.
- // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
logLevel = org.slf4j.event.Level.DEBUG
}
}
mods {
- // define mod <-> source bindings
- // these are used to tell the game which sources are for which mod
- // mostly optional in a single mod project
- // but multi mod projects should define one per mod
"${mod_id}" {
sourceSet(sourceSets.main)
}
}
}
-// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
@@ -135,31 +107,8 @@ dependencies {
runtimeOnly "curse.maven:sophisticated-core-618298:8046952"
runtimeOnly "curse.maven:sophisticated-storage-619320:8034906"
runtimeOnly "curse.maven:pocket-storage-367734:6834323"
-
- // Example mod dependency with JEI
- // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
- // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
- // compileOnly "mezz.jei:jei-${mc_version}-forge-api:${jei_version}"
- // runtimeOnly "mezz.jei:jei-${mc_version}-forge:${jei_version}"
-
- // Example mod dependency using a mod jar from ./libs with a flat dir repository
- // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
- // The group id is ignored when searching -- in this case, it is "blank"
- // implementation "blank:coolmod-${mc_version}:${coolmod_version}"
-
- // Example mod dependency using a file as dependency
- // implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar")
-
- // Example project dependency using a sister or child project:
- // implementation project(":myproject")
-
- // For more info:
- // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
- // http://www.gradle.org/docs/current/userguide/dependency_management.html
}
-// This block of code expands all declared replace properties in the specified resource targets.
-// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) {
var replaceProperties = [minecraft_version : minecraft_version,
minecraft_version_range: minecraft_version_range,
@@ -178,13 +127,9 @@ var generateModMetadata = tasks.register("generateModMetadata", ProcessResources
into "build/generated/sources/modMetadata"
}
-// Include the output of "generateModMetadata" as an input directory for the build
-// this works with both building through Gradle and the IDE.
sourceSets.main.resources.srcDir generateModMetadata
-// To avoid having to run "generateModMetadata" manually, make it run on every project reload
neoForge.ideSyncTask generateModMetadata
-// Example configuration to allow publishing using the maven-publish plugin
publishing {
publications {
register('mavenJava', MavenPublication) {
@@ -198,7 +143,6 @@ publishing {
}
}
-// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
idea {
module {
downloadSources = true
diff --git a/mclschcannoncompat.ipr b/mclschcannoncompat.ipr
new file mode 100644
index 0000000..758a596
--- /dev/null
+++ b/mclschcannoncompat.ipr
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.6
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mclschcannoncompat.iws b/mclschcannoncompat.iws
new file mode 100644
index 0000000..d5bc759
--- /dev/null
+++ b/mclschcannoncompat.iws
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ localhost
+ 5050
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index ada876e..90ae98f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -7,5 +7,5 @@ pluginManagement {
}
plugins {
- id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0'
}
diff --git a/src/generated/resources/.cache/37d63bb25ad2a1f1b3cea75df8f510fa196f89ed b/src/generated/resources/.cache/37d63bb25ad2a1f1b3cea75df8f510fa196f89ed
index 2cf11f4..4a2193c 100644
--- a/src/generated/resources/.cache/37d63bb25ad2a1f1b3cea75df8f510fa196f89ed
+++ b/src/generated/resources/.cache/37d63bb25ad2a1f1b3cea75df8f510fa196f89ed
@@ -1,4 +1,5 @@
-// 1.21.1 2026-05-05T23:41:11.6384884 Item Models: mclschcannoncompat
+// 1.21.1 2026-05-07T18:31:21.2712371 Item Models: mclschcannoncompat
52b70fbe9824ebbf99b2c3464e62faeb93a3ce63 assets/mclschcannoncompat/models/item/builder_assistant_staff.json
acd5e3967cb0dcf277da711c0b8f0efbc6271d6c assets/mclschcannoncompat/models/item/builder_assistant_staff_t2.json
d996164a02ec5598f0d421cc1ad0755b29621414 assets/mclschcannoncompat/models/item/builder_assistant_staff_t3.json
+a395cfa9425fbce9e6dad13f0044deceb4df8dcc assets/mclschcannoncompat/models/item/ornament_fabricator.json
diff --git a/src/generated/resources/.cache/6e801767522783b716853a5ad8062111be83b958 b/src/generated/resources/.cache/6e801767522783b716853a5ad8062111be83b958
index 0330876..4210393 100644
--- a/src/generated/resources/.cache/6e801767522783b716853a5ad8062111be83b958
+++ b/src/generated/resources/.cache/6e801767522783b716853a5ad8062111be83b958
@@ -1,2 +1,2 @@
-// 1.21.1 2026-05-07T13:46:09.9996673 Languages: ru_ru for mod: mclschcannoncompat
-3deacd406d126128077ef1e97aa26c68ef1759e7 assets/mclschcannoncompat/lang/ru_ru.json
+// 1.21.1 2026-05-09T02:10:42.4551668 Languages: ru_ru for mod: mclschcannoncompat
+a1fbe8c5922e6b9c566a16b14d42d6c67cc7e9d2 assets/mclschcannoncompat/lang/ru_ru.json
diff --git a/src/generated/resources/.cache/9ba92730b9239f67da1ce4d3431e6be06116236d b/src/generated/resources/.cache/9ba92730b9239f67da1ce4d3431e6be06116236d
index 524a983..5e1719c 100644
--- a/src/generated/resources/.cache/9ba92730b9239f67da1ce4d3431e6be06116236d
+++ b/src/generated/resources/.cache/9ba92730b9239f67da1ce4d3431e6be06116236d
@@ -1,2 +1,2 @@
-// 1.21.1 2026-05-07T13:46:10.0056649 Languages: en_us for mod: mclschcannoncompat
-a01abd18da057bba316103cb554505de33093034 assets/mclschcannoncompat/lang/en_us.json
+// 1.21.1 2026-05-09T02:10:42.4571469 Languages: en_us for mod: mclschcannoncompat
+f808ba452243bef6c6dc1efa6ce3a9d2ae74cb9e assets/mclschcannoncompat/lang/en_us.json
diff --git a/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e b/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e
index ad13b51..a376e69 100644
--- a/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e
+++ b/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e
@@ -1,7 +1,9 @@
-// 1.21.1 2026-05-05T23:41:11.6364906 Recipes
+// 1.21.1 2026-05-07T18:31:21.2702367 Recipes
+895b7a18109dacaaa77bafba58a50c941a9c2f56 data/mclschcannoncompat/advancement/recipes/decorations/ornament_fabricator.json
a4d166c9c7d5b081e15495f87588fa70f62bee84 data/mclschcannoncompat/advancement/recipes/tools/rec_fct1.json
c1b2ebefe2e42c4a01db164c8fbf90dd0aadea78 data/mclschcannoncompat/advancement/recipes/tools/rec_fct2.json
909da6934e35b03c890689105c7f29bae51ef330 data/mclschcannoncompat/advancement/recipes/tools/rec_fct3.json
+f42203a3bee2a747b9a5e1637a27fb043b1b97bd data/mclschcannoncompat/recipe/ornament_fabricator.json
594aec36a3be123023816c430342c9f081fd5bd4 data/mclschcannoncompat/recipe/rec_fct1.json
4d05680c3a8c9923bf0610a8429a821cf57bbfff data/mclschcannoncompat/recipe/rec_fct2.json
e4bfc8d84f08ce1637c0d24090653218ba04f5be data/mclschcannoncompat/recipe/rec_fct3.json
diff --git a/src/generated/resources/assets/mclschcannoncompat/lang/en_us.json b/src/generated/resources/assets/mclschcannoncompat/lang/en_us.json
index 05c3d04..1b1875b 100644
--- a/src/generated/resources/assets/mclschcannoncompat/lang/en_us.json
+++ b/src/generated/resources/assets/mclschcannoncompat/lang/en_us.json
@@ -1,6 +1,13 @@
{
- "item.asdf.assistantstaff.maxdepth": "Maximum depth: %s",
+ "block.mclschcannoncompat.ornament_fabricator": "Ornament Fabricator",
+ "item.assistantstaff.hover.maxdepth": "Maximum depth: %s",
"item.mclschcannoncompat.builder_assistant_staff": "Builder staff T1",
"item.mclschcannoncompat.builder_assistant_staff_t2": "Builder staff T2",
- "item.mclschcannoncompat.builder_assistant_staff_t3": "Builder staff T3"
+ "item.mclschcannoncompat.builder_assistant_staff_t3": "Builder staff T3",
+ "tooltip.btn.cyclestyle.down": "Cycle style backward",
+ "tooltip.btn.cyclestyle.up": "Cycle style forward",
+ "tooltip.btn.cyclevariant.down": "Cycle variant backward",
+ "tooltip.btn.cyclevariant.up": "Cycle variant forward",
+ "tooltip.btn.startcrafting": "Start crafting",
+ "tooltip.ornament_fabricator.no_preview": "Recipe is not supported for provided ingredients"
}
\ No newline at end of file
diff --git a/src/generated/resources/assets/mclschcannoncompat/lang/ru_ru.json b/src/generated/resources/assets/mclschcannoncompat/lang/ru_ru.json
index 9263fba..e01edd1 100644
--- a/src/generated/resources/assets/mclschcannoncompat/lang/ru_ru.json
+++ b/src/generated/resources/assets/mclschcannoncompat/lang/ru_ru.json
@@ -1,6 +1,13 @@
{
- "item.asdf.assistantstaff.maxdepth": "Максимальная блоков: %s",
+ "block.mclschcannoncompat.ornament_fabricator": "Орнаментный фабрикатор",
+ "item.assistantstaff.hover.maxdepth": "Максимальная глубина: %s",
"item.mclschcannoncompat.builder_assistant_staff": "Посох строителя T1",
"item.mclschcannoncompat.builder_assistant_staff_t2": "Посох строителя T2",
- "item.mclschcannoncompat.builder_assistant_staff_t3": "Посох строителя T3"
+ "item.mclschcannoncompat.builder_assistant_staff_t3": "Посох строителя T3",
+ "tooltip.btn.cyclestyle.down": "Переключить стиль назад",
+ "tooltip.btn.cyclestyle.up": "Переключить стиль вперёд",
+ "tooltip.btn.cyclevariant.down": "Переключить вариант назад",
+ "tooltip.btn.cyclevariant.up": "Переключить вариант вперёд",
+ "tooltip.btn.startcrafting": "Начать крафт",
+ "tooltip.ornament_fabricator.no_preview": "Рецепт не подходит этим ингредиентам"
}
\ No newline at end of file
diff --git a/src/generated/resources/assets/mclschcannoncompat/models/item/ornament_fabricator.json b/src/generated/resources/assets/mclschcannoncompat/models/item/ornament_fabricator.json
new file mode 100644
index 0000000..f34e5d0
--- /dev/null
+++ b/src/generated/resources/assets/mclschcannoncompat/models/item/ornament_fabricator.json
@@ -0,0 +1,3 @@
+{
+ "parent": "mclschcannoncompat:block/ornament_fabricator"
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/mclschcannoncompat/advancement/recipes/decorations/ornament_fabricator.json b/src/generated/resources/data/mclschcannoncompat/advancement/recipes/decorations/ornament_fabricator.json
new file mode 100644
index 0000000..1f724c0
--- /dev/null
+++ b/src/generated/resources/data/mclschcannoncompat/advancement/recipes/decorations/ornament_fabricator.json
@@ -0,0 +1,32 @@
+{
+ "parent": "minecraft:recipes/root",
+ "criteria": {
+ "has_amethyst": {
+ "conditions": {
+ "items": [
+ {
+ "items": "minecraft:amethyst_shard"
+ }
+ ]
+ },
+ "trigger": "minecraft:inventory_changed"
+ },
+ "has_the_recipe": {
+ "conditions": {
+ "recipe": "mclschcannoncompat:ornament_fabricator"
+ },
+ "trigger": "minecraft:recipe_unlocked"
+ }
+ },
+ "requirements": [
+ [
+ "has_the_recipe",
+ "has_amethyst"
+ ]
+ ],
+ "rewards": {
+ "recipes": [
+ "mclschcannoncompat:ornament_fabricator"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/mclschcannoncompat/recipe/ornament_fabricator.json b/src/generated/resources/data/mclschcannoncompat/recipe/ornament_fabricator.json
new file mode 100644
index 0000000..6674b99
--- /dev/null
+++ b/src/generated/resources/data/mclschcannoncompat/recipe/ornament_fabricator.json
@@ -0,0 +1,24 @@
+{
+ "type": "minecraft:crafting_shaped",
+ "category": "misc",
+ "key": {
+ "A": {
+ "item": "minecraft:amethyst_shard"
+ },
+ "C": {
+ "item": "minecraft:crafting_table"
+ },
+ "I": {
+ "item": "minecraft:iron_ingot"
+ }
+ },
+ "pattern": [
+ "III",
+ "ACA",
+ "III"
+ ],
+ "result": {
+ "count": 1,
+ "id": "mclschcannoncompat:ornament_fabricator"
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/xyz/nuark/mcmod/mclschcannoncompat/utils/ODBlockRecipiesCache.java b/src/main/java/xyz/nuark/mcmod/mclschcannoncompat/utils/ODBlockRecipiesCache.java
new file mode 100644
index 0000000..0d1be18
--- /dev/null
+++ b/src/main/java/xyz/nuark/mcmod/mclschcannoncompat/utils/ODBlockRecipiesCache.java
@@ -0,0 +1,54 @@
+package xyz.nuark.mcmod.mclschcannoncompat.utils;
+
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.ItemStack;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class ODBlockRecipiesCache {
+ private static final Logger LOGGER = LogManager.getLogger(Mclschcannoncompat.ID);
+
+ private static final List blockTypesCache = new ArrayList<>();
+ private static final TreeMap> blockVariantsCache = new TreeMap<>();
+ private static final TreeMap> recipesCache = new TreeMap<>();
+
+ public static List getBlockTypesCache() {
+ return blockTypesCache;
+ }
+ public static Map> getBlockVariantsCache() {
+ return blockVariantsCache;
+ }
+ public static Map> getRecipesCache() {
+ return recipesCache;
+ }
+
+ public static void initRecipesCache() {
+ if (!recipesCache.isEmpty()) return;
+
+ LOGGER.info("Started caching DO recipes...");
+ var startTime = System.currentTimeMillis();
+ var compIG = com.ldtteam.domumornamentum.block.ModBlocks.getInstance().getOrComputeItemGroups();
+ for (var ig : compIG.entrySet()) {
+ var loc = ig.getKey();
+
+ var variantsList = new ArrayList();
+ for (var variant : ig.getValue()) {
+ variantsList.add(variant.copy());
+ }
+
+ if (variantsList.isEmpty()) continue;
+
+ blockTypesCache.add(loc);
+ blockVariantsCache.put(blockTypesCache.size() - 1, variantsList);
+ recipesCache.put(loc, variantsList);
+ }
+ var elapsedTime = System.currentTimeMillis() - startTime;
+ LOGGER.info("Done caching DO recipes in $elapsedTime millis");
+ }
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/Mclschcannoncompat.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/Mclschcannoncompat.kt
index 8bcbe6f..57543a7 100644
--- a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/Mclschcannoncompat.kt
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/Mclschcannoncompat.kt
@@ -1,6 +1,5 @@
package xyz.nuark.mcmod.mclschcannoncompat
-import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.CreativeModeTabs
import net.neoforged.bus.api.SubscribeEvent
@@ -10,6 +9,7 @@ import net.neoforged.fml.common.Mod
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent
import net.neoforged.fml.event.lifecycle.FMLDedicatedServerSetupEvent
+import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent
import net.neoforged.neoforge.data.event.GatherDataEvent
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
@@ -19,12 +19,16 @@ import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import thedarkcolour.kotlinforforge.neoforge.forge.MOD_BUS
import thedarkcolour.kotlinforforge.neoforge.forge.runForDist
+import xyz.nuark.mcmod.mclschcannoncompat.block.ModBlockEntityTypes
import xyz.nuark.mcmod.mclschcannoncompat.block.ModBlocks
import xyz.nuark.mcmod.mclschcannoncompat.comands.ModCommands
import xyz.nuark.mcmod.mclschcannoncompat.datagen.ModItemModelProvider
import xyz.nuark.mcmod.mclschcannoncompat.datagen.ModLanguageProviders
import xyz.nuark.mcmod.mclschcannoncompat.datagen.ModRecipeProvider
import xyz.nuark.mcmod.mclschcannoncompat.item.ModItems
+import xyz.nuark.mcmod.mclschcannoncompat.menu.ModMenuTypes
+import xyz.nuark.mcmod.mclschcannoncompat.menu.OrnamentFabricatorMenu
+import xyz.nuark.mcmod.mclschcannoncompat.screen.OrnamentFabricatorScreen
@Mod(Mclschcannoncompat.ID)
@EventBusSubscriber
@@ -37,23 +41,28 @@ object Mclschcannoncompat {
LOGGER.log(Level.INFO, "Hello world!")
ModBlocks.REGISTRY.register(MOD_BUS)
-
- val obj = runForDist(clientTarget = {
- MOD_BUS.addListener(::onClientSetup)
- Minecraft.getInstance()
- }, serverTarget = {
- MOD_BUS.addListener(::onServerSetup)
- "test"
- })
-
+ ModBlockEntityTypes.REGISTRY.register(MOD_BUS)
+ ModMenuTypes.REGISTRY.register(MOD_BUS)
ModItems.REGISTRY.register(MOD_BUS)
- println(obj)
+ runForDist(
+ clientTarget = {
+ MOD_BUS.addListener(::onClientSetup)
+ MOD_BUS.addListener(::onRegisterMenuScreens)
+ },
+ serverTarget = {
+ MOD_BUS.addListener(::onServerSetup)
+ }
+ )
}
private fun onClientSetup(@Suppress("unused") event: FMLClientSetupEvent) {
}
+ private fun onRegisterMenuScreens(event: RegisterMenuScreensEvent) {
+ event.register(ModMenuTypes.ORNAMENT_FABRICATOR.get(), ::OrnamentFabricatorScreen)
+ }
+
private fun onServerSetup(@Suppress("unused") event: FMLDedicatedServerSetupEvent) {
}
@@ -68,6 +77,9 @@ object Mclschcannoncompat {
event.accept(ModItems.BUILDER_ASSISTANT_STAFF_T2.get())
event.accept(ModItems.BUILDER_ASSISTANT_STAFF_T3.get())
}
+ if (event.tabKey === CreativeModeTabs.FUNCTIONAL_BLOCKS) {
+ event.accept(ModItems.ORNAMENT_FABRICATOR_ITEM.get())
+ }
}
@SubscribeEvent
@@ -76,10 +88,9 @@ object Mclschcannoncompat {
}
@SubscribeEvent
- fun onNetworkRegistry(event: RegisterPayloadHandlersEvent)
- {
- val modVersion = ModList.get().getModContainerById(ID).get().getModInfo().getVersion().toString()
- @Suppress("unused") val registry = event.registrar(ID).versioned(modVersion)
+ fun onNetworkRegistry(event: RegisterPayloadHandlersEvent) {
+ val modVersion = ModList.get().getModContainerById(ID).get().modInfo.version.toString()
+ @Suppress("unused") val registrar = event.registrar(ID).versioned(modVersion)
}
@SubscribeEvent
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlockEntityTypes.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlockEntityTypes.kt
new file mode 100644
index 0000000..b77835f
--- /dev/null
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlockEntityTypes.kt
@@ -0,0 +1,24 @@
+package xyz.nuark.mcmod.mclschcannoncompat.block
+
+import net.minecraft.world.level.block.entity.BlockEntityType
+import net.neoforged.neoforge.registries.DeferredRegister
+import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
+import xyz.nuark.mcmod.mclschcannoncompat.block.entity.OrnamentFabricatorBlockEntity
+import java.util.function.Supplier
+
+object ModBlockEntityTypes {
+ val REGISTRY = DeferredRegister.create(
+ net.minecraft.core.registries.BuiltInRegistries.BLOCK_ENTITY_TYPE,
+ Mclschcannoncompat.ID
+ )
+
+ val ORNAMENT_FABRICATOR: Supplier> = REGISTRY.register(
+ "ornament_fabricator",
+ Supplier {
+ BlockEntityType.Builder.of(
+ ::OrnamentFabricatorBlockEntity,
+ ModBlocks.ORNAMENT_FABRICATOR.get()
+ ).build(null) as BlockEntityType
+ }
+ )
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlocks.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlocks.kt
index 6d5e6e8..f79a15c 100644
--- a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlocks.kt
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/ModBlocks.kt
@@ -1,8 +1,19 @@
package xyz.nuark.mcmod.mclschcannoncompat.block
+import net.minecraft.world.level.block.Blocks
+import net.minecraft.world.level.block.state.BlockBehaviour
+import net.minecraft.world.level.material.MapColor
import net.neoforged.neoforge.registries.DeferredRegister
import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
object ModBlocks {
val REGISTRY = DeferredRegister.createBlocks(Mclschcannoncompat.ID)
+
+ val ORNAMENT_FABRICATOR = REGISTRY.registerBlock(
+ "ornament_fabricator",
+ ::OrnamentFabricatorBlock,
+ BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)
+ .mapColor(MapColor.STONE)
+ .strength(3.0f, 6.0f)
+ )
}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/OrnamentFabricatorBlock.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/OrnamentFabricatorBlock.kt
new file mode 100644
index 0000000..ff34b9b
--- /dev/null
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/OrnamentFabricatorBlock.kt
@@ -0,0 +1,61 @@
+package xyz.nuark.mcmod.mclschcannoncompat.block
+
+import com.mojang.serialization.MapCodec
+import net.minecraft.core.BlockPos
+import net.minecraft.server.level.ServerPlayer
+import net.minecraft.world.InteractionResult
+import net.minecraft.world.entity.player.Player
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.level.Level
+import net.minecraft.world.level.LevelAccessor
+import net.minecraft.world.level.block.BaseEntityBlock
+import net.minecraft.world.level.block.RenderShape
+import net.minecraft.world.level.block.entity.BlockEntity
+import net.minecraft.world.level.block.entity.BlockEntityTicker
+import net.minecraft.world.level.block.entity.BlockEntityType
+import net.minecraft.world.level.block.state.BlockState
+import net.minecraft.world.level.storage.loot.LootParams
+import net.minecraft.world.phys.BlockHitResult
+import net.neoforged.neoforge.common.extensions.IPlayerExtension
+import xyz.nuark.mcmod.mclschcannoncompat.block.entity.OrnamentFabricatorBlockEntity
+
+class OrnamentFabricatorBlock(properties: Properties) : BaseEntityBlock(properties) {
+ override fun codec(): MapCodec = simpleCodec(::OrnamentFabricatorBlock)
+
+ override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity =
+ OrnamentFabricatorBlockEntity(pos, state)
+
+ override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
+
+ override fun useWithoutItem(
+ state: BlockState,
+ level: Level,
+ pos: BlockPos,
+ player: Player,
+ hitResult: BlockHitResult
+ ): InteractionResult {
+ if (level.isClientSide) return InteractionResult.SUCCESS
+
+ if (player is ServerPlayer) {
+ player.openMenu(state.getMenuProvider(level, pos), pos)
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide)
+ }
+
+ override fun getTicker(
+ level: Level,
+ state: BlockState,
+ type: BlockEntityType
+ ): BlockEntityTicker? {
+ if (level.isClientSide) return null
+ return createTickerHelper(type, ModBlockEntityTypes.ORNAMENT_FABRICATOR.get()) { _, _, _, be ->
+ be.tick()
+ }
+ }
+}
+
+data class OrnamentFabricatorEntry(
+ val block: net.minecraft.world.level.block.Block,
+ val components: List
+)
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/entity/OrnamentFabricatorBlockEntity.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/entity/OrnamentFabricatorBlockEntity.kt
new file mode 100644
index 0000000..a335741
--- /dev/null
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/block/entity/OrnamentFabricatorBlockEntity.kt
@@ -0,0 +1,310 @@
+package xyz.nuark.mcmod.mclschcannoncompat.block.entity
+
+import com.ldtteam.domumornamentum.block.IMateriallyTexturedBlock
+import com.ldtteam.domumornamentum.client.model.data.MaterialTextureData
+import com.ldtteam.domumornamentum.recipe.ModRecipeTypes
+import com.ldtteam.domumornamentum.recipe.architectscutter.ArchitectsCutterRecipeInput
+import com.minecolonies.api.util.ItemStackUtils
+import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.component.DataComponents
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.network.chat.Component
+import net.minecraft.world.Containers
+import net.minecraft.world.MenuProvider
+import net.minecraft.world.SimpleContainer
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.entity.player.Player
+import net.minecraft.world.inventory.AbstractContainerMenu
+import net.minecraft.world.inventory.ContainerData
+import net.minecraft.world.item.BlockItem
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.component.BlockItemStateProperties
+import net.minecraft.world.level.block.entity.BlockEntity
+import net.minecraft.world.level.block.state.BlockState
+import net.neoforged.neoforge.items.ItemStackHandler
+import xyz.nuark.mcmod.mclschcannoncompat.block.ModBlockEntityTypes
+import xyz.nuark.mcmod.mclschcannoncompat.menu.OrnamentFabricatorMenu
+import xyz.nuark.mcmod.mclschcannoncompat.utils.ODBlockRecipiesCache
+
+class OrnamentFabricatorBlockEntity(
+ pos: BlockPos,
+ state: BlockState
+) : BlockEntity(
+ ModBlockEntityTypes.ORNAMENT_FABRICATOR.get(), pos, state
+), MenuProvider {
+ init {
+ ODBlockRecipiesCache.initRecipesCache()
+ }
+
+ val itemHandler = object : ItemStackHandler(TOTAL_SLOTS) {
+ override fun onContentsChanged(slot: Int) {
+ if (slot != SLOT_PREVIEW) {
+ updateRecipe()
+ }
+ setChanged()
+ }
+
+ override fun isItemValid(slot: Int, stack: ItemStack): Boolean =
+ slot != SLOT_PREVIEW
+
+ override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
+ if (slot == SLOT_PREVIEW) return ItemStack.EMPTY
+ return super.extractItem(slot, amount, simulate)
+ }
+ }
+
+ var blockTypeIndex: Int = 0
+ set(value) {
+ field = value; setChanged()
+ }
+
+ var blockSubtypeIndex: Int = 0
+ set(value) {
+ field = value; setChanged()
+ }
+
+ var currentProcess: Int = 0
+ set(value) {
+ field = value; setChanged()
+ }
+
+ var maxProcess: Int = DEFAULT_PROCESS_TIME
+ set(value) {
+ field = value; setChanged()
+ }
+
+ var craftingEnabled: Boolean = false
+ set(value) {
+ field = value; setChanged()
+ }
+
+ private var cachedRecipe: FabricatorRecipe? = null
+
+ val containerData = object : ContainerData {
+ override fun get(index: Int): Int = when (index) {
+ DATA_BLOCK_TYPE -> blockTypeIndex
+ DATA_BLOCK_SUBTYPE -> blockSubtypeIndex
+ DATA_CURRENT -> currentProcess
+ DATA_MAX -> maxProcess
+ DATA_CRAFTING_ENABLED -> if (craftingEnabled) 1 else 0
+ else -> 0
+ }
+
+ override fun set(index: Int, value: Int) {
+ when (index) {
+ DATA_BLOCK_TYPE -> blockTypeIndex = value
+ DATA_BLOCK_SUBTYPE -> blockSubtypeIndex = value
+ DATA_CURRENT -> currentProcess = value
+ DATA_MAX -> maxProcess = value
+ DATA_CRAFTING_ENABLED -> craftingEnabled = value == 1
+ }
+
+ updateRecipe()
+ }
+
+ override fun getCount(): Int = DATA_SIZE
+ }
+
+ override fun getDisplayName(): Component =
+ Component.translatable("block.mclschcannoncompat.ornament_fabricator")
+
+ override fun createMenu(containerId: Int, playerInventory: Inventory, player: Player): AbstractContainerMenu =
+ OrnamentFabricatorMenu(containerId, playerInventory, this, containerData)
+
+ fun tick() {
+ val level = level ?: return
+ if (level.isClientSide) return
+
+ val inputA = itemHandler.getStackInSlot(SLOT_INPUT_A)
+ val inputB = itemHandler.getStackInSlot(SLOT_INPUT_B)
+ val output = itemHandler.getStackInSlot(SLOT_OUTPUT)
+
+ updateRecipe()
+ val recipe = cachedRecipe?.takeIf { ItemStackUtils.compareItemStacksIgnoreStackSize(it.result, itemHandler.getStackInSlot(SLOT_PREVIEW)) } ?: updateRecipe()
+
+ if (recipe != null && craftingEnabled) {
+ val canOutput = output.isEmpty ||
+ (ItemStack.isSameItemSameComponents(output, recipe.result) &&
+ output.count + recipe.result.count <= output.maxStackSize)
+
+ if (canOutput) {
+ currentProcess++
+ if (currentProcess >= maxProcess) {
+ currentProcess = 0
+ craftingEnabled = false
+
+ itemHandler.extractItem(SLOT_INPUT_A, 1, false)
+ itemHandler.extractItem(SLOT_INPUT_B, 1, false)
+
+ if (output.isEmpty) {
+ itemHandler.setStackInSlot(SLOT_OUTPUT, recipe.result.copy())
+ } else {
+ output.grow(recipe.result.count)
+ }
+ setChanged()
+ }
+ } else {
+ currentProcess = 0
+ }
+ } else if (recipe == null) {
+ currentProcess = 0
+ maxProcess = DEFAULT_PROCESS_TIME
+ }
+ }
+
+ private data class FabricatorRecipe(
+ val result: ItemStack,
+ val resultPreview: ItemStack,
+ val processTicks: Int
+ )
+
+ private fun findRecipe(inputA: ItemStack, inputB: ItemStack): FabricatorRecipe? {
+ if (inputA.isEmpty) return null
+ val level = level ?: return null
+
+ val input = ArchitectsCutterRecipeInput(SimpleContainer(inputA, inputB))
+
+ val type = ODBlockRecipiesCache.getBlockTypesCache().getOrNull(blockTypeIndex) ?: return null
+ val variant = ODBlockRecipiesCache.getBlockVariantsCache().getOrDefault(blockTypeIndex, emptyList())
+ .getOrNull(blockSubtypeIndex) ?: return null
+
+ val matchedRecipe = level.recipeManager.getAllRecipesFor(ModRecipeTypes.ARCHITECTS_CUTTER.get())
+ .firstNotNullOfOrNull { recipeHolder ->
+ val recipe = recipeHolder.value
+ val resultItem = recipe.getResultItem(level.registryAccess())
+ if (resultItem.item == variant.item) {
+ val resultBlockState =
+ resultItem.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY)
+ val currentBlockState =
+ variant.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY)
+
+ if (resultBlockState.isEmpty && currentBlockState.isEmpty || resultBlockState == currentBlockState) {
+ val output = recipe.assemble(input, level.registryAccess())
+
+ val textureData = MaterialTextureData.readFromItemStack(output)?.takeIf { !it.isEmpty }
+ ?: return@firstNotNullOfOrNull null
+
+ val generatedBlock =
+ recipe.block as? IMateriallyTexturedBlock ?: return@firstNotNullOfOrNull null
+ val recipeComponents = generatedBlock.components.toList()
+
+ return@firstNotNullOfOrNull when (textureData.getTexturedComponents.size) {
+ 2 if (!inputA.isEmpty && !inputB.isEmpty) -> {
+ val itemA = inputA.item.takeIf { it is BlockItem } as? BlockItem
+ ?: return@firstNotNullOfOrNull null
+ val itemB = inputB.item.takeIf { it is BlockItem } as? BlockItem
+ ?: return@firstNotNullOfOrNull null
+
+ val aOk = itemA.block.defaultBlockState().`is`(recipeComponents[0].validSkins)
+ val bOk = itemB.block.defaultBlockState().`is`(recipeComponents[1].validSkins)
+
+ aOk && bOk
+ recipeHolder to output
+ }
+
+ 1 if (inputB.isEmpty) -> {
+ val itemA = inputA.item.takeIf { it is BlockItem } as? BlockItem
+ ?: return@firstNotNullOfOrNull null
+
+ itemA.block.defaultBlockState().`is`(recipeComponents[0].validSkins)
+ recipeHolder to output
+ }
+
+ else -> null
+ }
+ }
+ }
+
+ null
+ } ?: return null
+
+ val (recipeUsed, output) = matchedRecipe
+
+ return FabricatorRecipe(
+ output,
+ output,
+ 100
+ )
+ }
+
+ private fun updateRecipe(): FabricatorRecipe? {
+ val inputA = itemHandler.getStackInSlot(SLOT_INPUT_A)
+ val inputB = itemHandler.getStackInSlot(SLOT_INPUT_B)
+
+ val prevRecipe = cachedRecipe
+ val recipe = findRecipe(inputA, inputB)
+ cachedRecipe = recipe
+
+ recipe?.let {
+ val previewStack = recipe.resultPreview.copy().also { it.count = recipe.result.count }
+ itemHandler.setStackInSlot(SLOT_PREVIEW, previewStack)
+
+ maxProcess = recipe.processTicks
+ } ?: run {
+ if (!itemHandler.getStackInSlot(SLOT_PREVIEW).isEmpty) {
+ itemHandler.setStackInSlot(SLOT_PREVIEW, ItemStack.EMPTY)
+ }
+ }
+
+ level.takeIf { it != null && !it.isClientSide && !ItemStackUtils.compareItemStacksIgnoreStackSize(prevRecipe?.result, cachedRecipe?.result) }?.let {
+ craftingEnabled = false
+ currentProcess = 0
+ }
+
+ return recipe
+ }
+
+ override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
+ super.saveAdditional(tag, registries)
+ tag.put("Inventory", itemHandler.serializeNBT(registries))
+ tag.putInt("BlockTypeIndex", blockTypeIndex)
+ tag.putInt("BlockSubtypeIndex", blockSubtypeIndex)
+ tag.putInt("CurrentProcess", currentProcess)
+ tag.putInt("MaxProcess", maxProcess)
+ tag.putBoolean("CraftingEnabled", craftingEnabled)
+ }
+
+ override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
+ super.loadAdditional(tag, registries)
+ itemHandler.deserializeNBT(registries, tag.getCompound("Inventory"))
+ blockTypeIndex = tag.getInt("BlockTypeIndex")
+ blockSubtypeIndex = tag.getInt("BlockSubtypeIndex")
+ currentProcess = tag.getInt("CurrentProcess")
+ maxProcess = tag.getInt("MaxProcess")
+ craftingEnabled = tag.getBoolean("CraftingEnabled")
+ }
+
+ override fun onLoad() {
+ super.onLoad()
+
+ updateRecipe()
+ }
+
+ fun drops() {
+ val inventory = SimpleContainer(TOTAL_SLOTS)
+ for (i in 0 until TOTAL_SLOTS) {
+ if (i == SLOT_PREVIEW) continue
+
+ inventory.setItem(i, itemHandler.getStackInSlot(i))
+ }
+ level?.let { Containers.dropContents(it, blockPos, inventory) }
+ }
+
+ companion object {
+ const val SLOT_INPUT_A = 0
+ const val SLOT_INPUT_B = 1
+ const val SLOT_OUTPUT = 2
+ const val SLOT_PREVIEW = 3
+ const val TOTAL_SLOTS = 4
+
+ const val DATA_BLOCK_TYPE = 0
+ const val DATA_BLOCK_SUBTYPE = 1
+ const val DATA_CURRENT = 2
+ const val DATA_MAX = 3
+ const val DATA_CRAFTING_ENABLED = 4
+ const val DATA_SIZE = 5
+
+ const val DEFAULT_PROCESS_TIME = 200 // in ticks
+ }
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModItemModelProvider.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModItemModelProvider.kt
index 6d05685..624a0e6 100644
--- a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModItemModelProvider.kt
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModItemModelProvider.kt
@@ -4,6 +4,7 @@ import net.minecraft.data.PackOutput
import net.neoforged.neoforge.client.model.generators.ItemModelProvider
import net.neoforged.neoforge.common.data.ExistingFileHelper
import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
+import xyz.nuark.mcmod.mclschcannoncompat.block.ModBlocks
import xyz.nuark.mcmod.mclschcannoncompat.item.ModItems
class ModItemModelProvider(output: PackOutput, existingFileHelper: ExistingFileHelper) : ItemModelProvider(output, Mclschcannoncompat.ID, existingFileHelper) {
@@ -11,5 +12,6 @@ class ModItemModelProvider(output: PackOutput, existingFileHelper: ExistingFileH
basicItem(ModItems.BUILDER_ASSISTANT_STAFF_T1.get())
basicItem(ModItems.BUILDER_ASSISTANT_STAFF_T2.get())
basicItem(ModItems.BUILDER_ASSISTANT_STAFF_T3.get())
+ simpleBlockItem(ModBlocks.ORNAMENT_FABRICATOR.get())
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModLanguageProviders.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModLanguageProviders.kt
index 127b1b6..771fa01 100644
--- a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModLanguageProviders.kt
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModLanguageProviders.kt
@@ -12,7 +12,15 @@ object ModLanguageProviders {
add(ModItems.BUILDER_ASSISTANT_STAFF_T1.get(), "Посох строителя T1")
add(ModItems.BUILDER_ASSISTANT_STAFF_T2.get(), "Посох строителя T2")
add(ModItems.BUILDER_ASSISTANT_STAFF_T3.get(), "Посох строителя T3")
- add("item.asdf.assistantstaff.maxdepth", "Максимальная блоков: %s")
+ add(ModItems.ORNAMENT_FABRICATOR_ITEM.get(), "Орнаментный фабрикатор")
+ add("item.assistantstaff.hover.maxdepth", "Максимальная глубина: %s")
+
+ add("tooltip.ornament_fabricator.no_preview", "Рецепт не подходит этим ингредиентам")
+ add("tooltip.btn.cyclestyle.up", "Переключить стиль вперёд")
+ add("tooltip.btn.cyclestyle.down", "Переключить стиль назад")
+ add("tooltip.btn.cyclevariant.up", "Переключить вариант вперёд")
+ add("tooltip.btn.cyclevariant.down", "Переключить вариант назад")
+ add("tooltip.btn.startcrafting", "Начать крафт")
}
}
@@ -21,7 +29,15 @@ object ModLanguageProviders {
add(ModItems.BUILDER_ASSISTANT_STAFF_T1.get(), "Builder staff T1")
add(ModItems.BUILDER_ASSISTANT_STAFF_T2.get(), "Builder staff T2")
add(ModItems.BUILDER_ASSISTANT_STAFF_T3.get(), "Builder staff T3")
- add("item.asdf.assistantstaff.maxdepth", "Maximum depth: %s")
+ add(ModItems.ORNAMENT_FABRICATOR_ITEM.get(), "Ornament Fabricator")
+ add("item.assistantstaff.hover.maxdepth", "Maximum depth: %s")
+
+ add("tooltip.ornament_fabricator.no_preview", "Recipe is not supported for provided ingredients")
+ add("tooltip.btn.cyclestyle.up", "Cycle style forward")
+ add("tooltip.btn.cyclestyle.down", "Cycle style backward")
+ add("tooltip.btn.cyclevariant.up", "Cycle variant forward")
+ add("tooltip.btn.cyclevariant.down", "Cycle variant backward")
+ add("tooltip.btn.startcrafting", "Start crafting")
}
}
@@ -29,4 +45,4 @@ object ModLanguageProviders {
generator.addProvider(shouldRun, ModRuRuLanguageProvider(generator.packOutput))
generator.addProvider(shouldRun, ModEnUsLanguageProvider(generator.packOutput))
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModRecipeProvider.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModRecipeProvider.kt
index 2f2dee6..562b55c 100644
--- a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModRecipeProvider.kt
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/datagen/ModRecipeProvider.kt
@@ -11,7 +11,6 @@ import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
import xyz.nuark.mcmod.mclschcannoncompat.item.ModItems
import java.util.concurrent.CompletableFuture
-
class ModRecipeProvider(output: PackOutput, registries: CompletableFuture) : RecipeProvider(output, registries) {
override fun buildRecipes(recipeOutput: RecipeOutput) {
ShapedRecipeBuilder.shaped(RecipeCategory.TOOLS, ModItems.BUILDER_ASSISTANT_STAFF_T1.get())
@@ -43,5 +42,15 @@ class ModRecipeProvider(output: PackOutput, registries: CompletableFuture BuilderAssistantStaff("builder_assistant_staff", props, 1) }
- val BUILDER_ASSISTANT_STAFF_T2 = REGISTRY.registerItem("builder_assistant_staff_t2") { props -> BuilderAssistantStaff("builder_assistant_staff_t2", props, 3) }
- val BUILDER_ASSISTANT_STAFF_T3 = REGISTRY.registerItem("builder_assistant_staff_t3") { props -> BuilderAssistantStaff("builder_assistant_staff_t3", props, 5) }
-}
\ No newline at end of file
+ val BUILDER_ASSISTANT_STAFF_T1 = REGISTRY.registerItem("builder_assistant_staff") { props ->
+ BuilderAssistantStaff("builder_assistant_staff", props, 1)
+ }
+ val BUILDER_ASSISTANT_STAFF_T2 = REGISTRY.registerItem("builder_assistant_staff_t2") { props ->
+ BuilderAssistantStaff("builder_assistant_staff_t2", props, 3)
+ }
+ val BUILDER_ASSISTANT_STAFF_T3 = REGISTRY.registerItem("builder_assistant_staff_t3") { props ->
+ BuilderAssistantStaff("builder_assistant_staff_t3", props, 5)
+ }
+
+ val ORNAMENT_FABRICATOR_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.ORNAMENT_FABRICATOR)
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/menu/ModMenuTypes.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/menu/ModMenuTypes.kt
new file mode 100644
index 0000000..f009a06
--- /dev/null
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/menu/ModMenuTypes.kt
@@ -0,0 +1,19 @@
+package xyz.nuark.mcmod.mclschcannoncompat.menu
+
+import net.minecraft.world.inventory.MenuType
+import net.neoforged.neoforge.common.extensions.IMenuTypeExtension
+import net.neoforged.neoforge.registries.DeferredRegister
+import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
+import java.util.function.Supplier
+
+object ModMenuTypes {
+ val REGISTRY = DeferredRegister.create(
+ net.minecraft.core.registries.BuiltInRegistries.MENU,
+ Mclschcannoncompat.ID
+ )
+
+ val ORNAMENT_FABRICATOR: Supplier> =
+ REGISTRY.register("ornament_fabricator", Supplier {
+ IMenuTypeExtension.create(OrnamentFabricatorMenu::fromNetwork)
+ })
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/menu/OrnamentFabricatorMenu.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/menu/OrnamentFabricatorMenu.kt
new file mode 100644
index 0000000..70ab254
--- /dev/null
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/menu/OrnamentFabricatorMenu.kt
@@ -0,0 +1,191 @@
+package xyz.nuark.mcmod.mclschcannoncompat.menu
+
+import net.minecraft.client.player.LocalPlayer
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.protocol.game.ServerboundContainerButtonClickPacket
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.entity.player.Player
+import net.minecraft.world.inventory.AbstractContainerMenu
+import net.minecraft.world.inventory.ContainerData
+import net.minecraft.world.inventory.Slot
+import net.minecraft.world.item.ItemStack
+import net.neoforged.neoforge.items.SlotItemHandler
+import xyz.nuark.mcmod.mclschcannoncompat.block.entity.OrnamentFabricatorBlockEntity
+import xyz.nuark.mcmod.mclschcannoncompat.utils.ODBlockRecipiesCache
+
+class OrnamentFabricatorMenu(
+ containerId: Int,
+ private val playerInventory: Inventory,
+ private val blockEntity: OrnamentFabricatorBlockEntity,
+ private val data: ContainerData
+) : AbstractContainerMenu(
+ ModMenuTypes.ORNAMENT_FABRICATOR.get(), containerId
+) {
+ init {
+ require(blockEntity.itemHandler.slots >= OrnamentFabricatorBlockEntity.TOTAL_SLOTS) { "Container size ${blockEntity.itemHandler.slots} is smaller than expected ${OrnamentFabricatorBlockEntity.TOTAL_SLOTS}" }
+ checkContainerDataCount(data, OrnamentFabricatorBlockEntity.DATA_SIZE)
+
+ addSlot(SlotItemHandler(blockEntity.itemHandler, OrnamentFabricatorBlockEntity.SLOT_INPUT_A, 12, 50))
+ addSlot(SlotItemHandler(blockEntity.itemHandler, OrnamentFabricatorBlockEntity.SLOT_INPUT_B, 30, 50))
+ addSlot(SlotItemHandler(blockEntity.itemHandler, OrnamentFabricatorBlockEntity.SLOT_OUTPUT, 84, 50))
+
+ addSlot(object : SlotItemHandler(
+ blockEntity.itemHandler, OrnamentFabricatorBlockEntity.SLOT_PREVIEW, 156, 50
+ ) {
+ override fun mayPlace(stack: ItemStack) = false
+ override fun mayPickup(player: Player) = false
+ override fun isActive() = true
+ })
+
+ for (row in 0..2) {
+ for (col in 0..8) {
+ addSlot(Slot(playerInventory, col + row * 9 + 9, INV_X + col * 18, INV_Y + row * 18))
+ }
+ }
+
+ for (col in 0..8) {
+ addSlot(Slot(playerInventory, col, INV_X + col * 18, HOTBAR_Y))
+ }
+
+ addDataSlots(data)
+ }
+
+ val blockTypeIndex: Int get() = data.get(OrnamentFabricatorBlockEntity.DATA_BLOCK_TYPE)
+ val blockSubtypeIndex: Int get() = data.get(OrnamentFabricatorBlockEntity.DATA_BLOCK_SUBTYPE)
+ val currentProcess: Int get() = data.get(OrnamentFabricatorBlockEntity.DATA_CURRENT)
+ val maxProcess: Int get() = data.get(OrnamentFabricatorBlockEntity.DATA_MAX)
+ val craftingEnabled: Boolean get() = data.get(OrnamentFabricatorBlockEntity.DATA_CRAFTING_ENABLED) == 1
+
+ val progressFraction: Float
+ get() = if (maxProcess > 0) currentProcess.toFloat() / maxProcess.toFloat() else 0f
+
+ override fun quickMoveStack(player: Player, slotIndex: Int): ItemStack {
+ var remainder = ItemStack.EMPTY
+ val slot = slots.getOrNull(slotIndex) ?: return remainder
+ if (!slot.hasItem()) return remainder
+
+ val stack = slot.item
+ remainder = stack.copy()
+
+ val beSlots = 0 until OrnamentFabricatorBlockEntity.TOTAL_SLOTS
+ val invSlots = OrnamentFabricatorBlockEntity.TOTAL_SLOTS until
+ OrnamentFabricatorBlockEntity.TOTAL_SLOTS + 27
+ val barSlots = OrnamentFabricatorBlockEntity.TOTAL_SLOTS + 27 until
+ OrnamentFabricatorBlockEntity.TOTAL_SLOTS + 36
+
+ if (slotIndex in beSlots) {
+ if (!moveItemStackTo(stack, invSlots.first, barSlots.last + 1, true))
+ return ItemStack.EMPTY
+ } else {
+ if (!moveItemStackTo(
+ stack, OrnamentFabricatorBlockEntity.SLOT_INPUT_A,
+ OrnamentFabricatorBlockEntity.SLOT_INPUT_B + 1, false
+ )
+ ) {
+ if (slotIndex in invSlots) {
+ if (!moveItemStackTo(stack, barSlots.first, barSlots.last + 1, false))
+ return ItemStack.EMPTY
+ } else {
+ if (!moveItemStackTo(stack, invSlots.first, invSlots.last + 1, false))
+ return ItemStack.EMPTY
+ }
+ }
+ }
+
+ if (stack.isEmpty) slot.set(ItemStack.EMPTY) else slot.setChanged()
+ if (stack.count == remainder.count) return ItemStack.EMPTY
+ slot.onTake(player, stack)
+ return remainder
+ }
+
+ override fun stillValid(player: Player): Boolean =
+ !blockEntity.isRemoved && player.canInteractWithBlock(blockEntity.blockPos, 8.0)
+
+ fun cycleBlockTypeUp() = sendTypeChangePacket(ButtonIds.BUTTON_TYPE_UP)
+ fun cycleBlockTypeDown() = sendTypeChangePacket(ButtonIds.BUTTON_TYPE_DOWN)
+ fun cycleSubtypeUp() = sendTypeChangePacket(ButtonIds.BUTTON_SUBTYPE_UP)
+ fun cycleSubtypeDown() = sendTypeChangePacket(ButtonIds.BUTTON_SUBTYPE_DOWN)
+ fun startCrafting() = sendTypeChangePacket(ButtonIds.BUTTON_BUTTON_START_CRAFTING)
+
+ /**
+ * Client-side handler for button clicks made by the user.
+ */
+ private fun sendTypeChangePacket(buttonId: Int) {
+ (playerInventory.player as? LocalPlayer)?.connection?.send(
+ ServerboundContainerButtonClickPacket(containerId, buttonId)
+ )
+ }
+
+ /**
+ * Server-side handler for button clicks sent by the client.
+ */
+ override fun clickMenuButton(player: Player, id: Int): Boolean {
+ when (id) {
+ ButtonIds.BUTTON_TYPE_UP -> {
+ blockEntity.blockTypeIndex = (blockEntity.blockTypeIndex + 1).coerceIn(
+ ODBlockRecipiesCache.getBlockTypesCache().indices
+ )
+ blockEntity.blockSubtypeIndex = 0
+
+ blockEntity.craftingEnabled = false
+ blockEntity.currentProcess = 0
+ }
+
+ ButtonIds.BUTTON_TYPE_DOWN -> {
+ blockEntity.blockTypeIndex = (blockEntity.blockTypeIndex - 1).coerceIn(
+ ODBlockRecipiesCache.getBlockTypesCache().indices
+ )
+ blockEntity.blockSubtypeIndex = 0
+
+ blockEntity.craftingEnabled = false
+ blockEntity.currentProcess = 0
+ }
+
+ ButtonIds.BUTTON_SUBTYPE_UP -> {
+ val range = ODBlockRecipiesCache.getBlockVariantsCache()[blockEntity.blockTypeIndex]?.indices ?: 0..0
+ blockEntity.blockSubtypeIndex = (blockEntity.blockSubtypeIndex + 1).coerceIn(range)
+
+ blockEntity.craftingEnabled = false
+ blockEntity.currentProcess = 0
+ }
+
+ ButtonIds.BUTTON_SUBTYPE_DOWN -> {
+ val range = ODBlockRecipiesCache.getBlockVariantsCache()[blockEntity.blockTypeIndex]?.indices ?: 0..0
+ blockEntity.blockSubtypeIndex = (blockEntity.blockSubtypeIndex - 1).coerceIn(range)
+
+ blockEntity.craftingEnabled = false
+ blockEntity.currentProcess = 0
+ }
+
+ ButtonIds.BUTTON_BUTTON_START_CRAFTING -> {
+ blockEntity.craftingEnabled = true
+ }
+
+ else -> {
+ return false
+ }
+ }
+ blockEntity.setChanged()
+ return true
+ }
+
+ companion object {
+ fun fromNetwork(containerId: Int, playerInventory: Inventory, buf: FriendlyByteBuf): OrnamentFabricatorMenu {
+ val pos = buf.readBlockPos()
+ val be = playerInventory.player.level().getBlockEntity(pos) as OrnamentFabricatorBlockEntity
+ return OrnamentFabricatorMenu(containerId, playerInventory, be, be.containerData)
+ }
+
+ const val INV_X = 12
+ const val INV_Y = 81
+ const val HOTBAR_Y = 139
+
+ object ButtonIds {
+ const val BUTTON_TYPE_UP = 0
+ const val BUTTON_TYPE_DOWN = 1
+ const val BUTTON_SUBTYPE_UP = 2
+ const val BUTTON_SUBTYPE_DOWN = 3
+ const val BUTTON_BUTTON_START_CRAFTING = 4
+ }
+ }
+}
diff --git a/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/screen/OrnamentFabricatorScreen.kt b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/screen/OrnamentFabricatorScreen.kt
new file mode 100644
index 0000000..336f3f7
--- /dev/null
+++ b/src/main/kotlin/xyz/nuark/mcmod/mclschcannoncompat/screen/OrnamentFabricatorScreen.kt
@@ -0,0 +1,181 @@
+package xyz.nuark.mcmod.mclschcannoncompat.screen
+
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.components.Button
+import net.minecraft.client.gui.components.Tooltip
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.world.entity.player.Inventory
+import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
+import xyz.nuark.mcmod.mclschcannoncompat.block.entity.OrnamentFabricatorBlockEntity
+import xyz.nuark.mcmod.mclschcannoncompat.menu.OrnamentFabricatorMenu
+import xyz.nuark.mcmod.mclschcannoncompat.utils.ODBlockRecipiesCache
+
+class OrnamentFabricatorScreen(
+ menu: OrnamentFabricatorMenu,
+ playerInventory: Inventory,
+ title: Component
+) : AbstractContainerScreen(menu, playerInventory, title) {
+ private lateinit var btnTypeUp: Button
+ private lateinit var btnTypeDown: Button
+ private lateinit var btnSubtypeUp: Button
+ private lateinit var btnSubtypeDown: Button
+ private lateinit var btnStartCrafting: Button
+
+ init {
+ imageWidth = BG_WIDTH
+ imageHeight = BG_HEIGHT
+
+ titleLabelX = 11
+ inventoryLabelY = 6
+
+ inventoryLabelX = 11
+ inventoryLabelY = 70
+ }
+
+ override fun init() {
+ super.init()
+
+ val bx = leftPos
+ val by = topPos
+
+ btnTypeUp = Button.builder(Component.literal(">")) { menu.cycleBlockTypeUp() }
+ .pos(bx + 161, by + 17)
+ .size(BTN_W, BTN_H)
+ .tooltip(Tooltip.create(Component.translatable("tooltip.btn.cyclestyle.up")))
+ .build()
+ .also { addRenderableWidget(it) }
+
+ btnTypeDown = Button.builder(Component.literal("<")) { menu.cycleBlockTypeDown() }
+ .pos(bx + 11, by + 17)
+ .size(BTN_W, BTN_H)
+ .tooltip(Tooltip.create(Component.translatable("tooltip.btn.cyclestyle.down")))
+ .build()
+ .also { addRenderableWidget(it) }
+
+ btnSubtypeUp = Button.builder(Component.literal(">")) { menu.cycleSubtypeUp() }
+ .pos(bx + 161, by + 33)
+ .size(BTN_W, BTN_H)
+ .tooltip(Tooltip.create(Component.translatable("tooltip.btn.cyclevariant.up")))
+ .build()
+ .also { addRenderableWidget(it) }
+
+ btnSubtypeDown = Button.builder(Component.literal("<")) { menu.cycleSubtypeDown() }
+ .pos(bx + 11, by + 33)
+ .size(BTN_W, BTN_H)
+ .tooltip(Tooltip.create(Component.translatable("tooltip.btn.cyclevariant.down")))
+ .build()
+ .also { addRenderableWidget(it) }
+
+ btnStartCrafting = Button.builder(Component.literal("v")) { menu.startCrafting() }
+ .pos(bx + 105, by + 52)
+ .size(BTN_W, BTN_H)
+ .tooltip(Tooltip.create(Component.translatable("tooltip.btn.startcrafting")))
+ .build()
+ .also { addRenderableWidget(it) }
+ }
+
+ override fun renderBg(graphics: GuiGraphics, partialTick: Float, mouseX: Int, mouseY: Int) {
+ graphics.blit(
+ TEXTURE,
+ leftPos, topPos, // screen position
+ 0f, 0f, // UV origin of background
+ imageWidth, imageHeight, // size to blit
+ TEX_WIDTH, TEX_HEIGHT
+ )
+
+ val progressWidth = (PROGRESS_WIDTH * menu.progressFraction).toInt().coerceAtLeast(0)
+ if (progressWidth > 0) {
+ graphics.blit(
+ TEXTURE,
+ leftPos + PROGRESS_X,
+ topPos + PROGRESS_Y,
+ PROGRESS_U.toFloat(), PROGRESS_V.toFloat(),
+ progressWidth, PROGRESS_HEIGHT,
+ TEX_WIDTH, TEX_HEIGHT
+ )
+ }
+ }
+
+ override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
+ super.render(graphics, mouseX, mouseY, partialTick)
+ renderTooltip(graphics, mouseX, mouseY)
+
+ val type = ODBlockRecipiesCache.getBlockTypesCache().getOrNull(menu.blockTypeIndex)
+ val variant = ODBlockRecipiesCache.getBlockVariantsCache().getOrDefault(menu.blockTypeIndex, emptyList()).getOrNull(menu.blockSubtypeIndex)
+
+ graphics.drawString(
+ font,
+ if (type != null) Component.literal("${menu.blockTypeIndex}. ").append(Component.translatable("cuttergroup.${type.namespace}.${type.path}")) else Component.translatable("tooltip.ornament_fabricator.no_preview"),
+ leftPos + 29, topPos + 19,
+ 0xBEBEBE, false
+ )
+ graphics.drawString(
+ font,
+ if (variant != null) Component.literal("${menu.blockSubtypeIndex}. ${variant.displayName.string}") else Component.translatable("tooltip.ornament_fabricator.no_preview"),
+ leftPos + 29, topPos + 35,
+ 0xBEBEBE, false
+ )
+ }
+
+ override fun renderTooltip(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
+ super.renderTooltip(graphics, mouseX, mouseY)
+
+ val previewAbsX = leftPos + 156
+ val previewAbsY = topPos + 50
+
+ if (mouseX in previewAbsX..(previewAbsX + 16) &&
+ mouseY in previewAbsY..(previewAbsY + 16)
+ ) {
+ val previewStack = menu.slots[3].item
+ if (!previewStack.isEmpty) {
+ graphics.renderTooltip(
+ this.font,
+ this.getTooltipFromContainerItem(previewStack),
+ previewStack.getTooltipImage(),
+ previewStack,
+ mouseX,
+ mouseY
+ )
+ } else {
+ graphics.renderTooltip(
+ font,
+ Component.translatable("tooltip.ornament_fabricator.no_preview"),
+ mouseX, mouseY
+ )
+ }
+ }
+ }
+
+ override fun renderLabels(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
+ graphics.drawString(font, title, titleLabelX, titleLabelY, 0xBEBEBE, false)
+ graphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0xBEBEBE, false)
+ }
+
+ companion object {
+ private val TEXTURE = ResourceLocation.fromNamespaceAndPath(
+ Mclschcannoncompat.ID, "textures/gui/ornament_fabricator.png"
+ )
+
+ // Background size (must match your PNG atlas dimensions you blit)
+ private const val BG_WIDTH = 184
+ private const val BG_HEIGHT = 163
+
+ // Progress bar (blitted as a partial sprite)
+ private const val PROGRESS_X = 51
+ private const val PROGRESS_Y = 53
+ private const val PROGRESS_U = 184 // UV X in texture
+ private const val PROGRESS_V = 0 // UV Y in texture
+ private const val PROGRESS_WIDTH = 28
+ private const val PROGRESS_HEIGHT = 10
+
+ // Texture sheet total size (usually 256×256)
+ private const val TEX_WIDTH = 256
+ private const val TEX_HEIGHT = 256
+
+ // Button positions (relative to background top-left, i.e. imageX / imageY)
+ private const val BTN_W = 12
+ private const val BTN_H = 12
+ }
+}
diff --git a/src/main/resources/assets/mclschcannoncompat/blockstates/ornament_fabricator.json b/src/main/resources/assets/mclschcannoncompat/blockstates/ornament_fabricator.json
new file mode 100644
index 0000000..6f7e2d8
--- /dev/null
+++ b/src/main/resources/assets/mclschcannoncompat/blockstates/ornament_fabricator.json
@@ -0,0 +1,5 @@
+{
+ "variants": {
+ "": { "model": "mclschcannoncompat:block/ornament_fabricator" }
+ }
+}
diff --git a/src/main/resources/assets/mclschcannoncompat/models/block/ornament_fabricator.json b/src/main/resources/assets/mclschcannoncompat/models/block/ornament_fabricator.json
new file mode 100644
index 0000000..f05c5eb
--- /dev/null
+++ b/src/main/resources/assets/mclschcannoncompat/models/block/ornament_fabricator.json
@@ -0,0 +1,6 @@
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "mclschcannoncompat:block/ornament_fabricator"
+ }
+}
diff --git a/src/main/resources/assets/mclschcannoncompat/textures/block/ornament_fabricator.png b/src/main/resources/assets/mclschcannoncompat/textures/block/ornament_fabricator.png
new file mode 100644
index 0000000..ff62ecf
Binary files /dev/null and b/src/main/resources/assets/mclschcannoncompat/textures/block/ornament_fabricator.png differ
diff --git a/src/main/resources/assets/mclschcannoncompat/textures/gui/ornament_fabricator.png b/src/main/resources/assets/mclschcannoncompat/textures/gui/ornament_fabricator.png
new file mode 100644
index 0000000..e0dc249
Binary files /dev/null and b/src/main/resources/assets/mclschcannoncompat/textures/gui/ornament_fabricator.png differ