feat: working custom arch. cutter machine

This commit is contained in:
Andrew 2026-05-09 09:52:05 +07:00
parent 4c3f441f41
commit 69b8c79e57
32 changed files with 1334 additions and 93 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ hs_err_pid*
# Common working directory
run
/repo/
/.kotlin/

View file

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

104
mclschcannoncompat.ipr Normal file
View file

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac"/>
<resourceExtensions>
<entry name=".+\.(properties|xml|html|dtd|tld)"/>
<entry name=".+\.(gif|png|jpeg|jpg)"/>
</resourceExtensions>
<wildcardResourcePatterns>
<entry name="!?*.class"/>
<entry name="!?*.scala"/>
<entry name="!?*.groovy"/>
<entry name="!?*.java"/>
</wildcardResourcePatterns>
<annotationProcessing enabled="false" useClasspath="true"/>
<bytecodeTargetLevel target="21"/>
</component>
<component name="CopyrightManager" default="">
<module2copyright/>
</component>
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false"/>
</component>
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"/>
<component name="GradleUISettings">
<setting name="root"/>
</component>
<component name="GradleUISettings2">
<setting name="root"/>
</component>
<component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C"/>
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY"/>
<option name="OPTION_SCOPE" value="protected"/>
<option name="OPTION_HIERARCHY" value="true"/>
<option name="OPTION_NAVIGATOR" value="true"/>
<option name="OPTION_INDEX" value="true"/>
<option name="OPTION_SEPARATE_INDEX" value="true"/>
<option name="OPTION_DOCUMENT_TAG_USE" value="false"/>
<option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false"/>
<option name="OPTION_DOCUMENT_TAG_VERSION" value="false"/>
<option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true"/>
<option name="OPTION_DEPRECATED_LIST" value="true"/>
<option name="OTHER_OPTIONS" value=""/>
<option name="HEAP_SIZE"/>
<option name="LOCALE"/>
<option name="OPEN_IN_BROWSER" value="true"/>
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/mclschcannoncompat.iml" filepath="$PROJECT_DIR$/mclschcannoncompat.iml"/>
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="21">
<output url="file://$PROJECT_DIR$/out"/>
</component>
<component name="SvnBranchConfigurationManager">
<option name="mySupportsUserInfoFilter" value="true"/>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs=""/>
</component>
<component name="masterDetails">
<states>
<state key="ArtifactsStructureConfigurable.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl/>
</splitter-proportions>
<settings/>
</UIState>
</state>
<state key="Copyright.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl/>
</splitter-proportions>
</UIState>
</state>
<state key="ProjectJDKs.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl>
<option name="proportions">
<list>
<option value="0.2"/>
</list>
</option>
</SplitterProportionsDataImpl>
</splitter-proportions>
<last-edited>1.6</last-edited>
</UIState>
</state>
<state key="ScopeChooserConfigurable.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl/>
</splitter-proportions>
<settings/>
</UIState>
</state>
</states>
</component>
</project>

207
mclschcannoncompat.iws Normal file
View file

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<option name="TRACKING_ENABLED" value="true"/>
<option name="SHOW_DIALOG" value="false"/>
<option name="HIGHLIGHT_CONFLICTS" value="true"/>
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false"/>
<option name="LAST_RESOLUTION" value="IGNORE"/>
</component>
<component name="ChangesViewManager" flattened_view="true" show_ignored="false"/>
<component name="CreatePatchCommitExecutor">
<option name="PATCH_PATH" value=""/>
<option name="REVERSE_PATCH" value="false"/>
</component>
<component name="DaemonCodeAnalyzer">
<disable_hints/>
</component>
<component name="DebuggerManager">
<breakpoint_any>
<breakpoint>
<option name="NOTIFY_CAUGHT" value="true"/>
<option name="NOTIFY_UNCAUGHT" value="true"/>
<option name="ENABLED" value="false"/>
<option name="LOG_ENABLED" value="false"/>
<option name="LOG_EXPRESSION_ENABLED" value="false"/>
<option name="SUSPEND_POLICY" value="SuspendAll"/>
<option name="COUNT_FILTER_ENABLED" value="false"/>
<option name="COUNT_FILTER" value="0"/>
<option name="CONDITION_ENABLED" value="false"/>
<option name="CLASS_FILTERS_ENABLED" value="false"/>
<option name="INSTANCE_FILTERS_ENABLED" value="false"/>
<option name="CONDITION" value=""/>
<option name="LOG_MESSAGE" value=""/>
</breakpoint>
<breakpoint>
<option name="NOTIFY_CAUGHT" value="true"/>
<option name="NOTIFY_UNCAUGHT" value="true"/>
<option name="ENABLED" value="false"/>
<option name="LOG_ENABLED" value="false"/>
<option name="LOG_EXPRESSION_ENABLED" value="false"/>
<option name="SUSPEND_POLICY" value="SuspendAll"/>
<option name="COUNT_FILTER_ENABLED" value="false"/>
<option name="COUNT_FILTER" value="0"/>
<option name="CONDITION_ENABLED" value="false"/>
<option name="CLASS_FILTERS_ENABLED" value="false"/>
<option name="INSTANCE_FILTERS_ENABLED" value="false"/>
<option name="CONDITION" value=""/>
<option name="LOG_MESSAGE" value=""/>
</breakpoint>
</breakpoint_any>
<breakpoint_rules/>
<ui_properties/>
</component>
<component name="ModuleEditorState">
<option name="LAST_EDITED_MODULE_NAME"/>
<option name="LAST_EDITED_TAB_NAME"/>
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state/>
</entry>
</component>
<component name="ProjectLevelVcsManager">
<OptionsSetting value="true" id="Add"/>
<OptionsSetting value="true" id="Remove"/>
<OptionsSetting value="true" id="Checkout"/>
<OptionsSetting value="true" id="Update"/>
<OptionsSetting value="true" id="Status"/>
<OptionsSetting value="true" id="Edit"/>
<ConfirmationsSetting value="0" id="Add"/>
<ConfirmationsSetting value="0" id="Remove"/>
</component>
<component name="ProjectReloadState">
<option name="STATE" value="0"/>
</component>
<component name="PropertiesComponent">
<property name="GoToFile.includeJavaFiles" value="false"/>
<property name="GoToClass.toSaveIncludeLibraries" value="false"/>
<property name="MemberChooser.sorted" value="false"/>
<property name="MemberChooser.showClasses" value="true"/>
<property name="GoToClass.includeLibraries" value="false"/>
<property name="MemberChooser.copyJavadoc" value="false"/>
</component>
<component name="RunManager">
<configuration default="true" type="Remote" factoryName="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true"/>
<option name="SERVER_MODE" value="false"/>
<option name="SHMEM_ADDRESS" value="javadebug"/>
<option name="HOST" value="localhost"/>
<option name="PORT" value="5005"/>
<method>
<option name="BuildArtifacts" enabled="false"/>
</method>
</configuration>
<configuration default="true" type="Applet" factoryName="Applet">
<module name=""/>
<option name="MAIN_CLASS_NAME"/>
<option name="HTML_FILE_NAME"/>
<option name="HTML_USED" value="false"/>
<option name="WIDTH" value="400"/>
<option name="HEIGHT" value="300"/>
<option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy"/>
<option name="VM_PARAMETERS"/>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/>
<option name="ALTERNATIVE_JRE_PATH"/>
<method>
<option name="BuildArtifacts" enabled="false"/>
<option name="Make" enabled="true"/>
</method>
</configuration>
<configuration default="true" type="Application" factoryName="Application">
<extension name="coverage" enabled="false" merge="false"/>
<option name="MAIN_CLASS_NAME"/>
<option name="VM_PARAMETERS"/>
<option name="PROGRAM_PARAMETERS"/>
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$"/>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/>
<option name="ALTERNATIVE_JRE_PATH"/>
<option name="ENABLE_SWING_INSPECTOR" value="false"/>
<option name="ENV_VARIABLES"/>
<option name="PASS_PARENT_ENVS" value="true"/>
<module name=""/>
<envs/>
<method>
<option name="BuildArtifacts" enabled="false"/>
<option name="Make" enabled="true"/>
</method>
</configuration>
<configuration default="true" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false"/>
<module name=""/>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/>
<option name="ALTERNATIVE_JRE_PATH"/>
<option name="PACKAGE_NAME"/>
<option name="MAIN_CLASS_NAME"/>
<option name="METHOD_NAME"/>
<option name="TEST_OBJECT" value="class"/>
<option name="VM_PARAMETERS"/>
<option name="PARAMETERS"/>
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$"/>
<option name="ENV_VARIABLES"/>
<option name="PASS_PARENT_ENVS" value="true"/>
<option name="TEST_SEARCH_SCOPE">
<value defaultName="moduleWithDependencies"/>
</option>
<envs/>
<method>
<option name="BuildArtifacts" enabled="false"/>
<option name="Make" enabled="true"/>
</method>
</configuration>
<list size="0"/>
<configuration name="&lt;template&gt;" type="WebApp" default="true" selected="false">
<Host>localhost</Host>
<Port>5050</Port>
</configuration>
</component>
<component name="ShelveChangesManager" show_recycled="false"/>
<component name="SvnConfiguration" maxAnnotateRevisions="500">
<option name="USER" value=""/>
<option name="PASSWORD" value=""/>
<option name="LAST_MERGED_REVISION"/>
<option name="UPDATE_RUN_STATUS" value="false"/>
<option name="MERGE_DRY_RUN" value="false"/>
<option name="MERGE_DIFF_USE_ANCESTRY" value="true"/>
<option name="UPDATE_LOCK_ON_DEMAND" value="false"/>
<option name="IGNORE_SPACES_IN_MERGE" value="false"/>
<option name="DETECT_NESTED_COPIES" value="true"/>
<option name="IGNORE_SPACES_IN_ANNOTATE" value="true"/>
<option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true"/>
<myIsUseDefaultProxy>false</myIsUseDefaultProxy>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task"/>
<servers/>
</component>
<component name="VcsManagerConfiguration">
<option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true"/>
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true"/>
<option name="PERFORM_UPDATE_IN_BACKGROUND" value="true"/>
<option name="PERFORM_COMMIT_IN_BACKGROUND" value="true"/>
<option name="PERFORM_EDIT_IN_BACKGROUND" value="true"/>
<option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true"/>
<option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true"/>
<option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false"/>
<option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false"/>
<option name="ENABLE_BACKGROUND_PROCESSES" value="false"/>
<option name="CHANGED_ON_SERVER_INTERVAL" value="60"/>
<option name="FORCE_NON_EMPTY_COMMENT" value="false"/>
<option name="LAST_COMMIT_MESSAGE"/>
<option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true"/>
<option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false"/>
<option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false"/>
<option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false"/>
<option name="REFORMAT_BEFORE_FILE_COMMIT" value="false"/>
<option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8"/>
<option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5"/>
<option name="ACTIVE_VCS_NAME"/>
<option name="UPDATE_GROUP_BY_PACKAGES" value="false"/>
<option name="UPDATE_GROUP_BY_CHANGELIST" value="false"/>
<option name="SHOW_FILE_HISTORY_AS_TREE" value="false"/>
<option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6"/>
</component>
<component name="XDebuggerManager">
<breakpoint-manager/>
</component>
</project>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
}

View file

@ -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": "Рецепт не подходит этим ингредиентам"
}

View file

@ -0,0 +1,3 @@
{
"parent": "mclschcannoncompat:block/ornament_fabricator"
}

View file

@ -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"
]
}
}

View file

@ -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"
}
}

View file

@ -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<ResourceLocation> blockTypesCache = new ArrayList<>();
private static final TreeMap<Integer, List<ItemStack>> blockVariantsCache = new TreeMap<>();
private static final TreeMap<ResourceLocation, List<ItemStack>> recipesCache = new TreeMap<>();
public static List<ResourceLocation> getBlockTypesCache() {
return blockTypesCache;
}
public static Map<Integer, List<ItemStack>> getBlockVariantsCache() {
return blockVariantsCache;
}
public static Map<ResourceLocation, List<ItemStack>> 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<ItemStack>();
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");
}
}

View file

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

View file

@ -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<BlockEntityType<OrnamentFabricatorBlockEntity>> = REGISTRY.register(
"ornament_fabricator",
Supplier {
BlockEntityType.Builder.of(
::OrnamentFabricatorBlockEntity,
ModBlocks.ORNAMENT_FABRICATOR.get()
).build(null) as BlockEntityType<OrnamentFabricatorBlockEntity>
}
)
}

View file

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

View file

@ -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<out OrnamentFabricatorBlock> = 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 <T : BlockEntity> getTicker(
level: Level,
state: BlockState,
type: BlockEntityType<T>
): BlockEntityTicker<T>? {
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<com.ldtteam.domumornamentum.block.IMateriallyTexturedBlockComponent>
)

View file

@ -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
}
}

View file

@ -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())
}
}

View file

@ -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")
}
}

View file

@ -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<HolderLookup.Provider>) : 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<Holder
.define('S', ModItems.BUILDER_ASSISTANT_STAFF_T2.get())
.unlockedBy("has_netherite_block", has(Items.NETHERITE_BLOCK))
.save(recipeOutput, Mclschcannoncompat.resource("rec_fct3"))
ShapedRecipeBuilder.shaped(RecipeCategory.DECORATIONS, ModItems.ORNAMENT_FABRICATOR_ITEM.get())
.pattern("III")
.pattern("ACA")
.pattern("III")
.define('I', Items.IRON_INGOT)
.define('A', Items.AMETHYST_SHARD)
.define('C', Items.CRAFTING_TABLE)
.unlockedBy("has_amethyst", has(Items.AMETHYST_SHARD))
.save(recipeOutput, Mclschcannoncompat.resource("ornament_fabricator"))
}
}

View file

@ -82,7 +82,7 @@ class BuilderAssistantStaff(
flagIn: TooltipFlag
) {
tooltipList.add(
Component.translatable("item.asdf.assistantstaff.maxdepth", reach * MAX_PROPAGATION_DEPTH).withStyle(ChatFormatting.BLUE)
Component.translatable("item.assistantstaff.hover.maxdepth", reach * MAX_PROPAGATION_DEPTH).withStyle(ChatFormatting.BLUE)
)
tooltipList.add(
Component.translatable("item.minecolonies.assistanthammer.desc").withStyle(ChatFormatting.ITALIC)

View file

@ -2,11 +2,20 @@ package xyz.nuark.mcmod.mclschcannoncompat.item
import net.neoforged.neoforge.registries.DeferredRegister
import xyz.nuark.mcmod.mclschcannoncompat.Mclschcannoncompat
import xyz.nuark.mcmod.mclschcannoncompat.block.ModBlocks
object ModItems {
val REGISTRY = DeferredRegister.createItems(Mclschcannoncompat.ID)
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 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)
}

View file

@ -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<MenuType<OrnamentFabricatorMenu>> =
REGISTRY.register("ornament_fabricator", Supplier {
IMenuTypeExtension.create(OrnamentFabricatorMenu::fromNetwork)
})
}

View file

@ -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
}
}
}

View file

@ -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<OrnamentFabricatorMenu>(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
}
}

View file

@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "mclschcannoncompat:block/ornament_fabricator" }
}
}

View file

@ -0,0 +1,6 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "mclschcannoncompat:block/ornament_fabricator"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB