From 0b5bde6047920472036856b9a4c541cb01958bd0 Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Thu, 11 Sep 2025 02:46:40 +0800 Subject: [PATCH] =?UTF-8?q?feature:=201.=E6=B8=B2=E6=9F=93=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=AE=9E=E7=8E=B0=202.=E4=BC=98=E5=8C=96=E4=BA=86?= =?UTF-8?q?=E4=BA=9B=E7=BB=86=E8=8A=82=E5=AE=9E=E7=8E=B0(=E5=A6=82?= =?UTF-8?q?=E4=B8=BB=E6=89=8B=E6=8C=81=E6=9C=89=E6=8B=B4=E7=BB=B3=E5=B7=A6?= =?UTF-8?q?=E9=94=AE=E5=AE=9E=E4=BD=93(+shift)=E5=8F=AF=E6=96=AD=E5=BC=80?= =?UTF-8?q?=E6=8B=B4=E7=BB=B3=E5=AF=B9=E8=B1=A1(=E6=89=80=E6=9C=89)?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5)=203.=E6=B8=B8=E6=88=8F=E8=A7=84=E5=88=99=20?= =?UTF-8?q?todo:1.=E6=B8=B2=E6=9F=93=E5=99=A8=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=B9=B6=E6=9C=AA=E6=B8=85=E9=99=A4=EF=BC=88=E6=9A=82?= =?UTF-8?q?=E6=97=B6=EF=BC=89=202.=E6=B0=B8=E6=81=92=E5=9C=9F=E8=B1=86?= =?UTF-8?q?=E5=BE=85=E5=88=86=E7=A6=BB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .idea/runConfigurations/Client.xml | 15 ++ .idea/runConfigurations/RemoteDebug.run.xml | 16 ++ .run/RemoteDebug.run.xml | 15 ++ build.gradle | 47 +++++ doc/1.RenderSystem.MD | 91 +++++++++ .../superleadrope/CommonEventHandler.java | 34 +++- .../client/ClientEventHandler.java | 17 ++ .../client/renderer/LeashRenderHandler.java | 168 ++-------------- .../client/renderer/SLPRenderType.java | 7 +- .../client/renderer/SLPShaderRegistry.java | 39 ++++ .../client/renderer/SuperLeashRenderer.java | 186 ++++++++++++++++++ .../resolver/SuperLeashStateResolver.java | 7 +- .../renderer/state/SuperLeashRenderState.java | 9 +- .../capability/impi/EternalPotatoImpl.java | 4 +- .../capability/impi/LeashDataImpl.java | 122 +++++++++++- .../inter/ILeashDataCapability.java | 17 +- .../content/gamerule/SLPGamerules.java | 46 +++++ .../server/TeleportWithLeashedPlayers.java | 33 ++++ .../content/item/SuperLeadRopeItem.java | 15 +- .../core/leash/LeashInteractHandler.java | 24 ++- .../punishment/IObligationCompletion.java | 6 +- .../core/register/SLPGameruleRegistry.java | 76 +++++++ ...a => SLPObligationCompletionRegistry.java} | 2 +- .../datagen/data/SLPLangKeyValue.java | 11 ++ .../superleadrope/util/lang/ModPartEnum.java | 1 + .../shaders/core/super_leash.fsh | 44 +++++ .../shaders/core/super_leash.json | 34 ++++ .../shaders/core/super_leash.vsh | 91 +++++++++ .../textures/rope/rope_leash.png | Bin 0 -> 304 bytes 30 files changed, 986 insertions(+), 192 deletions(-) create mode 100644 .idea/runConfigurations/Client.xml create mode 100644 .idea/runConfigurations/RemoteDebug.run.xml create mode 100644 .run/RemoteDebug.run.xml create mode 100644 doc/1.RenderSystem.MD create mode 100644 src/main/java/top/r3944realms/superleadrope/client/renderer/SLPShaderRegistry.java create mode 100644 src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/gamerule/server/TeleportWithLeashedPlayers.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/register/SLPGameruleRegistry.java rename src/main/java/top/r3944realms/superleadrope/core/register/{ObligationCompletionRegistry.java => SLPObligationCompletionRegistry.java} (97%) create mode 100644 src/main/resources/assets/superleadrope/shaders/core/super_leash.fsh create mode 100644 src/main/resources/assets/superleadrope/shaders/core/super_leash.json create mode 100644 src/main/resources/assets/superleadrope/shaders/core/super_leash.vsh create mode 100644 src/main/resources/assets/superleadrope/textures/rope/rope_leash.png diff --git a/.gitignore b/.gitignore index 529eeb7..153ecf6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ bin/ .DS_Store # Ignore Gradle build output directory build +/RenderDoc_1.40_64/ diff --git a/.idea/runConfigurations/Client.xml b/.idea/runConfigurations/Client.xml new file mode 100644 index 0000000..d0f88ad --- /dev/null +++ b/.idea/runConfigurations/Client.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/RemoteDebug.run.xml b/.idea/runConfigurations/RemoteDebug.run.xml new file mode 100644 index 0000000..ecb2e91 --- /dev/null +++ b/.idea/runConfigurations/RemoteDebug.run.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/.run/RemoteDebug.run.xml b/.run/RemoteDebug.run.xml new file mode 100644 index 0000000..7ac6d5e --- /dev/null +++ b/.run/RemoteDebug.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index f9ed9be..d1cb255 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,7 @@ base { println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" repositories { + maven { url = "https://libraries.minecraft.net/" } maven { url = "https://neoforged.forgecdn.net/releases" } maven { url = "https://neoforged.forgecdn.net/mojang-meta" } @@ -70,6 +71,7 @@ repositories { includeModule 'me.lucko', 'spark-api' } } + mavenCentral() flatDir { dir "libs" } @@ -94,6 +96,15 @@ legacyForge { devLogin = true client() systemProperty 'forge.enabledGameTestNamespaces', project.mod_id +// // 设置 RenderDoc library 路径 +// // 系统属性,使用项目相对路径 +// systemProperty 'neoforge.rendernurse.renderdoc.library', file('RenderDoc_1.40_64/renderdoc.dll').absolutePath +// +// // JVM 参数 +// jvmArgument "-javaagent:${file('libs/RenderNurse-0.0.9.jar').absolutePath}" +// jvmArgument "--enable-preview" +// jvmArgument "--enable-native-access=ALL-UNNAMED" + } client { client() @@ -253,6 +264,42 @@ afterEvaluate { } } +tasks.register("runWithRenderDoc", Exec) { + group = "minecraft" + description = "Run Minecraft with RenderDoc using runClientAuth configuration" + dependsOn("classes") // 确保源码编译完成 + def jdwpArgs = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" + + def renderDocCmd = file("RenderDoc_1.40_64/renderdoccmd.exe").absolutePath + def captureDir = file("$buildDir/renderdoc/capture").absolutePath + + def runClientTask = tasks.named("runClientAuth", JavaExec).get() + def javaLauncher = runClientTask.getJavaLauncher().get() + def javaExecPath = new File(javaLauncher.metadata.installationPath.asFile, "bin/java.exe").absolutePath + + commandLine = [ + renderDocCmd, + "capture", + "--opt-hook-children", + "--wait-for-exit", + "--working-dir", "$projectDir/run", + "--capture-file", "${captureDir}/minecraft_capture", + javaExecPath, + jdwpArgs, + "@${buildDir}/moddev/clientRunVmArgs.txt", // JVM 参数 + "@${buildDir}/moddev/clientRunProgramArgs.txt" // Program 参数 + ] + + environment "MOD_CLASSES", "superleadrope%%${buildDir}/classes/java/main;superleadrope%%${buildDir}/resources/main" + + doFirst { + println "Using JVM: ${javaExecPath}" + println "RenderDoc capture dir: ${captureDir}" + println "Environment MOD_CLASSES: ${environment['MOD_CLASSES']}" + } +} + + // IDEA 支持 idea { module { diff --git a/doc/1.RenderSystem.MD b/doc/1.RenderSystem.MD new file mode 100644 index 0000000..af4cecd --- /dev/null +++ b/doc/1.RenderSystem.MD @@ -0,0 +1,91 @@ +## 1️⃣ `VertexFormat` 的作用 + +在 Minecraft 渲染系统中,`VertexFormat` 是**顶点格式的描述类**。它定义了: + +* 每个顶点包含哪些属性(position、color、normal、uv 等) +* 每个属性的数据类型和大小 +* 这些属性在 GPU 缓冲区中的排列顺序(stride 和 offset) + +换句话说,它告诉 GPU **如何从 VBO 里正确读取顶点数据**。 + +Minecraft 不再直接使用 `glVertex3f` 或 `glColor4f`,而是通过 `VertexFormat` + `BufferBuilder` 构建顶点数组,然后一次性上传到 GPU。 + +--- + +## 2️⃣ `VertexFormat` 的核心结构 + +在 Minecraft 中,`VertexFormat` 类主要包含: + +| 成员 | 含义 | +| ------------ | ---------------------------------- | +| `elements` | 顶点属性列表,每个元素是 `VertexFormatElement` | +| `vertexSize` | 每个顶点的总字节数(stride) | +| `hasColor` | 是否包含颜色属性 | +| `hasNormal` | 是否包含法线 | +| `hasUV` | 是否包含纹理坐标 | + +### `VertexFormatElement` + +每个元素描述一个顶点属性,包含: + +| 属性 | 含义 | +| ------- | -------------------------------------- | +| `type` | 数据类型(FLOAT、UBYTE 等) | +| `usage` | 属性用途(POSITION、COLOR、UV、NORMAL、PADDING) | +| `count` | 该属性的分量个数(例如 POSITION = 3, COLOR = 4) | +| `index` | 纹理坐标索引(有多个 UV 时用) | + +**示例:** +Minecraft 默认块渲染顶点格式可能包含: + +```text +POSITION (3 floats) +COLOR (4 bytes) +UV0 (2 floats) +NORMAL (3 bytes) +UV1 (2 floats, 光照贴图) +``` + +--- + +## 3️⃣ `VertexFormat` 在渲染管线中的位置 + +1. **顶点构建** + + ```java + BufferBuilder buffer = Tesselator.getInstance().getBuilder(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); + buffer.vertex(x, y, z).color(r, g, b, a).uv(u, v).normal(nx, ny, nz).endVertex(); + ``` + +2. **上传 GPU** + + * `BufferBuilder` 会根据 `VertexFormat` 的定义把每个顶点的数据打包成连续的 **VBO 字节流** + * 使用 `glVertexAttribPointer` 告诉 GPU 每个属性的偏移量和类型 + +3. **绑定着色器** + + * 着色器使用 `layout(location = X)` 对应 VertexFormat 的属性 + * GPU 就能正确读取每个顶点属性用于渲染 + +--- + +## 4️⃣ 常用顶点格式示例 + +Minecraft 1.20.1 内置了几种常用格式: + +| 名称 | 用途 | +| ----------------------------------------------- | ----------------------------------------- | +| `DefaultVertexFormat.POSITION_COLOR` | GUI、简单方块渲染 | +| `DefaultVertexFormat.BLOCK` | 方块渲染,包含 position/color/uv/normal/lightmap | +| `DefaultVertexFormat.ITEM` | 物品渲染,类似 BLOCK | +| `DefaultVertexFormat.POSITION_TEX_COLOR_NORMAL` | 高级渲染,需要所有属性 | + +--- + +## 5️⃣ 总结 + +* `VertexFormat` 是 **顶点数据布局描述器**,告诉 GPU 每个顶点包含哪些属性、类型和顺序。 +* Minecraft 的现代渲染完全基于 **VBO + VAO + 着色器**,`VertexFormat` 作为桥梁,让 Java 层构建的顶点数据正确映射到 GPU。 +* 对于 mod 开发或自定义渲染,理解 VertexFormat 非常关键,因为你需要确保 **BufferBuilder 构建的数据格式和着色器匹配**。 + diff --git a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java index d95e9fe..a4c9959 100644 --- a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java @@ -40,6 +40,7 @@ import net.minecraftforge.event.entity.EntityJoinLevelEvent; import net.minecraftforge.event.entity.EntityLeaveLevelEvent; import net.minecraftforge.event.entity.EntityTeleportEvent; import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.entity.player.AttackEntityEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.level.LevelEvent; @@ -54,11 +55,13 @@ import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato; import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; +import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedPlayers; import top.r3944realms.superleadrope.content.item.EternalPotatoItem; import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem; import top.r3944realms.superleadrope.core.leash.LeashInteractHandler; import top.r3944realms.superleadrope.core.leash.LeashSyncManager; import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade; +import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry; import top.r3944realms.superleadrope.core.register.SLPItems; import top.r3944realms.superleadrope.core.util.PotatoMode; import top.r3944realms.superleadrope.core.util.PotatoModeHelper; @@ -84,6 +87,13 @@ public class CommonEventHandler { if (entity.level().isClientSide) return; if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) { entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager::track); + if (entity instanceof ServerPlayer serverPlayer) { + LeashSyncManager.forEach(i -> { + if (i.isLeashedBy(serverPlayer) && i.isInDelayedLeash(serverPlayer.getUUID())) { + i.removeDelayedLeash(serverPlayer.getUUID());//重新加入去除延迟 + } + }); + } } } @@ -92,6 +102,13 @@ public class CommonEventHandler { Entity entity = event.getEntity(); if (entity.level().isClientSide) return; if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) { + if (entity instanceof ServerPlayer serverPlayer) { + LeashSyncManager.forEach(i -> { + if(i.isLeashedBy(serverPlayer)) { + i.addDelayedLeash(serverPlayer); //添加延迟 + } + }); + } entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager::untrack); } } @@ -230,7 +247,11 @@ public class CommonEventHandler { // 获取范围内可被拴住实体 List entities = LeashDataImpl.leashableInArea(telEntity); - + //规则关闭则禁止 + if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedPlayers.ID)) { + entities.forEach(i -> i.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(j -> j.removeLeash(i))); + return; + } for (Entity beLeashedEntity : entities) { // --- 保存状态快照 --- Pose originalPose = beLeashedEntity.getPose(); @@ -245,6 +266,7 @@ public class CommonEventHandler { cap.removeLeash(telEntity); }); + // --- 保存骑乘关系(可修改列表) --- RidingRelationship originalRidingRelationship = RidingSaver.save(beLeashedEntity, true); @@ -309,10 +331,13 @@ public class CommonEventHandler { LeashSyncManager.forEach(ILeashDataCapability::applyLeashForces); } } - + @SubscribeEvent + public static void onEntityAttack (AttackEntityEvent event) { + LeashInteractHandler.onEntityLeftInteract(event.getEntity().level(), event.getTarget(), event.getEntity(), event); + } @SubscribeEvent public static void onEntityInteract (PlayerInteractEvent.EntityInteract event) { - LeashInteractHandler.onEntityInteract(event.getLevel(), event.getHand(), event.getTarget(), event.getEntity(), event); //处理实体互动 + LeashInteractHandler.onEntityRightInteract(event.getLevel(), event.getHand(), event.getTarget(), event.getEntity(), event); //处理实体互动 } @SubscribeEvent @@ -324,8 +349,7 @@ public class CommonEventHandler { public static class Mod { @SubscribeEvent public static void onCommonInit (FMLCommonSetupEvent event) { - event.enqueueWork(() -> { - }); + event.enqueueWork(SLPGameruleRegistry::register);//规则注册 } @SubscribeEvent public static void registerCapability(RegisterCapabilitiesEvent event) { diff --git a/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java b/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java index 13bf104..80e66d1 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java @@ -22,23 +22,36 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.EntityRenderersEvent; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.client.event.RenderLevelStageEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.client.model.SuperLeashKnotModel; import top.r3944realms.superleadrope.client.model.geom.SLPModelLayers; +import top.r3944realms.superleadrope.client.renderer.LeashRenderHandler; +import top.r3944realms.superleadrope.client.renderer.SLPShaderRegistry; import top.r3944realms.superleadrope.client.renderer.entity.SuperLeashKnotRenderer; import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade; import top.r3944realms.superleadrope.core.register.SLPEntityTypes; import top.r3944realms.superleadrope.core.register.SLPItems; import top.r3944realms.superleadrope.core.util.PotatoMode; +import java.io.IOException; + import static top.r3944realms.superleadrope.core.util.PotatoModeHelper.getCurrentMode; @OnlyIn(Dist.CLIENT) public class ClientEventHandler { @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, value = Dist.CLIENT, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE) public static class Game { + @SubscribeEvent + public static void onLevelRenderer (RenderLevelStageEvent event) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_ENTITIES) { + return; + } + LeashRenderHandler.onRenderLevelStage(event.getPoseStack(), event.getPartialTick()); + } // 未使用-注释 @SubscribeEvent public static void onPlayerLoggedOut(ClientPlayerNetworkEvent.LoggingOut event) { @@ -71,5 +84,9 @@ public class ClientEventHandler { public static void onRegisterRenderer (EntityRenderersEvent.RegisterRenderers event) { event.registerEntityRenderer(SLPEntityTypes.SUPER_LEAD_KNOT.get(), SuperLeashKnotRenderer::new); } + @SubscribeEvent + public static void onRegisterShaders(RegisterShadersEvent event) throws IOException { + SLPShaderRegistry.registerShaders(event); + } } } diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java index ee0e208..eefaf27 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java @@ -16,23 +16,14 @@ package top.r3944realms.superleadrope.client.renderer; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.client.event.RenderLevelStageEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import org.joml.Matrix4f; import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver; -import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState; import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; @@ -40,195 +31,62 @@ import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import java.util.Optional; import java.util.UUID; -@Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, value = Dist.CLIENT) public class LeashRenderHandler { - - @OnlyIn(Dist.CLIENT) - @SubscribeEvent - public static void onRenderLevelStage(RenderLevelStageEvent event) { - if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_ENTITIES) { - return; - } - - renderAllCustomLeashes(event.getPoseStack(), event.getPartialTick()); + public static void onRenderLevelStage(PoseStack poseStack, float partialTick) { + renderAllCustomLeashes(poseStack, partialTick); } private static void renderAllCustomLeashes(PoseStack poseStack, float partialTick) { Minecraft minecraft = Minecraft.getInstance(); Level level = minecraft.level; Entity cameraEntity = minecraft.getCameraEntity(); - if (level == null || cameraEntity == null) return; MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource(); + // 遍历摄像机附近所有实体 for (Entity entity : level.getEntitiesOfClass(Entity.class, cameraEntity.getBoundingBox().inflate(50))) { entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashData -> { + if(leashData instanceof ILeashDataCapability) {} for (ILeashDataCapability.LeashInfo leashInfo : leashData.getAllLeashes()) { renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick); } }); } - } private static void renderLeashFromInfo(Entity entity, ILeashDataCapability.LeashInfo leashInfo, PoseStack poseStack, MultiBufferSource bufferSource, float partialTick) { try { - Optional holderOpt = getHolderFromLeashInfo((ClientLevel) entity.level(), leashInfo); + Optional holderOpt = getHolderFromLeashInfo(entity.level(), leashInfo); if (holderOpt.isEmpty()) return; Entity holder = holderOpt.get(); - Optional stateOpt = SuperLeashStateResolver.resolve( - holder, entity, leashInfo, partialTick + // 构建渲染状态 + SuperLeashStateResolver.resolve(entity, holder, leashInfo, partialTick).ifPresent( + leashRenderState -> SuperLeashRenderer.renderLeash(leashRenderState, poseStack, bufferSource) ); - stateOpt.ifPresent(state -> { - // ✅ 每个渲染都创建自己的 PoseStack,避免污染全局 - PoseStack localStack = new PoseStack(); - localStack.mulPoseMatrix(poseStack.last().pose()); - - renderSingleLeash(state, localStack, bufferSource, partialTick); - }); } catch (Exception e) { - SuperLeadRope.logger.error("Error rendering leash: {}", e.getMessage()); + SuperLeadRope.logger.error("Failed to render leash", e); } } - private static Optional getHolderFromLeashInfo(ClientLevel level, ILeashDataCapability.LeashInfo leashInfo) { - // 根据LeashInfo类型获取持有者 + private static Optional getHolderFromLeashInfo(Level level, ILeashDataCapability.LeashInfo leashInfo) { if (leashInfo.blockPosOpt().isPresent()) { - // 拴绳结持有者 BlockPos pos = leashInfo.blockPosOpt().get(); - SuperLeashKnotEntity knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos); - return Optional.of(knot); + return Optional.of(SuperLeashKnotEntity.getOrCreateKnot(level, pos)); } else if (leashInfo.holderUUIDOpt().isPresent()) { - // 实体持有者 UUID holderUUID = leashInfo.holderUUIDOpt().get(); - // 在客户端,我们需要通过ID查找实体 - if (leashInfo.holderIdOpt().isPresent()) { - int holderId = leashInfo.holderIdOpt().get(); - Entity holder = level.getEntity(holderId); - if (holder != null) { - return Optional.of(holder); - } - } - // 备用方案:通过UUID查找(可能效率较低) - for (Entity entity : level.entitiesForRendering()) { - if (entity.getUUID().equals(holderUUID)) { - return Optional.of(entity); - } + for (Entity e : ((ClientLevel)level).entitiesForRendering()) { + if (e.getUUID().equals(holderUUID)) return Optional.of(e); } } return Optional.empty(); } - - private static void renderSingleLeash(SuperLeashRenderState state, PoseStack poseStack, - MultiBufferSource bufferSource, float partialTick) { - poseStack.pushPose(); - try { - // 相机位置平移 - Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); - poseStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z); - - // 获取缓冲区 - VertexConsumer consumer = bufferSource.getBuffer(SLPRenderType.leashType()); - - // 绘制拴绳 - renderLeashAsTriangleStrip(state, consumer, poseStack, partialTick); - - } finally { - // ✅ 保证无论发生什么都会弹栈 - poseStack.popPose(); - } - } - - private static void renderLeashAsTriangleStrip(SuperLeashRenderState state, - VertexConsumer consumer, - PoseStack poseStack, - float partialTick) { - Vec3 start = state.startPos(); - Vec3 end = state.endPos(); - double length = start.distanceTo(end); - - if (length < 0.001) return; - - Vec3 direction = end.subtract(start).normalize(); - - // 计算垂直向量用于厚度 - Vec3 perpendicular = calculatePerpendicularVector(direction); - - Matrix4f pose = poseStack.last().pose(); - int color = state.color(); - float r = ((color >> 16) & 0xFF) / 255.0f; - float g = ((color >> 8) & 0xFF) / 255.0f; - float b = (color & 0xFF) / 255.0f; - float a = state.isCritical() ? 0.8f : 1.0f; - - // 分段渲染(使用三角形带) - final int SEGMENTS = 12; - for (int i = 0; i <= SEGMENTS; i++) { - float progress = (float) i / SEGMENTS; - - // 计算当前段中心位置(包含摆动效果) - Vec3 center = start.add(direction.scale(length * progress)); - Vec3 swingOffset = state.getSwingOffset(progress, partialTick); - center = center.add(swingOffset); - - // 应用张力拉伸效果 - if (state.stretchRatio() > 1.0f) { - float stretchFactor = (float)Math.sin(progress * Math.PI) * 0.2f * (state.stretchRatio() - 1.0f); - center = center.add(direction.scale(stretchFactor * length)); - } - - // 计算当前厚度 - float currentThickness = state.thickness() * (1 - progress * 0.3f); - - // 添加两个顶点形成带子 - Vec3 top = center.add(perpendicular.scale(currentThickness)); - Vec3 bottom = center.subtract(perpendicular.scale(currentThickness)); - - consumer.vertex(pose, (float)top.x, (float)top.y, (float)top.z) - .color(r, g, b, a) - .uv(0.0F, 0.0F) // 固定UV,随便填 - .uv2(0xF000F0) // 光照,全亮 - .normal(0.0F, 1.0F, 0.0F) // 法线,朝上 - .endVertex(); - - consumer.vertex(pose, (float)bottom.x, (float)bottom.y, (float)bottom.z) - .color(r, g, b, a) - .uv(1.0F, 0.0F) // 对应另一边UV - .uv2(0xF000F0) - .normal(0.0F, 1.0F, 0.0F) - .endVertex(); - } - } - - private static Vec3 calculatePerpendicularVector(Vec3 direction) { - // 计算一个与方向向量垂直的向量 - Vec3 perpendicular = new Vec3(-direction.z, 0, direction.x).normalize(); - if (perpendicular.lengthSqr() < 1.0E-7D) { - // 如果方向是垂直的,使用另一个计算方法 - perpendicular = new Vec3(0, -direction.z, direction.y).normalize(); - } - return perpendicular; - } - - // 距离和视野检查(性能优化) - private static boolean shouldRenderLeash(Entity entity, Entity holder) { - Minecraft minecraft = Minecraft.getInstance(); - Entity cameraEntity = minecraft.getCameraEntity(); - - if (cameraEntity == null) return false; - - // 距离检查(只渲染50格内) - double distanceToEntity = entity.distanceToSqr(cameraEntity); - double distanceToHolder = holder.distanceToSqr(cameraEntity); - return !(distanceToEntity > 50 * 50) || !(distanceToHolder > 50 * 50); - } } \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java index a1c54f1..dc6756b 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java @@ -20,6 +20,8 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormatElement; import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import top.r3944realms.superleadrope.SuperLeadRope; public class SLPRenderType extends RenderType { public SLPRenderType(String name, VertexFormat format, VertexFormat.Mode mode, int bufferSize, boolean affectsCrumbling, boolean sortOnUpload, Runnable setupState, Runnable clearState) { @@ -35,6 +37,7 @@ public class SLPRenderType extends RenderType { ImmutableMap.builder() .put("Position", DefaultVertexFormat.ELEMENT_POSITION) .put("Color", DefaultVertexFormat.ELEMENT_COLOR) + .put("UV0", DefaultVertexFormat.ELEMENT_UV0) // 纹理坐标 .put("UV2", DefaultVertexFormat.ELEMENT_UV2) // 光照 .put("Normal", DefaultVertexFormat.ELEMENT_NORMAL) // 法线 .put("Padding", DefaultVertexFormat.ELEMENT_PADDING) @@ -47,8 +50,8 @@ public class SLPRenderType extends RenderType { false, // not used for crumbling false, // sortOnUpload CompositeState.builder() - .setShaderState(RENDERTYPE_LEASH_SHADER) // 使用实体着色器 - .setTextureState(NO_TEXTURE) // 无纹理 + .setShaderState(new ShaderStateShard(() -> SLPShaderRegistry.ROPE_SHADER)) // 使用实体着色器 + .setTextureState(new TextureStateShard(new ResourceLocation(SuperLeadRope.MOD_ID, "textures/rope/rope_leash.png"), false, false)) .setTransparencyState(TRANSLUCENT_TRANSPARENCY) .setLightmapState(LIGHTMAP) // 启用光照 .setCullState(NO_CULL) // 双面渲染 diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPShaderRegistry.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPShaderRegistry.java new file mode 100644 index 0000000..a897072 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPShaderRegistry.java @@ -0,0 +1,39 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.client.renderer; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.event.RegisterShadersEvent; +import top.r3944realms.superleadrope.SuperLeadRope; + +import java.io.IOException; + +public class SLPShaderRegistry { + private static final ResourceLocation RL_SUPER_ROPE = new ResourceLocation(SuperLeadRope.MOD_ID, "super_leash"); + public static ShaderInstance ROPE_SHADER; + public static void registerShaders(RegisterShadersEvent event) throws IOException { + event.registerShader( + new ShaderInstance( + event.getResourceProvider(), + RL_SUPER_ROPE, + DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP + ), + shader -> ROPE_SHADER = shader + ); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java new file mode 100644 index 0000000..7bc829d --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/SuperLeashRenderer.java @@ -0,0 +1,186 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.client.renderer; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState; + +@SuppressWarnings("UnnecessaryLocalVariable") +public class SuperLeashRenderer { + + private static final int LEASH_STEPS = 24; + + public static void renderLeash(SuperLeashRenderState state, PoseStack poseStack, MultiBufferSource buffer) { + poseStack.pushPose(); + + Vec3 startWorld = state.startPos(); + Vec3 endWorld = state.endPos(); + + VertexConsumer consumer = buffer.getBuffer(RenderType.leash()); + + Minecraft mc = Minecraft.getInstance(); + Vec3 camPos = mc.gameRenderer.getMainCamera().getPosition(); + poseStack.translate(startWorld.x - camPos.x, startWorld.y - camPos.y, startWorld.z - camPos.z); + Matrix4f matrix = poseStack.last().pose(); + + int blockLightStart = getBlockLight(BlockPos.containing(startWorld)); + int blockLightEnd = getBlockLight(BlockPos.containing(endWorld)); + int skyLightStart = getSkyLight(BlockPos.containing(startWorld)); + int skyLightEnd = getSkyLight(BlockPos.containing(endWorld)); + + // 差向量 + 偏移 + Offsets offsets = computeOffsets(state, startWorld, endWorld); + + // pass1: 0 → N + for (int i = 0; i <= LEASH_STEPS; i++) { + float f = (float)i / LEASH_STEPS; + int packedLight = packLight(blockLightStart, blockLightEnd, skyLightStart, skyLightEnd, f); + float[] rgb = computeColor(state, i, false); + addVertexPair(consumer, matrix, offsets, packedLight, rgb, i, true); + } + + // pass2: N → 0 + for (int i = LEASH_STEPS; i >= 0; i--) { + float f = (float)i / LEASH_STEPS; + int packedLight = packLight(blockLightStart, blockLightEnd, skyLightStart, skyLightEnd, f); + float[] rgb = computeColor(state, i, true); + addVertexPair(consumer, matrix, offsets, packedLight, rgb, i, false); + } + + poseStack.popPose(); + } + + + + /** 计算差向量和偏移 */ + private static Offsets computeOffsets(SuperLeashRenderState state, Vec3 start, Vec3 end) { + float dx = (float) (end.x - start.x); + float dy = (float) (end.y - start.y); + float dz = (float) (end.z - start.z); + + float base = 0.05f; + float horiz = dx * dx + dz * dz; + float inv = (horiz > 1e-8F) ? Mth.invSqrt(horiz) * base / 2.0F : 0.0F; + float xOffset = dz * inv; + float zOffset = dx * inv; + + float yOffsetPass1 = base; + float dyOffsetPass1 = base; + + // pass2 纵向不改 py,交叉通过厚度方向 ± 偏移实现 + float yOffsetPass2 = base; + float dyOffsetPass2 = -dyOffsetPass1; // 反向交叉 + + return new Offsets(dx, dy, dz, xOffset, zOffset, + yOffsetPass1, dyOffsetPass1, yOffsetPass2, dyOffsetPass2); + } + + // 光照计算 + private static int packLight(int blockStart, int blockEnd, int skyStart, int skyEnd, float f) { + int block = (int) Mth.lerp(f, blockStart, blockEnd); + int sky = (int) Mth.lerp(f, skyStart, skyEnd); + return LightTexture.pack(block, sky); + } + + // 颜色渐变 + private static float[] computeColor(SuperLeashRenderState state, int index, boolean reversePass) { + float distance = (float) state.startPos().distanceTo(state.endPos()); + float ratio = Mth.clamp(distance / (state.maxDistance() * 2f), 0f, 1f); + + // 定义颜色 + float rStart = 0.42f; // 深棕 R + float gStart = 0.31f; // 深棕 G + float bStart = 0.18f; // 深棕 B + + float rEnd = 0.69f; // 暗红 R + float gEnd = 0.23f; // 暗红 G + float bEnd = 0.18f; // 暗红 B + + // 线性插值 + float r = Mth.lerp(ratio, rStart, rEnd); + float g = Mth.lerp(ratio, gStart, gEnd); + float b = Mth.lerp(ratio, bStart, bEnd); + + // 原版交替颜色 + float colorFactor = index % 2 == (reversePass ? 1 : 0) ? 0.7f : 1.0f; + return new float[]{r * colorFactor, g * colorFactor, b * colorFactor}; + } + + // 顶点生成 + private static void addVertexPair(VertexConsumer consumer, Matrix4f matrix, Offsets offsets, int packedLight, float[] rgb, int index, boolean usePass1) { + float f = (float) index / LEASH_STEPS; + float px = offsets.dx * f; + // py 保持曲线,用于纹理 + float py = offsets.dy > 0 ? offsets.dy * f * f : offsets.dy - offsets.dy * (1 - f) * (1 - f); + float pz = offsets.dz * f; + + // 微摆动,避免长绳子顶点重合 + float sway = 0.01f * (float)Math.sin(f * Math.PI * 6 + index); + + float yOffset = usePass1 ? offsets.yOffsetPass1 : offsets.yOffsetPass2; + float dyOffset = usePass1 ? offsets.dyOffsetPass1 : offsets.dyOffsetPass2; + + consumer.vertex(matrix, px - offsets.xOffset, py + dyOffset + sway, pz + offsets.zOffset) + .color(rgb[0], rgb[1], rgb[2], 1f).uv2(packedLight).endVertex(); + consumer.vertex(matrix, px + offsets.xOffset, py + yOffset - dyOffset - sway, pz - offsets.zOffset) + .color(rgb[0], rgb[1], rgb[2], 1f).uv2(packedLight).endVertex(); + } + private static int getBlockLight(BlockPos pos) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) return 15; // 默认亮度,防止空指针 + return mc.level.getBrightness(LightLayer.BLOCK, pos); + } + + private static int getSkyLight(BlockPos pos) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) return 15; // 默认亮度,防止空指针 + return mc.level.getBrightness(LightLayer.SKY, pos); + } + + /** 横纵偏移数据 */ + private static class Offsets { + public final float dx, dy, dz; // 绳子差向量 + public final float xOffset, zOffset; // 横向偏移 + public final float yOffsetPass1, dyOffsetPass1; // pass1 纵向偏移 + public final float yOffsetPass2, dyOffsetPass2; // pass2 纵向偏移 + + public Offsets(float dx, float dy, float dz, + float xOffset, float zOffset, + float yOffsetPass1, float dyOffsetPass1, + float yOffsetPass2, float dyOffsetPass2) { + this.dx = dx; + this.dy = dy; + this.dz = dz; + this.xOffset = xOffset; + this.zOffset = zOffset; + this.yOffsetPass1 = yOffsetPass1; + this.dyOffsetPass1 = dyOffsetPass1; + this.yOffsetPass2 = yOffsetPass2; + this.dyOffsetPass2 = dyOffsetPass2; + } + } + +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java index 500a1f4..9304c87 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java @@ -25,7 +25,7 @@ import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapabili import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import java.util.*; - +//TODO: 未来实现更高级的渲染 public class SuperLeashStateResolver { private static final float MAX_TENSION = 1.5f; private static final float THICKNESS_BASE = 0.1f; @@ -68,7 +68,7 @@ public class SuperLeashStateResolver { double distance = currentHolderPos.distanceTo(currentEntityPos); double maxDistance = leashInfo.maxDistance(); double elasticDistance = leashInfo.elasticDistance(); - float tension = calculateTension(distance, maxDistance, elasticDistance); + float tension = calculateTension(distance, maxDistance, elasticDistance);//这里暂时没用上 float stretchRatio = (float) (distance / maxDistance); boolean isCritical = distance > maxDistance * 1.5; @@ -97,7 +97,8 @@ public class SuperLeashStateResolver { selectColor(tension, isCritical), THICKNESS_BASE + (tension * THICKNESS_TENSION), swing.angle(), - swing.speed() + swing.speed(), + (float) leashInfo.maxDistance() )); } diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java index 8365ee3..ce84aec 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/state/SuperLeashRenderState.java @@ -30,12 +30,13 @@ public record SuperLeashRenderState( int color, // 颜色(根据状态变化) float thickness, // 线宽(根据张力变化) float swingAngle, // 当前摆动角度(弧度) - float swingSpeed // 摆动速度(弧度/tick) + float swingSpeed, // 摆动速度(弧度/tick) + float maxDistance // 最大距离 ) { // 预定义颜色常量 - public static final int COLOR_NORMAL = 0xFF8B4513; // 棕色(正常) - public static final int COLOR_TENSION = 0xFFFFA500; // 橙色(紧张) - public static final int COLOR_CRITICAL = 0xFFFF0000; // 红色(临界) + public static final int COLOR_NORMAL = 0xFF6B4E2E; // 深棕色(木绳色,温暖自然) + public static final int COLOR_TENSION = 0xFFD9A066; // 黄色偏橙(张力稍高时微亮) + public static final int COLOR_CRITICAL = 0xFFB03A2E; // 暗红色(即将断裂,警告色) /** * 计算当前帧的摆动偏移量(用于波浪效果) diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java index 27d6203..39d75db 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java @@ -23,7 +23,7 @@ import net.minecraftforge.server.ServerLifecycleHooks; import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato; import top.r3944realms.superleadrope.core.punishment.IObligationCompletion; import top.r3944realms.superleadrope.core.punishment.PunishmentDefinition; -import top.r3944realms.superleadrope.core.register.ObligationCompletionRegistry; +import top.r3944realms.superleadrope.core.register.SLPObligationCompletionRegistry; import top.r3944realms.superleadrope.network.NetworkHandler; import top.r3944realms.superleadrope.network.toClient.EternalPotatoSyncCapPacket; @@ -298,7 +298,7 @@ public class EternalPotatoImpl implements IEternalPotato { // 完成规则反序列化 if (nbt.contains(TAG_COMPLETION_ID)) { String id = nbt.getString(TAG_COMPLETION_ID); - IObligationCompletion rule = ObligationCompletionRegistry.byId(id); + IObligationCompletion rule = SLPObligationCompletionRegistry.byId(id); completionRule = rule != null ? rule : IObligationCompletion.NONE; } } diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java index 29a8db2..80a93fb 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java @@ -25,6 +25,7 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.animal.horse.Llama; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.entity.vehicle.Minecart; import net.minecraft.world.item.ItemStack; @@ -44,6 +45,7 @@ import top.r3944realms.superleadrope.util.riding.RindingLeash; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -83,10 +85,11 @@ public class LeashDataImpl implements ILeashDataCapability { private static final double LEASH_EXTREME_SNAP_DIST_FACTOR = 2.0; // 断裂距离 = 最大距离 * 2 //TODO:未来可配置 private static final float SPRING_DAMPENING = 0.7f; // 阻尼系数 private static final Vec3 AXIS_SPECIFIC_ELASTICITY = new Vec3(0.8, 0.2, 0.8); // 轴向弹性系数(Y轴较弱) - private static final int MAX_LEASHES_PER_ENTITY = 3;//一个实体最多链接多少个拴绳 //TODO:未来可配置 + private static final int MAX_LEASHES_PER_ENTITY = 6;//一个实体最多链接多少个拴绳 //TODO:未来可配置 private final Entity entity; private boolean needsSync = false; private long lastSyncTime; + private final Set delayedHolders = new CopyOnWriteArraySet<>(); private final Map leashHolders = new ConcurrentHashMap<>(); // 引入解决 绳结不保存导致第二进入持有者不存在的问题 private final Map leashKnots = new ConcurrentHashMap<>(); @@ -144,9 +147,16 @@ public class LeashDataImpl implements ILeashDataCapability { @Override public boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks) { boolean isSuperKnot = holder instanceof SuperLeashKnotEntity; - if (!canBeLeashed() || (!isSuperKnot && leashHolders.containsKey(holder.getUUID()) || (isSuperKnot && leashKnots.containsKey(((SuperLeashKnotEntity) holder).getPos())))) { + if ((!isSuperKnot && leashHolders.containsKey(holder.getUUID()) || (isSuperKnot && leashKnots.containsKey(((SuperLeashKnotEntity) holder).getPos())))) { return false; } + if (!canBeLeashed()) { + Optional uuidOptional = occupyLeash(); + if (uuidOptional.isEmpty()) { + return false; + } + removeLeash(uuidOptional.get()); + } LeashInfo info = LeashInfo.CreateLeashInfo( holder, leashStack.getItem().getDescription().toString(), @@ -169,6 +179,16 @@ public class LeashDataImpl implements ILeashDataCapability { return addLeash(holder, ItemStack.EMPTY, leashInfo.maxDistance(), leashInfo.elasticDistance(), leashInfo.maxKeepLeashTicks()); } + @Override + public boolean addDelayedLeash(Player holderPlayer) { + return delayedHolders.add(holderPlayer.getUUID()); + } + + @Override + public boolean removeDelayedLeash(UUID onceHolderUUID) { + return delayedHolders.remove(onceHolderUUID); + } + @Override public boolean setMaxDistance(Entity holder, double newMaxDistance) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? @@ -395,8 +415,10 @@ public class LeashDataImpl implements ILeashDataCapability { if (uuidHolder != null) { return calculateLeashForce(uuidHolder, entry); } else { - SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found(it will be removed from list).", uuid); - leashHolders.remove(uuid); + if (!delayedHolders.contains(uuid)) { + SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found(it will be removed from list).", uuid); + leashHolders.remove(uuid); + } return null; } } @@ -517,6 +539,25 @@ public class LeashDataImpl implements ILeashDataCapability { return removed; } + @Override + public void removeAllLeashes() { + leashHolders.clear(); + leashKnots.clear(); + markForSync(); + } + + @Override + public void removeAllHolderLeashes() { + leashHolders.clear(); + markForSync(); + } + + @Override + public void removeAllKnotLeashes() { + leashKnots.clear(); + markForSync(); + } + @Override public boolean transferLeash(Entity holder, Entity newHolder) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? @@ -591,6 +632,21 @@ public class LeashDataImpl implements ILeashDataCapability { return true; } + @Override + public boolean hasLeash() { + return !leashKnots.isEmpty() || !leashHolders.isEmpty(); + } + + @Override + public boolean hasKnotLeash() { + return !leashKnots.isEmpty(); + } + + @Override + public boolean hasHolderLeash() { + return !leashHolders.isEmpty(); + } + //只能系在这些实体上,在这里,其它情况一律忽略 //TODO: 标签支持控制 public static boolean isLeashable(Entity entity) { @@ -625,6 +681,11 @@ public class LeashDataImpl implements ILeashDataCapability { return leashKnots.containsKey(knotPos); } + @Override + public boolean isInDelayedLeash(UUID holderUUID) { + return delayedHolders.contains(holderUUID); + } + @Override public Optional getLeashInfo(Entity holder) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? @@ -646,7 +707,7 @@ public class LeashDataImpl implements ILeashDataCapability { public CompoundTag serializeNBT() { CompoundTag tag = new CompoundTag(); ListTag holdersList = new ListTag(); - + ListTag delayedHolderList = new ListTag(); for (LeashInfo info : leashHolders.values()) { CompoundTag infoTag = generateCompoundTagFromUUIDLeashInfo(info); holdersList.add(infoTag); @@ -655,10 +716,19 @@ public class LeashDataImpl implements ILeashDataCapability { CompoundTag infoTag = generateCompoundTagFromBlockPosLeashInfo(info); holdersList.add(infoTag); } + for (UUID uuid : delayedHolders) { + CompoundTag infoTag = generateCompoundTagFromUUID(uuid); + delayedHolderList.add(infoTag); + } tag.put("LeashHolders", holdersList); + tag.put("DelayedHolders", delayedHolderList); return tag; } - + private static @NotNull CompoundTag generateCompoundTagFromUUID(@NotNull UUID uuid) { + CompoundTag infoTag = new CompoundTag(); + infoTag.putUUID("DelayHolderUUID", uuid); + return infoTag; + } private static @NotNull CompoundTag generateCompoundTagFromUUIDLeashInfo(@NotNull LeashInfo info) { CompoundTag infoTag = new CompoundTag(); if (info.holderUUIDOpt().isEmpty()) { @@ -699,6 +769,7 @@ public class LeashDataImpl implements ILeashDataCapability { public void deserializeNBT(@NotNull CompoundTag nbt) { leashHolders.clear(); leashKnots.clear(); + delayedHolders.clear(); if (nbt.contains("LeashHolders", ListTag.TAG_LIST)) { ListTag holdersList = nbt.getList("LeashHolders", ListTag.TAG_COMPOUND); @@ -713,13 +784,50 @@ public class LeashDataImpl implements ILeashDataCapability { } } } + if (nbt.contains("DelayedHolders", ListTag.TAG_LIST)) { + ListTag delayedHolderList = nbt.getList("DelayedHolders", ListTag.TAG_COMPOUND); + for (int i = 0; i < delayedHolderList.size(); i++) { + CompoundTag infoTag = delayedHolderList.getCompound(i); + UUID delayedUUIDFormListTag = getDelayedUUIDFormListTag(infoTag); + delayedHolders.add(delayedUUIDFormListTag); + } + } } @Override public boolean canBeLeashed() { - return leashHolders.size() <= MAX_LEASHES_PER_ENTITY; + return (leashHolders.size() + leashKnots.size()) <= MAX_LEASHES_PER_ENTITY; } + @Override + public Optional occupyLeash() { + if (canBeLeashed() || delayedHolders.isEmpty()) return Optional.empty(); + // 从 Set 随机取一个 UUID + int size = delayedHolders.size(); + int index = (int) (Math.random() * size); // 0 ~ size-1 + UUID selected = null; + int i = 0; + for (UUID uuid : delayedHolders) { + if (i == index) { + selected = uuid; + break; + } + i++; + } + + if (selected != null) { + delayedHolders.remove(selected); + return Optional.of(selected); + } + + return Optional.empty(); // 理论上不会到这里 + } + + private static @NotNull UUID getDelayedUUIDFormListTag(@NotNull CompoundTag infoTag) { + if (infoTag.contains("DelayHolderUUID")) + return infoTag.getUUID("DelayHolderUUID"); + throw new IllegalArgumentException("LeashInfo.intId is empty"); + } @Contract("_ -> new") private static @NotNull LeashInfo getUUIDLeashDataFormListTag(@NotNull CompoundTag infoTag) { if (infoTag.contains("HolderUUID")){ diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java index d333196..e27f8fe 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java @@ -18,6 +18,7 @@ package top.r3944realms.superleadrope.content.capability.inter; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.util.INBTSerializable; @@ -32,7 +33,8 @@ public interface ILeashDataCapability extends INBTSerializable { boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance); boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks); boolean addLeash(Entity holder, LeashInfo leashInfo); - + boolean addDelayedLeash(Player holderPlayer); + boolean removeDelayedLeash(UUID onceHolderPlayerUUID); boolean setMaxDistance(Entity holder, double newMaxDistance); boolean setMaxDistance(Entity holder,double newMaxDistance, int newMaxKeepLeashTicks); boolean setMaxDistance(UUID holderUUID, double newMaxDistance); @@ -51,6 +53,9 @@ public interface ILeashDataCapability extends INBTSerializable { boolean removeLeash(Entity holder); boolean removeLeash(UUID holderUUID); boolean removeLeash(BlockPos knotPos); + void removeAllLeashes(); + void removeAllHolderLeashes(); + void removeAllKnotLeashes(); boolean transferLeash(Entity holder, Entity newHolder); boolean transferLeash(Entity holder, Entity newHolder, ItemStack stack); @@ -60,15 +65,25 @@ public interface ILeashDataCapability extends INBTSerializable { boolean transferLeash(BlockPos knotPos, Entity newHolder, ItemStack stack); // 查询方法 + boolean hasLeash(); + boolean hasKnotLeash(); + boolean hasHolderLeash(); Collection getAllLeashes(); boolean isLeashedBy(Entity holder); boolean isLeashedBy(UUID holderUUID); boolean isLeashedBy(BlockPos knotPos); + boolean isInDelayedLeash(UUID holderUUID); Optional getLeashInfo(Entity holder); Optional getLeashInfo(UUID holderUUID); Optional getLeashInfo(BlockPos knotPos); boolean canBeLeashed(); + + /** + * 抢占位(已离线玩家) + * 用于解决玩家下线后所持有我对象,会移除持有者的问题(实际上是占用个弱集合) + */ + Optional occupyLeash(); boolean canBeAttachedTo(Entity pEntity); void markForSync(); void immediateSync(); diff --git a/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java b/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java new file mode 100644 index 0000000..e15bda1 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/gamerule/SLPGamerules.java @@ -0,0 +1,46 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.content.gamerule; + +import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry; + +import java.util.HashMap; + +public class SLPGamerules { + public static final String GAMERULE_PREFIX = "SLP."; + public static final SLPGameruleRegistry GAMERULE_REGISTRY = SLPGameruleRegistry.INSTANCE; + public static final HashMap gamerulesBooleanValuesClient = new HashMap<>(); + public static final HashMap gameruleIntegerValuesClient = new HashMap<>(); + public static final HashMap gameruleFloatValuesClient = new HashMap<>(); + public static final String RULE_KEY_PERFix_ = "gamerule." + GAMERULE_PREFIX; + public static String getDescriptionKey(Class gameRuleClass) { + return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName() + ".description"; + } + public static String getDescriptionKey(String gameRuleName) { + return RULE_KEY_PERFix_ + gameRuleName + ".description"; + } + public static String getGameruleName(Class clazz) { + return SLPGamerules.GAMERULE_PREFIX + clazz.getSimpleName(); + } + public static String getGameruleName(String gamerulesName) { + return SLPGamerules.GAMERULE_PREFIX + gamerulesName; + } + + public static String getNameKey(Class gameRuleClass) { + return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName(); + } + +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/gamerule/server/TeleportWithLeashedPlayers.java b/src/main/java/top/r3944realms/superleadrope/content/gamerule/server/TeleportWithLeashedPlayers.java new file mode 100644 index 0000000..ca62fc1 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/gamerule/server/TeleportWithLeashedPlayers.java @@ -0,0 +1,33 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.content.gamerule.server; + +import net.minecraft.world.level.GameRules; +import top.r3944realms.superleadrope.content.gamerule.SLPGamerules; + +import static top.r3944realms.superleadrope.content.gamerule.SLPGamerules.GAMERULE_REGISTRY; + +public class TeleportWithLeashedPlayers { + public static final boolean DEFAULT_VALUE = true; + public static final String ID = SLPGamerules.getGameruleName(TeleportWithLeashedPlayers.class); + public static final String DESCRIPTION_KEY = SLPGamerules.getDescriptionKey(TeleportWithLeashedPlayers.class); + public static final String NAME_KEY = SLPGamerules.getNameKey(TeleportWithLeashedPlayers.class); + public static final GameRules.Category CATEGORY = GameRules.Category.PLAYER; + + public static void register() { + GAMERULE_REGISTRY.registerGamerule(ID, CATEGORY, DEFAULT_VALUE); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java index cdcce2a..bf6c238 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java +++ b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java @@ -186,16 +186,15 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem { knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos); knot.playPlacementSound(); } - AtomicBoolean canBeAttachedTo = new AtomicBoolean(false); SuperLeashKnotEntity finalKnot = knot; LazyOptional iLeashDataCapability = e.getCapability(CapabilityHandler.LEASH_DATA_CAP); - iLeashDataCapability.ifPresent(i -> canBeAttachedTo.set(i.canBeAttachedTo(finalKnot))); - if(canBeAttachedTo.get()) {//canBeAttachedTo - iLeashDataCapability.ifPresent(i -> { - i.transferLeash(uuid, finalKnot); - isSuccess.set(true); - }); - } + iLeashDataCapability.ifPresent(i -> { + boolean flag = i.canBeAttachedTo(finalKnot); + if (flag) { + i.transferLeash(uuid, finalKnot); + isSuccess.set(true); + } + }); } } if (isSuccess.get()) { diff --git a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java index 9ba2cb3..d94475c 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java @@ -24,6 +24,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.event.entity.player.AttackEntityEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; @@ -34,7 +35,7 @@ import top.r3944realms.superleadrope.core.register.SLPSoundEvents; public class LeashInteractHandler { //只有玩家可以互动触发(其它的暂不支持(考虑到0 Mixin) - public static void onEntityInteract (Level level, InteractionHand hand, Entity target , Player player, PlayerInteractEvent.EntityInteract event) { + public static void onEntityRightInteract(Level level, InteractionHand hand, Entity target , Player player, PlayerInteractEvent.EntityInteract event) { //WARNING: 主手和副手都会触发一次该事件 // ===== 卫语句 ===== @@ -113,4 +114,25 @@ public class LeashInteractHandler { } } + public static void onEntityLeftInteract(Level level, Entity target , Player player, AttackEntityEvent event) { + boolean flag = LeashDataImpl.isLeashable(target) && player.getItemInHand(InteractionHand.MAIN_HAND).is(SLPItems.SUPER_LEAD_ROPE.get()); + if (level.isClientSide) { + if (flag) { + event.setCanceled(true); + } + } else { + if (flag) { + target.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashDataCapability -> { + if (leashDataCapability.hasLeash()){ + if (player.isSecondaryUseActive()) + leashDataCapability.removeAllLeashes(); + else + leashDataCapability.removeAllHolderLeashes(); + target.playSound(SLPSoundEvents.LEAD_UNTIED.get()); + } + event.setCanceled(true); + }); + } + } + } } diff --git a/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java b/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java index aca02ba..8659825 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java +++ b/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java @@ -18,7 +18,7 @@ package top.r3944realms.superleadrope.core.punishment; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; -import top.r3944realms.superleadrope.core.register.ObligationCompletionRegistry; +import top.r3944realms.superleadrope.core.register.SLPObligationCompletionRegistry; import java.util.Map; @@ -46,7 +46,7 @@ public interface IObligationCompletion { * 获取注册 ID */ default String getId() { - for (Map.Entry entry : ObligationCompletionRegistry.getAll().entrySet()) { + for (Map.Entry entry : SLPObligationCompletionRegistry.getAll().entrySet()) { if (entry.getValue() == this) return entry.getKey(); } return "none"; @@ -58,7 +58,7 @@ public interface IObligationCompletion { static IObligationCompletion fromNetwork(FriendlyByteBuf buf) { String id = buf.readUtf(); - return ObligationCompletionRegistry.byId(id); // 如果没找到,返回 NONE + return SLPObligationCompletionRegistry.byId(id); // 如果没找到,返回 NONE } /** * 一个便捷的静态空实现(默认永不完成) diff --git a/src/main/java/top/r3944realms/superleadrope/core/register/SLPGameruleRegistry.java b/src/main/java/top/r3944realms/superleadrope/core/register/SLPGameruleRegistry.java new file mode 100644 index 0000000..2e4d966 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/register/SLPGameruleRegistry.java @@ -0,0 +1,76 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.core.register; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import top.r3944realms.superleadrope.content.gamerule.SLPGamerules; +import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedPlayers; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public enum SLPGameruleRegistry { + INSTANCE; + public static final Map> gamerules = new HashMap<>();; + public static final Map gameruleDataTypes = new HashMap<>(); + public enum RuleDataType { + BOOLEAN, + INTEGER, + } + @SuppressWarnings("unchecked") + + public static boolean getGameruleBoolValue(Level level, String gameruleName) { + if (level.isClientSide && SLPGamerules.gamerulesBooleanValuesClient.containsKey(gameruleName)) { + return SLPGamerules.gamerulesBooleanValuesClient.get(gameruleName); + } + if (gameruleDataTypes.get(gameruleName) != RuleDataType.BOOLEAN) { + return false; + } + return level.getGameRules().getBoolean((GameRules.Key) gamerules.get(gameruleName)); + } + @SuppressWarnings("unchecked") + public static Integer getGameruleIntValue(Level level, String gameruleName) { + if (level.isClientSide && SLPGamerules.gameruleIntegerValuesClient.containsKey(gameruleName)) { + return SLPGamerules.gameruleIntegerValuesClient.get(gameruleName); + } + if (gameruleDataTypes.get(gameruleName) != RuleDataType.INTEGER) { + return 0; + } + return level.getGameRules().getInt((GameRules.Key)gamerules.get(gameruleName)); + } + + public void registerGamerule(String gameruleName, GameRules.Category category, boolean pDefault) { + registerGamerule(gameruleName, category, pDefault, (s,i)->{});//最后一个仅占位无用 + } + public void registerGamerule(String gameruleName, GameRules.Category category, boolean pDefault, BiConsumer pChangeListener) { + gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.BooleanValue.create(pDefault, pChangeListener))); + gameruleDataTypes.put(gameruleName, RuleDataType.BOOLEAN); + } + public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault) { + registerGamerule(gameruleName, category, pDefault, (BiConsumer) (s, i)->{});//最后一个仅占位无用 + } + public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault, BiConsumer pChangeListener) { + gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.IntegerValue.create(pDefault, pChangeListener))); + gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER); + } + public static void register() { + TeleportWithLeashedPlayers.register(); + } + +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java b/src/main/java/top/r3944realms/superleadrope/core/register/SLPObligationCompletionRegistry.java similarity index 97% rename from src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java rename to src/main/java/top/r3944realms/superleadrope/core/register/SLPObligationCompletionRegistry.java index 9f32984..8ab1881 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java +++ b/src/main/java/top/r3944realms/superleadrope/core/register/SLPObligationCompletionRegistry.java @@ -24,7 +24,7 @@ import java.util.Map; /** * IObligationCompletion 注册与反序列化管理器 */ -public class ObligationCompletionRegistry { +public class SLPObligationCompletionRegistry { /** ID -> IObligationCompletion 实例 */ private static final Map REGISTRY = new HashMap<>(); diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java b/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java index 829fad7..330f1ea 100644 --- a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java +++ b/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java @@ -18,6 +18,7 @@ package top.r3944realms.superleadrope.datagen.data; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import org.jetbrains.annotations.NotNull; +import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedPlayers; import top.r3944realms.superleadrope.content.item.EternalPotatoItem; import top.r3944realms.superleadrope.core.register.SLPEntityTypes; import top.r3944realms.superleadrope.core.register.SLPItems; @@ -186,6 +187,16 @@ public enum SLPLangKeyValue { SLPEntityTypes.getEntityNameKey("super_lead_knot"), ModPartEnum.ENTITY, "Super Lead Knot", "超级拴绳结", "超級拴繩結", "神駒羈縻索結" ), + TELEPORT_WITH_LEASHED_PLAYERS_NAME(TeleportWithLeashedPlayers.NAME_KEY, ModPartEnum.GAME_RULE, + "Teleport leashed player with player holder", + "被拴实体随玩家持有者传送", + "被拴实体随玩家持有者傳送" + ), + TELEPORT_WITH_LEASHED_DESCRIPTION(TeleportWithLeashedPlayers.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION, + "Holder will teleport with their leashed players ", + "传送时将被拴玩家与持有者一起传送", + "將被拴玩家將隨持有者一起傳送" + ), ; private final Supplier supplier; diff --git a/src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java b/src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java index b68e212..84e8774 100644 --- a/src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java +++ b/src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java @@ -28,6 +28,7 @@ public enum ModPartEnum { AUTHOR, TITLE, NAME, + GAME_RULE, DESCRIPTION, INFO, MESSAGE, diff --git a/src/main/resources/assets/superleadrope/shaders/core/super_leash.fsh b/src/main/resources/assets/superleadrope/shaders/core/super_leash.fsh new file mode 100644 index 0000000..22d7e93 --- /dev/null +++ b/src/main/resources/assets/superleadrope/shaders/core/super_leash.fsh @@ -0,0 +1,44 @@ +#version 150 +#moj_import + +in vec2 texCoord; // interpolated texture UV0 +in vec3 fragNormal; // normal (if you want lighting later) +in float vertexDistance; // distance for fog +in vec4 vertexColor; // per-vertex color (ARGB) +in vec2 lightmapUV; // normalized UV from vertex shader + +uniform sampler2D Sampler2; // rope texture +uniform sampler2D LightmapSampler; // lightmap texture +uniform float AlphaMultiplier; + +// Fog parameters +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +out vec4 FragColor; + +vec3 applyLight(vec3 color, vec2 uv) { + vec4 light = texture(LightmapSampler, uv); + return color * light.rgb; +} + +void main() { + // 1. Sample base rope texture + vec4 texColor = texture(Sampler2, texCoord); + + // 2. Multiply with vertex color + vec4 baseColor = texColor * vertexColor; + + // 3. Apply lightmap + vec3 litColor = applyLight(baseColor.rgb, lightmapUV); + + // 4. Alpha (from texture * vertex alpha * multiplier) + float alpha = baseColor.a * AlphaMultiplier; + + // 5. Final color + vec4 color = vec4(litColor, alpha); + + // 6. Apply vanilla fog + FragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); +} diff --git a/src/main/resources/assets/superleadrope/shaders/core/super_leash.json b/src/main/resources/assets/superleadrope/shaders/core/super_leash.json new file mode 100644 index 0000000..8b76717 --- /dev/null +++ b/src/main/resources/assets/superleadrope/shaders/core/super_leash.json @@ -0,0 +1,34 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "superleadrope:super_leash", + "fragment": "superleadrope:super_leash", + "attributes": [ + "Position", + "Normal", + "UV0", + "Color", + "UV2" + ], + "samplers": [ + { "name": "Sampler2", "file": "superleadrope:textures/rope/rope_leash" }, + { "name": "LightmapSampler", "file": "minecraft:textures/misc/lightmap" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [1.0,1.0,1.0,1.0] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [0.0] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [100.0] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [0.8,0.8,0.8,1.0] }, + { "name": "FogShape", "type": "int", "count": 1, "values": [0] }, + { "name": "Time", "type": "float", "count": 1, "values": [0.0] }, + { "name": "HolderPos", "type": "float", "count": 3, "values": [0.0,0.0,0.0] }, + { "name": "EntityPos", "type": "float", "count": 3, "values": [0.0,0.0,0.0] }, + { "name": "MaxDistance", "type": "float", "count": 1, "values": [16.0] }, + { "name": "AlphaMultiplier", "type": "float", "count": 1, "values": [1.0] } + ] +} diff --git a/src/main/resources/assets/superleadrope/shaders/core/super_leash.vsh b/src/main/resources/assets/superleadrope/shaders/core/super_leash.vsh new file mode 100644 index 0000000..6514747 --- /dev/null +++ b/src/main/resources/assets/superleadrope/shaders/core/super_leash.vsh @@ -0,0 +1,91 @@ +#version 150 +#moj_import + +in vec3 Position; +in vec4 Color; +in vec2 UV0; +in vec2 UV2; // Vertex lightmap UV (float) +in vec3 Normal; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform sampler2D Sampler2; +uniform vec4 ColorModulator; +uniform int FogShape; +uniform float Time; // Game time, controls rope swing +uniform vec3 HolderPos; // Rope holder position +uniform vec3 EntityPos; // Tied entity position +uniform float MaxDistance; // Maximum rope length + +out vec2 texCoord; +out vec3 fragNormal; +out float vertexDistance; +out vec4 vertexColor; +out vec2 lightmapUV; // Normalized UV for lightmap + +// Catenary function (rope sag) +float cosh(float x) { return (exp(x) + exp(-x)) * 0.5; } + +void main() { + float progress = UV0.x; + + // ---------------------------- + // Rope sag + // ---------------------------- + float a = 0.15; + float sag = a * (cosh(progress * 2.0 - 1.0) - 1.0); + + // ---------------------------- + // Rope sway (enhanced) + // ---------------------------- + float sway = sin(progress * 3.14159 + Time) * 0.05 + // Main sway + sin(progress * 5.0 + Time * 1.5) * 0.03 + // Secondary frequency + sin(progress * 8.0 + Time * 2.0) * 0.01; // High-frequency micro sway + + vec3 ropeDir = normalize(vec3(0.0, 1.0, 0.0)); + vec3 swayDir = normalize(cross(ropeDir, Normal)); + vec3 offset = swayDir * sway + vec3(0.0, -sag, 0.0); + + // ---------------------------- + // Final world position + // ---------------------------- + vec4 worldPos = vec4(Position + offset, 1.0); + gl_Position = ProjMat * ModelViewMat * worldPos; + + // ---------------------------- + // Diagonal UV sampling + texture repeat + // ---------------------------- + float repeatFactor = 4.0; // Texture repeat count, adjustable + vec2 diagUV = vec2(progress, progress) * repeatFactor; + texCoord = diagUV; + + // ---------------------------- + // Normal and fog + // ---------------------------- + fragNormal = normalize((ModelViewMat * vec4(Normal, 0.0)).xyz); + vertexDistance = fog_distance(ModelViewMat, Position + offset, FogShape); + + // ---------------------------- + // Rope stretch ratio (enhanced) + // ---------------------------- + float distance = length(HolderPos - EntityPos); + float ratio = clamp(distance / MaxDistance * 5.0, 0.0, 1.0); // Amplify short distance + float eased = pow(ratio, 0.5); // Non-linear easing, visible at short distances + + // Dynamic color (brown → red) + noise + vec3 colorNormal = vec3(0.55, 0.27, 0.07); + vec3 colorCritical = vec3(1.0, 0.0, 0.0); + float noise = fract(sin(dot(Position.xy ,vec2(12.9898,78.233))) * 43758.5453); + vec3 dynamicColor = mix(colorNormal, colorCritical, eased) + noise * 0.03; + + // ---------------------------- + // Texture sampling and modulation + // ---------------------------- + vec4 texColor = texture(Sampler2, diagUV); + vertexColor = vec4(dynamicColor, texColor.a) * ColorModulator * texColor; + + // ---------------------------- + // Lightmap + // ---------------------------- + lightmapUV = UV2 / 512.0; // Normalization for 512x512 texture +} \ No newline at end of file diff --git a/src/main/resources/assets/superleadrope/textures/rope/rope_leash.png b/src/main/resources/assets/superleadrope/textures/rope/rope_leash.png new file mode 100644 index 0000000000000000000000000000000000000000..5897f0b855616cbd385b5f4c9b138bae59ae0ae6 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7#Nv>)VXbLJAo8qage(cKaXmOIyEV=!IBZh_P~@etf|(1r zJ(9o-hFS+9elV*c>Vat-L~QB-mXCWG!R!)7PaZI10l&v1h)@9co@3k)F`*d>`^>@2 t20evNi1ZgmPaCihLssMU3W%5qlYHLm#>$JoKi>sOdAj literal 0 HcmV?d00001