feature: 1.渲染测试实现 2.优化了些细节实现(如主手持有拴绳左键实体(+shift)可断开拴绳对象(所有)连接) 3.游戏规则
todo:1.渲染器相关代码并未清除(暂时) 2.永恒土豆待分离逻辑
This commit is contained in:
parent
ec373ee1cf
commit
0b5bde6047
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -43,3 +43,4 @@ bin/
|
|||
.DS_Store
|
||||
# Ignore Gradle build output directory
|
||||
build
|
||||
/RenderDoc_1.40_64/
|
||||
|
|
|
|||
15
.idea/runConfigurations/Client.xml
Normal file
15
.idea/runConfigurations/Client.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Client" type="Application" factoryName="Application">
|
||||
<envs>
|
||||
<env name="MOD_CLASSES" value="superleadrope%%G:\WhimsyMod\SuperLeadRope\build\classes\java\main;superleadrope%%G:\WhimsyMod\SuperLeadRope\build\resources\main" />
|
||||
</envs>
|
||||
<option name="MAIN_CLASS_NAME" value="net.neoforged.devlaunch.Main" />
|
||||
<module name="SuperLeadRope.main" />
|
||||
<option name="PROGRAM_PARAMETERS" value="@G:\\WhimsyMod\\SuperLeadRope\\build\\moddev\\clientRunProgramArgs.txt" />
|
||||
<option name="VM_PARAMETERS" value="@G:\\WhimsyMod\\SuperLeadRope\\build\\moddev\\clientRunVmArgs.txt -Dfml.modFolders=superleadrope%%G:\\WhimsyMod\\SuperLeadRope\\build\\classes\\java\\main;superleadrope%%G:\\WhimsyMod\\SuperLeadRope\\build\\resources\\main" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/run" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
16
.idea/runConfigurations/RemoteDebug.run.xml
Normal file
16
.idea/runConfigurations/RemoteDebug.run.xml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="RemoteDebug" type="Remote">
|
||||
<module name="SuperLeadRope.main" />
|
||||
<option name="USE_SOCKET_TRANSPORT" value="true" />
|
||||
<option name="SERVER_MODE" value="false" />
|
||||
<option name="SHMEM_ADDRESS" value="javadebug" />
|
||||
<option name="HOST" value="localhost" />
|
||||
<option name="PORT" value="5005" />
|
||||
<option name="AUTO_RESTART" value="false" />
|
||||
<RunnerSettings RunnerId="Debug">
|
||||
<option name="DEBUG_PORT" value="5005" />
|
||||
<option name="LOCAL" value="false" />
|
||||
</RunnerSettings>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
15
.run/RemoteDebug.run.xml
Normal file
15
.run/RemoteDebug.run.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Remote Debug" type="Remote">
|
||||
<option name="USE_SOCKET_TRANSPORT" value="true" />
|
||||
<option name="SERVER_MODE" value="false" />
|
||||
<option name="SHMEM_ADDRESS" value="javadebug" />
|
||||
<option name="HOST" value="localhost" />
|
||||
<option name="PORT" value="5005" />
|
||||
<option name="AUTO_RESTART" value="false" />
|
||||
<RunnerSettings RunnerId="Debug">
|
||||
<option name="DEBUG_PORT" value="5005" />
|
||||
<option name="LOCAL" value="false" />
|
||||
</RunnerSettings>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
47
build.gradle
47
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 {
|
||||
|
|
|
|||
91
doc/1.RenderSystem.MD
Normal file
91
doc/1.RenderSystem.MD
Normal file
|
|
@ -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 构建的数据格式和着色器匹配**。
|
||||
|
||||
|
|
@ -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<Entity> 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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Entity> holderOpt = getHolderFromLeashInfo((ClientLevel) entity.level(), leashInfo);
|
||||
Optional<Entity> holderOpt = getHolderFromLeashInfo(entity.level(), leashInfo);
|
||||
if (holderOpt.isEmpty()) return;
|
||||
|
||||
Entity holder = holderOpt.get();
|
||||
|
||||
Optional<SuperLeashRenderState> 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<Entity> getHolderFromLeashInfo(ClientLevel level, ILeashDataCapability.LeashInfo leashInfo) {
|
||||
// 根据LeashInfo类型获取持有者
|
||||
private static Optional<Entity> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.<String, VertexFormatElement>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) // 双面渲染
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; // 暗红色(即将断裂,警告色)
|
||||
|
||||
/**
|
||||
* 计算当前帧的摆动偏移量(用于波浪效果)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UUID> delayedHolders = new CopyOnWriteArraySet<>();
|
||||
private final Map<UUID, LeashInfo> leashHolders = new ConcurrentHashMap<>();
|
||||
// 引入解决 绳结不保存导致第二进入持有者不存在的问题
|
||||
private final Map<BlockPos, LeashInfo> 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<UUID> 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<LeashInfo> 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<UUID> 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")){
|
||||
|
|
|
|||
|
|
@ -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<CompoundTag> {
|
|||
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<CompoundTag> {
|
|||
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<CompoundTag> {
|
|||
boolean transferLeash(BlockPos knotPos, Entity newHolder, ItemStack stack);
|
||||
|
||||
// 查询方法
|
||||
boolean hasLeash();
|
||||
boolean hasKnotLeash();
|
||||
boolean hasHolderLeash();
|
||||
Collection<LeashInfo> getAllLeashes();
|
||||
boolean isLeashedBy(Entity holder);
|
||||
boolean isLeashedBy(UUID holderUUID);
|
||||
boolean isLeashedBy(BlockPos knotPos);
|
||||
boolean isInDelayedLeash(UUID holderUUID);
|
||||
Optional<LeashInfo> getLeashInfo(Entity holder);
|
||||
Optional<LeashInfo> getLeashInfo(UUID holderUUID);
|
||||
Optional<LeashInfo> getLeashInfo(BlockPos knotPos);
|
||||
|
||||
boolean canBeLeashed();
|
||||
|
||||
/**
|
||||
* 抢占位(已离线玩家)
|
||||
* 用于解决玩家下线后所持有我对象,会移除持有者的问题(实际上是占用个弱集合)
|
||||
*/
|
||||
Optional<UUID> occupyLeash();
|
||||
boolean canBeAttachedTo(Entity pEntity);
|
||||
void markForSync();
|
||||
void immediateSync();
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, Boolean> gamerulesBooleanValuesClient = new HashMap<>();
|
||||
public static final HashMap<String, Integer> gameruleIntegerValuesClient = new HashMap<>();
|
||||
public static final HashMap<String, Float> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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> 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()) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, IObligationCompletion> entry : ObligationCompletionRegistry.getAll().entrySet()) {
|
||||
for (Map.Entry<String, IObligationCompletion> 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
|
||||
}
|
||||
/**
|
||||
* 一个便捷的静态空实现(默认永不完成)
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, GameRules.Key<?>> gamerules = new HashMap<>();;
|
||||
public static final Map<String, RuleDataType> 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.BooleanValue>) 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.IntegerValue>)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<MinecraftServer, GameRules.BooleanValue> 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<MinecraftServer, GameRules.IntegerValue>) (s, i)->{});//最后一个仅占位无用
|
||||
}
|
||||
public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault, BiConsumer<MinecraftServer, GameRules.IntegerValue> pChangeListener) {
|
||||
gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.IntegerValue.create(pDefault, pChangeListener)));
|
||||
gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER);
|
||||
}
|
||||
public static void register() {
|
||||
TeleportWithLeashedPlayers.register();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ import java.util.Map;
|
|||
/**
|
||||
* IObligationCompletion 注册与反序列化管理器
|
||||
*/
|
||||
public class ObligationCompletionRegistry {
|
||||
public class SLPObligationCompletionRegistry {
|
||||
|
||||
/** ID -> IObligationCompletion 实例 */
|
||||
private static final Map<String, IObligationCompletion> REGISTRY = new HashMap<>();
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public enum ModPartEnum {
|
|||
AUTHOR,
|
||||
TITLE,
|
||||
NAME,
|
||||
GAME_RULE,
|
||||
DESCRIPTION,
|
||||
INFO,
|
||||
MESSAGE,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
#version 150
|
||||
#moj_import <fog.glsl>
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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] }
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
#version 150
|
||||
#moj_import <fog.glsl>
|
||||
|
||||
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
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 304 B |
Loading…
Reference in New Issue
Block a user