Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 20 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,28 @@
# JouTak Mod Template (Java Edition)
# RouteRecorder
Сделан на основе JouTak Mod Template (Java Edition) в качестве [тестового задания](https://github.com/JouTak/TemplateMod-Java/issues/4).

Заготовка для мода на JouTak (ДжоуТек) ([joutak.ru](https://joutak.ru))
**Minecraft 26.1.2**, **Fabric** и **Java 25**.

Шаблон рассчитан на **Minecraft 26.1.2**, **Fabric** и **Java 25**.
Клиентский мод: записывает маршрут и создаёт вейпоинты в виде стрелочек в месте, где прошёл игрок.
По умолчанию запись включается и выключается на кнопку C, запись можно поставить на паузу с помощью кнопки V.

После завершения записи нажми R, чтобы включить возврат по маршруту. Повторное нажатие R останавливает возврат.
Иди по стрелке на HUD: она показывает направление к текущей точке, расстояние и количество оставшихся точек.
При приближении на 1,5 блока цель переключается на предыдущую точку записи. Высота тоже учитывается.
У начала маршрута возврат завершается, HUD скрывается, а запись сохраняется для повторного использования.

# Требования
P выводит список записанных точек в чат. Delete полностью очищает маршрут и останавливает запись и возврат.
Все клавиши можно переназначить в настройках управления.
Новая запись заменяет старую. При смерти, выходе из мира или смене измерения маршрут очищается.

- **JDK 25** для Gradle JVM и компиляции мода
- **IntelliJ IDEA 2025.3** или новее
Интервал записи можно поменять в `config/PathTrackMod-config.json`, параметр `recordingIntervalTicks`.
По умолчанию точка сохраняется каждые 40 тиков, если игрок сдвинулся с предыдущей точки. Минимальный интервал — 1 тик.
После изменения конфига нужно перезапустить игру.

После открытия проекта выберите JDK 25 в `Settings` → `Build, Execution, Deployment` → `Build Tools` → `Gradle` → `Gradle JVM`. Без Java 25 Gradle и конфигурация `[Dev] Run Client` не запустятся.
Для установки нужны Fabric Loader и Fabric API. Mod Menu, Cloth Config и LambDynamicLights ставятся по желанию:

Gradle 9.5.1 уже включён через Gradle Wrapper, поэтому устанавливать Gradle отдельно не нужно. Для Minecraft 26.1.2 шаблон использует non-remapping Loom: зависимости Fabric подключаются через `implementation`, а конфигурация `mappings` отсутствует.
- [Mod Menu](https://modrinth.com/mod/modmenu) вместе с [Cloth Config](https://modrinth.com/mod/cloth-config) добавляют экран настройки интервала записи.
- [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) добавляет свет от точек маршрута.


# Перед использованием

1. Отредактируйте:
- `gradle.properties` — укажите **версию** и **название** мода, а также **ссылку на репозиторий** с вашим модом
- `src/main/java/ru/joutak/` — переименуйте **основной класс** под название вашего мода
- `src/main/resources/fabric.mod.json` — укажите **правильный путь** до основного класса мода и укажите себя как **автора**
- `gradle/libs.versions.toml` — измените **версии** **JDK**, **Minecraft**, **Fabric** и **других библиотек** по необходимости; при переходе на Minecraft `1.21.11` или старше также потребуется вернуть remapping-конфигурацию Loom
- Укажите **полный путь до папки `mods/`** тестового клиента Minecraft в переменной окружения системы `MODS_PATH`

> [!TIP]
> Указав путь до папки `mods/` тестового клиента, можно собирать мод с помощью конфигурации `[Snapshot] Build`, тогда jar-архив с вашим модом сразу будет создаваться в нужной папке автоматически.


# Сборка мода

1. Открыть проект в **IntelliJ IDEA 2025.3** или новее и дождаться завершения импорта Gradle
2. Нажать кнопку `Run` справа сверху для сборки:
- `[Dev] Run Client` — запускает Minecraft-клиент прямо из IDE с загруженным модом (для разработки)
- `[Snapshot]` — создает архив с модом в папке `mods/` тестового клиента (путь до которого нужно указать в переменной окружения `MODS_PATH`)
- `[Release]` — создает архив с модом в папке `build/` проекта


# Перед началом выполнения тестового задания

> [!NOTE]
> Данный параграф предназначен для тех, кто захотел попробовать себя в роли разработчика модов для Minecraft в составе команды JouTak.

Базовое описание, что нужно сделать:
1) Сделать собственный **fork** данного репозитория
2) Склонировать созданный репозиторий себе по **SSH** (`git clone`)
3) Открыть проект в любой удобной для вас **IDE**

> [!TIP]
> Рекомендуем использовать **IntelliJ IDEA**, так как в ней есть полноценная поддержка **Java** со всеми фишками и плюшками, но выбор за вами

4) Разобраться с расположением папочек и синтаксисом **Java**, затем прописать базовую логику, которая была описана в вашем задании в issues (Любая креативность поощряется)
5) Собрать мод с помощью **Gradle**

> [!TIP]
> Конфиги уже заранее прописаны (один для разработки, один для тестовых целей и один для полноценного релиза), вам самим ничего прописывать не надо. Для разработки лучше использовать `[Dev] Run Client`, а для тестов на реальном клиенте — `[Snapshot]` версию, чтобы каждый раз не перекидывать вручную мод в папку клиента, главное не забудьте создать переменную окружения `MODS_PATH`!

6) Запустить свой мод через конфигурацию `[Dev] Run Client` или подключить к своему локальному Minecraft-клиенту
7) Проверить, что мод работает исправно
8) Отправить **pull request** на **GitHub** и дождаться ответа

> [!TIP]
> Если долго никто не отвечает, то можно пингануть кого-нибудь, чтобы реквест отсмотрели и приняли :)

Если мод написан без базовых ошибок (синтаксических и т. п.), то на **GitHub**'е автоматически соберется такой же jar-архив, который будет так же работать на твоем локальном Minecraft-клиенте.

Поначалу всё можно писать в основном классе, но потом все вещи по типу команд, обработчиков событий и различной логики нужно выделить в отдельные классы и пакеты.

> [!IMPORTANT]
> Главное, если есть какие-то вопросы — не стесняйся спрашивать!
Для сборки нужен JDK 25. В корне проекта выполни `./gradlew build` (на Windows — `.\gradlew.bat build`).
Готовый JAR появится в `build/libs/`.
30 changes: 30 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,42 @@ val javaVersion = libs.versions.jdk.get().toInt()
repositories {
mavenCentral()
maven("https://maven.fabricmc.net/") { name = "Fabric" }
maven("https://maven.terraformersmc.com/releases/") {
name = "TerraformersMC"
}

maven("https://maven.shedaniel.me/") {
name = "Shedaniel"
}
maven("https://maven.gegy.dev") {
name = "Gegy"
}
}

dependencies {
minecraft("com.mojang:minecraft:${libs.versions.minecraft.get()}")
implementation("net.fabricmc:fabric-loader:${libs.versions.fabric.loader.get()}")
implementation(libs.fabric.api)
compileOnly(libs.modmenu) {
exclude(group = "net.fabricmc.fabric-api")
}

compileOnly(libs.cloth.config) {
exclude(group = "net.fabricmc.fabric-api")
}

localRuntime(libs.modmenu) {
exclude(group = "net.fabricmc.fabric-api")
}

localRuntime(libs.cloth.config) {
exclude(group = "net.fabricmc.fabric-api")
}
compileOnly(libs.lambdynamiclights.api)

localRuntime(libs.lambdynamiclights.runtime) {
exclude(group = "net.fabricmc.fabric-api")
}
}

java {
Expand Down
5 changes: 3 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Mod properties
repo=https://github.com/JouTak/TemplateMod-Java
repo=https://github.com/UrSick/TemplateMod-Java-WeNeedFood
group=ru.joutak
mod_name=TemplateMod-Java
mod_id=templatemod
version=1.0.0
version=1.5

# Gradle properties
org.gradle.parallel=true
org.gradle.configuration-cache=true
7 changes: 7 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ minecraft = "26.1.2"
fabric-loader = "0.19.5"
fabric-api = "0.155.2+26.1.2"
loom = "1.17.20"
modmenu = "18.0.0"
cloth-config = "26.1.154"
lambdynamiclights = "4.11.2+26.1.2"

[libraries]
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
cloth-config = { module = "me.shedaniel.cloth:cloth-config-fabric", version.ref = "cloth-config" }
lambdynamiclights-api = { module = "dev.lambdaurora.lambdynamiclights:lambdynamiclights-api", version.ref = "lambdynamiclights" }
lambdynamiclights-runtime = { module = "dev.lambdaurora.lambdynamiclights:lambdynamiclights-runtime", version.ref = "lambdynamiclights" }

[plugins]
fabric-loom = { id = "net.fabricmc.fabric-loom", version.ref = "loom" }
2 changes: 1 addition & 1 deletion src/main/java/ru/joutak/templatemod/TemplateMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public static TemplateMod getInstance() {
@Override
public void onInitialize() {
instance = this;
LOGGER.info("Mod {} initialized!", MOD_ID);
LOGGER.info("Мод {} загружен", MOD_ID);
}
}
41 changes: 41 additions & 0 deletions src/main/java/ru/joutak/templatemod/client/RecordingHud.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru.joutak.templatemod.client;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.resources.Identifier;
import ru.joutak.templatemod.TemplateMod;
import ru.joutak.templatemod.client.RoutRecorder.RecordingState;
public class RecordingHud {
private static final Identifier RECORDING_TEXTURE =
Identifier.fromNamespaceAndPath(
TemplateMod.MOD_ID,
"textures/recording.png"
);
private static final Identifier PAUSED_TEXTURE =
Identifier.fromNamespaceAndPath(
TemplateMod.MOD_ID,
"textures/pause.png"
);
public static void draw(GuiGraphicsExtractor graphics, RecordingState state) {
if (state == RecordingState.STOPPED) {
return;
}

Identifier texture;

if (state == RecordingState.RECORDING) {
texture = RECORDING_TEXTURE;
} else {
texture = PAUSED_TEXTURE;
}

graphics.blit(
RenderPipelines.GUI_TEXTURED,
texture,
8, graphics.guiHeight() - 24,
0f, 0f,
16, 16,
32, 32,
32, 32
);
}
}
49 changes: 49 additions & 0 deletions src/main/java/ru/joutak/templatemod/client/ReturnHUD.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ru.joutak.templatemod.client;

import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import ru.joutak.templatemod.TemplateMod;

import java.util.Locale;

public class ReturnHUD {
private static final Identifier ARROW_TEXTURE = Identifier.fromNamespaceAndPath(
TemplateMod.MOD_ID, "textures/waypoint.png"
);

public static void draw(GuiGraphicsExtractor graphics, float angleDegrees, double distance, int remainingPoints) {
var matrices = graphics.pose();

matrices.pushMatrix();
matrices.translate(40f, graphics.guiHeight() - 56f);
matrices.rotate((float) Math.toRadians(angleDegrees + 180f));

graphics.blit(
RenderPipelines.GUI_TEXTURED,
ARROW_TEXTURE,
-16, -16,
0f, 0f,
32, 32,
32, 32,
32, 32
);

matrices.popMatrix();

graphics.text(
Minecraft.getInstance().font,
Component.translatable("hud.templatemod.return_distance", String.format(Locale.ROOT, "%.1f", distance)),
8, graphics.guiHeight() - 36,
0xFFFFFFFF
);
graphics.text(
Minecraft.getInstance().font,
Component.translatable("hud.templatemod.return_progress", remainingPoints),
8, graphics.guiHeight() - 24,
0xFFFFFFFF
);
}
}
16 changes: 16 additions & 0 deletions src/main/java/ru/joutak/templatemod/client/RoutLighting.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ru.joutak.templatemod.client;
import net.minecraft.world.phys.Vec3;
import java.util.List;
import java.util.function.Consumer;

public class RoutLighting {
private static Consumer<List<Vec3>> updater = points -> {};

public static void setUpdater(Consumer<List<Vec3>> newUpdater) {
updater = newUpdater;
}

public static void update(List<Vec3> points) {
updater.accept(points);
}
}
66 changes: 66 additions & 0 deletions src/main/java/ru/joutak/templatemod/client/RoutRecorder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ru.joutak.templatemod.client;
import net.minecraft.world.phys.Vec3;
import java.util.ArrayList;
import java.util.List;

public class RoutRecorder {
private final int recordingIntervalTicks;
public RoutRecorder(int recordingIntervalTicks) {
this.recordingIntervalTicks = recordingIntervalTicks;
}
public enum RecordingState {
STOPPED,
RECORDING,
PAUSED
}
public void start(Vec3 position) {
points.clear();
points.add(position);
this.state = RecordingState.RECORDING;
this.ticksSinceLastPoint = 0;
}

public void tick(Vec3 position) {
if (this.state != RecordingState.RECORDING) {
return;
}
this.ticksSinceLastPoint ++;
if (this.ticksSinceLastPoint >= this.recordingIntervalTicks) {
if (!points.getLast().equals(position)) {
points.add(position);
}
this.ticksSinceLastPoint = 0;
}
}
public void toggleRecording(Vec3 position) {
if (this.state == RecordingState.STOPPED) {
start(position);
}
else if (this.state == RecordingState.RECORDING || this.state == RecordingState.PAUSED) {
this.state = RecordingState.STOPPED;
this.ticksSinceLastPoint = 0;
}
}
public void togglePause() {
if (this.state == RecordingState.RECORDING) {
this.state = RecordingState.PAUSED;
} else if (this.state == RecordingState.PAUSED) {
this.state = RecordingState.RECORDING;
}
}
public void clear() {
this.points.clear();
this.state = RecordingState.STOPPED;
this.ticksSinceLastPoint = 0;
}
public List<Vec3> getPoints() {
return List.copyOf(points);
}
public RecordingState getState() {
return state;
}
private RecordingState state = RecordingState.STOPPED;
private int ticksSinceLastPoint = 0;
private ArrayList<Vec3> points = new ArrayList<>();

}
Loading
Loading