From 802bbb6c7b72092eb79f5bceb7c00991b1ccbd3d Mon Sep 17 00:00:00 2001 From: Andrew nuark G Date: Mon, 6 Apr 2026 02:03:46 +0700 Subject: [PATCH] feat: working block implementation --- .gitignore | 2 + .kotlin/errors/errors-1775391978696.log | 71 ++++ .kotlin/errors/errors-1775393382933.log | 71 ++++ .kotlin/errors/errors-1775393603849.log | 71 ++++ EnchantmentTransfer.ipr | 104 ++++++ EnchantmentTransfer.iws | 207 +++++++++++ README.en.md | 51 +++ README.md | 51 +++ build.gradle | 4 +- generate_textures.py | 18 + gradle.properties | 5 +- .../redstone/enchantment_transferrer.json | 32 ++ .../enct/recipe/enchantment_transferrer.json | 24 ++ .../nuark/mcmod/enct/Enchantmenttransfer.kt | 94 +++-- .../enct/block/EnchantmentTransferrerBlock.kt | 108 ++++++ .../xyz/nuark/mcmod/enct/block/ModBlocks.kt | 37 +- .../EnchantmentTransferrerBlockEntity.kt | 339 ++++++++++++++++++ .../mcmod/enct/datagen/DataGenerators.kt | 24 ++ .../mcmod/enct/datagen/ModRecipeProvider.kt | 29 ++ .../enct/menu/EnchantmentTransferrerMenu.kt | 108 ++++++ .../screen/EnchantmentTransferrerScreen.kt | 134 +++++++ .../blockstates/enchantment_transferrer.json | 7 + .../resources/assets/enct/lang/en_us.json | 13 +- .../resources/assets/enct/lang/ru_ru.json | 12 + .../models/block/enchantment_transferrer.json | 12 + .../models/item/enchantment_transferrer.json | 3 + .../block/enchantment_transferrer_bottom.png | Bin 0 -> 1866 bytes .../block/enchantment_transferrer_side.png | Bin 0 -> 2155 bytes .../block/enchantment_transferrer_top.png | Bin 0 -> 2260 bytes .../textures/gui/enchantment_transferrer.png | Bin 0 -> 4570 bytes .../assets/enct/textures/gui/ets_gauge.png | Bin 0 -> 221 bytes 31 files changed, 1580 insertions(+), 51 deletions(-) create mode 100644 .kotlin/errors/errors-1775391978696.log create mode 100644 .kotlin/errors/errors-1775393382933.log create mode 100644 .kotlin/errors/errors-1775393603849.log create mode 100644 EnchantmentTransfer.ipr create mode 100644 EnchantmentTransfer.iws create mode 100644 README.en.md create mode 100644 README.md create mode 100644 generate_textures.py create mode 100644 src/generated/resources/data/enct/advancement/recipes/redstone/enchantment_transferrer.json create mode 100644 src/generated/resources/data/enct/recipe/enchantment_transferrer.json create mode 100644 src/main/kotlin/xyz/nuark/mcmod/enct/block/EnchantmentTransferrerBlock.kt create mode 100644 src/main/kotlin/xyz/nuark/mcmod/enct/block/entity/EnchantmentTransferrerBlockEntity.kt create mode 100644 src/main/kotlin/xyz/nuark/mcmod/enct/datagen/DataGenerators.kt create mode 100644 src/main/kotlin/xyz/nuark/mcmod/enct/datagen/ModRecipeProvider.kt create mode 100644 src/main/kotlin/xyz/nuark/mcmod/enct/menu/EnchantmentTransferrerMenu.kt create mode 100644 src/main/kotlin/xyz/nuark/mcmod/enct/screen/EnchantmentTransferrerScreen.kt create mode 100644 src/main/resources/assets/enct/blockstates/enchantment_transferrer.json create mode 100644 src/main/resources/assets/enct/lang/ru_ru.json create mode 100644 src/main/resources/assets/enct/models/block/enchantment_transferrer.json create mode 100644 src/main/resources/assets/enct/models/item/enchantment_transferrer.json create mode 100644 src/main/resources/assets/enct/textures/block/enchantment_transferrer_bottom.png create mode 100644 src/main/resources/assets/enct/textures/block/enchantment_transferrer_side.png create mode 100644 src/main/resources/assets/enct/textures/block/enchantment_transferrer_top.png create mode 100644 src/main/resources/assets/enct/textures/gui/enchantment_transferrer.png create mode 100644 src/main/resources/assets/enct/textures/gui/ets_gauge.png diff --git a/.gitignore b/.gitignore index 9e4b940..41c5e41 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ hs_err_pid* # Common working directory run +/src/generated/resources/.cache/ +/repo/ diff --git a/.kotlin/errors/errors-1775391978696.log b/.kotlin/errors/errors-1775391978696.log new file mode 100644 index 0000000..66c0be5 --- /dev/null +++ b/.kotlin/errors/errors-1775391978696.log @@ -0,0 +1,71 @@ +kotlin version: 2.0.0 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 1. The daemon process output: + ... (53 more lines) + 54. at java.base/sun.nio.ch.Net.bind0(Native Method) + 55. at java.base/sun.nio.ch.Net.bind(Net.java:555) + 56. at java.base/sun.nio.ch.Net.bind(Net.java:544) + 57. at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:640) + 58. at java.base/java.net.ServerSocket.bind(ServerSocket.java:392) + 59. at java.base/java.net.ServerSocket.(ServerSocket.java:274) + 60. at org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface$ServerLoopbackSocketFactory.createServerSocket(NetworkUtils.kt:69) + 61. at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:673) + 62. at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:344) + 63. ... 14 more + +error message: The daemon has terminated unexpectedly on startup attempt #2 with error code: 1. The daemon process output: + ... (53 more lines) + 54. at java.base/sun.nio.ch.Net.bind0(Native Method) + 55. at java.base/sun.nio.ch.Net.bind(Net.java:555) + 56. at java.base/sun.nio.ch.Net.bind(Net.java:544) + 57. at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:640) + 58. at java.base/java.net.ServerSocket.bind(ServerSocket.java:392) + 59. at java.base/java.net.ServerSocket.(ServerSocket.java:274) + 60. at org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface$ServerLoopbackSocketFactory.createServerSocket(NetworkUtils.kt:69) + 61. at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:673) + 62. at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:344) + 63. ... 14 more + +error message: Failed connecting to the daemon in 3 retries + +error message: Daemon compilation failed: Could not connect to Kotlin compile daemon +java.lang.RuntimeException: Could not connect to Kotlin compile daemon + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:187) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:120) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:162) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:264) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:128) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:133) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:157) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:126) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:833) + + diff --git a/.kotlin/errors/errors-1775393382933.log b/.kotlin/errors/errors-1775393382933.log new file mode 100644 index 0000000..838bbd1 --- /dev/null +++ b/.kotlin/errors/errors-1775393382933.log @@ -0,0 +1,71 @@ +kotlin version: 2.0.0 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 1. The daemon process output: + ... (53 more lines) + 54. at java.base/sun.nio.ch.Net.bind0(Native Method) + 55. at java.base/sun.nio.ch.Net.bind(Net.java:565) + 56. at java.base/sun.nio.ch.Net.bind(Net.java:554) + 57. at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:636) + 58. at java.base/java.net.ServerSocket.bind(ServerSocket.java:391) + 59. at java.base/java.net.ServerSocket.(ServerSocket.java:278) + 60. at org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface$ServerLoopbackSocketFactory.createServerSocket(NetworkUtils.kt:69) + 61. at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:672) + 62. at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:344) + 63. ... 14 more + +error message: The daemon has terminated unexpectedly on startup attempt #2 with error code: 1. The daemon process output: + ... (53 more lines) + 54. at java.base/sun.nio.ch.Net.bind0(Native Method) + 55. at java.base/sun.nio.ch.Net.bind(Net.java:565) + 56. at java.base/sun.nio.ch.Net.bind(Net.java:554) + 57. at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:636) + 58. at java.base/java.net.ServerSocket.bind(ServerSocket.java:391) + 59. at java.base/java.net.ServerSocket.(ServerSocket.java:278) + 60. at org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface$ServerLoopbackSocketFactory.createServerSocket(NetworkUtils.kt:69) + 61. at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:672) + 62. at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:344) + 63. ... 14 more + +error message: Failed connecting to the daemon in 3 retries + +error message: Daemon compilation failed: Could not connect to Kotlin compile daemon +java.lang.RuntimeException: Could not connect to Kotlin compile daemon + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:187) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:120) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:162) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:264) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:128) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:133) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:157) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:126) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) + at java.base/java.lang.Thread.run(Thread.java:1583) + + diff --git a/.kotlin/errors/errors-1775393603849.log b/.kotlin/errors/errors-1775393603849.log new file mode 100644 index 0000000..838bbd1 --- /dev/null +++ b/.kotlin/errors/errors-1775393603849.log @@ -0,0 +1,71 @@ +kotlin version: 2.0.0 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 1. The daemon process output: + ... (53 more lines) + 54. at java.base/sun.nio.ch.Net.bind0(Native Method) + 55. at java.base/sun.nio.ch.Net.bind(Net.java:565) + 56. at java.base/sun.nio.ch.Net.bind(Net.java:554) + 57. at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:636) + 58. at java.base/java.net.ServerSocket.bind(ServerSocket.java:391) + 59. at java.base/java.net.ServerSocket.(ServerSocket.java:278) + 60. at org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface$ServerLoopbackSocketFactory.createServerSocket(NetworkUtils.kt:69) + 61. at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:672) + 62. at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:344) + 63. ... 14 more + +error message: The daemon has terminated unexpectedly on startup attempt #2 with error code: 1. The daemon process output: + ... (53 more lines) + 54. at java.base/sun.nio.ch.Net.bind0(Native Method) + 55. at java.base/sun.nio.ch.Net.bind(Net.java:565) + 56. at java.base/sun.nio.ch.Net.bind(Net.java:554) + 57. at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:636) + 58. at java.base/java.net.ServerSocket.bind(ServerSocket.java:391) + 59. at java.base/java.net.ServerSocket.(ServerSocket.java:278) + 60. at org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface$ServerLoopbackSocketFactory.createServerSocket(NetworkUtils.kt:69) + 61. at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:672) + 62. at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:344) + 63. ... 14 more + +error message: Failed connecting to the daemon in 3 retries + +error message: Daemon compilation failed: Could not connect to Kotlin compile daemon +java.lang.RuntimeException: Could not connect to Kotlin compile daemon + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:187) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:120) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:162) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:264) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:128) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:133) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:157) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:126) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) + at java.base/java.lang.Thread.run(Thread.java:1583) + + diff --git a/EnchantmentTransfer.ipr b/EnchantmentTransfer.ipr new file mode 100644 index 0000000..dfb2a2c --- /dev/null +++ b/EnchantmentTransfer.ipr @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.6 + + + + + + + + + + + + + diff --git a/EnchantmentTransfer.iws b/EnchantmentTransfer.iws new file mode 100644 index 0000000..d5bc759 --- /dev/null +++ b/EnchantmentTransfer.iws @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + + + + + + + + + diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..720e2ec --- /dev/null +++ b/README.en.md @@ -0,0 +1,51 @@ +[Русская версия](README.md) + +# Enchantment Transfer Mod + +A mod for Minecraft 1.21.1 that adds a machine for transferring enchantments between items. + +## Features + +- **Enchantment Transfer**: Transfers enchantments from one item to another (as well as to books) +- **Charge System**: Machine requires energy in the form of items +- **Redstone Control**: Only works with redstone signal +- **Processing Progress**: Processing time depends on amount and level of enchantments + +## Accepted Fuel Sources + +| Item | Charge | +|-----------------|-------| +| Redstone | 16 | +| Copper Ingot | 1 | +| Iron Ingot | 4 | +| Gold Ingot | 2 | +| Diamond | 32 | +| Netherite Ingot | 64 | + +Maximum charge capacity: 1 000 000 units + +## How to Use + +1. Place the **Enchantment Transferrer** block in the world +2. Put an enchanted item, target item and, optionally, a **book** +3. Put **fuel items** in the fuel slot +4. Provide a **redstone signal** to activate +5. Wait for the process to complete + +## Installation + +1. Install NeoForge 21.1.219 for Minecraft 1.21.1 +2. Place the `.jar` mod file in the `mods` folder +3. Launch the game + +## Building + +```bash +gradlew.bat build +``` + +The built file will be located in `build/libs/` + +## 3rd-party Assets + +UI and block textures were taken from [Ycarx/artoftecharium](https://github.com/Ycarx/artoftecharium) and altered to suite my needs. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f392c79 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +[English version](README.en.md) + +# Мод Enchantment Transfer + +Мод для Minecraft 1.21.1, добавляющий машину для переноса зачарований. + +## Возможности + +- **Перенос зачарований**: Переносит зачарования с одного предмета на другой (в т.ч. - на книги) +- **Система заряда**: Машина требует энергию в виде предметов +- **Редстоун контроль**: Работает только при наличии редстоун сигнала +- **Прогресс обработки**: Время обработки зависит от количества и уровня зачарований + +## Принимаемые источники энергии + +| Предмет | Заряд | +|----------|-------| +| Редстоун | 16 | +| Медь | 1 | +| Железо | 4 | +| Золото | 2 | +| Алмаз | 32 | +| Незерит | 64 | + +Максимальная вместимость заряда: 1 000 000 единиц + +## Как использовать + +1. Поместите **Enchantment Transferrer** в мир +2. Поместите зачарованный предмет, целевой предмет в **средний слот**, опционально - книгу +3. Поместите **топливо** в слот +4. Подайте **редстоун сигнал** для активации +5. Дождитесь завершения процесса + +## Установка + +1. Установите NeoForge 21.1.219 для Minecraft 1.21.1 +2. Поместите `.jar` файл мода в папку `mods` +3. Запустите игру + +## Сборка + +```bash +gradlew.bat build +``` + +Собранный файл будет находиться в `build/libs/` + +## Чужие ассеты + +Текстуры для UI и блоков были взяты из [Ycarx/artoftecharium](https://github.com/Ycarx/artoftecharium) из изменены по потребности \ No newline at end of file diff --git a/build.gradle b/build.gradle index 985819c..61b7119 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'maven-publish' id 'idea' id 'net.neoforged.moddev' version '2.0.141' - id 'org.jetbrains.kotlin.jvm' version '2.0.0' + id 'org.jetbrains.kotlin.jvm' version '2.3.0' } version = mod_version @@ -103,7 +103,7 @@ sourceSets.main.resources { srcDir 'src/generated/resources' } dependencies { - implementation 'thedarkcolour:kotlinforforge-neoforge:5.3.0' + implementation 'thedarkcolour:kotlinforforge-neoforge:5.11.0' // Example mod dependency with JEI // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime diff --git a/generate_textures.py b/generate_textures.py new file mode 100644 index 0000000..2745a4a --- /dev/null +++ b/generate_textures.py @@ -0,0 +1,18 @@ +from PIL import Image +import os + +textures_dir = "src/main/resources/assets/enct/textures/block" +os.makedirs(textures_dir, exist_ok=True) + +colors = { + "enchantment_transferrer_top": (120, 80, 200), + "enchantment_transferrer_bottom": (80, 80, 80), + "enchantment_transferrer_side": (100, 100, 120), + "enchantment_transferrer_front": (140, 100, 220), +} + +for name, color in colors.items(): + img = Image.new('RGBA', (16, 16), color + (255,)) + img.save(f"{textures_dir}/{name}.png", 'PNG') + +print("Textures generated successfully!") diff --git a/gradle.properties b/gradle.properties index 744f5f1..d173c57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.caching=true org.gradle.configuration-cache=true +kotlin.compiler.execution.strategy=in-process ## Environment Properties # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge # The Minecraft version must agree with the Neo version to get a valid artifact @@ -35,6 +36,6 @@ mod_version=1.0.0 # See https://maven.apache.org/guides/mini/guide-naming-conventions.html mod_group_id=xyz.nuark.mcmod # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. -mod_authors= +mod_authors=nuark # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. -mod_description= +mod_description=Ever been in a situation, when you have new toy, but now you need to move enchantments from previous one? Say no more! diff --git a/src/generated/resources/data/enct/advancement/recipes/redstone/enchantment_transferrer.json b/src/generated/resources/data/enct/advancement/recipes/redstone/enchantment_transferrer.json new file mode 100644 index 0000000..88abc31 --- /dev/null +++ b/src/generated/resources/data/enct/advancement/recipes/redstone/enchantment_transferrer.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_ench_table": { + "conditions": { + "items": [ + { + "items": "minecraft:enchanting_table" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "enct:enchantment_transferrer" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_ench_table" + ] + ], + "rewards": { + "recipes": [ + "enct:enchantment_transferrer" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/enct/recipe/enchantment_transferrer.json b/src/generated/resources/data/enct/recipe/enchantment_transferrer.json new file mode 100644 index 0000000..53c25e5 --- /dev/null +++ b/src/generated/resources/data/enct/recipe/enchantment_transferrer.json @@ -0,0 +1,24 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "redstone", + "key": { + "B": { + "item": "minecraft:blaze_rod" + }, + "E": { + "item": "minecraft:enchanting_table" + }, + "I": { + "item": "minecraft:iron_block" + } + }, + "pattern": [ + "III", + "BEB", + "III" + ], + "result": { + "count": 1, + "id": "enct:enchantment_transferrer" + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/Enchantmenttransfer.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/Enchantmenttransfer.kt index 0ed09f5..fd93844 100644 --- a/src/main/kotlin/xyz/nuark/mcmod/enct/Enchantmenttransfer.kt +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/Enchantmenttransfer.kt @@ -1,63 +1,83 @@ package xyz.nuark.mcmod.enct -import xyz.nuark.mcmod.enct.block.ModBlocks -import net.minecraft.client.Minecraft +import net.minecraft.core.registries.Registries +import net.minecraft.world.flag.FeatureFlags +import net.minecraft.world.inventory.MenuType +import net.minecraft.world.item.CreativeModeTabs import net.neoforged.bus.api.SubscribeEvent import net.neoforged.fml.common.EventBusSubscriber 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.capabilities.Capabilities +import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent +import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent +import net.neoforged.neoforge.network.IContainerFactory +import net.neoforged.neoforge.registries.DeferredHolder +import net.neoforged.neoforge.registries.DeferredRegister import org.apache.logging.log4j.Level 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.enct.block.ModBlocks +import xyz.nuark.mcmod.enct.block.entity.EnchantmentTransferrerBlockEntity +import xyz.nuark.mcmod.enct.menu.EnchantmentTransferrerMenu +import xyz.nuark.mcmod.enct.screen.EnchantmentTransferrerScreen +import java.util.function.Supplier -/** - * Main mod class. - * - * An example for blocks is in the `blocks` package of this mod. - */ @Mod(Enchantmenttransfer.ID) -@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) +@EventBusSubscriber object Enchantmenttransfer { const val ID = "enct" - // the logger for our mod - val LOGGER: Logger = LogManager.getLogger(ID) + val LOGGER = LogManager.getLogger(ID) + + val MENU_TYPES: DeferredRegister> = DeferredRegister.create(Registries.MENU, ID) + + @Suppress("UNCHECKED_CAST") + val ENCHANTMENT_TRANSFERRER_MENU_TYPE: DeferredHolder, MenuType> = MENU_TYPES.register("enchantment_transferrer", Supplier { + MenuType( + IContainerFactory { containerId, playerInventory, buf -> + val pos = buf.readBlockPos() + val level = playerInventory.player.level() + val blockEntity = level.getBlockEntity(pos) as? EnchantmentTransferrerBlockEntity + if (blockEntity != null) { + EnchantmentTransferrerMenu(containerId, playerInventory, blockEntity, blockEntity.dataAccess) + } else { + throw IllegalStateException("BlockEntity not found at $pos") + } + }, + FeatureFlags.VANILLA_SET + ) + }) init { - LOGGER.log(Level.INFO, "Hello world!") + ModBlocks.register(MOD_BUS) + MENU_TYPES.register(MOD_BUS) - // Register the KDeferredRegister to the mod-specific event bus - ModBlocks.REGISTRY.register(MOD_BUS) - - val obj = runForDist(clientTarget = { - MOD_BUS.addListener(::onClientSetup) - Minecraft.getInstance() - }, serverTarget = { - MOD_BUS.addListener(::onServerSetup) - "test" - }) - - println(obj) + MOD_BUS.addListener { event -> + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, + ModBlocks.ENCHANTMENT_TRANSFERRER_BE.value(), + { be, side -> be.getHandlerForSide(side) } + ) + } } - /** - * This is used for initializing client specific - * things such as renderers and keymaps - * Fired on the mod specific event bus. - */ - private fun onClientSetup(event: FMLClientSetupEvent) { + @SubscribeEvent + fun onBuildCreativeTabContents(event: BuildCreativeModeTabContentsEvent) { + if (event.tabKey == CreativeModeTabs.REDSTONE_BLOCKS) { + event.accept(ModBlocks.ENCHANTMENT_TRANSFERRER_ITEM.get()) + } + } + + @SubscribeEvent + fun onClientSetup(event: FMLClientSetupEvent) { LOGGER.log(Level.INFO, "Initializing client...") } - /** - * Fired on the global Forge bus. - */ - private fun onServerSetup(event: FMLDedicatedServerSetupEvent) { - LOGGER.log(Level.INFO, "Server starting...") + @SubscribeEvent + fun onRegisterMenuScreens(event: RegisterMenuScreensEvent) { + event.register(ENCHANTMENT_TRANSFERRER_MENU_TYPE.value(), ::EnchantmentTransferrerScreen) } @SubscribeEvent diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/block/EnchantmentTransferrerBlock.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/block/EnchantmentTransferrerBlock.kt new file mode 100644 index 0000000..9d76626 --- /dev/null +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/block/EnchantmentTransferrerBlock.kt @@ -0,0 +1,108 @@ +package xyz.nuark.mcmod.enct.block + +import com.mojang.serialization.MapCodec +import xyz.nuark.mcmod.enct.block.entity.EnchantmentTransferrerBlockEntity +import net.minecraft.core.BlockPos +import net.minecraft.world.InteractionResult +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +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.phys.BlockHitResult +import net.neoforged.neoforge.capabilities.Capabilities +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent +import org.jetbrains.annotations.Nullable + +class EnchantmentTransferrerBlock(properties: Properties) : BaseEntityBlock(properties) { + + companion object { + val CODEC: MapCodec = MapCodec.unit { EnchantmentTransferrerBlock(Properties.of()) } + + fun registerCapabilities(event: RegisterCapabilitiesEvent) { + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, + ModBlocks.ENCHANTMENT_TRANSFERRER_BE.value(), + ) { be, side -> + be.getHandlerForSide(side) + } + } + } + + override fun codec(): MapCodec = CODEC + + @Nullable + override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? { + return EnchantmentTransferrerBlockEntity(pos, state) + } + + override fun useWithoutItem( + state: BlockState, + level: Level, + pos: BlockPos, + player: Player, + hitResult: BlockHitResult + ): InteractionResult { + if (level.isClientSide) { + return InteractionResult.SUCCESS + } + + val blockEntity = level.getBlockEntity(pos) + if (blockEntity is EnchantmentTransferrerBlockEntity) { + player.openMenu(blockEntity) { buf -> + buf.writeBlockPos(pos) + } + } + + return InteractionResult.CONSUME + } + + override fun setPlacedBy(level: Level, pos: BlockPos, state: BlockState, @Nullable placer: LivingEntity?, stack: ItemStack) { + super.setPlacedBy(level, pos, state, placer, stack) + if (level.isClientSide) return + + val blockEntity = level.getBlockEntity(pos) + // TODO: Maybe link machine to player? XP usage instead of fuel :thinking: + } + + override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, isMoving: Boolean) { + if (state.block !== newState.block) { + val blockEntity = level.getBlockEntity(pos) + if (blockEntity is EnchantmentTransferrerBlockEntity) { + for (i in 0 until EnchantmentTransferrerBlockEntity.TOTAL_SLOTS) { + val stack = blockEntity.getItemHandler().getStackInSlot(i) + if (!stack.isEmpty) { + popResource(level, pos, stack) + } + } + } + super.onRemove(state, level, pos, newState, isMoving) + } + } + + override fun getRenderShape(state: BlockState): RenderShape { + return RenderShape.MODEL + } + + @Nullable + override fun getTicker( + level: Level, + state: BlockState, + type: BlockEntityType + ): BlockEntityTicker? { + if (level.isClientSide) return null + + val blockEntityType = ModBlocks.ENCHANTMENT_TRANSFERRER_BE.value() + if (type !== blockEntityType) return null + + @Suppress("UNCHECKED_CAST") + return BlockEntityTicker { l, pos, s, be -> + EnchantmentTransferrerBlockEntity.serverTick(l, pos, s, be as EnchantmentTransferrerBlockEntity) + } + } +} diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/block/ModBlocks.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/block/ModBlocks.kt index f8295c2..ca6d545 100644 --- a/src/main/kotlin/xyz/nuark/mcmod/enct/block/ModBlocks.kt +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/block/ModBlocks.kt @@ -1,18 +1,41 @@ package xyz.nuark.mcmod.enct.block import xyz.nuark.mcmod.enct.Enchantmenttransfer +import xyz.nuark.mcmod.enct.block.entity.EnchantmentTransferrerBlockEntity +import net.minecraft.core.registries.Registries +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockBehaviour +import net.neoforged.bus.api.IEventBus import net.neoforged.neoforge.registries.DeferredRegister - -// THIS LINE IS REQUIRED FOR USING PROPERTY DELEGATES -import thedarkcolour.kotlinforforge.neoforge.forge.getValue +import net.neoforged.neoforge.registries.DeferredHolder +import java.util.function.Supplier object ModBlocks { - val REGISTRY = DeferredRegister.createBlocks(Enchantmenttransfer.ID) + val BLOCKS = DeferredRegister.create(Registries.BLOCK, Enchantmenttransfer.ID) + val ITEMS = DeferredRegister.create(Registries.ITEM, Enchantmenttransfer.ID) + val BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, Enchantmenttransfer.ID) - // If you get an "overload resolution ambiguity" error, include the arrow at the start of the closure. - val EXAMPLE_BLOCK by REGISTRY.register("example_block") { -> - Block(BlockBehaviour.Properties.of().lightLevel { 15 }.strength(3.0f)) + val ENCHANTMENT_TRANSFERRER: DeferredHolder = BLOCKS.register("enchantment_transferrer", Supplier { + EnchantmentTransferrerBlock(BlockBehaviour.Properties.of().strength(3.0f).requiresCorrectToolForDrops()) + }) + + val ENCHANTMENT_TRANSFERRER_ITEM: DeferredHolder = ITEMS.register("enchantment_transferrer", Supplier { + BlockItem(ENCHANTMENT_TRANSFERRER.get(), Item.Properties()) + }) + + val ENCHANTMENT_TRANSFERRER_BE: DeferredHolder, BlockEntityType> = BLOCK_ENTITIES.register("enchantment_transferrer", Supplier { + BlockEntityType.Builder.of( + { pos, state -> EnchantmentTransferrerBlockEntity(pos, state) }, + ENCHANTMENT_TRANSFERRER.get() + ).build(null) + }) + + fun register(eventBus: IEventBus) { + BLOCKS.register(eventBus) + ITEMS.register(eventBus) + BLOCK_ENTITIES.register(eventBus) } } diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/block/entity/EnchantmentTransferrerBlockEntity.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/block/entity/EnchantmentTransferrerBlockEntity.kt new file mode 100644 index 0000000..12ab853 --- /dev/null +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/block/entity/EnchantmentTransferrerBlockEntity.kt @@ -0,0 +1,339 @@ +package xyz.nuark.mcmod.enct.block.entity + +import xyz.nuark.mcmod.enct.Enchantmenttransfer +import xyz.nuark.mcmod.enct.block.ModBlocks +import xyz.nuark.mcmod.enct.menu.EnchantmentTransferrerMenu +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +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.network.protocol.Packet +import net.minecraft.network.protocol.game.ClientGamePacketListener +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket +import net.minecraft.world.MenuProvider +import net.minecraft.world.Nameable +import net.minecraft.world.WorldlyContainer +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.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.enchantment.EnchantmentHelper +import net.minecraft.world.item.enchantment.ItemEnchantments +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.neoforged.neoforge.items.IItemHandler +import net.neoforged.neoforge.items.ItemStackHandler +import net.neoforged.neoforge.items.wrapper.SidedInvWrapper +import org.jetbrains.annotations.Nullable + +class EnchantmentTransferrerBlockEntity( + pos: BlockPos, + state: BlockState +) : BlockEntity(ModBlocks.ENCHANTMENT_TRANSFERRER_BE.value(), pos, state), MenuProvider, Nameable, WorldlyContainer { + + companion object { + const val SLOT_MAIN = 0 + const val SLOT_BOOK = 1 + const val SLOT_TARGET = 2 + const val SLOT_FUEL = 3 + const val TOTAL_SLOTS = 4 + + const val MAX_CHARGE = 1_000_000 + + // Charge values for different items + val CHARGE_VALUES = mapOf( + Items.COPPER_INGOT to 1, + Items.IRON_INGOT to 4, + Items.GOLD_INGOT to 2, + Items.DIAMOND to 32, + Items.NETHERITE_INGOT to 64, + Items.REDSTONE to 16 + ) + + // Base time per enchantment (in ticks) + const val BASE_TIME_PER_ENCHANTMENT = 100 + + @JvmStatic + fun serverTick(level: Level, pos: BlockPos, state: BlockState, be: EnchantmentTransferrerBlockEntity) { + be.tick() + } + } + + private val itemHandler = object : ItemStackHandler(TOTAL_SLOTS) { + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return when (slot) { + SLOT_MAIN -> EnchantmentHelper.hasAnyEnchantments(stack) + SLOT_TARGET -> !EnchantmentHelper.hasAnyEnchantments(stack) && EnchantmentHelper.canStoreEnchantments(stack) + SLOT_BOOK -> stack.item == Items.BOOK || stack.item == Items.ENCHANTED_BOOK + SLOT_FUEL -> CHARGE_VALUES.containsKey(stack.item) + else -> false + } + } + } + + override fun getSlotsForFace(side: Direction): IntArray { + return when (side) { + Direction.UP -> intArrayOf(SLOT_BOOK) + Direction.DOWN -> intArrayOf() + else -> intArrayOf(SLOT_FUEL) + } + } + + override fun canPlaceItemThroughFace(index: Int, itemStack: ItemStack, @Nullable direction: Direction?): Boolean { + return itemHandler.isItemValid(index, itemStack) + } + + override fun canTakeItemThroughFace(index: Int, itemStack: ItemStack, direction: Direction): Boolean { + return false + } + + override fun getContainerSize(): Int = TOTAL_SLOTS + + override fun isEmpty(): Boolean = (0 until TOTAL_SLOTS).all { itemHandler.getStackInSlot(it).isEmpty } + + override fun getItem(slot: Int): ItemStack = itemHandler.getStackInSlot(slot) + + override fun removeItem(slot: Int, amount: Int): ItemStack { + return itemHandler.extractItem(slot, amount, false) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + val stack = itemHandler.getStackInSlot(slot) + itemHandler.setStackInSlot(slot, ItemStack.EMPTY) + return stack + } + + override fun setItem(slot: Int, stack: ItemStack) { + itemHandler.setStackInSlot(slot, stack) + } + + override fun stillValid(player: Player): Boolean = true + + override fun clearContent() { + for (i in 0 until TOTAL_SLOTS) { + itemHandler.setStackInSlot(i, ItemStack.EMPTY) + } + } + + private val handlerUp = SidedInvWrapper(this, Direction.UP) + private val handlerSides = SidedInvWrapper(this, Direction.NORTH) + + private var charge: Int = 0 + private var processTime: Int = 0 + private var totalTime: Int = 0 + private var isProcessing: Boolean = false + private var wasPowered: Boolean = false + + // ContainerData for GUI sync + val dataAccess = object : ContainerData { + override fun get(index: Int): Int = when (index) { + 0 -> this@EnchantmentTransferrerBlockEntity.charge + 1 -> this@EnchantmentTransferrerBlockEntity.processTime + 2 -> this@EnchantmentTransferrerBlockEntity.totalTime + 3 -> if (this@EnchantmentTransferrerBlockEntity.isProcessing) 1 else 0 + else -> 0 + } + + override fun set(index: Int, value: Int) { + when (index) { + 0 -> this@EnchantmentTransferrerBlockEntity.charge = value + 1 -> this@EnchantmentTransferrerBlockEntity.processTime = value + 2 -> this@EnchantmentTransferrerBlockEntity.totalTime = value + 3 -> this@EnchantmentTransferrerBlockEntity.isProcessing = value == 1 + } + } + + override fun getCount(): Int = 4 + } + + fun getItemHandler(): IItemHandler = itemHandler + + fun getHandlerForSide(side: Direction?): IItemHandler? { + return when (side) { + Direction.UP -> handlerUp + Direction.DOWN -> null + else -> handlerSides + } + } + + override fun getName(): Component = Component.translatable("block.${Enchantmenttransfer.ID}.enchantment_transferrer") + + override fun getDisplayName(): Component = name + + override fun createMenu(containerId: Int, playerInventory: Inventory, player: Player): AbstractContainerMenu { + return EnchantmentTransferrerMenu(containerId, playerInventory, this, dataAccess) + } + + override fun saveAdditional(tag: CompoundTag, registries: net.minecraft.core.HolderLookup.Provider) { + super.saveAdditional(tag, registries) + tag.put("items", itemHandler.serializeNBT(registries)) + tag.putInt("charge", charge) + tag.putInt("processTime", processTime) + tag.putInt("totalTime", totalTime) + tag.putBoolean("isProcessing", isProcessing) + } + + override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) { + super.loadAdditional(tag, registries) + + itemHandler.deserializeNBT(registries, tag.getCompound("items")) + charge = tag.getInt("charge") + processTime = tag.getInt("processTime") + totalTime = tag.getInt("totalTime") + isProcessing = tag.getBoolean("isProcessing") + + setChanged() + } + + override fun getUpdateTag(registries: HolderLookup.Provider): CompoundTag { + val tag = CompoundTag() + saveAdditional(tag, registries) + return tag + } + + override fun getUpdatePacket(): Packet? { + return ClientboundBlockEntityDataPacket.create(this) + } + + private fun tick() { + if (level?.isClientSide == true) return + + val isPowered = level?.hasNeighborSignal(worldPosition) == true + + if (charge < MAX_CHARGE) { + consumeFuel() + } + + if (!isPowered) { + if (wasPowered) { + setChanged() + } + wasPowered = false + return + } + + wasPowered = true + + if (!isProcessing) { + if (canProcess()) { + startProcessing() + } + return + } + + if (charge > 0) { + if (!canProcess()) { + processTime = 0 + setChanged() + return + } + + processTime++ + charge-- + + if (processTime >= totalTime) { + finishProcessing() + } + setChanged() + level?.sendBlockUpdated(worldPosition, blockState, blockState, 3) + } + } + + private fun consumeFuel() { + val stack = getItem(SLOT_FUEL) + if (!stack.isEmpty) { + val chargeValue = CHARGE_VALUES[stack.item] ?: 0 + if (chargeValue > 0 && charge + chargeValue <= MAX_CHARGE) { + stack.shrink(1) + setItem(SLOT_FUEL, stack) + charge += chargeValue + setChanged() + return + } + } + } + + private fun canProcess(): Boolean { + val mainStack = getItem(SLOT_MAIN) + if (mainStack.isEmpty || !EnchantmentHelper.hasAnyEnchantments(mainStack)) return false + + val targetStack = getItem(SLOT_TARGET) + + val mainEnchantments = EnchantmentHelper.getEnchantmentsForCrafting(mainStack) + if (mainEnchantments.isEmpty) return false + + val targetEnchantments = EnchantmentHelper.getEnchantmentsForCrafting(targetStack) + if (!targetEnchantments.isEmpty) return false + + val hasValidTarget = !targetStack.isEmpty + return hasValidTarget + } + + private fun startProcessing() { + val mainStack = getItem(SLOT_MAIN) + val mainEnchantments = EnchantmentHelper.getEnchantmentsForCrafting(mainStack).entrySet() + + var totalLevels = 0 + mainEnchantments.forEach { entry -> + totalLevels += entry.intValue + } + + val enchantmentCount = mainEnchantments.size + val avgLevel = if (enchantmentCount > 0) totalLevels.toFloat() / enchantmentCount else 1f + totalTime = (enchantmentCount * avgLevel * BASE_TIME_PER_ENCHANTMENT).toInt().coerceAtLeast(100) + + processTime = 0 + isProcessing = true + setChanged() + } + + private fun finishProcessing() { + val mainStack = getItem(SLOT_MAIN) + val targetStack = getItem(SLOT_TARGET) + val bookStack = getItem(SLOT_BOOK) + + val originalEnchantments = EnchantmentHelper.getEnchantmentsForCrafting(mainStack).entrySet() + val (possible, impossible) = originalEnchantments.partition { targetStack.supportsEnchantment(it.key) } + + val possibleEnchantments = ItemEnchantments.Mutable(ItemEnchantments.EMPTY).apply { + possible.forEach{ + set(it.key, it.intValue) + } + }.toImmutable() + val impossibleEnchantments = ItemEnchantments.Mutable(ItemEnchantments.EMPTY).apply { + impossible.forEach{ + set(it.key, it.intValue) + } + }.toImmutable() + + if (possible.isNotEmpty()) { + EnchantmentHelper.setEnchantments(targetStack, possibleEnchantments) + } + if (bookStack.isEmpty) { + setItem(SLOT_MAIN, mainStack.apply { + set(DataComponents.ENCHANTMENTS, impossibleEnchantments) + }) + } else { + setItem(SLOT_MAIN, mainStack.apply { + set(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY) + }) + setItem(SLOT_BOOK, ItemStack(Items.ENCHANTED_BOOK).apply { + EnchantmentHelper.setEnchantments(this, impossibleEnchantments) + }) + } + + resetProcessing() + } + + private fun resetProcessing() { + processTime = 0 + totalTime = 0 + isProcessing = false + setChanged() + } +} diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/datagen/DataGenerators.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/datagen/DataGenerators.kt new file mode 100644 index 0000000..e29aab3 --- /dev/null +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/datagen/DataGenerators.kt @@ -0,0 +1,24 @@ +package xyz.nuark.mcmod.enct.datagen + +import net.minecraft.core.HolderLookup +import net.minecraft.data.DataGenerator +import net.minecraft.data.PackOutput +import net.neoforged.bus.api.SubscribeEvent +import net.neoforged.fml.common.EventBusSubscriber +import net.neoforged.neoforge.data.event.GatherDataEvent +import java.util.concurrent.CompletableFuture + +@EventBusSubscriber +object DataGenerators { + + @SubscribeEvent + fun gatherData(event: GatherDataEvent) { + val generator: DataGenerator = event.generator + val packOutput: PackOutput = generator.packOutput + val registries: CompletableFuture = event.lookupProvider + + if (event.includeServer()) { + generator.addProvider(true, ModRecipeProvider(packOutput, registries)) + } + } +} diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/datagen/ModRecipeProvider.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/datagen/ModRecipeProvider.kt new file mode 100644 index 0000000..6c1665a --- /dev/null +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/datagen/ModRecipeProvider.kt @@ -0,0 +1,29 @@ +package xyz.nuark.mcmod.enct.datagen + +import xyz.nuark.mcmod.enct.block.ModBlocks +import net.minecraft.core.HolderLookup +import net.minecraft.data.PackOutput +import net.minecraft.data.recipes.RecipeCategory +import net.minecraft.data.recipes.RecipeOutput +import net.minecraft.data.recipes.RecipeProvider +import net.minecraft.data.recipes.ShapedRecipeBuilder +import net.minecraft.world.item.Items +import java.util.concurrent.CompletableFuture + +class ModRecipeProvider( + output: PackOutput, + registries: CompletableFuture +) : RecipeProvider(output, registries) { + + override fun buildRecipes(recipeOutput: RecipeOutput) { + ShapedRecipeBuilder.shaped(RecipeCategory.REDSTONE, ModBlocks.ENCHANTMENT_TRANSFERRER_ITEM.get()) + .define('I', Items.IRON_BLOCK) + .define('B', Items.BLAZE_ROD) + .define('E', Items.ENCHANTING_TABLE) + .pattern("III") + .pattern("BEB") + .pattern("III") + .unlockedBy("has_ench_table", has(Items.ENCHANTING_TABLE)) + .save(recipeOutput) + } +} diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/menu/EnchantmentTransferrerMenu.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/menu/EnchantmentTransferrerMenu.kt new file mode 100644 index 0000000..69ab013 --- /dev/null +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/menu/EnchantmentTransferrerMenu.kt @@ -0,0 +1,108 @@ +package xyz.nuark.mcmod.enct.menu + +import xyz.nuark.mcmod.enct.Enchantmenttransfer +import xyz.nuark.mcmod.enct.block.entity.EnchantmentTransferrerBlockEntity +import net.minecraft.world.Container +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.SimpleContainerData +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.enchantment.EnchantmentHelper +import xyz.nuark.mcmod.enct.block.entity.EnchantmentTransferrerBlockEntity.Companion.CHARGE_VALUES + +class EnchantmentTransferrerMenu( + containerId: Int, + private val playerInventory: Inventory, + private val container: Container, + private val data: ContainerData = SimpleContainerData(4) +) : AbstractContainerMenu(Enchantmenttransfer.ENCHANTMENT_TRANSFERRER_MENU_TYPE.value(), containerId) { + + init { + checkContainerSize(container, EnchantmentTransferrerBlockEntity.TOTAL_SLOTS) + checkContainerDataCount(data, 4) + + // Block inventory slots + addSlot(object : Slot(container, EnchantmentTransferrerBlockEntity.SLOT_MAIN, 35, 23) { + override fun mayPlace(stack: ItemStack): Boolean = EnchantmentHelper.hasAnyEnchantments(stack) + }) + addSlot(object : Slot(container, EnchantmentTransferrerBlockEntity.SLOT_BOOK, 65, 23) { + override fun mayPlace(stack: ItemStack): Boolean = stack.item == Items.BOOK || stack.item == Items.ENCHANTED_BOOK + + override fun getMaxStackSize(): Int = 1 + }) + addSlot(object : Slot(container, EnchantmentTransferrerBlockEntity.SLOT_TARGET, 124, 23) { + override fun mayPlace(stack: ItemStack): Boolean = !EnchantmentHelper.hasAnyEnchantments(stack) && EnchantmentHelper.canStoreEnchantments(stack) + }) + addSlot(object : Slot(container, EnchantmentTransferrerBlockEntity.SLOT_FUEL, 3, 23) { + override fun mayPlace(stack: ItemStack): Boolean = CHARGE_VALUES.containsKey(stack.item) + }) + + // Player inventory slots + for (row in 0 until 3) { + for (col in 0 until 9) { + addSlot(Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, 72 + row * 18)) + } + } + // Player hotbar + for (col in 0 until 9) { + addSlot(Slot(playerInventory, col, 8 + col * 18, 130)) + } + + addDataSlots(data) + } + + override fun stillValid(player: Player): Boolean { + return container.stillValid(player) + } + + override fun quickMoveStack(player: Player, index: Int): ItemStack { + var movedStack = ItemStack.EMPTY + val slot = this.slots[index] + + if (slot.hasItem()) { + val slotStack = slot.item + movedStack = slotStack.copy() + + if (index < EnchantmentTransferrerBlockEntity.TOTAL_SLOTS) { + if (!this.moveItemStackTo(slotStack, EnchantmentTransferrerBlockEntity.TOTAL_SLOTS, this.slots.size, true)) { + return ItemStack.EMPTY + } + } else { + val item = slotStack.item + + if (CHARGE_VALUES.containsKey(item)) { + if (!this.moveItemStackTo(slotStack, EnchantmentTransferrerBlockEntity.SLOT_FUEL, EnchantmentTransferrerBlockEntity.SLOT_FUEL+1, false)) { + return ItemStack.EMPTY + } + } else { + if (!this.moveItemStackTo(slotStack, 0, EnchantmentTransferrerBlockEntity.SLOT_FUEL, false)) { + return ItemStack.EMPTY + } + } + } + + if (slotStack.isEmpty) { + slot.setByPlayer(ItemStack.EMPTY) + } else { + slot.setChanged() + } + + if (slotStack.count == movedStack.count) { + return ItemStack.EMPTY + } + + slot.onTake(player, slotStack) + } + + return movedStack + } + + fun getCharge(): Int = data.get(0) + fun getProcessTime(): Int = data.get(1) + fun getTotalTime(): Int = data.get(2) + fun isProcessing(): Boolean = data.get(3) == 1 +} diff --git a/src/main/kotlin/xyz/nuark/mcmod/enct/screen/EnchantmentTransferrerScreen.kt b/src/main/kotlin/xyz/nuark/mcmod/enct/screen/EnchantmentTransferrerScreen.kt new file mode 100644 index 0000000..0516fd3 --- /dev/null +++ b/src/main/kotlin/xyz/nuark/mcmod/enct/screen/EnchantmentTransferrerScreen.kt @@ -0,0 +1,134 @@ +package xyz.nuark.mcmod.enct.screen + +import net.minecraft.client.gui.GuiGraphics +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.enct.Enchantmenttransfer +import xyz.nuark.mcmod.enct.block.entity.EnchantmentTransferrerBlockEntity +import xyz.nuark.mcmod.enct.menu.EnchantmentTransferrerMenu + +class EnchantmentTransferrerScreen( + menu: EnchantmentTransferrerMenu, + playerInventory: Inventory, + title: Component +) : AbstractContainerScreen(menu, playerInventory, title) { + + companion object { + val BACKGROUND_LOCATION: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Enchantmenttransfer.ID, "textures/gui/enchantment_transferrer.png") + val PROGRESS_GAUGE: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Enchantmenttransfer.ID, "textures/gui/ets_gauge.png") + + const val GAUGE_WIDTH = 117 + const val GAUGE_HEIGHT = 7 + } + + override fun init() { + super.init() + + imageWidth = 176 + imageHeight = 154 + } + + override fun renderBg(guiGraphics: GuiGraphics, partialTick: Float, mouseX: Int, mouseY: Int) { + guiGraphics.blit(BACKGROUND_LOCATION, leftPos, topPos, 0, 0, imageWidth, imageHeight) + + // Progress bar + if (menu.isProcessing()) { + val progress = menu.getProcessTime() + val total = menu.getTotalTime() + if (total > 0) { + val progressWidth = ((progress.toDouble() / total) * GAUGE_WIDTH).toInt() + guiGraphics.blit(PROGRESS_GAUGE, leftPos+33, topPos+5, 0f, 0f, progressWidth, GAUGE_HEIGHT, GAUGE_WIDTH, GAUGE_HEIGHT) + } + } + + // Charge bar + val charge = menu.getCharge() + val maxCharge = EnchantmentTransferrerBlockEntity.MAX_CHARGE + if (charge > 0) { + val chargeWidth = ((charge.toDouble() / maxCharge) * GAUGE_WIDTH).toInt() + guiGraphics.blit(PROGRESS_GAUGE, leftPos+33, topPos+50, 0f, 0f, chargeWidth, GAUGE_HEIGHT, GAUGE_WIDTH, GAUGE_HEIGHT) + } + } + + override fun renderLabels(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int) { + // No labels for me, thanks + } + + override fun renderTooltip(guiGraphics: GuiGraphics, x: Int, y: Int) { + val xRange = (leftPos + 33)..(leftPos + 33 + GAUGE_WIDTH) + + when (y) { + in (topPos + 5)..(topPos + 5 + GAUGE_HEIGHT) if x in xRange -> { // Over progress bar + val progress = menu.getProcessTime() + val total = menu.getTotalTime() + val processing = menu.isProcessing() + + val status = if (total > 0 && processing) { + val perc = ((progress.toDouble() / total) * 100).toInt() + "$perc%" + } else if (total > 0) { + Component.translatable("container.enct.processing.waiting").withColor(0xFFFF00) + } else { + Component.translatable("container.enct.processing.idle").withColor(0x00FFFF) + } + + guiGraphics.renderTooltip( + this.font, + Component.translatable("container.enct.processing", status), + x, + y + ) + } + in (topPos + 50)..(topPos + 50 + GAUGE_HEIGHT) if x in xRange -> { // Over charge bar + val charge = menu.getCharge() + guiGraphics.renderTooltip( + this.font, + Component.translatable("container.enct.charge", charge.toString()), + x, + y + ) + } + else -> { + val hoveredSlot = this.hoveredSlot + if (hoveredSlot != null && !hoveredSlot.hasItem()) { // Showing tooltips for empty slots + when (hoveredSlot.slotIndex) { + EnchantmentTransferrerBlockEntity.SLOT_MAIN -> guiGraphics.renderTooltip( + this.font, + Component.translatable("container.enct.info.main"), + x, + y + ) + EnchantmentTransferrerBlockEntity.SLOT_TARGET -> guiGraphics.renderTooltip( + this.font, + Component.translatable("container.enct.info.target"), + x, + y + ) + EnchantmentTransferrerBlockEntity.SLOT_BOOK -> guiGraphics.renderTooltip( + this.font, + Component.translatable("container.enct.info.book"), + x, + y + ) + EnchantmentTransferrerBlockEntity.SLOT_FUEL -> guiGraphics.renderTooltip( + this.font, + Component.translatable("container.enct.info.fuel"), + x, + y + ) + } + } else { + super.renderTooltip(guiGraphics, x, y) + } + } + } + } + + override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + renderBackground(guiGraphics, mouseX, mouseY, partialTick) + super.render(guiGraphics, mouseX, mouseY, partialTick) + renderTooltip(guiGraphics, mouseX, mouseY) + } +} diff --git a/src/main/resources/assets/enct/blockstates/enchantment_transferrer.json b/src/main/resources/assets/enct/blockstates/enchantment_transferrer.json new file mode 100644 index 0000000..2a3694c --- /dev/null +++ b/src/main/resources/assets/enct/blockstates/enchantment_transferrer.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "enct:block/enchantment_transferrer" + } + } +} diff --git a/src/main/resources/assets/enct/lang/en_us.json b/src/main/resources/assets/enct/lang/en_us.json index b79ec17..9ecbab1 100644 --- a/src/main/resources/assets/enct/lang/en_us.json +++ b/src/main/resources/assets/enct/lang/en_us.json @@ -1,5 +1,12 @@ { - "itemGroup.enct": "Example Mod Tab", - "block.enct.example_block": "Example Block", - "item.enct.example_item": "Example Item" + "block.enct.enchantment_transferrer": "Enchantment Transferrer", + "container.enct.enchantment_transferrer": "Enchantment Transferrer", + "container.enct.charge": "Charge: %s", + "container.enct.processing": "Process: %s", + "container.enct.processing.waiting": "waiting", + "container.enct.processing.idle": "idle", + "container.enct.info.main": "Item with enchantments", + "container.enct.info.target": "Item to transfer enchantments to", + "container.enct.info.book": "Optional book", + "container.enct.info.fuel": "\"Charge\" fuel: copper, iron, gold, diamonds, redstone or netherite" } diff --git a/src/main/resources/assets/enct/lang/ru_ru.json b/src/main/resources/assets/enct/lang/ru_ru.json new file mode 100644 index 0000000..92f2228 --- /dev/null +++ b/src/main/resources/assets/enct/lang/ru_ru.json @@ -0,0 +1,12 @@ +{ + "block.enct.enchantment_transferrer": "Переносчик зачарований", + "container.enct.enchantment_transferrer": "Переносчик зачарований", + "container.enct.charge": "Заряд: %s", + "container.enct.processing": "Процесс: %s", + "container.enct.processing.waiting": "ждём", + "container.enct.processing.idle": "спим", + "container.enct.info.main": "Зачарованный предмет", + "container.enct.info.target": "Цель для переноса", + "container.enct.info.book": "Опциональная книга", + "container.enct.info.fuel": "Топливо для \"заряда\": медь, железо, золото, алмазы, редстоун or незерит" +} diff --git a/src/main/resources/assets/enct/models/block/enchantment_transferrer.json b/src/main/resources/assets/enct/models/block/enchantment_transferrer.json new file mode 100644 index 0000000..ed0a826 --- /dev/null +++ b/src/main/resources/assets/enct/models/block/enchantment_transferrer.json @@ -0,0 +1,12 @@ +{ + "parent": "block/cube", + "textures": { + "down": "enct:block/enchantment_transferrer_bottom", + "up": "enct:block/enchantment_transferrer_top", + "north": "enct:block/enchantment_transferrer_side", + "east": "enct:block/enchantment_transferrer_side", + "south": "enct:block/enchantment_transferrer_side", + "west": "enct:block/enchantment_transferrer_side", + "particle": "enct:block/enchantment_transferrer_top" + } +} diff --git a/src/main/resources/assets/enct/models/item/enchantment_transferrer.json b/src/main/resources/assets/enct/models/item/enchantment_transferrer.json new file mode 100644 index 0000000..3f9e9d8 --- /dev/null +++ b/src/main/resources/assets/enct/models/item/enchantment_transferrer.json @@ -0,0 +1,3 @@ +{ + "parent": "enct:block/enchantment_transferrer" +} diff --git a/src/main/resources/assets/enct/textures/block/enchantment_transferrer_bottom.png b/src/main/resources/assets/enct/textures/block/enchantment_transferrer_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..537b019aa87a00b86c976f30e5e3825767096c51 GIT binary patch literal 1866 zcmah}4NMbf7(RvuL10X$GN*E$f1quzKepPlrIxmoPKt#V3fpiVy=!~Y_G<5-g((bV zic_;m6cfOofj`JpFixg%&I}bzHYIHQ=w_yK(>b%~ROXgt(S27c!sc9)%k_KS=Y78S zp7;CS^`;DcOw{Bk2!dh^Y1%CCj^my&;Sdz|)?C$D@OqolXiNr;1~&OA# z(O!8JPJ9pUE7_1T*G$oJ>@(p)%hTBcNqDv}f@>-u@$O}u0Iy8GC# z``fJBBBHLoUV5OTYvR7Sf02g|Ri*A}m*Ljilg^bLuIoF}ktN^Z+Pmw~r{`LBOE=e# z-QfS?b|-vhl={tdQD@?s0ST`-E+OnsLcb|cE-$N{us-|jikG|dX6xz{%QDB-D5obU zm8RI&HugR~wOU#eUh&$)KKRnk^alqcCV#T)W2$cFy1jW=gxH*Qytvlg>wTN*^*8?Gsn!t~rJe#OkodBdFy=i$21dScfJz-E?Lp%hHs-P^n zY_2gKv(at=PS6FUAmH`_HUueD0WWSVBw4tCbWk28a`(h31f~cjVwD<&Mz4l+QfZ|= z(p;Khv6U9us_51w-zeGU$9H>Yxm!m>4Di-qrgU^(BSUkY@ zFf#^4LNZ#CvH2)3OVJ*flf?_@B36lj3WBAvl>Mo=hY1+~tU$R4DiR2}VuMsNK>Y`m zi;SRpois}`PTD(E_Xz2s@Q5Vf*|X^Z-1|?W(fE9-+dWh^!|IB`2Z#I{QO6Tu#!})X z(JYdoi+naxS4?`?83@c7fEnDlgG?tq4%VqeM6%(il_ExfT)wM_GdkNtfgQyma_-ZB z5?LTdMIk)z-#BL^W}*V5D_2XoNe=_W2Dg10$$M5rL%V3785~v;&=GjZU@RCg(geUI z!*MXzLSs#dFj@K(*r3zML~@yoFBSj+&MVM&e zJmCUNC=5}-p(bz^|37l@bi?4M02>&MSQ^E!w6A0c4xL#uxpEj30$mgwY$b-I78c6Kkg&d4$wa(qPE*9(1#LOA`pU$6(aeaBuIVE=b*!OR-oLC8D5P7 zeOBRvykR#js4<9wYYbK(@5xNyRzq`z^nhXF15-oMysrREob3j!+7fRyE{%*m4M$71 z#DCRs>+s`KMHgE#HdGO-M!&Q1m)z)g&%aW&Sz_8sygH>%-KS~Kx^fJUyqvV=_`xf) zSG2xy*A3rjFyrQEU2Vk+FBOMx$-Xv6biMVnrkYi~C5`&7Zoy3E_r|8O%o(-eNnr~l zZOv$EerHR&cX#XQ8`bB_^>G=8KKi+Q<#zLn&0Pb@^?mL4zARhT|Jx%7-xGn3sy#B5 Pn~erthW1d(^2)yfpmUFe literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/enct/textures/block/enchantment_transferrer_side.png b/src/main/resources/assets/enct/textures/block/enchantment_transferrer_side.png new file mode 100644 index 0000000000000000000000000000000000000000..83c9759715fa945b0c03d56b4d255e6bb182fca4 GIT binary patch literal 2155 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 P>km&7u|`}kOfC2y%>`Qk literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/enct/textures/gui/enchantment_transferrer.png b/src/main/resources/assets/enct/textures/gui/enchantment_transferrer.png new file mode 100644 index 0000000000000000000000000000000000000000..834190577e060a0aaf4817232ceae84476128692 GIT binary patch literal 4570 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/enct/textures/gui/ets_gauge.png b/src/main/resources/assets/enct/textures/gui/ets_gauge.png new file mode 100644 index 0000000000000000000000000000000000000000..d667567bdffbe3d72f8289d3e50dd53abe28c553 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^r9jNi!py+HxcRvLWgv&KILO_JVcj{Imp~3nx}&cn z1H;CC?mvmFK)z^zPl&5k^+vCy$MPS(42p}HzI;hzcV}ir*#_+c-arMM1s;*b3=DjS zL74G){)!Z!V4$aqV~E7%1exB{0rGfTL{d|+T?a|`*a z;V_}GQEd6O!=oQbFFw1QL;($F N@O1TaS?83{1OUApMBM-Y literal 0 HcmV?d00001