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 + + + + + + + + + + + + + + + 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