From 51654a4f53626ed422003e482afa6f4f232da4e0 Mon Sep 17 00:00:00 2001 From: UrSick Date: Sun, 6 Sep 2026 19:56:34 +0300 Subject: [PATCH 01/13] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B.=20=D0=A1=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81,=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B0=20=D0=BD=D0=B0=D0=B6=D0=B0=D1=82=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 3 ++- .../templatemod/client/RoutRecorder.java | 21 +++++++++++++++ .../templatemod/client/TemplateModClient.java | 26 ++++++++++++++++++- .../assets/templatemod/lang/en_us.json | 4 +++ src/main/resources/fabric.mod.json | 4 +-- 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/joutak/templatemod/client/RoutRecorder.java create mode 100644 src/main/resources/assets/templatemod/lang/en_us.json diff --git a/gradle.properties b/gradle.properties index 75dbe5b..c2e5709 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,8 @@ repo=https://github.com/JouTak/TemplateMod-Java group=ru.joutak mod_name=TemplateMod-Java mod_id=templatemod -version=1.0.0 +version=0.0.1 # Gradle properties org.gradle.parallel=true +org.gradle.configuration-cache=true \ No newline at end of file diff --git a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java new file mode 100644 index 0000000..29577bc --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java @@ -0,0 +1,21 @@ +package ru.joutak.templatemod.client; +import net.minecraft.world.phys.Vec3; +import java.util.ArrayList; + +public class RoutRecorder { + enum RecordingState { + STOPPED, + RECORDING, + PAUSED + } + public void start(Vec3 position) { + points.clear(); + points.add(position); + this.state = RecordingState.RECORDING; + this.ticksSinceLastPoint = 0; + } + private RecordingState state = RecordingState.STOPPED; + private int ticksSinceLastPoint = 0; + private ArrayList points = new ArrayList<>(); + +} diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index 0a91541..2f7228a 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -2,11 +2,35 @@ import net.fabricmc.api.ClientModInitializer; import ru.joutak.templatemod.TemplateMod; +import net.minecraft.client.KeyMapping; +import net.minecraft.resources.Identifier; +import com.mojang.blaze3d.platform.InputConstants; +import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.minecraft.network.chat.Component; public class TemplateModClient implements ClientModInitializer { @Override public void onInitializeClient() { - TemplateMod.LOGGER.info("Client side of mod {} initialized!", TemplateMod.MOD_ID); + TemplateMod.LOGGER.info("Mod runned", TemplateMod.MOD_ID); + ClientTickEvents.END_CLIENT_TICK.register(client -> { + while (this.StartRecording.consumeClick()) { + if (client.player != null) { + client.player.sendSystemMessage(Component.literal("Запись начата!")); + } + } + }); } + + KeyMapping.Category Pathtrack = KeyMapping.Category.register( + Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "controls") + ); + KeyMapping StartRecording = KeyMappingHelper.registerKeyMapping( + new KeyMapping( + "key.PathtrackMod.start-recording", + InputConstants.Type.KEYSYM, + InputConstants.KEY_C, + this.Pathtrack + )); } diff --git a/src/main/resources/assets/templatemod/lang/en_us.json b/src/main/resources/assets/templatemod/lang/en_us.json new file mode 100644 index 0000000..a4b5ba2 --- /dev/null +++ b/src/main/resources/assets/templatemod/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "key.category.templatemod.controls": "PathTrack", + "key.PathtrackMod.start-recording": "Start Recording" +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2be069a..e037854 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -3,9 +3,9 @@ "id": "${MOD_ID}", "version": "${VERSION}", "name": "${MOD_NAME}", - "description": "Заготовка для мода на JouTak (joutak.ru)", + "description": "Мод для сохранения пути", "authors": [ - "JouTak" + "WeNeedFood" ], "contact": { "homepage": "https://joutak.ru", From f4a9739bf546ee2a6eb2dd1db371f8b23443b087 Mon Sep 17 00:00:00 2001 From: UrSick Date: Sun, 6 Sep 2026 22:47:01 +0300 Subject: [PATCH 02/13] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=BE/=D0=B7=D0=B0=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B8=20=D0=BD=D0=B0=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D1=83=20C=20=D0=B8=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=BC?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=B8=D0=B2=D0=B0=20=D1=81=20=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=BD=D0=B0=20=D0=BA=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D0=BA=D1=83=20P.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../templatemod/client/RoutRecorder.java | 31 +++++++++++++++++- .../templatemod/client/TemplateModClient.java | 32 ++++++++++++++++++- .../assets/templatemod/lang/en_us.json | 3 +- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index c2e5709..4d967d1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ repo=https://github.com/JouTak/TemplateMod-Java group=ru.joutak mod_name=TemplateMod-Java mod_id=templatemod -version=0.0.1 +version=0.1 # Gradle properties org.gradle.parallel=true diff --git a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java index 29577bc..5604e7e 100644 --- a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java +++ b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java @@ -1,9 +1,10 @@ package ru.joutak.templatemod.client; import net.minecraft.world.phys.Vec3; import java.util.ArrayList; +import java.util.List; public class RoutRecorder { - enum RecordingState { + public enum RecordingState { STOPPED, RECORDING, PAUSED @@ -14,6 +15,34 @@ public void start(Vec3 position) { this.state = RecordingState.RECORDING; this.ticksSinceLastPoint = 0; } + + public void tick(Vec3 position) { + if (this.state != RecordingState.RECORDING) { + return; + } + this.ticksSinceLastPoint ++; + if (this.ticksSinceLastPoint >= 20) { + 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 List getPoints() { + return List.copyOf(points); + } + public RecordingState getState() { + return state; + } private RecordingState state = RecordingState.STOPPED; private int ticksSinceLastPoint = 0; private ArrayList points = new ArrayList<>(); diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index 2f7228a..cceec04 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -1,6 +1,8 @@ package ru.joutak.templatemod.client; import net.fabricmc.api.ClientModInitializer; +import net.minecraft.core.BlockPos; +import net.minecraft.world.phys.Vec3; import ru.joutak.templatemod.TemplateMod; import net.minecraft.client.KeyMapping; import net.minecraft.resources.Identifier; @@ -8,6 +10,7 @@ import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.minecraft.network.chat.Component; +import ru.joutak.templatemod.client.RoutRecorder.RecordingState; public class TemplateModClient implements ClientModInitializer { @@ -15,13 +18,34 @@ public class TemplateModClient implements ClientModInitializer { public void onInitializeClient() { TemplateMod.LOGGER.info("Mod runned", TemplateMod.MOD_ID); ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (client.player == null || client.isPaused()) { + return; + } while (this.StartRecording.consumeClick()) { if (client.player != null) { - client.player.sendSystemMessage(Component.literal("Запись начата!")); + if (recorder.getState() == RecordingState.STOPPED ) { + client.player.sendSystemMessage(Component.literal("Запись начата!")); + } + else if (recorder.getState() == RecordingState.RECORDING) { + client.player.sendSystemMessage(Component.literal("Запись завершена")); + } + } + recorder.toggleRecording(client.player.position()); + } + while (this.ShowList.consumeClick()) { + if (recorder.getPoints().isEmpty()){ + client.player.sendSystemMessage(Component.literal("Список пуст")); + } + else { + for (Vec3 pos : recorder.getPoints()){ + client.player.sendSystemMessage(Component.literal(pos.toString())); + } } } + recorder.tick(client.player.position()); }); } + private final RoutRecorder recorder = new RoutRecorder(); KeyMapping.Category Pathtrack = KeyMapping.Category.register( Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "controls") @@ -33,4 +57,10 @@ public void onInitializeClient() { InputConstants.KEY_C, this.Pathtrack )); + KeyMapping ShowList = KeyMappingHelper.registerKeyMapping( + new KeyMapping("key.templatemod.show_list", + InputConstants.Type.KEYSYM, + InputConstants.KEY_P, + this.Pathtrack + )); } diff --git a/src/main/resources/assets/templatemod/lang/en_us.json b/src/main/resources/assets/templatemod/lang/en_us.json index a4b5ba2..3828a64 100644 --- a/src/main/resources/assets/templatemod/lang/en_us.json +++ b/src/main/resources/assets/templatemod/lang/en_us.json @@ -1,4 +1,5 @@ { "key.category.templatemod.controls": "PathTrack", - "key.PathtrackMod.start-recording": "Start Recording" + "key.PathtrackMod.start-recording": "Start Recording", + "key.templatemod.show_list": "Show route points" } \ No newline at end of file From 6fac8a4f3b78f0772d9b6670b835ce4a4939628d Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 7 Sep 2026 12:21:38 +0300 Subject: [PATCH 03/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=80=D0=B5=D0=BD=D0=B4=D0=B5=D1=80=20=D0=B2=D0=B5?= =?UTF-8?q?=D0=B9=D0=BF=D0=BE=D0=B9=D0=BD=D1=82=D0=BE=D0=B2=20=D0=B2=20?= =?UTF-8?q?=D0=B2=D0=B8=D0=B4=D0=B5=20=D0=B1=D0=B8=D0=BB=D0=BB=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=B4=D0=BE=D0=B2.=20=D0=91=D0=B0=D0=B3=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=20=D0=BD=D0=B5=20=D1=84=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B8=20=D1=82=D0=B4,=20=D0=BD=D0=BE=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=20=D0=BC=D0=BE=D0=B4=20=D0=B3?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templatemod/client/RouteMarkers.java | 58 ++++++++++++++++++ .../templatemod/client/TemplateModClient.java | 5 ++ .../assets/templatemod/textures/waypoint.png | Bin 0 -> 359 bytes 3 files changed, 63 insertions(+) create mode 100644 src/main/java/ru/joutak/templatemod/client/RouteMarkers.java create mode 100644 src/main/resources/assets/templatemod/textures/waypoint.png diff --git a/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java b/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java new file mode 100644 index 0000000..7149324 --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java @@ -0,0 +1,58 @@ +package ru.joutak.templatemod.client; +import java.util.List; +import net.minecraft.resources.Identifier; +import org.joml.Vector3f; +import ru.joutak.templatemod.TemplateMod; +import net.minecraft.world.phys.Vec3; +import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.renderer.rendertype.RenderTypes; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.util.LightCoordsUtil; + +public class RouteMarkers { + private static final Identifier TEXTURE = + Identifier.fromNamespaceAndPath( + TemplateMod.MOD_ID, "textures/waypoint.png" + ); + private static List renderPoints = List.of(); + public static void extract(List points) { + renderPoints = List.copyOf(points); + } + public static void draw(LevelRenderContext context) { + Vec3 camera = context.levelState().cameraRenderState.pos; + PoseStack matrices = context.poseStack(); + for (Vec3 point : renderPoints) { + double x = point.x - camera.x; + double y = point.y - camera.y; + double z = point.z - camera.z; + matrices.pushPose(); + matrices.translate(x, y+0.5, z); + matrices.mulPose(context.levelState().cameraRenderState.orientation); + + context.submitNodeCollector().submitCustomGeometry( + matrices, + RenderTypes.entityTranslucentEmissive(TEXTURE), + (pose, buffer) -> { + vertex(buffer, pose, -0.25f, -0.25f, 0f, 1f); + vertex(buffer, pose, 0.25f, -0.25f, 1f, 1f); + vertex(buffer, pose, 0.25f, 0.25f, 1f, 0f); + vertex(buffer, pose, -0.25f, 0.25f, 0f, 0f); + + } + ); + matrices.popPose(); + } + } + private static void vertex( + VertexConsumer buffer, PoseStack.Pose pose, float x, float y, float u, float v + ) { + buffer.addVertex(pose,x,y,0f) + .setColor(255,255,255,255) + .setUv(u,v) + .setOverlay(OverlayTexture.NO_OVERLAY) + .setLight(LightCoordsUtil.FULL_BRIGHT) + .setNormal(pose,0.0F,0.0F, 1f); + } +} diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index cceec04..0fbef33 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -11,6 +11,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.minecraft.network.chat.Component; import ru.joutak.templatemod.client.RoutRecorder.RecordingState; +import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents; public class TemplateModClient implements ClientModInitializer { @@ -44,6 +45,10 @@ else if (recorder.getState() == RecordingState.RECORDING) { } recorder.tick(client.player.position()); }); + LevelRenderEvents.END_EXTRACTION.register(context -> { + RouteMarkers.extract(recorder.getPoints()); + }); + LevelRenderEvents.COLLECT_SUBMITS.register(RouteMarkers::draw); } private final RoutRecorder recorder = new RoutRecorder(); diff --git a/src/main/resources/assets/templatemod/textures/waypoint.png b/src/main/resources/assets/templatemod/textures/waypoint.png new file mode 100644 index 0000000000000000000000000000000000000000..81104d1386d7e98e7aedae1c77c8c3325839f0ae GIT binary patch literal 359 zcmV-t0hs=YP)%d$*T@vC4P{bxzUc2Mf}xIMLN z0bm&xr&(9$1a0pv0HE(Sc7B{10Khu0>X=myz%O(GTmVG?^OZDWQ3m>MGc}&CI`@>*N*lKZ_EYXGY2&T)`h6TzI>Vagq}g8^i{p%Kv z_ZPFRO8fCOPR&j#QiNEUMw00&_;Hv{+(B#Su!ya2^l+p|rji Date: Mon, 7 Sep 2026 12:50:19 +0300 Subject: [PATCH 04/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=BE=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BA=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=BE=D0=B8=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../java/ru/joutak/templatemod/client/RouteMarkers.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4d967d1..ec9fcee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ repo=https://github.com/JouTak/TemplateMod-Java group=ru.joutak mod_name=TemplateMod-Java mod_id=templatemod -version=0.1 +version=1.1 # Gradle properties org.gradle.parallel=true diff --git a/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java b/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java index 7149324..98b6358 100644 --- a/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java +++ b/src/main/java/ru/joutak/templatemod/client/RouteMarkers.java @@ -10,6 +10,7 @@ import net.minecraft.client.renderer.rendertype.RenderTypes; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.util.LightCoordsUtil; +import net.minecraft.util.Util; public class RouteMarkers { private static final Identifier TEXTURE = @@ -17,8 +18,13 @@ public class RouteMarkers { TemplateMod.MOD_ID, "textures/waypoint.png" ); private static List renderPoints = List.of(); + private static double bobOffset = 0.0; public static void extract(List points) { renderPoints = List.copyOf(points); + double seconds = Util.getMillis() / 1000.0; + double period = 4.0; + double amplitude = 0.1; + bobOffset = Math.sin(seconds * 2 * Math.PI / period) * amplitude; } public static void draw(LevelRenderContext context) { Vec3 camera = context.levelState().cameraRenderState.pos; @@ -28,7 +34,7 @@ public static void draw(LevelRenderContext context) { double y = point.y - camera.y; double z = point.z - camera.z; matrices.pushPose(); - matrices.translate(x, y+0.5, z); + matrices.translate(x, y + 0.5 + bobOffset, z); matrices.mulPose(context.levelState().cameraRenderState.orientation); context.submitNodeCollector().submitCustomGeometry( From 013197eb3999b2a44199e1c770b1f698c5e81cec Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 7 Sep 2026 12:57:52 +0300 Subject: [PATCH 05/13] =?UTF-8?q?=D1=82=D0=B5=D0=BA=D1=81=D1=82=D1=83?= =?UTF-8?q?=D1=80=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/templatemod/textures/waypoint.png | Bin 359 -> 457 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/assets/templatemod/textures/waypoint.png b/src/main/resources/assets/templatemod/textures/waypoint.png index 81104d1386d7e98e7aedae1c77c8c3325839f0ae..d5fbfd66033e9c0224c6297a765cba73e7562194 100644 GIT binary patch delta 431 zcmV;g0Z{(u0?7lAB!7@eL_t(oh3%HBa>FnXMXxd;VJ-_a49pa1Ca5_6ihfwWBE?3X zG0GSg8kV)pFa_-<@@6HkR(+6OByg&4AL5*-g`mA_BGUV+XbKoljB%TQ#lD40}-out@$iK3W66I z@7I{C03-;~Zv9zg{9w|Ws{tekGA9v4j(HY93c_lh4JaT%kSyjV013isZUzV;NEUMd Z_yELz{hITGe!Tzy002ovPDHLkV1nOL#LWNz delta 332 zcmV-S0ki(e1Lp#eB!4bRL_t(oh3%HH5rZ%cMZY;3wqS%@Du!T+48{};LB%lwTTmuV z6dWVVvP@F(t6&@bXGz3%Q0n%$J+*5AU>O#tSy$%-ZSO4rpzk(zew-Qrz&fw$m{ktI zFLVK107U@f)O*N*lKZ_EYXG zY2&T)`h6TzI>Vagq}g8^i{p%Kv_ZPFRO Date: Mon, 7 Sep 2026 17:07:02 +0300 Subject: [PATCH 06/13] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D1=83=20=D0=BA=20mod?= =?UTF-8?q?menu=20=D0=B8=20modconfigscreen,=20=D0=B2=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D0=B5=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8F=D1=82=D1=8C=20=D1=82=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8,=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B8=D0=BA=D0=BE=D0=BD=D0=BA=D0=B0=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 22 +++++++ gradle/libs.versions.toml | 4 ++ .../templatemod/client/RoutRecorder.java | 6 +- .../templatemod/client/TemplateModClient.java | 10 ++- .../templatemod/compat/ModConfigScreen.java | 40 ++++++++++++ .../compat/ModMenuIntegration.java | 16 +++++ .../templatemod/config/ConfigLoader.java | 60 ++++++++++++++++++ .../joutak/templatemod/config/ModConfig.java | 16 +++++ .../assets/templatemod/textures/icon.png | Bin 0 -> 1173 bytes src/main/resources/fabric.mod.json | 6 +- 10 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java create mode 100644 src/main/java/ru/joutak/templatemod/compat/ModMenuIntegration.java create mode 100644 src/main/java/ru/joutak/templatemod/config/ConfigLoader.java create mode 100644 src/main/java/ru/joutak/templatemod/config/ModConfig.java create mode 100644 src/main/resources/assets/templatemod/textures/icon.png diff --git a/build.gradle.kts b/build.gradle.kts index f931969..a62cdd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,12 +17,34 @@ 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" + } } 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") + } } java { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fca8bf0..1a01193 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,9 +4,13 @@ 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" [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" } [plugins] fabric-loom = { id = "net.fabricmc.fabric-loom", version.ref = "loom" } diff --git a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java index 5604e7e..b499764 100644 --- a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java +++ b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java @@ -4,6 +4,10 @@ import java.util.List; public class RoutRecorder { + private final int recordingIntervalTicks; + public RoutRecorder(int recordingIntervalTicks) { + this.recordingIntervalTicks = recordingIntervalTicks; + } public enum RecordingState { STOPPED, RECORDING, @@ -21,7 +25,7 @@ public void tick(Vec3 position) { return; } this.ticksSinceLastPoint ++; - if (this.ticksSinceLastPoint >= 20) { + if (this.ticksSinceLastPoint >= this.recordingIntervalTicks) { if (!points.getLast().equals(position)) { points.add(position); } diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index 0fbef33..d54046e 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -1,5 +1,6 @@ package ru.joutak.templatemod.client; - +import ru.joutak.templatemod.config.ConfigLoader; +import ru.joutak.templatemod.config.ModConfig; import net.fabricmc.api.ClientModInitializer; import net.minecraft.core.BlockPos; import net.minecraft.world.phys.Vec3; @@ -17,6 +18,11 @@ public class TemplateModClient implements ClientModInitializer { @Override public void onInitializeClient() { + ModConfig config = ConfigLoader.load(); + + this.recorder = new RoutRecorder( + config.getRecordingIntervalTicks() + ); TemplateMod.LOGGER.info("Mod runned", TemplateMod.MOD_ID); ClientTickEvents.END_CLIENT_TICK.register(client -> { if (client.player == null || client.isPaused()) { @@ -50,7 +56,7 @@ else if (recorder.getState() == RecordingState.RECORDING) { }); LevelRenderEvents.COLLECT_SUBMITS.register(RouteMarkers::draw); } - private final RoutRecorder recorder = new RoutRecorder(); + private RoutRecorder recorder; KeyMapping.Category Pathtrack = KeyMapping.Category.register( Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "controls") diff --git a/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java b/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java new file mode 100644 index 0000000..84a54b1 --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java @@ -0,0 +1,40 @@ +package ru.joutak.templatemod.compat; + +import me.shedaniel.clothconfig2.api.ConfigBuilder; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import ru.joutak.templatemod.config.ConfigLoader; +import ru.joutak.templatemod.config.ModConfig; + +public class ModConfigScreen { + public static Screen create(Screen parent) { + ModConfig config = ConfigLoader.load(); + + ConfigBuilder builder = ConfigBuilder.create() + .setParentScreen(parent) + .setTitle(Component.literal("Настройки маршрута")); + + var category = builder.getOrCreateCategory( + Component.literal("Запись") + ); + + category.addEntry( + builder.entryBuilder() + .startIntField( + Component.literal("Интервал записи, тики"), + config.getRecordingIntervalTicks() + ) + .setMin(1) + .setDefaultValue( + new ModConfig().getRecordingIntervalTicks() + ) + .setSaveConsumer(config::setRecordingIntervalTicks) + .requireRestart() + .build() + ); + + builder.setSavingRunnable(() -> ConfigLoader.save(config)); + + return builder.build(); + } +} diff --git a/src/main/java/ru/joutak/templatemod/compat/ModMenuIntegration.java b/src/main/java/ru/joutak/templatemod/compat/ModMenuIntegration.java new file mode 100644 index 0000000..afcb3ca --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/compat/ModMenuIntegration.java @@ -0,0 +1,16 @@ +package ru.joutak.templatemod.compat; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; +import net.fabricmc.loader.api.FabricLoader; + +public class ModMenuIntegration implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + if (!FabricLoader.getInstance().isModLoaded("cloth-config")) { + return ModMenuApi.super.getModConfigScreenFactory(); + } + + return parent -> ModConfigScreen.create(parent); + } +} diff --git a/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java b/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java new file mode 100644 index 0000000..b39726b --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java @@ -0,0 +1,60 @@ +package ru.joutak.templatemod.config; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import net.fabricmc.loader.api.FabricLoader; +import ru.joutak.templatemod.TemplateMod; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +public class ConfigLoader { + private static final Gson GSON = new Gson(); + + public static ModConfig load() { + Path path = FabricLoader.getInstance() + .getConfigDir() + .resolve("PathTrackMod-config.json"); + + ModConfig defaults = new ModConfig(); + + try { + if (Files.notExists(path)) { + Files.createDirectories(path.getParent()); + Files.writeString(path, GSON.toJson(defaults)); + return defaults; + } + + String json = Files.readString(path); + ModConfig config = GSON.fromJson(json, ModConfig.class); + + if (config == null || config.getRecordingIntervalTicks() < 1) { + TemplateMod.LOGGER.warn( + "No", + path, defaults.getRecordingIntervalTicks() + ); + return defaults; + } + + return config; + } catch (IOException | JsonParseException e) { + TemplateMod.LOGGER.warn( + "error: " + path, e + ); + return defaults; + } + } + + public static void save(ModConfig config) { + Path path = FabricLoader.getInstance() + .getConfigDir() + .resolve("PathTrackMod-config.json"); + + try { + Files.createDirectories(path.getParent()); + Files.writeString(path, GSON.toJson(config)); + } catch (IOException e) { + TemplateMod.LOGGER.error(">=1" + path, e); + } + + } +} diff --git a/src/main/java/ru/joutak/templatemod/config/ModConfig.java b/src/main/java/ru/joutak/templatemod/config/ModConfig.java new file mode 100644 index 0000000..1a2db9d --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/config/ModConfig.java @@ -0,0 +1,16 @@ +package ru.joutak.templatemod.config; + +public class ModConfig { + private int recordingIntervalTicks = 40; + + public void setRecordingIntervalTicks(int value) { + if (value < 1) { + throw new IllegalArgumentException(">=1"); + } + + this.recordingIntervalTicks = value; + } + public int getRecordingIntervalTicks() { + return recordingIntervalTicks; + } +} diff --git a/src/main/resources/assets/templatemod/textures/icon.png b/src/main/resources/assets/templatemod/textures/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb0160e53ec84617afbf9b0fbebd6f2630d1c27c GIT binary patch literal 1173 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV2SZ`aSW-L^LFmWqN@f1uDQ=l zOD+UH3k+mi<+EbNL+wNP0iR@la;;dI5Y##)q;mZdWzi{%wuTws3k=&f^-i(X@w#ow z_b{%cFUEO~3Z+!h;?z#UfYR_!T*UL}q_di+H^z)m<@#;HQ z&U}5r2r@x|p|I>8quH|C5>MXh-~484FZDP&`1Q^xd;ThhLV*Sg4u?5&&9}+azTa}= z`-K-fnvd>#?w(xt`g3H|_V(X<&U{|RF2K&%0d&nuIra_7Kh2hjB;QV}e?24q^rf8* zQ90F1qKg~W%73_3z2U8GeJ$L~L(D&tPkuJccrEeX(9ZhyFR?Vq_*?sr+}i!2puJ|Y zoP!LD0w0sWaas0?7c~~Y#jWh_ZrwBc-vYR0-x&Dc?!Eu%>8qD-{zaYNVCMQx;fD>} zM_l|5p7vLF|1{fwziQ3>dHFA;4o2;H9#~_Z@QN3%2<$nCJrEy2L*wb4t+l;hos(y1 zODqG14p3SDzugS$*)CY``!01*;|n;!Uyea6|M$pLb*-$ZVUy+3we zTzjYg)_aCGA78%9y7>C0Y5z9$_`@AAPc1*e>T>;Q@bdrP4l^HM0sC|LF1du48%y5* z*?abD^f_${`*j;ieqWh4AEqVU=cUc;z<*zl+lW8dd)+PjSyuM9H(&qBe4g?9@ApkV z|7_R)B=P5u%6FBE|CfusuR8uZdE4L5!jBmY7{O5^dHL;x>W1^5f4*L7+o)=_J%6qH zhon8JZ+Et5C+}nBXOM(A-jFZh`Mj@Lx3+y_+4?+RcJuUq(+s3O=j(l*k$+tr9`uE; z8FP3x&O2~S;WcO0@ngDiKC!>6|GlVT4$Mm4$8HNsF_4hZxW0bAx|QF9wFfWnW?!*) z|1Hz@sN*%)c$#>?epY|OsFVGiZ_oEPxBk@S{&jf$J6HCzZUw^$2XFw-s%G%c|H_iR zx%ABT2aF2LV2^0p9N1vae&g$NU*DPA>-MH*`)??b{eR2wezlxt{jS3WC(K{M67?$E zhNb2H8_IkarM{gJFPHzXfxQ6|N#~x)9T57;BDc-%fY99JjE^;Tatu*T;Ow;HD}yVK z%mE4LzSoRdn#JtXwtZ!uwn9DP zZ}eUh`)T=|RXR2dDUbx$%C7P0>{gMVFTECBe?NEa?eu>322OCeo~xH#a`|qq{r`)= P;)cP~)z4*}Q$iB}o{A?; literal 0 HcmV?d00001 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e037854..269fb65 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -3,7 +3,8 @@ "id": "${MOD_ID}", "version": "${VERSION}", "name": "${MOD_NAME}", - "description": "Мод для сохранения пути", + "icon": "assets/templatemod/textures/icon.png", + "description": "Waypoint creating mod", "authors": [ "WeNeedFood" ], @@ -19,6 +20,9 @@ ], "client": [ "ru.joutak.templatemod.client.TemplateModClient" + ], + "modmenu": [ + "ru.joutak.templatemod.compat.ModMenuIntegration" ] }, "mixins": [], From 6dc34767692f7cc5024e51e3e02210ccc4c87bf9 Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 7 Sep 2026 18:39:23 +0300 Subject: [PATCH 07/13] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=BF=D0=B0=D1=83=D0=B7=D1=8B=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20HUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../templatemod/client/RecordingHud.java | 41 +++++++++++++++++++ .../templatemod/client/RoutRecorder.java | 7 ++++ .../templatemod/client/TemplateModClient.java | 20 ++++++++- .../assets/templatemod/lang/en_us.json | 3 +- 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/joutak/templatemod/client/RecordingHud.java diff --git a/gradle.properties b/gradle.properties index ec9fcee..0cb6ac1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ repo=https://github.com/JouTak/TemplateMod-Java group=ru.joutak mod_name=TemplateMod-Java mod_id=templatemod -version=1.1 +version=1.3 # Gradle properties org.gradle.parallel=true diff --git a/src/main/java/ru/joutak/templatemod/client/RecordingHud.java b/src/main/java/ru/joutak/templatemod/client/RecordingHud.java new file mode 100644 index 0000000..28f1865 --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/client/RecordingHud.java @@ -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 + ); + } +} diff --git a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java index b499764..7ee4cd0 100644 --- a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java +++ b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java @@ -41,6 +41,13 @@ else if (this.state == RecordingState.RECORDING || this.state == RecordingState. 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 List getPoints() { return List.copyOf(points); } diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index d54046e..a81f430 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -13,7 +13,8 @@ import net.minecraft.network.chat.Component; import ru.joutak.templatemod.client.RoutRecorder.RecordingState; import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents; - +import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements; public class TemplateModClient implements ClientModInitializer { @Override @@ -49,12 +50,21 @@ else if (recorder.getState() == RecordingState.RECORDING) { } } } + while (this.pauseRecording.consumeClick()) { + recorder.togglePause(); + } recorder.tick(client.player.position()); }); LevelRenderEvents.END_EXTRACTION.register(context -> { RouteMarkers.extract(recorder.getPoints()); }); LevelRenderEvents.COLLECT_SUBMITS.register(RouteMarkers::draw); + HudElementRegistry.attachElementBefore( + VanillaHudElements.CHAT, + Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "recording_hud"), + (graphics, deltaTracker) -> + RecordingHud.draw(graphics, recorder.getState()) + ); } private RoutRecorder recorder; @@ -68,6 +78,14 @@ else if (recorder.getState() == RecordingState.RECORDING) { InputConstants.KEY_C, this.Pathtrack )); + KeyMapping pauseRecording = KeyMappingHelper.registerKeyMapping( + new KeyMapping( + "key.templatemod.pause_recording", + InputConstants.Type.KEYSYM, + InputConstants.KEY_V, + this.Pathtrack + ) + ); KeyMapping ShowList = KeyMappingHelper.registerKeyMapping( new KeyMapping("key.templatemod.show_list", InputConstants.Type.KEYSYM, diff --git a/src/main/resources/assets/templatemod/lang/en_us.json b/src/main/resources/assets/templatemod/lang/en_us.json index 3828a64..2584d36 100644 --- a/src/main/resources/assets/templatemod/lang/en_us.json +++ b/src/main/resources/assets/templatemod/lang/en_us.json @@ -1,5 +1,6 @@ { "key.category.templatemod.controls": "PathTrack", "key.PathtrackMod.start-recording": "Start Recording", - "key.templatemod.show_list": "Show route points" + "key.templatemod.show_list": "Show route points", + "key.templatemod.pause_recording": "Pause / resume recording" } \ No newline at end of file From 9f88f8658e638b0aefdb43350e4f0127e0bf074b Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 7 Sep 2026 18:39:32 +0300 Subject: [PATCH 08/13] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=BF=D0=B0=D1=83=D0=B7=D1=8B=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20HUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/templatemod/textures/pause.png | Bin 0 -> 509 bytes .../assets/templatemod/textures/recording.png | Bin 0 -> 656 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/templatemod/textures/pause.png create mode 100644 src/main/resources/assets/templatemod/textures/recording.png diff --git a/src/main/resources/assets/templatemod/textures/pause.png b/src/main/resources/assets/templatemod/textures/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..a395f15b82a773be564649d7f2c04b9a99f1101b GIT binary patch literal 509 zcmV`sQQ+0IzTFtKaA&63*aszCc;lP?q(uR;)MD9upn3Kbc%6HlNl>qX)8k)dV0m;i^Ws)E)+ zz!LBsG|a0nthnaOy;PP_5%^2|UjnqO-1_$Qc3kt>_{w&c0N1^w`sQR(CuAk}50Arl zOesMNMQt-uoa`urHaL)$6^wV}R(32;M%lnzc7HIU?4GCBSD+jppN&1VyJ2ycZb z0^B@sbpjYx=`L7WvQZI`I^j}?x1lk|s1ih|RDm(a_}dUiYa|guhU(IhK8gUso%Ogf zbZ5s9umpSuZSJAL$)&k4xDy_<4o<6+1lB8Z)ay`XCn**5w-k(6UV#nFCQNeLPSISoed?eC&Fo)Vs9HF z#%Umld!%nbu}F;e`LqEpnV2Pcg;*oN$&1;ZFrPLq%{|+?6iAl@B5G|Si~??M@7sDZ z*BlN-F-uUFUG!3zMQ8$usI4dSqli@zz&08N5hMO{m1V6Axh>Si!aSBw8)t45vjp{6 zXs2i=;3~^{12&?e=n2~PO-z{|H)FZ9QXP@_`J673jAK(~68+~p`QE!(4<7T1=f zYl09W3Mxc062&4xUN(4qxG+1enqB_b$N_X)vF<)cb}NGj2RQ&dK3pI#J>&|ao7?;L z>H8db(+N3z3I7E)ar}P;0FE3uRT-AB<-?iD*fb%pPJax6Z7J@qVBx)jLu!GET=T<9ac~aE zEA_5=_mU6UzM#8+*Y;wTbVbGhxpIDY1PKsAL@B7POMM%Rjtq^dDThpRdH4aP4x~%! zRlU$v*r4_klAL2FK<(9zCAW!CxwI~oh}wEA-2L5wV?6W{S`zYn0FG}Dw0Kd+EYU3fK9De}=4_DamcVh7X0000 Date: Mon, 7 Sep 2026 21:09:21 +0300 Subject: [PATCH 09/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BA=20LambDynamicLights,=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D0=BB=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 74 ++----------------- build.gradle.kts | 8 ++ gradle.properties | 2 +- gradle/libs.versions.toml | 3 + .../templatemod/client/RoutLighting.java | 16 ++++ .../templatemod/client/RoutRecorder.java | 5 ++ .../templatemod/client/TemplateModClient.java | 10 ++- .../compat/DynamicLightsIntegration.java | 49 ++++++++++++ .../templatemod/compat/WaypointLight.java | 52 +++++++++++++ src/main/resources/fabric.mod.json | 3 + 10 files changed, 154 insertions(+), 68 deletions(-) create mode 100644 src/main/java/ru/joutak/templatemod/client/RoutLighting.java create mode 100644 src/main/java/ru/joutak/templatemod/compat/DynamicLightsIntegration.java create mode 100644 src/main/java/ru/joutak/templatemod/compat/WaypointLight.java diff --git a/README.md b/README.md index e64036f..4784f22 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,13 @@ -# JouTak Mod Template (Java Edition) +# RouteRecorder +# Сделан на основе JouTak Mod Template (Java Edition) в качестве тестового задания -Заготовка для мода на JouTak (ДжоуТек) ([joutak.ru](https://joutak.ru)) +**Minecraft 26.1.2**, **Fabric** и **Java 25**. -Шаблон рассчитан на **Minecraft 26.1.2**, **Fabric** и **Java 25**. +Мод создаёт вейпоинты в виде стрелочек в месте, где прошёл игрок. +По умолчанию запись включается и выключается на кнопку C, запись можно поставить на паузу с помощью кнопки V. +В планах сделать функционал для автоматического прохода игрока по точкам в обратную сторону. - -# Требования - -- **JDK 25** для Gradle JVM и компиляции мода -- **IntelliJ IDEA 2025.3** или новее - -После открытия проекта выберите JDK 25 в `Settings` → `Build, Execution, Deployment` → `Build Tools` → `Gradle` → `Gradle JVM`. Без Java 25 Gradle и конфигурация `[Dev] Run Client` не запустятся. - -Gradle 9.5.1 уже включён через Gradle Wrapper, поэтому устанавливать Gradle отдельно не нужно. Для Minecraft 26.1.2 шаблон использует non-remapping Loom: зависимости Fabric подключаются через `implementation`, а конфигурация `mappings` отсутствует. - - -# Перед использованием - -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/` проекта - - -# Перед началом выполнения тестового задания +Мод имеет необязательную интеграцию с LambDynamicLights > [!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] -> Главное, если есть какие-то вопросы — не стесняйся спрашивать! +> (https://modrinth.com/mod/lambdynamiclights) diff --git a/build.gradle.kts b/build.gradle.kts index a62cdd4..edda13b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,9 @@ repositories { maven("https://maven.shedaniel.me/") { name = "Shedaniel" } + maven("https://maven.gegy.dev") { + name = "Gegy" + } } dependencies { @@ -45,6 +48,11 @@ dependencies { 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 { diff --git a/gradle.properties b/gradle.properties index 0cb6ac1..dd3862e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ repo=https://github.com/JouTak/TemplateMod-Java group=ru.joutak mod_name=TemplateMod-Java mod_id=templatemod -version=1.3 +version=1.4 # Gradle properties org.gradle.parallel=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a01193..aa8a229 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,11 +6,14 @@ 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" } diff --git a/src/main/java/ru/joutak/templatemod/client/RoutLighting.java b/src/main/java/ru/joutak/templatemod/client/RoutLighting.java new file mode 100644 index 0000000..4837923 --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/client/RoutLighting.java @@ -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> updater = points -> {}; + + public static void setUpdater(Consumer> newUpdater) { + updater = newUpdater; + } + + public static void update(List points) { + updater.accept(points); + } +} diff --git a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java index 7ee4cd0..58b171e 100644 --- a/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java +++ b/src/main/java/ru/joutak/templatemod/client/RoutRecorder.java @@ -48,6 +48,11 @@ public void togglePause() { this.state = RecordingState.RECORDING; } } + public void clear() { + this.points.clear(); + this.state = RecordingState.STOPPED; + this.ticksSinceLastPoint = 0; + } public List getPoints() { return List.copyOf(points); } diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index a81f430..c405108 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -15,6 +15,8 @@ import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents; import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements; +import net.minecraft.client.multiplayer.ClientLevel; + public class TemplateModClient implements ClientModInitializer { @Override @@ -26,6 +28,11 @@ public void onInitializeClient() { ); TemplateMod.LOGGER.info("Mod runned", TemplateMod.MOD_ID); ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (this.routeLevel != client.level) { + this.recorder.clear(); + RoutLighting.update(this.recorder.getPoints()); + this.routeLevel = client.level; + } if (client.player == null || client.isPaused()) { return; } @@ -54,6 +61,7 @@ else if (recorder.getState() == RecordingState.RECORDING) { recorder.togglePause(); } recorder.tick(client.player.position()); + RoutLighting.update(recorder.getPoints()); }); LevelRenderEvents.END_EXTRACTION.register(context -> { RouteMarkers.extract(recorder.getPoints()); @@ -67,7 +75,7 @@ else if (recorder.getState() == RecordingState.RECORDING) { ); } private RoutRecorder recorder; - + private ClientLevel routeLevel; KeyMapping.Category Pathtrack = KeyMapping.Category.register( Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "controls") ); diff --git a/src/main/java/ru/joutak/templatemod/compat/DynamicLightsIntegration.java b/src/main/java/ru/joutak/templatemod/compat/DynamicLightsIntegration.java new file mode 100644 index 0000000..e8cbc41 --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/compat/DynamicLightsIntegration.java @@ -0,0 +1,49 @@ +package ru.joutak.templatemod.compat; + +import dev.lambdaurora.lambdynlights.api.DynamicLightsContext; +import dev.lambdaurora.lambdynlights.api.DynamicLightsInitializer; +import ru.joutak.templatemod.TemplateMod; +import dev.lambdaurora.lambdynlights.api.behavior.DynamicLightBehaviorManager; +import net.minecraft.world.phys.Vec3; +import ru.joutak.templatemod.client.RoutLighting; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.HashSet; + +public class DynamicLightsIntegration implements DynamicLightsInitializer { + private final Map activeLights = new HashMap<>(); + @Override + public void onInitializeDynamicLights(DynamicLightsContext context) { + TemplateMod.LOGGER.info("Поддержка LambDynamicLights подключена"); + + var lights = context.dynamicLightBehaviorManager(); + + RoutLighting.setUpdater(points -> updateLights(points, lights)); + } + private void updateLights( + List points, + DynamicLightBehaviorManager lights + ) { + var currentPoints = new HashSet<>(points); + + for (Vec3 oldPoint : List.copyOf(this.activeLights.keySet())) { + if (!currentPoints.contains(oldPoint)) { + WaypointLight light = this.activeLights.remove(oldPoint); + lights.remove(light); + } + } + + for (Vec3 point : currentPoints) { + if (!this.activeLights.containsKey(point)) { + WaypointLight light = new WaypointLight( + point.add(0.0, 0.5, 0.0), + 10 + ); + + lights.add(light); + this.activeLights.put(point, light); + } + } + } +} diff --git a/src/main/java/ru/joutak/templatemod/compat/WaypointLight.java b/src/main/java/ru/joutak/templatemod/compat/WaypointLight.java new file mode 100644 index 0000000..f4440ab --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/compat/WaypointLight.java @@ -0,0 +1,52 @@ +package ru.joutak.templatemod.compat; +import dev.lambdaurora.lambdynlights.api.behavior.DynamicLightBehavior; +import net.minecraft.core.BlockPos; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Range; + +public class WaypointLight implements DynamicLightBehavior{ + private final Vec3 position; + private final int luminance; + private boolean changed = true; + + public WaypointLight(Vec3 position, int luminance) { + if (luminance < 0 || luminance > 15) { + throw new IllegalArgumentException("Яркость должна быть от 0 до 15"); + } + + this.position = position; + this.luminance = luminance; + } + + @Override + public @Range(from = 0L, to = 15L) double lightAtPos(BlockPos pos, double falloffRatio) { + double dx = pos.getX() + 0.5 - this.position.x; + double dy = pos.getY() + 0.5 - this.position.y; + double dz = pos.getZ() + 0.5 - this.position.z; + + double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + + return Math.max(this.luminance - distance * falloffRatio, 0.0); + } + + @Override + public BoundingBox getBoundingBox() { + BlockPos block = BlockPos.containing(this.position); + + return new BoundingBox( + block.getX(), + block.getY(), + block.getZ(), + block.getX() + 1, + block.getY() + 1, + block.getZ() + 1 + ); + } + + @Override + public boolean hasChanged() { + boolean result = this.changed; + this.changed = false; + return result; + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 269fb65..3d95374 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -23,6 +23,9 @@ ], "modmenu": [ "ru.joutak.templatemod.compat.ModMenuIntegration" + ], + "lambdynlights:initializer": [ + "ru.joutak.templatemod.compat.DynamicLightsIntegration" ] }, "mixins": [], From 507b580c2c661b57ad4c2b39372985aeaa039401 Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 14 Sep 2026 03:34:47 +0300 Subject: [PATCH 10/13] =?UTF-8?q?=D0=BF=D0=BE=D1=80=D0=B0=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B0=D1=82=D1=8C.=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B5=D0=BB=D0=BE=D1=87=D0=BA=D1=83=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++ .../ru/joutak/templatemod/TemplateMod.java | 2 +- .../joutak/templatemod/client/ReturnHUD.java | 49 ++++++++++ .../templatemod/client/RouteNavigator.java | 49 ++++++++++ .../templatemod/client/TemplateModClient.java | 89 +++++++++++++++++-- .../templatemod/config/ConfigLoader.java | 6 +- .../joutak/templatemod/config/ModConfig.java | 2 +- .../assets/templatemod/lang/en_us.json | 25 ++++-- .../assets/templatemod/lang/ru_ru.json | 21 +++++ src/main/resources/fabric.mod.json | 2 +- 10 files changed, 235 insertions(+), 19 deletions(-) create mode 100644 src/main/java/ru/joutak/templatemod/client/ReturnHUD.java create mode 100644 src/main/java/ru/joutak/templatemod/client/RouteNavigator.java create mode 100644 src/main/resources/assets/templatemod/lang/ru_ru.json diff --git a/README.md b/README.md index 4784f22..fa74813 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,15 @@ Мод создаёт вейпоинты в виде стрелочек в месте, где прошёл игрок. По умолчанию запись включается и выключается на кнопку C, запись можно поставить на паузу с помощью кнопки V. + +После завершения записи нажми R, чтобы включить возврат по маршруту. Повторное нажатие R останавливает возврат. +Иди по стрелке на HUD: она показывает направление к текущей точке, расстояние и количество оставшихся точек. +При приближении на 1,5 блока цель переключается на предыдущую точку записи. Высота тоже учитывается. +У начала маршрута возврат завершается, HUD скрывается, а запись сохраняется для повторного использования. + +Delete полностью очищает маршрут и останавливает запись и возврат. Все клавиши можно переназначить в настройках управления. +Новая запись заменяет старую. При смерти, выходе из мира или смене измерения маршрут очищается. + В планах сделать функционал для автоматического прохода игрока по точкам в обратную сторону. Мод имеет необязательную интеграцию с LambDynamicLights diff --git a/src/main/java/ru/joutak/templatemod/TemplateMod.java b/src/main/java/ru/joutak/templatemod/TemplateMod.java index 670de0e..8a919c4 100644 --- a/src/main/java/ru/joutak/templatemod/TemplateMod.java +++ b/src/main/java/ru/joutak/templatemod/TemplateMod.java @@ -18,6 +18,6 @@ public static TemplateMod getInstance() { @Override public void onInitialize() { instance = this; - LOGGER.info("Mod {} initialized!", MOD_ID); + LOGGER.info("Мод {} загружен", MOD_ID); } } diff --git a/src/main/java/ru/joutak/templatemod/client/ReturnHUD.java b/src/main/java/ru/joutak/templatemod/client/ReturnHUD.java new file mode 100644 index 0000000..bb393db --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/client/ReturnHUD.java @@ -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 + ); + } +} diff --git a/src/main/java/ru/joutak/templatemod/client/RouteNavigator.java b/src/main/java/ru/joutak/templatemod/client/RouteNavigator.java new file mode 100644 index 0000000..8be0195 --- /dev/null +++ b/src/main/java/ru/joutak/templatemod/client/RouteNavigator.java @@ -0,0 +1,49 @@ +package ru.joutak.templatemod.client; + +import net.minecraft.world.phys.Vec3; + +import java.util.List; + +public class RouteNavigator { + private static final double REACH_RADIUS = 1.5; + + private List points = List.of(); + private int targetIndex = -1; + + public boolean start(List route) { + this.points = List.copyOf(route); + this.targetIndex = this.points.size() - 1; + return isActive(); + } + + public boolean tick(Vec3 position) { + if (!isActive()) { + return false; + } + + while (isActive() + && position.distanceToSqr(this.points.get(this.targetIndex)) <= REACH_RADIUS * REACH_RADIUS) { + this.targetIndex--; + } + + // Сообщаем о завершении только в тот тик, когда достигнута первая точка. + return !isActive(); + } + + public void stop() { + this.points = List.of(); + this.targetIndex = -1; + } + + public boolean isActive() { + return this.targetIndex >= 0; + } + + public Vec3 getTarget() { + return isActive() ? this.points.get(this.targetIndex) : null; + } + + public int getRemainingPointCount() { + return this.targetIndex + 1; + } +} diff --git a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java index c405108..9a17817 100644 --- a/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java +++ b/src/main/java/ru/joutak/templatemod/client/TemplateModClient.java @@ -6,6 +6,8 @@ import net.minecraft.world.phys.Vec3; import ru.joutak.templatemod.TemplateMod; import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; import net.minecraft.resources.Identifier; import com.mojang.blaze3d.platform.InputConstants; import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; @@ -26,30 +28,43 @@ public void onInitializeClient() { this.recorder = new RoutRecorder( config.getRecordingIntervalTicks() ); - TemplateMod.LOGGER.info("Mod runned", TemplateMod.MOD_ID); + TemplateMod.LOGGER.info("Клиентская часть мода {} запущена", TemplateMod.MOD_ID); ClientTickEvents.END_CLIENT_TICK.register(client -> { if (this.routeLevel != client.level) { this.recorder.clear(); + this.navigator.stop(); RoutLighting.update(this.recorder.getPoints()); this.routeLevel = client.level; } if (client.player == null || client.isPaused()) { return; } + if (!client.player.isAlive()) { + recorder.clear(); + navigator.stop(); + RoutLighting.update(recorder.getPoints()); + return; + } while (this.StartRecording.consumeClick()) { - if (client.player != null) { - if (recorder.getState() == RecordingState.STOPPED ) { - client.player.sendSystemMessage(Component.literal("Запись начата!")); - } - else if (recorder.getState() == RecordingState.RECORDING) { - client.player.sendSystemMessage(Component.literal("Запись завершена")); + navigator.stop(); + if (recorder.getState() == RecordingState.STOPPED) { + client.player.sendSystemMessage(Component.translatable("message.templatemod.recording_started")); + } else { + client.player.sendSystemMessage(Component.translatable("message.templatemod.recording_stopped")); + if (this.startReturn.isUnbound()) { + client.player.sendSystemMessage(Component.translatable("message.templatemod.bind_return_key")); + } else { + client.player.sendSystemMessage(Component.translatable( + "message.templatemod.return_hint", + this.startReturn.getTranslatedKeyMessage() + )); } } recorder.toggleRecording(client.player.position()); } while (this.ShowList.consumeClick()) { if (recorder.getPoints().isEmpty()){ - client.player.sendSystemMessage(Component.literal("Список пуст")); + client.player.sendSystemMessage(Component.translatable("message.templatemod.points_empty")); } else { for (Vec3 pos : recorder.getPoints()){ @@ -60,7 +75,27 @@ else if (recorder.getState() == RecordingState.RECORDING) { while (this.pauseRecording.consumeClick()) { recorder.togglePause(); } + while (this.startReturn.consumeClick()) { + if (navigator.isActive()) { + navigator.stop(); + client.player.sendSystemMessage(Component.translatable("message.templatemod.return_stopped")); + } else if (recorder.getState() != RecordingState.STOPPED) { + client.player.sendSystemMessage(Component.translatable("message.templatemod.finish_recording")); + } else if (navigator.start(recorder.getPoints())) { + client.player.sendSystemMessage(Component.translatable("message.templatemod.return_started")); + } else { + client.player.sendSystemMessage(Component.translatable("message.templatemod.no_route")); + } + } + while (this.clearRoute.consumeClick()) { + recorder.clear(); + navigator.stop(); + client.player.sendSystemMessage(Component.translatable("message.templatemod.route_cleared")); + } recorder.tick(client.player.position()); + if (navigator.tick(client.player.position())) { + client.player.sendSystemMessage(Component.translatable("message.templatemod.return_finished")); + } RoutLighting.update(recorder.getPoints()); }); LevelRenderEvents.END_EXTRACTION.register(context -> { @@ -73,8 +108,30 @@ else if (recorder.getState() == RecordingState.RECORDING) { (graphics, deltaTracker) -> RecordingHud.draw(graphics, recorder.getState()) ); + HudElementRegistry.attachElementBefore( + VanillaHudElements.CHAT, + Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "return_hud"), + (graphics, deltaTracker) -> { + var client = Minecraft.getInstance(); + if (client.player == null || !navigator.isActive()) { + return; + } + Vec3 target = navigator.getTarget(); + Vec3 position = client.player.position(); + double dx = target.x - position.x; + double dz = target.z - position.z; + float angleDegrees = 0f; + if (dx * dx + dz * dz > 1.0E-6) { + float targetYaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); + angleDegrees = Mth.wrapDegrees(targetYaw - client.player.getYRot()); + } + + ReturnHUD.draw(graphics, angleDegrees, position.distanceTo(target), navigator.getRemainingPointCount()); + } + ); } private RoutRecorder recorder; + private final RouteNavigator navigator = new RouteNavigator(); private ClientLevel routeLevel; KeyMapping.Category Pathtrack = KeyMapping.Category.register( Identifier.fromNamespaceAndPath(TemplateMod.MOD_ID, "controls") @@ -100,4 +157,20 @@ else if (recorder.getState() == RecordingState.RECORDING) { InputConstants.KEY_P, this.Pathtrack )); + KeyMapping clearRoute = KeyMappingHelper.registerKeyMapping( + new KeyMapping( + "key.templatemod.clear_route", + InputConstants.Type.KEYSYM, + InputConstants.KEY_DELETE, + this.Pathtrack + ) + ); + KeyMapping startReturn = KeyMappingHelper.registerKeyMapping( + new KeyMapping( + "key.templatemod.start_return", + InputConstants.Type.KEYSYM, + InputConstants.KEY_R, + this.Pathtrack + ) + ); } diff --git a/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java b/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java index b39726b..c9a86b9 100644 --- a/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java +++ b/src/main/java/ru/joutak/templatemod/config/ConfigLoader.java @@ -29,7 +29,7 @@ public static ModConfig load() { if (config == null || config.getRecordingIntervalTicks() < 1) { TemplateMod.LOGGER.warn( - "No", + "В конфиге {} неверный интервал записи. Использую {} тиков.", path, defaults.getRecordingIntervalTicks() ); return defaults; @@ -38,7 +38,7 @@ public static ModConfig load() { return config; } catch (IOException | JsonParseException e) { TemplateMod.LOGGER.warn( - "error: " + path, e + "Не удалось прочитать конфиг {}. Использую стандартные настройки.", path, e ); return defaults; } @@ -53,7 +53,7 @@ public static void save(ModConfig config) { Files.createDirectories(path.getParent()); Files.writeString(path, GSON.toJson(config)); } catch (IOException e) { - TemplateMod.LOGGER.error(">=1" + path, e); + TemplateMod.LOGGER.error("Не удалось сохранить конфиг {}.", path, e); } } diff --git a/src/main/java/ru/joutak/templatemod/config/ModConfig.java b/src/main/java/ru/joutak/templatemod/config/ModConfig.java index 1a2db9d..e5606f7 100644 --- a/src/main/java/ru/joutak/templatemod/config/ModConfig.java +++ b/src/main/java/ru/joutak/templatemod/config/ModConfig.java @@ -5,7 +5,7 @@ public class ModConfig { public void setRecordingIntervalTicks(int value) { if (value < 1) { - throw new IllegalArgumentException(">=1"); + throw new IllegalArgumentException("Интервал записи должен быть не меньше одного тика"); } this.recordingIntervalTicks = value; diff --git a/src/main/resources/assets/templatemod/lang/en_us.json b/src/main/resources/assets/templatemod/lang/en_us.json index 2584d36..a78542b 100644 --- a/src/main/resources/assets/templatemod/lang/en_us.json +++ b/src/main/resources/assets/templatemod/lang/en_us.json @@ -1,6 +1,21 @@ { - "key.category.templatemod.controls": "PathTrack", - "key.PathtrackMod.start-recording": "Start Recording", - "key.templatemod.show_list": "Show route points", - "key.templatemod.pause_recording": "Pause / resume recording" -} \ No newline at end of file + "key.category.templatemod.controls": "Маршрут", + "key.PathtrackMod.start-recording": "Начать / закончить запись", + "key.templatemod.show_list": "Показать список точек", + "key.templatemod.pause_recording": "Пауза / продолжить запись", + "key.templatemod.clear_route": "Очистить маршрут", + "key.templatemod.start_return": "Начать / остановить возвращение", + "message.templatemod.recording_started": "Запись начата!", + "message.templatemod.recording_stopped": "Запись завершена", + "message.templatemod.return_hint": "Нажмите %s, чтобы начать возвращение", + "message.templatemod.bind_return_key": "Назначьте кнопку возвращения в настройках управления", + "message.templatemod.points_empty": "Список пуст", + "message.templatemod.route_cleared": "Маршрут очищен", + "message.templatemod.return_started": "Возвращение начато", + "message.templatemod.return_stopped": "Возвращение остановлено", + "message.templatemod.no_route": "Сначала запиши маршрут", + "message.templatemod.finish_recording": "Сначала заверши запись", + "message.templatemod.return_finished": "Ты вернулся к началу маршрута", + "hud.templatemod.return_distance": "%s бл.", + "hud.templatemod.return_progress": "Осталось точек: %s" +} diff --git a/src/main/resources/assets/templatemod/lang/ru_ru.json b/src/main/resources/assets/templatemod/lang/ru_ru.json new file mode 100644 index 0000000..a78542b --- /dev/null +++ b/src/main/resources/assets/templatemod/lang/ru_ru.json @@ -0,0 +1,21 @@ +{ + "key.category.templatemod.controls": "Маршрут", + "key.PathtrackMod.start-recording": "Начать / закончить запись", + "key.templatemod.show_list": "Показать список точек", + "key.templatemod.pause_recording": "Пауза / продолжить запись", + "key.templatemod.clear_route": "Очистить маршрут", + "key.templatemod.start_return": "Начать / остановить возвращение", + "message.templatemod.recording_started": "Запись начата!", + "message.templatemod.recording_stopped": "Запись завершена", + "message.templatemod.return_hint": "Нажмите %s, чтобы начать возвращение", + "message.templatemod.bind_return_key": "Назначьте кнопку возвращения в настройках управления", + "message.templatemod.points_empty": "Список пуст", + "message.templatemod.route_cleared": "Маршрут очищен", + "message.templatemod.return_started": "Возвращение начато", + "message.templatemod.return_stopped": "Возвращение остановлено", + "message.templatemod.no_route": "Сначала запиши маршрут", + "message.templatemod.finish_recording": "Сначала заверши запись", + "message.templatemod.return_finished": "Ты вернулся к началу маршрута", + "hud.templatemod.return_distance": "%s бл.", + "hud.templatemod.return_progress": "Осталось точек: %s" +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3d95374..a494824 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -4,7 +4,7 @@ "version": "${VERSION}", "name": "${MOD_NAME}", "icon": "assets/templatemod/textures/icon.png", - "description": "Waypoint creating mod", + "description": "Записывает маршрут, ставит стрелочки и помогает вернуться обратно.", "authors": [ "WeNeedFood" ], From b3262085e72da39ffd1273fcab7f8ee90346f1e7 Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 14 Sep 2026 03:34:52 +0300 Subject: [PATCH 11/13] =?UTF-8?q?=D0=BF=D0=BE=D1=80=D0=B0=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B0=D1=82=D1=8C.=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B5=D0=BB=D0=BE=D1=87=D0=BA=D1=83=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index dd3862e..e7e706f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ repo=https://github.com/JouTak/TemplateMod-Java group=ru.joutak mod_name=TemplateMod-Java mod_id=templatemod -version=1.4 +version=1.5 # Gradle properties org.gradle.parallel=true From 4119ab0b01ae38f3bbab25b76de4be20d464d055 Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 14 Sep 2026 03:44:12 +0300 Subject: [PATCH 12/13] weneedfood --- README.md | 20 +++++++++++++------- gradle.properties | 4 ++-- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fa74813..eff4176 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # RouteRecorder -# Сделан на основе JouTak Mod Template (Java Edition) в качестве тестового задания +Сделан на основе JouTak Mod Template (Java Edition) в качестве [тестового задания](https://github.com/JouTak/TemplateMod-Java/issues/4). **Minecraft 26.1.2**, **Fabric** и **Java 25**. -Мод создаёт вейпоинты в виде стрелочек в месте, где прошёл игрок. +Клиентский мод: записывает маршрут и создаёт вейпоинты в виде стрелочек в месте, где прошёл игрок. По умолчанию запись включается и выключается на кнопку C, запись можно поставить на паузу с помощью кнопки V. После завершения записи нажми R, чтобы включить возврат по маршруту. Повторное нажатие R останавливает возврат. @@ -11,12 +11,18 @@ При приближении на 1,5 блока цель переключается на предыдущую точку записи. Высота тоже учитывается. У начала маршрута возврат завершается, HUD скрывается, а запись сохраняется для повторного использования. -Delete полностью очищает маршрут и останавливает запись и возврат. Все клавиши можно переназначить в настройках управления. +P выводит список записанных точек в чат. Delete полностью очищает маршрут и останавливает запись и возврат. +Все клавиши можно переназначить в настройках управления. Новая запись заменяет старую. При смерти, выходе из мира или смене измерения маршрут очищается. -В планах сделать функционал для автоматического прохода игрока по точкам в обратную сторону. +Интервал записи можно поменять в `config/PathTrackMod-config.json`, параметр `recordingIntervalTicks`. +По умолчанию точка сохраняется каждые 40 тиков, если игрок сдвинулся с предыдущей точки. Минимальный интервал — 1 тик. +После изменения конфига нужно перезапустить игру. -Мод имеет необязательную интеграцию с LambDynamicLights +Для установки нужны Fabric Loader и Fabric API. Mod Menu, Cloth Config и LambDynamicLights ставятся по желанию: -> [!NOTE] -> (https://modrinth.com/mod/lambdynamiclights) +- [Mod Menu](https://modrinth.com/mod/modmenu) вместе с [Cloth Config](https://modrinth.com/mod/cloth-config) добавляют экран настройки интервала записи. +- [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) добавляет свет от точек маршрута. + +Для сборки нужен JDK 25. В корне проекта выполни `./gradlew build` (на Windows — `.\gradlew.bat build`). +Готовый JAR появится в `build/libs/`. diff --git a/gradle.properties b/gradle.properties index e7e706f..8e45e23 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # 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 @@ -7,4 +7,4 @@ version=1.5 # Gradle properties org.gradle.parallel=true -org.gradle.configuration-cache=true \ No newline at end of file +org.gradle.configuration-cache=true diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a494824..3e4595c 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -13,7 +13,7 @@ "sources": "${WEBSITE}" }, "license": "MIT", - "environment": "*", + "environment": "client", "entrypoints": { "main": [ "ru.joutak.templatemod.TemplateMod" From a8d443584eafc2e661890241b939573f8c231022 Mon Sep 17 00:00:00 2001 From: UrSick Date: Mon, 14 Sep 2026 04:01:38 +0300 Subject: [PATCH 13/13] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20=D0=B8=D0=BD=D0=B3=D0=BB=D0=B8=D1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templatemod/compat/ModConfigScreen.java | 6 +-- .../assets/templatemod/lang/en_us.json | 42 ++++++++++--------- .../assets/templatemod/lang/ru_ru.json | 6 ++- src/main/resources/fabric.mod.json | 2 +- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java b/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java index 84a54b1..1e66df8 100644 --- a/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java +++ b/src/main/java/ru/joutak/templatemod/compat/ModConfigScreen.java @@ -12,16 +12,16 @@ public static Screen create(Screen parent) { ConfigBuilder builder = ConfigBuilder.create() .setParentScreen(parent) - .setTitle(Component.literal("Настройки маршрута")); + .setTitle(Component.translatable("config.templatemod.title")); var category = builder.getOrCreateCategory( - Component.literal("Запись") + Component.translatable("config.templatemod.category.recording") ); category.addEntry( builder.entryBuilder() .startIntField( - Component.literal("Интервал записи, тики"), + Component.translatable("config.templatemod.recording_interval_ticks"), config.getRecordingIntervalTicks() ) .setMin(1) diff --git a/src/main/resources/assets/templatemod/lang/en_us.json b/src/main/resources/assets/templatemod/lang/en_us.json index a78542b..feb176b 100644 --- a/src/main/resources/assets/templatemod/lang/en_us.json +++ b/src/main/resources/assets/templatemod/lang/en_us.json @@ -1,21 +1,25 @@ { - "key.category.templatemod.controls": "Маршрут", - "key.PathtrackMod.start-recording": "Начать / закончить запись", - "key.templatemod.show_list": "Показать список точек", - "key.templatemod.pause_recording": "Пауза / продолжить запись", - "key.templatemod.clear_route": "Очистить маршрут", - "key.templatemod.start_return": "Начать / остановить возвращение", - "message.templatemod.recording_started": "Запись начата!", - "message.templatemod.recording_stopped": "Запись завершена", - "message.templatemod.return_hint": "Нажмите %s, чтобы начать возвращение", - "message.templatemod.bind_return_key": "Назначьте кнопку возвращения в настройках управления", - "message.templatemod.points_empty": "Список пуст", - "message.templatemod.route_cleared": "Маршрут очищен", - "message.templatemod.return_started": "Возвращение начато", - "message.templatemod.return_stopped": "Возвращение остановлено", - "message.templatemod.no_route": "Сначала запиши маршрут", - "message.templatemod.finish_recording": "Сначала заверши запись", - "message.templatemod.return_finished": "Ты вернулся к началу маршрута", - "hud.templatemod.return_distance": "%s бл.", - "hud.templatemod.return_progress": "Осталось точек: %s" + "key.category.templatemod.controls": "Route", + "key.PathtrackMod.start-recording": "Start / stop recording", + "key.templatemod.show_list": "Show recorded points", + "key.templatemod.pause_recording": "Pause / resume recording", + "key.templatemod.clear_route": "Clear route", + "key.templatemod.start_return": "Start / stop return guidance", + "message.templatemod.recording_started": "Recording started!", + "message.templatemod.recording_stopped": "Recording finished", + "message.templatemod.return_hint": "Press %s to start heading back", + "message.templatemod.bind_return_key": "Set a return key in the controls settings", + "message.templatemod.points_empty": "No points recorded", + "message.templatemod.route_cleared": "Route cleared", + "message.templatemod.return_started": "Heading back", + "message.templatemod.return_stopped": "Return guidance stopped", + "message.templatemod.no_route": "Record a route first", + "message.templatemod.finish_recording": "Finish recording first", + "message.templatemod.return_finished": "You're back at the start of the route", + "hud.templatemod.return_distance": "%s blocks", + "hud.templatemod.return_progress": "Points left: %s", + "config.templatemod.title": "Route settings", + "config.templatemod.category.recording": "Recording", + "config.templatemod.recording_interval_ticks": "Recording interval (ticks)", + "modmenu.descriptionTranslation.templatemod": "Records your route, marks it with arrows, and helps you find your way back." } diff --git a/src/main/resources/assets/templatemod/lang/ru_ru.json b/src/main/resources/assets/templatemod/lang/ru_ru.json index a78542b..98c1234 100644 --- a/src/main/resources/assets/templatemod/lang/ru_ru.json +++ b/src/main/resources/assets/templatemod/lang/ru_ru.json @@ -17,5 +17,9 @@ "message.templatemod.finish_recording": "Сначала заверши запись", "message.templatemod.return_finished": "Ты вернулся к началу маршрута", "hud.templatemod.return_distance": "%s бл.", - "hud.templatemod.return_progress": "Осталось точек: %s" + "hud.templatemod.return_progress": "Осталось точек: %s", + "config.templatemod.title": "Настройки маршрута", + "config.templatemod.category.recording": "Запись", + "config.templatemod.recording_interval_ticks": "Интервал записи, тики", + "modmenu.descriptionTranslation.templatemod": "Записывает маршрут, ставит стрелочки и помогает вернуться обратно." } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3e4595c..33aceec 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -4,7 +4,7 @@ "version": "${VERSION}", "name": "${MOD_NAME}", "icon": "assets/templatemod/textures/icon.png", - "description": "Записывает маршрут, ставит стрелочки и помогает вернуться обратно.", + "description": "Records your route, marks it with arrows, and helps you find your way back.", "authors": [ "WeNeedFood" ],