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 0000000..537b019 Binary files /dev/null and b/src/main/resources/assets/enct/textures/block/enchantment_transferrer_bottom.png differ 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 0000000..83c9759 Binary files /dev/null and b/src/main/resources/assets/enct/textures/block/enchantment_transferrer_side.png differ diff --git a/src/main/resources/assets/enct/textures/block/enchantment_transferrer_top.png b/src/main/resources/assets/enct/textures/block/enchantment_transferrer_top.png new file mode 100644 index 0000000..788d455 Binary files /dev/null and b/src/main/resources/assets/enct/textures/block/enchantment_transferrer_top.png differ 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 0000000..8341905 Binary files /dev/null and b/src/main/resources/assets/enct/textures/gui/enchantment_transferrer.png differ 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 0000000..d667567 Binary files /dev/null and b/src/main/resources/assets/enct/textures/gui/ets_gauge.png differ