feat: API封装

fix: 部分内容
This commit is contained in:
叁玖领域 2025-09-15 00:37:57 +08:00
parent 1a56faad9f
commit e82f84f2a4
39 changed files with 2530 additions and 334 deletions

View File

@ -135,7 +135,10 @@ legacyForge {
}
//
sourceSets.main.resources { srcDir 'src/generated/resources' }
sourceSets.main.resources {
srcDir 'src/generated/resources'
srcDir 'src/main/resources' // coremods
}
// ========== ==========
configurations {
@ -150,8 +153,10 @@ dependencies {
modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modRuntimeOnly("curse.maven:spark-361579:4738952")
compileOnly ('me.lucko:spark-api:0.1-SNAPSHOT')
implementation 'org.ow2.asm:asm:9.6'
implementation 'org.ow2.asm:asm-tree:9.6'
implementation 'org.ow2.asm:asm-commons:9.6'
implementation 'org.ow2.asm:asm-util:9.6'
}
// ========== ==========
@ -221,6 +226,14 @@ publishing {
}
}
}
// ProGuard
configurations {
proguardLibs {
canBeResolved = true
canBeConsumed = false
extendsFrom modCompileOnly
}
}
// ========== ProGuard ==========
tasks.register('proguard', ProGuardTask) {
@ -231,8 +244,8 @@ tasks.register('proguard', ProGuardTask) {
// JDK jmods
libraryjars "${System.getProperty('java.home')}/jmods"
//
configurations.compileClasspath.files.each { file ->
// 使
configurations.proguardLibs.resolve().each { file ->
libraryjars file.absolutePath
}
@ -298,7 +311,14 @@ tasks.register("runWithRenderDoc", Exec) {
println "Environment MOD_CLASSES: ${environment['MOD_CLASSES']}"
}
}
tasks.register("copyCoreMods", Copy) {
from("src/main/resources/coremods") //
into("$buildDir/classes/java/main/coremods") //
}
tasks.named("classes") {
dependsOn("copyCoreMods")
}
// IDEA
idea {

4
proguard.pro vendored
View File

@ -36,6 +36,10 @@
-keepclassmembers class cpw.mods.** { *; }
-dontwarn cpw.mods.**
-keep class mezz.jei.**
-keepclassmembers class mezz.jei.**{ *; }
-dontwarn mezz.jei.**
#---------------------------------------
# 保留资源文件 (mods.toml / assets / data / pack.mcmeta)
#---------------------------------------

View File

@ -50,8 +50,11 @@ import net.minecraftforge.event.level.LevelEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.config.LeashConfigManager;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.CapabilityRemainder;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
@ -71,6 +74,8 @@ import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.util.PotatoMode;
import top.r3944realms.superleadrope.core.util.PotatoModeHelper;
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import top.r3944realms.superleadrope.util.capability.LeashStateAPI;
import top.r3944realms.superleadrope.util.model.RidingRelationship;
import top.r3944realms.superleadrope.util.riding.RidingApplier;
import top.r3944realms.superleadrope.util.riding.RidingDismounts;
@ -84,6 +89,7 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
public class CommonEventHandler {
public volatile static LeashConfigManager leashConfigManager;
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE)
public static class Game {
@SubscribeEvent
@ -91,8 +97,8 @@ public class CommonEventHandler {
Entity entity = event.getEntity();
if (entity.level().isClientSide) return;
if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::track);
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::track);
LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::track);
LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::track);
if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.Data.forEach(i -> {
if (i.isLeashedBy(serverPlayer) && i.isInDelayedLeash(serverPlayer.getUUID())) {
@ -115,8 +121,8 @@ public class CommonEventHandler {
}
});
}
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager.Data::untrack);
entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).ifPresent(LeashSyncManager.State::untrack);
LeashDataAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::untrack);
LeashStateAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::untrack);
}
}
@SubscribeEvent
@ -139,9 +145,6 @@ public class CommonEventHandler {
@SubscribeEvent
public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) {
Level level = event.getLevel();
if (level.isClientSide) {
return;
}
BlockPos blockPos = event.getHitVec().getBlockPos();
BlockState blockState = level.getBlockState(blockPos);
Player player = event.getEntity();
@ -256,7 +259,7 @@ public class CommonEventHandler {
List<Entity> entities = LeashDataImpl.leashableInArea(telEntity);
//规则关闭则禁止
if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedEntities.ID)) {
entities.forEach(i -> i.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(j -> j.removeLeash(i)));
entities.forEach(entity -> LeashDataAPI.LeashOperations.detach(entity, telEntity));
return;
}
for (Entity beLeashedEntity : entities) {
@ -268,9 +271,9 @@ public class CommonEventHandler {
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
AtomicReference<ILeashData.LeashInfo> originalLeashInfo = new AtomicReference<>();
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> {
originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null));
cap.removeLeash(telEntity);
LeashDataAPI.getLeashData(beLeashedEntity).ifPresent(data -> {
originalLeashInfo.set(data.getLeashInfo(telEntity).orElse(null));
data.removeLeash(telEntity);
});
@ -302,14 +305,11 @@ public class CommonEventHandler {
entity.setPose(originalPose);
}
// --- 恢复拴绳 ---
// --- 将holder替换 ---
ILeashData.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get())
.map(info -> info.transferHolder(telEntity))
.orElse(ILeashData.LeashInfo.EMPTY);
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(
cap -> cap.addLeash(telEntity, leashInfo)
);
LeashDataAPI.LeashOperations.attachWithInfo(beLeashedEntity, telEntity, leashInfo);
// --- 重新应用骑乘关系仅保留白名单根载具 ---
RidingRelationship filteredRelationship = RidingSaver.filterByWhitelistRoot(originalRidingRelationship);
@ -362,7 +362,8 @@ public class CommonEventHandler {
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus= net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD)
public static class Mod {
@SubscribeEvent
public static void onCommonInit (FMLCommonSetupEvent event) {
public static void onFMLCommonInit(FMLCommonSetupEvent event) {
event.enqueueWork(Mod::checkAndSet);
event.enqueueWork(SLPGameruleRegistry::register);//规则注册
}
@SubscribeEvent
@ -375,6 +376,43 @@ public class CommonEventHandler {
event.accept(SLPItems.SUPER_LEAD_ROPE);
}
}
@SubscribeEvent
public void onConfigReloading(ModConfigEvent.Reloading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
SuperLeadRope.logger.debug("Config reloading detected...");
}
}
private static void checkAndSet() {
if (leashConfigManager == null) {
synchronized (LeashConfigManager.class) {
if (leashConfigManager == null) {
leashConfigManager = new LeashConfigManager();
}
}
}
}
@SubscribeEvent
public void onConfigLoaded(ModConfigEvent.Loading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
checkAndSet();
LeashConfigManager.loading(leashConfigManager);
}
}
@SubscribeEvent
public void onConfigReloaded(ModConfigEvent.Reloading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
checkAndSet();
LeashConfigManager.reloading(leashConfigManager);
}
}
@SubscribeEvent
public void onConfigUnloaded(ModConfigEvent.Unloading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
LeashConfigManager.unloading(leashConfigManager);
}
}
}
}

View File

@ -33,6 +33,7 @@ import top.r3944realms.superleadrope.util.file.ConfigUtil;
@Mod(value = SuperLeadRope.MOD_ID)
public class SuperLeadRope {
public static final Logger logger = LoggerFactory.getLogger(SuperLeadRope.class);
public static final String MOD_ID = "superleadrope";
public SuperLeadRope() {
IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus();
@ -41,6 +42,7 @@ public class SuperLeadRope {
SLPSoundEvents.register(eventBus);
NetworkHandler.register();
initialize();
}
public static void initialize() {
logger.info("Initializing SuperLeadRope");

View File

@ -69,7 +69,7 @@ public class ClientEventHandler {
(itemStack, clientLevel, livingEntity, i) -> {
if (!itemStack.isDamageableItem()) return 0.0F;
return itemStack.getDamageValue() > 1024 - 50 ? 1.0F : 0.0F; // 损坏 返回 1.0触发 override
return itemStack.getDamageValue() > 1024 - 33 ? 1.0F : 0.0F; // 损坏 返回 1.0触发 override
}
);
PotatoMode mode = getCurrentMode();

View File

@ -24,9 +24,9 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.Optional;
import java.util.UUID;
@ -46,10 +46,9 @@ public class LeashRenderHandler {
// 遍历摄像机附近所有实体
for (Entity entity : level.getEntitiesOfClass(Entity.class,
cameraEntity.getBoundingBox().inflate(50))) {
cameraEntity.getBoundingBox().inflate(100))) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashData -> {
if(leashData instanceof ILeashData) {}
LeashDataAPI.getLeashData(entity).ifPresent(leashData -> {
for (ILeashData.LeashInfo leashInfo : leashData.getAllLeashes()) {
renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick);
}

View File

@ -52,7 +52,7 @@ public class SuperLeashRenderer {
int skyLightEnd = getSkyLight(BlockPos.containing(endWorld));
// 差向量 + 偏移
Offsets offsets = computeOffsets(state, startWorld, endWorld);
Offsets offsets = computeOffsets(startWorld, endWorld);
// pass1: 0 N
for (int i = 0; i <= LEASH_STEPS; i++) {
@ -76,7 +76,7 @@ public class SuperLeashRenderer {
/** 计算差向量和偏移 */
private static Offsets computeOffsets(SuperLeashRenderState state, Vec3 start, Vec3 end) {
private static Offsets computeOffsets(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);

View File

@ -20,11 +20,10 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import top.r3944realms.superleadrope.util.capability.LeashStateAPI;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@ -56,7 +55,7 @@ public class SuperLeashStateResolver {
}
AtomicReference<Vec3> holderOffset = new AtomicReference<>();
AtomicReference<Vec3> entityOffset = new AtomicReference<>();
LeashUtil.getLeashState(leashedEntity).ifPresent(state ->
LeashStateAPI.getLeashState(leashedEntity).ifPresent(state ->
state
.getLeashState(holder)
.ifPresent(ls -> {
@ -110,7 +109,6 @@ public class SuperLeashStateResolver {
return Optional.of(new SuperLeashRenderState(
currentHolderPos,
currentEntityPos,
leashInfo.attachOffset(),
lastHolderPos,
lastEntityPos,
tension,

View File

@ -20,7 +20,6 @@ import net.minecraft.world.phys.Vec3;
public record SuperLeashRenderState(
Vec3 startPos, // 当前帧起点位置
Vec3 endPos, // 当前帧终点位置
Vec3 attachOffset, // 拴绳附着点偏移
Vec3 lastStartPos, // 上一帧起点位置(用于摆动计算)
Vec3 lastEndPos, // 上一帧终点位置(用于摆动计算)
float tension, // 张力强度(0.0-1.0)

View File

@ -18,6 +18,8 @@ package top.r3944realms.superleadrope.config;
import net.minecraftforge.common.ForgeConfigSpec;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LeashCommonConfig {
public static ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
@ -38,13 +40,18 @@ public class LeashCommonConfig {
public final ForgeConfigSpec.DoubleValue springDampening;
public final ForgeConfigSpec.ConfigValue<List<? extends Double>> axisSpecificElasticity;
public final ForgeConfigSpec.IntValue maxLeashesPerEntity;
public final ForgeConfigSpec.ConfigValue<List<? extends String>> defaultApplyEntityLocationOffset;
// 正则表达式模式
static final Pattern OFFSET_PATTERN = Pattern.compile(
"(?i)(?:vec3|vec3d|vector3|offset)\\s*\\(\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*\\)\\s*:\\s*\\[\\s*([^]]+?)\\s*]\\s*" );
public Common(ForgeConfigSpec.Builder builder) {
BUILDER.push("Command");
EnableSLPModCommandPrefix = builder
.comment("The prefix of this mod's commands")
.define("SLPModCommandPrefix", true);
SLPModCommandPrefix = builder
.comment("The prefix of this mod's commands"," [ Default:'slp'] ")
.comment("The prefix of this mod's commands", " [ Default:'slp'] ")
.define("EnableSLPModCommandPrefix", "slp");
BUILDER.pop();
builder.push("Entity");
@ -59,7 +66,7 @@ public class LeashCommonConfig {
.defineListAllowEmpty(
List.of("allowedTeleportEntities"),
List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"),
o -> o instanceof String s && isValidFormat(s)
o -> o instanceof String s && isValidEntityRefFormat(s)
);
builder.pop();
builder.push("LeashSettings");
@ -87,20 +94,70 @@ public class LeashCommonConfig {
.defineInRange("maxLeashesPerEntity", 6, 1, 24);
builder.pop();
builder.push("LeashStateSettings");
defaultApplyEntityLocationOffset = builder
.comment(
"Default leash attachment point offsets for entities.",
"Format: vec3(x,y,z) : [entity_list]",
"Optional Name : vector3, vec3d, offset",
"Entity list can contain:",
" - modid:entity_id : specific entity (e.g. minecraft:bee)",
" - #modid:tag_name : entity type tag (e.g. #minecraft:boats)",
" - #modid : mod-wide (e.g. #minecraft)",
"Multiple entities can be separated by commas",
"Example: vec3(0,0.2,0) : [minecraft:bee, minecraft:horse]",
"Priority: specific entity > tag > mod"
)
.defineListAllowEmpty(
List.of("defaultApplyEntityLocationOffset"),
List.of(
"vec3(0,0.2,0) : [minecraft:bee]",
"vec3(0,1.0,0) : [minecraft:horse, minecraft:donkey]",
"vec3(0,0.5,0) : [#minecraft:boats]",
"vec3(0,0.4,0) : [#minecraft:minecarts]",
"vec3(0,0.3,0) : [#minecraft]",
"vec3(0,0.6,0) : [#modernlife]"
),
o -> o instanceof String s && isValidOffsetRefFormat(s)
);
BUILDER.pop();
}
private static boolean isValidFormat(String s) {
private static boolean isValidEntityRefFormat(String s) {
if (s.startsWith("#")) {
String body = s.substring(1);
// 支持 #modid 整个模组
if (body.matches("[a-z0-9_]+")) {
return true;
}
// 支持 #modid:tag_name 标签
return body.matches("[a-z0-9_]+:[a-z0-9_/]+");
// 支持 #modid 整个模组 #modid:tag_name 标签
return body.matches("[a-z0-9_]+(:[a-z0-9_/]+)?");
}
// 普通实体 ID
// 普通实体 ID: modid:entity_id
return s.matches("[a-z0-9_]+:[a-z0-9_/]+");
}
private static boolean isValidOffsetRefFormat(String s) {
// 匹配格式: vec3(x,y,z) : [entity_list]
Matcher matcher = Common.OFFSET_PATTERN.matcher(s);
if (!matcher.matches()) {
return false;
}
// 检查坐标值是否有效
try {
Double.parseDouble(matcher.group(1));
Double.parseDouble(matcher.group(2));
Double.parseDouble(matcher.group(3));
// 检查实体列表格式
String entityList = matcher.group(4);
String[] entities = entityList.split(",");
for (String entity : entities) {
if (!isValidEntityRefFormat(entity.trim())) {
return false;
}
}
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
}

View File

@ -0,0 +1,361 @@
/*
* 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.config;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import top.r3944realms.superleadrope.SuperLeadRope;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import static top.r3944realms.superleadrope.config.LeashCommonConfig.Common.OFFSET_PATTERN;
public class LeashConfigManager {
private final Map<String, double[]> entityOffsetMap = new ConcurrentHashMap<>();
private final Map<String, double[]> tagOffsetMap = new ConcurrentHashMap<>();
private final Map<String, double[]> modOffsetMap = new ConcurrentHashMap<>();
// 缓存常用配置值以提高性能
private volatile List<String> teleportWhitelistCache;
private volatile String commandPrefixCache;
private volatile boolean enableCommandPrefixCache;
private volatile double maxLeashLengthCache;
private volatile double elasticDistanceCache;
private volatile double extremeSnapFactorCache;
private volatile double springDampeningCache;
private volatile List<Double> axisSpecificElasticityCache;
private volatile int maxLeashesPerEntityCache;
public LeashConfigManager() {
this.reloadAll();
}
/**
* 解析偏移配置线程安全
*/
public void parseOffsetConfig() {
Map<String, double[]> newEntityOffsetMap = new HashMap<>();
Map<String, double[]> newTagOffsetMap = new HashMap<>();
Map<String, double[]> newModOffsetMap = new HashMap<>();
List<? extends String> offsets = LeashCommonConfig.COMMON.defaultApplyEntityLocationOffset.get();
for (String offsetConfig : offsets) {
Matcher matcher = OFFSET_PATTERN.matcher(offsetConfig);
if (!matcher.matches()) continue;
try {
double x = Double.parseDouble(matcher.group(1).trim());
double y = Double.parseDouble(matcher.group(2).trim());
double z = Double.parseDouble(matcher.group(3).trim());
double[] offset = new double[]{x, y, z};
String entityList = matcher.group(4);
String[] entities = entityList.split(",");
for (String entity : entities) {
String trimmed = entity.trim();
if (trimmed.startsWith("#")) {
String body = trimmed.substring(1).trim();
if (body.contains(":")) {
newTagOffsetMap.put(body, offset);
} else {
newModOffsetMap.put(body, offset);
}
} else {
newEntityOffsetMap.put(trimmed, offset);
}
}
} catch (NumberFormatException e) {
System.err.println("Invalid offset config: " + offsetConfig);
}
}
// 原子性更新映射
entityOffsetMap.clear();
entityOffsetMap.putAll(newEntityOffsetMap);
tagOffsetMap.clear();
tagOffsetMap.putAll(newTagOffsetMap);
modOffsetMap.clear();
modOffsetMap.putAll(newModOffsetMap);
}
/**
* 重新加载所有配置值到缓存
*/
public void reloadAll() {
// 加载偏移配置
parseOffsetConfig();
// 加载其他配置到缓存
teleportWhitelistCache = new ArrayList<>(LeashCommonConfig.COMMON.teleportWhitelist.get());
commandPrefixCache = LeashCommonConfig.COMMON.SLPModCommandPrefix.get();
enableCommandPrefixCache = LeashCommonConfig.COMMON.EnableSLPModCommandPrefix.get();
maxLeashLengthCache = LeashCommonConfig.COMMON.maxLeashLength.get();
elasticDistanceCache = LeashCommonConfig.COMMON.elasticDistance.get();
extremeSnapFactorCache = LeashCommonConfig.COMMON.extremeSnapFactor.get();
springDampeningCache = LeashCommonConfig.COMMON.springDampening.get();
axisSpecificElasticityCache = new ArrayList<>(LeashCommonConfig.COMMON.axisSpecificElasticity.get());
maxLeashesPerEntityCache = LeashCommonConfig.COMMON.maxLeashesPerEntity.get();
SuperLeadRope.logger.debug("All configs reloaded: {}", getStats());
}
// ========== 偏移配置相关方法 ==========
/**
* 获取实体类型的偏移量
*/
@SuppressWarnings("deprecation")
public Vec3 getEntityOffset(EntityType<?> entityType) {
String entityId = entityType.builtInRegistryHolder().key().location().toString();
String modId = entityId.split(":")[0]; // 从实体ID提取modId
// 获取实体的标签
List<String> tagStrings = new ArrayList<>();
for (var tag : entityType.builtInRegistryHolder().tags().toList()) {
tagStrings.add(tag.location().toString());
}
double[] offset = getEntityOffset(entityId, modId, tagStrings);
return offset != null ? new Vec3(offset[0], offset[1], offset[2]) : Vec3.ZERO;
}
/**
* 获取实体对象的偏移量便捷方法
*/
public Vec3 getEntityOffset(Entity entity) {
return getEntityOffset(entity.getType());
}
/**
* 获取实体偏移量原始数据
*/
public double[] getEntityOffset(String entityId, String modId, List<String> tags) {
// 1. 首先检查特定实体
if (entityOffsetMap.containsKey(entityId)) {
return entityOffsetMap.get(entityId);
}
// 2. 检查标签
for (String tag : tags) {
if (tagOffsetMap.containsKey(tag)) {
return tagOffsetMap.get(tag);
}
}
// 3. 检查模组
if (modOffsetMap.containsKey(modId)) {
return modOffsetMap.get(modId);
}
return null;
}
// ========== 传送白名单相关方法 ==========
public List<String> getTeleportWhitelist() {
return Collections.unmodifiableList(teleportWhitelistCache);
}
@SuppressWarnings("deprecation")
public boolean isEntityTeleportAllowed(EntityType<?> entityType) {
String entityId = entityType.builtInRegistryHolder().key().location().toString();
String modid = entityId.split(":")[0];
for (String entry : teleportWhitelistCache) {
if (entry.startsWith("#")) {
String body = entry.substring(1);
// Case 1: #modid allow all entities from this mod
if (!body.contains(":")) {
if (modid.equals(body)) {
return true;
}
}
// Case 2: #modid:tag_name allow all entities under this tag
else {
ResourceLocation tagId = new ResourceLocation(body);
TagKey<EntityType<?>> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
if (entityType.builtInRegistryHolder().is(tag)) {
return true;
}
}
} else {
// Case 3: modid:entity_name allow a specific entity
if (entry.equals(entityId)) {
return true;
}
}
}
return false;
}
public boolean isEntityTeleportAllowed(String entityId) {
// 对于字符串ID我们无法检查标签只能检查模组和特定实体
String modid = entityId.contains(":") ? entityId.split(":")[0] : "minecraft";
for (String entry : teleportWhitelistCache) {
if (entry.startsWith("#")) {
String body = entry.substring(1);
// Case 1: #modid allow all entities from this mod
if (!body.contains(":")) {
if (modid.equals(body)) {
return true;
}
}
// Case 2: #modid:tag_name 字符串ID无法检查标签跳过
// 如果需要支持标签检查需要传入EntityType而不是String
} else {
// Case 3: modid:entity_name allow a specific entity
if (entry.equals(entityId)) {
return true;
}
}
}
return false;
}
// 辅助方法检查实体ID是否匹配模式用于旧的匹配逻辑
private boolean matchesTeleportPattern(String pattern, String entityId) {
if (pattern.startsWith("#")) {
String body = pattern.substring(1);
if (body.contains(":")) {
// 标签格式: #modid:tag_name - 字符串ID无法准确匹配标签
// 返回模组匹配作为近似
String patternModId = body.split(":")[0];
String entityModId = entityId.split(":")[0];
return entityModId.equals(patternModId);
} else {
// 模组格式: #modid
String entityModId = entityId.split(":")[0];
return entityModId.equals(body);
}
} else {
// 实体格式: modid:entity_id
return entityId.equals(pattern);
}
}
// 添加一个重载方法方便使用Entity对象
public boolean isEntityTeleportAllowed(Entity entity) {
return isEntityTeleportAllowed(entity.getType());
}
// ========== 命令配置相关方法 ==========
public String getCommandPrefix() {
return commandPrefixCache;
}
public boolean isCommandPrefixEnabled() {
return enableCommandPrefixCache;
}
public String getFullCommand(String subCommand) {
return isCommandPrefixEnabled() ?
getCommandPrefix() + " " + subCommand :
subCommand;
}
// ========== 拴绳物理配置相关方法 ==========
public double getMaxLeashLength() {
return maxLeashLengthCache;
}
public double getElasticDistance() {
return elasticDistanceCache;
}
public double getExtremeSnapFactor() {
return extremeSnapFactorCache;
}
public double getBreakDistance() {
return getMaxLeashLength() * getExtremeSnapFactor();
}
public double getSpringDampening() {
return springDampeningCache;
}
public List<Double> getAxisSpecificElasticity() {
return Collections.unmodifiableList(axisSpecificElasticityCache);
}
public double getXElasticity() {
return !axisSpecificElasticityCache.isEmpty() ? axisSpecificElasticityCache.get(0) : 0.8;
}
public double getYElasticity() {
return axisSpecificElasticityCache.size() > 1 ? axisSpecificElasticityCache.get(1) : 0.2;
}
public double getZElasticity() {
return axisSpecificElasticityCache.size() > 2 ? axisSpecificElasticityCache.get(2) : 0.8;
}
// ========== 实体限制配置相关方法 ==========
public int getMaxLeashesPerEntity() {
return maxLeashesPerEntityCache;
}
public boolean canEntityAcceptMoreLeashes(Entity entity, int currentLeashCount) {
return currentLeashCount < getMaxLeashesPerEntity();
}
// ========== 管理方法 ==========
public void reload() {
reloadAll();
}
public void clear() {
entityOffsetMap.clear();
tagOffsetMap.clear();
modOffsetMap.clear();
teleportWhitelistCache = Collections.emptyList();
}
public String getStats() {
return String.format("Entities: %d, Tags: %d, Mods: %d, TeleportWhitelist: %d",
entityOffsetMap.size(), tagOffsetMap.size(), modOffsetMap.size(),
teleportWhitelistCache.size());
}
public static void loading(LeashConfigManager manager) {
manager.reloadAll();
}
public static void reloading(LeashConfigManager manager) {
manager.reloadAll();
}
public static void unloading(LeashConfigManager manager) {
manager.clear();
}
}

View File

@ -18,7 +18,7 @@ package top.r3944realms.superleadrope.content.capability;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.player.PlayerEvent;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import top.r3944realms.superleadrope.util.capability.LeashStateAPI;
public class CapabilityRemainder {
public static void onPlayerClone(PlayerEvent.Clone event) {
@ -26,9 +26,9 @@ public class CapabilityRemainder {
if(newEntity instanceof ServerPlayer newPlayer) {
Player original = event.getOriginal();
original.reviveCaps();
LeashUtil.getLeashState(original)
LeashStateAPI.getLeashState(original)
.ifPresent(oldCap ->
LeashUtil.getLeashState(newPlayer)
LeashStateAPI.getLeashState(newPlayer)
.ifPresent(newData ->
newData.copy(oldCap, newEntity)
)

View File

@ -21,8 +21,8 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
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;
@ -30,26 +30,26 @@ 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.level.Level;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.PacketDistributor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import top.r3944realms.superleadrope.util.nbt.NBTReader;
import top.r3944realms.superleadrope.util.nbt.NBTWriter;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import top.r3944realms.superleadrope.util.capability.LeashStateAPI;
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.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -85,34 +85,6 @@ import java.util.stream.Stream;
* </table>
*/
public class LeashDataImpl implements ILeashData {
private static final class Config {
private Config() {} // 私有构造防止实例化
static double maxLeashDistance() {
return LeashCommonConfig.COMMON.maxLeashLength.get();
}
static double leashElasticDist() {
return LeashCommonConfig.COMMON.elasticDistance.get();
}
static double leashExtremeSnapDistFactor() {
return LeashCommonConfig.COMMON.extremeSnapFactor.get();
}
static double springDampening() {
return LeashCommonConfig.COMMON.springDampening.get();
}
static Vec3 axisSpecificElasticity() {
List<? extends Double> list = LeashCommonConfig.COMMON.axisSpecificElasticity.get();
return new Vec3(list.get(0), list.get(1), list.get(2));
}
static int maxLeashesPerEntity() {
return LeashCommonConfig.COMMON.maxLeashesPerEntity.get();
}
}
private final Entity entity;
private boolean needsSync = false;
private long lastSyncTime;
@ -172,18 +144,18 @@ public class LeashDataImpl implements ILeashData {
@Override
public boolean addLeash(Entity holder) {
return addLeash(holder, Config.maxLeashDistance());
return addLeash(holder, CommonEventHandler.leashConfigManager.getMaxLeashLength());
}
@Override
public boolean addLeash(Entity holder, String reserved) {
return addLeash(holder, Config.maxLeashDistance(), reserved);
return addLeash(holder, CommonEventHandler.leashConfigManager.getMaxLeashLength(), reserved);
}
// 添加拴绳支持自定义最大长度
@Override
public boolean addLeash(Entity holder, double maxDistance) {
return addLeash(holder, maxDistance, Config.leashElasticDist(), 0, "");
return addLeash(holder, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
// 添加拴绳支持自定义最大长度和弹性距离
@ -195,7 +167,7 @@ public class LeashDataImpl implements ILeashData {
// 添加拴绳支持自定义最大长度 + reserved 字段
@Override
public boolean addLeash(Entity holder, double maxDistance, String reserved) {
return addLeash(holder, maxDistance, Config.leashElasticDist(), 0, reserved);
return addLeash(holder, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, reserved);
}
// 添加拴绳最终实现支持最大长度弹性距离保持 Tickreserved
@ -219,7 +191,6 @@ public class LeashDataImpl implements ILeashData {
LeashInfo info = LeashInfo.create(
holder,
reserved,
calculateAttachOffset(entity),
maxDistance,
elasticDistance,
maxKeepLeashTicks,
@ -231,7 +202,7 @@ public class LeashDataImpl implements ILeashData {
} else {
leashHolders.put(holder.getUUID(), info);
}
LeashStateAPI.Operations.attach(entity, holder);
markForSync();
return true;
}
@ -301,7 +272,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
old.elasticDistance(),
old.keepLeashTicks(),
@ -316,7 +286,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -331,7 +300,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
distance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), maxKeepTicks),
@ -346,7 +314,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
old.elasticDistance(),
old.keepLeashTicks(),
@ -361,7 +328,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
newMaxDistance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -376,7 +342,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
distance,
old.elasticDistance(),
Math.min(old.keepLeashTicks(), maxKeepTicks),
@ -399,7 +364,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
old.keepLeashTicks(),
@ -414,7 +378,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
old.keepLeashTicks(),
@ -444,7 +407,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -459,7 +421,6 @@ public class LeashDataImpl implements ILeashData {
old.holderUUIDOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
old.maxDistance(),
distance,
Math.min(old.keepLeashTicks(), maxKeepTicks),
@ -474,7 +435,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(),
old.holderIdOpt().get(),
old.reserved(),
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
old.keepLeashTicks(),
@ -489,7 +449,6 @@ public class LeashDataImpl implements ILeashData {
old.blockPosOpt().get(),
old.holderIdOpt().get(),
reserved,
old.attachOffset(),
old.maxDistance(),
newElasticDistance,
Math.min(old.keepLeashTicks(), newMaxKeepLeashTicks),
@ -502,13 +461,17 @@ public class LeashDataImpl implements ILeashData {
*/
@Override
public void applyLeashForces() {
Vec3 combinedForce = Vec3.ZERO; // 初始化合力向量
Vec3 combinedForce = Vec3.ZERO;
Vec3 combinedDirection = Vec3.ZERO;
int validLeashes = 0;
// 计算所有拴绳的合力
// 计算所有拴绳的合力和平均方向
for (Map.Entry<UUID, LeashInfo> entry : leashHolders.entrySet()) {
Vec3 force = calculateLeashForceForUUID(entry);
if (force != null) {
combinedForce = combinedForce.add(force);
combinedDirection = combinedDirection.add(force.normalize());
validLeashes++;
}
}
@ -516,27 +479,152 @@ public class LeashDataImpl implements ILeashData {
Vec3 force = calculateLeashForceForBlockPos(entry);
if (force != null) {
combinedForce = combinedForce.add(force);
combinedDirection = combinedDirection.add(force.normalize());
validLeashes++;
}
}
boolean hasForce = !combinedForce.equals(Vec3.ZERO);
Entity finalApplyEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
if (hasForce) {
if (hasForce) {
// 处理玩家和其他实体
if (finalApplyEntity instanceof ServerPlayer player) {
RindingLeash.applyForceToPlayer(player, combinedForce);
return;
} else {
finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce));
finalApplyEntity.hurtMarked = true;
// 对生物使用合力方向进行移动只有在能够移动时才执行
if (finalApplyEntity instanceof Mob mob && validLeashes > 0 && canMobMove(mob)) {
moveMobTowardsCombinedDirection(mob, combinedDirection, validLeashes, combinedForce.length());
} else if (finalApplyEntity instanceof Mob mob) {
// 无法移动时停止导航
mob.getNavigation().stop();
}
}
RindingLeash.protectAnimalMovement(finalApplyEntity, true);
} else {
RindingLeash.protectAnimalMovement(finalApplyEntity, false);
// 没有力时也停止导航
if (finalApplyEntity instanceof Mob mob) {
mob.getNavigation().stop();
}
}
}
/**
* 检查生物是否能够移动
*/
private boolean canMobMove(Mob mob) {
// 检查各种无法移动的情况
return !mob.isNoGravity() && // 有重力才能移动
!mob.isSleeping() && // 没有在睡觉
!mob.isDeadOrDying() && // 没有死亡或濒死
!mob.isFreezing() && // 没有被冻结
mob.canUpdate() && // 可以更新
mob.isEffectiveAi() && // AI有效
mob.getDeltaMovement().lengthSqr() < 100.0; // 移动速度不是特别快防止异常情况
}
/**
* 让生物朝着合力方向移动
*/
private void moveMobTowardsCombinedDirection(Mob mob, Vec3 combinedDirection, int leashCount, double forceMagnitude) {
if (combinedDirection.equals(Vec3.ZERO)) return;
// 再次检查是否能够移动
if (!canMobMove(mob)) {
mob.getNavigation().stop();
return;
}
// 计算平均方向
Vec3 averageDirection = combinedDirection.scale(1.0 / leashCount).normalize();
// 根据力的大小调整移动速度
double speed = calculateMobSpeed(mob, forceMagnitude);
// 计算目标位置在合力方向上稍微超前一点
Vec3 targetPos = mob.position().add(averageDirection.scale(3.0)); // 3格距离
// 检查目标位置是否可达
if (isPositionReachable(mob, targetPos)) {
// 设置移动目标
mob.getNavigation().moveTo(targetPos.x, targetPos.y, targetPos.z, speed);
// 设置生物朝向合力方向
mob.getLookControl().setLookAt(targetPos);
} else {
// 位置不可达时停止导航
mob.getNavigation().stop();
}
}
/**
* 检查位置是否可达
*/
private boolean isPositionReachable(Mob mob, Vec3 targetPos) {
// 简单的距离检查
double distance = mob.position().distanceTo(targetPos);
if (distance > 20.0) { // 距离太远
return false;
}
// 检查是否有导航路径
Path path = mob.getNavigation().createPath(targetPos.x, targetPos.y, targetPos.z, 0);
return path != null && !path.isDone();
}
/**
* 增强的移动能力检查
*/
private boolean canMobMoveEnhanced(Mob mob) {
// 基础移动检查
if (!canMobMove(mob)) {
return false;
}
// 检查导航系统是否可用
if (mob.getNavigation().isDone() || mob.getNavigation().isStuck()) {
return false;
}
// 检查生物是否被拴绳过度拉扯防止无限尝试移动
Vec3 motion = mob.getDeltaMovement();
if (motion.lengthSqr() > 4.0 && mob.tickCount % 20 == 0) {
// 如果移动速度过快偶尔跳过移动尝试
return false;
}
// 检查生物是否在尝试移动但实际没有移动卡住检测
if (mob.getNavigation().getPath() != null &&
!mob.getNavigation().getPath().isDone()) {
// 获取上一tick的位置进行比较
double distanceMoved = mob.position().distanceTo(new Vec3(mob.xOld, mob.yOld, mob.zOld));
// 生物卡住了有路径但几乎没有移动
return !(distanceMoved < 0.1);
}
return true;
}
/**
* 根据生物类型和力的大小计算移动速度
*/
private double calculateMobSpeed(Mob mob, double forceMagnitude) {
double baseSpeed = mob instanceof Llama ? 2.0 : 1.0;
// 力越大移动速度越快但有上限
double forceFactor = Math.min(forceMagnitude * 0.5, 2.0); // 限制最大加速2倍
return baseSpeed * (1.0 + forceFactor);
}
/**
* 为UUID拴绳计算力
@ -569,9 +657,9 @@ public class LeashDataImpl implements ILeashData {
private Vec3 calculateLeashForce(Entity holder, Map.Entry<?, LeashInfo> entry) {
Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0);
LeashInfo info = entry.getValue();
Vec3 entityPos = entity.position().add(info.attachOffset());
Vec3 entityPos = entity.position();
double distance = holderPos.distanceTo(entityPos);
double extremeSnapDist = info.maxDistance() * Config.leashExtremeSnapDistFactor();
double extremeSnapDist = info.maxDistance() * CommonEventHandler.leashConfigManager.getExtremeSnapFactor();
// 1. 检查是否超出断裂距离
if (distance > extremeSnapDist) {
@ -583,6 +671,8 @@ public class LeashDataImpl implements ILeashData {
}
// 断裂
removeLeash(holder);
//TODO: 是不是应该考虑让断裂统一发出声音还是就这样由断裂发出
entity.level().playSound(null, holder.getOnPos(), SLPSoundEvents.LEAD_BREAK.get(), SoundSource.PLAYERS);
return null;
}
@ -590,15 +680,6 @@ public class LeashDataImpl implements ILeashData {
Vec3 pullForce = Vec3.ZERO;
if (distance > info.elasticDistance()) {
pullForce = calculatePullForce(holderPos, entityPos, distance, info);
// 生物添加跟随逻辑保持不变
if(entity instanceof Mob mob) {
Vec3 vec3 = (new Vec3(holder.getX() - entity.getX(), holder.getY() - entity.getY(), holder.getZ() - entity.getZ()))
.normalize()
.scale(Math.max(distance - 2.0F, 0.0F));
double speed = mob instanceof Llama ? 2.0 : 1.0;
mob.getNavigation().moveTo(entity.getX() + vec3.x, entity.getY() + vec3.y, entity.getZ() + vec3.z, speed);
}
}
// 3. 重置缓冲Tick
@ -621,13 +702,13 @@ public class LeashDataImpl implements ILeashData {
}
Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * Config.springDampening()
(distance - info.elasticDistance()) * pullStrength * CommonEventHandler.leashConfigManager.getSpringDampening()
);
return new Vec3(
pullForce.x * Config.axisSpecificElasticity().x,
pullForce.y * Config.axisSpecificElasticity().y,
pullForce.z * Config.axisSpecificElasticity().z
pullForce.x * CommonEventHandler.leashConfigManager.getXElasticity(),
pullForce.y * CommonEventHandler.leashConfigManager.getXElasticity(),
pullForce.z * CommonEventHandler.leashConfigManager.getZElasticity()
);
}
@ -638,13 +719,13 @@ public class LeashDataImpl implements ILeashData {
double pullStrength = 1.0 + excessRatio * 2.0;
Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * Config.springDampening()
(distance - info.elasticDistance()) * pullStrength * CommonEventHandler.leashConfigManager.getSpringDampening()
);
return new Vec3(
pullForce.x * Config.axisSpecificElasticity().x,
pullForce.y * Config.axisSpecificElasticity().y,
pullForce.z * Config.axisSpecificElasticity().z
pullForce.x * CommonEventHandler.leashConfigManager.getXElasticity(),
pullForce.y * CommonEventHandler.leashConfigManager.getYElasticity(),
pullForce.z * CommonEventHandler.leashConfigManager.getZElasticity()
);
}
@ -658,16 +739,20 @@ public class LeashDataImpl implements ILeashData {
@Override
public boolean removeLeash(UUID holderUUID) {
boolean removed = leashHolders.remove(holderUUID) != null;
if (removed)
if (removed) {
LeashStateAPI.Operations.detach(entity, holderUUID);
markForSync();
}
return removed;
}
@Override
public boolean removeLeash(BlockPos knotPos) {
boolean removed = leashKnots.remove(knotPos) != null;
if (removed)
if (removed) {
LeashStateAPI.Operations.detach(entity, knotPos);
markForSync();
}
return removed;
}
@ -675,18 +760,21 @@ public class LeashDataImpl implements ILeashData {
public void removeAllLeashes() {
leashHolders.clear();
leashKnots.clear();
LeashStateAPI.Offset.removeAll(entity);
markForSync();
}
@Override
public void removeAllHolderLeashes() {
leashHolders.clear();
LeashStateAPI.Offset.removeAllUUIDs(entity);
markForSync();
}
@Override
public void removeAllKnotLeashes() {
leashKnots.clear();
LeashStateAPI.Offset.removeAllBlockPoses(entity);
markForSync();
}
@ -716,6 +804,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder);
leashHolders.put(newHolder.getUUID(), leashInfo);
}
LeashStateAPI.Operations.transfer(entity, oldHolderUUID, newHolder);
markForSync();
return true;
}
@ -730,6 +819,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder, reserved);
leashHolders.put(newHolder.getUUID(), leashInfo);
}
LeashStateAPI.Operations.transfer(entity, oldHolderUUID, newHolder);
markForSync();
return true;
}
@ -745,6 +835,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder);
leashHolders.put(newHolder.getUUID(), leashInfo);
}
LeashStateAPI.Operations.transfer(entity, knotPos, newHolder);
markForSync();
return true;
}
@ -760,6 +851,7 @@ public class LeashDataImpl implements ILeashData {
LeashInfo leashInfo = info.transferHolder(newHolder, reserved);
leashHolders.put(newHolder.getUUID(), leashInfo);
}
LeashStateAPI.Operations.transfer(entity, knotPos, newHolder);
markForSync();
return true;
}
@ -885,7 +977,6 @@ public class LeashDataImpl implements ILeashData {
}
infoTag.putInt("HolderID", info.holderIdOpt().get());
infoTag.putString("LeashItem", info.reserved());
infoTag.put("Offset", NBTWriter.writeVec3(info.attachOffset()));
infoTag.putDouble("MaxDistance", info.maxDistance());
infoTag.putDouble("ElasticDistance", info.elasticDistance());
infoTag.putInt("KeepLeashTicks", info.keepLeashTicks());
@ -933,7 +1024,6 @@ public class LeashDataImpl implements ILeashData {
infoTag.getUUID("HolderUUID"),
infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"),
NBTReader.readVec3(infoTag.getCompound("Offset")),
infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"),
@ -949,7 +1039,6 @@ public class LeashDataImpl implements ILeashData {
NbtUtils.readBlockPos(infoTag.getCompound("KnotBlockPos")),
infoTag.getInt("HolderID"),
infoTag.getString("LeashItem"),
NBTReader.readVec3(infoTag.getCompound("Offset")),
infoTag.getDouble("MaxDistance"),
infoTag.contains("ElasticDistance") ? infoTag.getDouble("ElasticDistance") : 6.0,
infoTag.getInt("KeepLeashTicks"),
@ -961,7 +1050,7 @@ public class LeashDataImpl implements ILeashData {
@Override
public boolean canBeLeashed() {
return (leashHolders.size() + leashKnots.size()) <= Config.maxLeashesPerEntity();
return (leashHolders.size() + leashKnots.size()) <= CommonEventHandler.leashConfigManager.getMaxLeashesPerEntity();
}
@Override
@ -1007,48 +1096,28 @@ public class LeashDataImpl implements ILeashData {
return leashableInArea(holder, i -> isLeashHolder(i, holder), 1024D);
}
public boolean canBeAttachedTo(Entity pEntity) {
if(pEntity == entity) {
if (pEntity == entity) {
return false;
} else {
Optional<LeashInfo> leashInfo = getLeashInfo(pEntity);
return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= Config.leashElasticDist() * Config.leashExtremeSnapDistFactor()) && canBeLeashed();//距离最大,则不可以被固定或转移
return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= CommonEventHandler.leashConfigManager.getElasticDistance() * CommonEventHandler.leashConfigManager.getExtremeSnapFactor()) && canBeLeashed();//距离最大,则不可以被固定或转移
}
}
public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) {
AtomicBoolean isTarget = new AtomicBoolean(false);
LeashUtil.getLeashData(pEntity)
.ifPresent(i ->
isTarget.set(i.isLeashedBy(pHolderUUID))
);
return isTarget.get();
}
public static boolean isLeashHolder(@NotNull Entity pEntity, BlockPos pKnotPos) {
AtomicBoolean isTarget = new AtomicBoolean(false);
LeashUtil.getLeashData(pEntity)
.ifPresent(i ->
isTarget.set(i.isLeashedBy(pKnotPos))
);
return isTarget.get();
}
public static boolean isLeashHolder(@NotNull Entity pEntity, Entity pTestHolder) {
return pTestHolder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
isLeashHolder(pEntity, superLeashKnotEntity.getPos()) :
isLeashHolder(pEntity, pTestHolder.getUUID());
}
// 计算拴绳附着点
@Contract("_ -> new")
private @NotNull Vec3 calculateAttachOffset(@NotNull Entity entity) {
EntityType<?> type = entity.getType();
if (type == EntityType.HORSE || type == EntityType.DONKEY) {
return new Vec3(0, 1.4, 0.3);
} else if (type == EntityType.IRON_GOLEM) {
return new Vec3(0, 1.8, 0);
}
//TODO: 未来自定义配置
return new Vec3(0, entity.getBbHeight() * 0.8, 0);
public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) {
return LeashDataAPI.getLeashData(pEntity)
.map(leashData -> leashData.isLeashedBy(pHolderUUID))
.orElse(false);
}
public static boolean isLeashHolder(@NotNull Entity pEntity, BlockPos pKnotPos) {
return LeashDataAPI.getLeashData(pEntity)
.map(leashData -> leashData.isLeashedBy(pKnotPos))
.orElse(false);
}
}

View File

@ -127,7 +127,7 @@ public class LeashStateImpl implements ILeashState {
@Override
public void resetLeashHolderLocationOffset(Entity holder) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
if (holder instanceof SuperLeashKnotEntity leashKnot) {
resetLeashHolderLocationOffset(leashKnot.getPos());
} else resetLeashHolderLocationOffset(holder.getUUID());
}
@ -146,45 +146,97 @@ public class LeashStateImpl implements ILeashState {
@Override
public void setLeashHolderLocationOffset(Entity holder, Vec3 offset) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
if (holder instanceof SuperLeashKnotEntity leashKnot) {
setLeashHolderLocationOffset(leashKnot.getPos(), offset);
} else setLeashHolderLocationOffset(holder.getUUID(), offset);
}
@Override
public void setLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) {
leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.setHolderLocationOffset(offset));
LeashState currentState = leashHolders.get(holderUUID);
if (currentState == null) {
// 创建新的状态使用默认的应用实体偏移量
leashHolders.put(holderUUID, new LeashState(
offset,
getDefaultLeashApplyEntityLocationOffset(),
Vec3.ZERO // 或者合适的默认值
));
} else {
// 更新现有状态
leashHolders.put(holderUUID,
currentState.setHolderLocationOffset(offset)
);
}
markForSync();
}
@Override
public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 leashHolderLocationOffset) {
leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(leashHolderLocationOffset));
public void setLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) {
LeashState currentState = leashKnots.get(knotPos);
if (currentState == null) {
// 创建新的状态
leashKnots.put(knotPos, new LeashState(
offset,
getDefaultLeashApplyEntityLocationOffset(),
Vec3.ZERO
));
} else {
// 更新现有状态
leashKnots.put(knotPos,
currentState.setHolderLocationOffset(offset)
);
}
markForSync();
}
@Override
public void addLeashHolderLocationOffset(Entity holder, Vec3 offset) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
if (holder instanceof SuperLeashKnotEntity leashKnot) {
addLeashHolderLocationOffset(leashKnot.getPos(), offset);
} else addLeashHolderLocationOffset(holder.getUUID(), offset);
}
@Override
public void addLeashHolderLocationOffset(UUID holderUUID, Vec3 offset) {
leashHolders.computeIfPresent(holderUUID, (uuid, state) -> state.setHolderLocationOffset(state.holderLocationOffset().add(offset)));
LeashState currentState = leashHolders.get(holderUUID);
if (currentState == null) {
// 创建新的状态使用默认的应用实体偏移量
leashHolders.put(holderUUID, new LeashState(
offset,
getDefaultLeashApplyEntityLocationOffset(),
Vec3.ZERO // 或者合适的默认值
));
} else {
// 更新现有状态
leashHolders.put(holderUUID,
currentState.setHolderLocationOffset(currentState.holderLocationOffset().add(offset))
);
}
markForSync();
}
@Override
public void addLeashHolderLocationOffset(BlockPos knotPos, Vec3 offset) {
leashKnots.computeIfPresent(knotPos, (blockPos, state) -> state.setHolderLocationOffset(state.holderLocationOffset().add(offset)));
LeashState currentState = leashKnots.get(knotPos);
if (currentState == null) {
// 创建新的状态
leashKnots.put(knotPos, new LeashState(
offset,
getDefaultLeashApplyEntityLocationOffset(),
Vec3.ZERO
));
} else {
// 更新现有状态
leashKnots.put(knotPos,
currentState.setHolderLocationOffset(currentState.holderLocationOffset().add(offset))
);
}
markForSync();
}
@Override
public void removeLeashHolderLocationOffset(Entity holder) {
if (entity instanceof SuperLeashKnotEntity leashKnot) {
if (holder instanceof SuperLeashKnotEntity leashKnot) {
removeLeashHolderLocationOffset(leashKnot.getPos());
} else removeLeashHolderLocationOffset(holder.getUUID());
}
@ -201,6 +253,25 @@ public class LeashStateImpl implements ILeashState {
markForSync();
}
@Override
public void removeAllLeashHolderLocationOffset() {
leashKnots.clear();
leashHolders.clear();
markForSync();
}
@Override
public void removeAllLeashHolderUUIDLocationOffset() {
leashHolders.clear();
markForSync();
}
@Override
public void removeAllLeashHolderBlockPosLocationOffset() {
leashKnots.clear();
markForSync();
}
@Override
public void resetAllLeashHolderLocationsOffset() {
leashKnots.replaceAll((pos, leashState) -> leashState.resetHolderLocationOffset());

View File

@ -19,7 +19,6 @@ 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.phys.Vec3;
import net.minecraftforge.common.util.INBTSerializable;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
@ -142,7 +141,6 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
Optional<UUID> holderUUIDOpt,
Optional<Integer> holderIdOpt, // Only for client side use
String reserved, // 保留字段
Vec3 attachOffset,
double maxDistance,
double elasticDistance,
int keepLeashTicks, // 剩余 Tick
@ -150,14 +148,13 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
) {
public static final LeashInfo EMPTY = new LeashInfo(
Optional.empty(), Optional.empty(), Optional.empty(),
"", Vec3.ZERO, 12.0D, 6.0D, 0, 0
"", 12.0D, 6.0D, 0, 0
);
/* ---------- Factory ---------- */
public static LeashInfo create(
Entity entity,
String reserved,
Vec3 offset,
double maxDistance,
double elasticDistance,
int keepTicks,
@ -165,32 +162,31 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
) {
return entity instanceof SuperLeashKnotEntity knot
? new LeashInfo(knot.getPos(), entity.getId(), reserved,
offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks)
: new LeashInfo(entity.getUUID(), entity.getId(), reserved,
offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
maxDistance, elasticDistance, keepTicks, maxKeepTicks)
: new LeashInfo(entity.getUUID(), entity.getId(), reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
}
public LeashInfo(UUID holderUUID, int holderId, String reserved, Vec3 offset,
public LeashInfo(UUID holderUUID, int holderId, String reserved,
double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) {
this(Optional.empty(), Optional.of(holderUUID), Optional.of(holderId),
reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
}
public LeashInfo(BlockPos knotPos, int holderId, String reserved, Vec3 offset,
public LeashInfo(BlockPos knotPos, int holderId, String reserved,
double maxDistance, double elasticDistance, int keepTicks, int maxKeepTicks) {
this(Optional.of(knotPos), Optional.empty(), Optional.of(holderId),
reserved, offset, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
reserved, maxDistance, elasticDistance, keepTicks, maxKeepTicks);
}
/* ---------- State updates ---------- */
public LeashInfo decrementKeepTicks() {
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset,
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved,
maxDistance, elasticDistance,
Math.max(0, keepLeashTicks - 1), maxKeepLeashTicks);
}
public LeashInfo resetKeepTicks() {
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved, attachOffset,
return new LeashInfo(blockPosOpt, holderUUIDOpt, holderIdOpt, reserved,
maxDistance, elasticDistance,
maxKeepLeashTicks, maxKeepLeashTicks);
}
@ -205,7 +201,7 @@ public interface ILeashData extends INBTSerializable<CompoundTag> {
isKnot ? Optional.of(((SuperLeashKnotEntity) entity).getPos()) : Optional.empty(),
!isKnot ? Optional.of(entity.getUUID()) : Optional.empty(),
Optional.of(entity.getId()),
newReserved, attachOffset, maxDistance, elasticDistance,
newReserved, maxDistance, elasticDistance,
keepLeashTicks, maxKeepLeashTicks
);
}

View File

@ -67,6 +67,9 @@ public interface ILeashState extends INBTSerializable<CompoundTag> {
void removeLeashHolderLocationOffset(Entity holder);
void removeLeashHolderLocationOffset(UUID holderUUID);
void removeLeashHolderLocationOffset(BlockPos knotPos);
void removeAllLeashHolderLocationOffset();
void removeAllLeashHolderUUIDLocationOffset();
void removeAllLeashHolderBlockPosLocationOffset();
/* ----------------------
* Apply-entity offset

View File

@ -19,13 +19,13 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.CommonEventHandler;
import java.util.List;
public class Command {
public static final String PREFIX = LeashCommonConfig.COMMON.SLPModCommandPrefix.get();
public static boolean SHOULD_USE_PREFIX = LeashCommonConfig.COMMON.EnableSLPModCommandPrefix.get();
public static final String PREFIX = CommonEventHandler.leashConfigManager.getCommandPrefix();
public static boolean SHOULD_USE_PREFIX = CommonEventHandler.leashConfigManager.isCommandPrefixEnabled();
static LiteralArgumentBuilder<CommandSourceStack> getLiterArgumentBuilderOfCSS(String name, boolean shouldAddToList, @Nullable List<LiteralArgumentBuilder<CommandSourceStack>> list) {
LiteralArgumentBuilder<CommandSourceStack> literal = Commands.literal(name);
if (shouldAddToList) {

View File

@ -16,13 +16,525 @@
package top.r3944realms.superleadrope.content.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.*;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.content.gamerule.server.CreateSuperLeashKnotEntityIfAbsent;
import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static top.r3944realms.superleadrope.content.command.Command.*;
import static top.r3944realms.superleadrope.content.command.Command.SHOULD_USE_PREFIX;
public class LeashDataCommand {
// 获取Data
// 设置Data
// <add/transfer/remove> Holder<BlockPos/Entity<需判断实体类型>>
// 设置对应目标的 最大长度 最长断裂距离 保持不断裂时间刻
public static final String SLP_LEASH_MESSAGE_ = SuperLeadRope.MOD_ID + ".command.leash.message.";
public static final String LEASH_DATA_GET_ = SLP_LEASH_MESSAGE_ + ".get.",
TITLE = LEASH_DATA_GET_ + "title",
TOTAL = LEASH_DATA_GET_ + "total",
BLOCK = LEASH_DATA_GET_ + "block",
UUID = LEASH_DATA_GET_ + "uuid",
MAX = LEASH_DATA_GET_ + "max",
ELASTIC = LEASH_DATA_GET_ + "elastic",
KEEP = LEASH_DATA_GET_ + "keep",
RESERVED = LEASH_DATA_GET_ + "reserved"
;
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
@Nullable List<LiteralArgumentBuilder<CommandSourceStack>> nodeList = SHOULD_USE_PREFIX ? null : new ArrayList<>();
LiteralArgumentBuilder<CommandSourceStack> literalArgumentBuilder = Commands.literal(PREFIX);
LiteralArgumentBuilder<CommandSourceStack> $$leashDataRoot = getLiterArgumentBuilderOfCSS("leashdata", !SHOULD_USE_PREFIX, nodeList);
RequiredArgumentBuilder<CommandSourceStack, EntitySelector> $$$add$holder = Commands.argument("holder", EntityArgument.entity())
.executes(LeashDataCommand::addLeash)
.then(Commands.argument("maxDistance", DoubleArgumentType.doubleArg(1.0, 256.0))
.executes(context -> addLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance")))
.then(Commands.argument("elasticDistance", DoubleArgumentType.doubleArg(1.0, 128.0))
.executes(context -> addLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance"),
DoubleArgumentType.getDouble(context, "elasticDistance")))
.then(Commands.argument("keepTicks", IntegerArgumentType.integer(0))
.executes(context -> addLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance"),
DoubleArgumentType.getDouble(context, "elasticDistance"),
IntegerArgumentType.getInteger(context, "keepTicks")))
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> addLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance"),
DoubleArgumentType.getDouble(context, "elasticDistance"),
IntegerArgumentType.getInteger(context, "keepTicks"),
StringArgumentType.getString(context, "reserved")))
)
)
)
);
LiteralArgumentBuilder<CommandSourceStack> $$$add$pos = Commands.literal("block")
.then(Commands.argument("pos", BlockPosArgument.blockPos())
.executes(LeashDataCommand::addBlockLeash)
.then(Commands.argument("maxDistance", DoubleArgumentType.doubleArg(1.0, 256.0))
.executes(context -> addBlockLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance")))
.then(Commands.argument("elasticDistance", DoubleArgumentType.doubleArg(1.0, 128.0))
.executes(context -> addBlockLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance"),
DoubleArgumentType.getDouble(context, "elasticDistance"), 0, ""))
.then(Commands.argument("keepTicks", IntegerArgumentType.integer(0))
.executes(context -> addBlockLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance"),
DoubleArgumentType.getDouble(context, "elasticDistance"),
IntegerArgumentType.getInteger(context, "keepTicks")))
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> addBlockLeash(context,
DoubleArgumentType.getDouble(context, "maxDistance"),
DoubleArgumentType.getDouble(context, "elasticDistance"),
IntegerArgumentType.getInteger(context, "keepTicks"),
StringArgumentType.getString(context, "reserved")))
)
)
)
)
);
LiteralArgumentBuilder<CommandSourceStack> $$$add = Commands.literal("add")
.then(Commands.argument("target", EntityArgument.entities())
// 实体拴绳
.then($$$add$holder)
// 方块拴绳
.then($$$add$pos)
);
LiteralArgumentBuilder<CommandSourceStack> $$$remove = Commands.literal("remove")
.then(Commands.argument("target", EntityArgument.entities())
// 移除特定实体拴绳
.then(Commands.argument("holder", EntityArgument.entity())
.executes(LeashDataCommand::removeLeash)
)
// 移除方块拴绳
.then(Commands.literal("block")
.then(Commands.argument("pos", BlockPosArgument.blockPos())
.executes(LeashDataCommand::removeBlockLeash)
)
)
// 批量移除
.then(Commands.literal("all")
.executes(LeashDataCommand::removeAllLeashes)
)
.then(Commands.literal("holders")
.executes(LeashDataCommand::removeAllHolderLeashes)
)
.then(Commands.literal("blocks")
.executes(LeashDataCommand::removeAllBlockLeashes)
)
);
LiteralArgumentBuilder<CommandSourceStack> $$$transfer = Commands.literal("transfer")
.then(Commands.argument("target", EntityArgument.entities())
// 实体到实体转移
.then(Commands.argument("from", EntityArgument.entity())
.then(Commands.argument("to", EntityArgument.entity())
.executes(LeashDataCommand::transferLeash)
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> transferLeash(context,
StringArgumentType.getString(context, "reserved")))
)
)
)
// 方块到实体转移
.then(Commands.literal("fromBlock")
.then(Commands.argument("fromPos", BlockPosArgument.blockPos())
.then(Commands.argument("to", EntityArgument.entity())
.executes(LeashDataCommand::transferFromBlock)
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> transferFromBlock(context,
StringArgumentType.getString(context, "reserved")))
)
)
)
)
);
RequiredArgumentBuilder<CommandSourceStack, EntitySelector> $$$set$holder = Commands.argument("holder", EntityArgument.entity())
// 设置最大距离
.then(Commands.literal("maxDistance")
.then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 256.0))
.executes(LeashDataCommand::setMaxDistance)
.then(Commands.argument("keepTicks", IntegerArgumentType.integer(0))
.executes(context -> setMaxDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks")))
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> setMaxDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks"),
StringArgumentType.getString(context, "reserved")))
)
)
)
)
// 设置弹性距离
.then(Commands.literal("elasticDistance")
.then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 128.0))
.executes(context -> setElasticDistance(context, 0, ""))
.then(Commands.argument("keepTicks", IntegerArgumentType.integer(0))
.executes(context -> setElasticDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks"), ""))
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> setElasticDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks"),
StringArgumentType.getString(context, "reserved")))
)
)
)
);
LiteralArgumentBuilder<CommandSourceStack> $$$set$pos = Commands.literal("block")
.then(Commands.argument("pos", BlockPosArgument.blockPos())
// 设置最大距离
.then(Commands.literal("maxDistance")
.then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 256.0))
.executes(LeashDataCommand::setBlockMaxDistance)
.then(Commands.argument("keepTicks", IntegerArgumentType.integer(0))
.executes(context -> setBlockMaxDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks")))
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> setBlockMaxDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks"),
StringArgumentType.getString(context, "reserved")))
)
)
)
)
// 设置弹性距离
.then(Commands.literal("elasticDistance")
.then(Commands.argument("distance", DoubleArgumentType.doubleArg(1.0, 128.0))
.executes(LeashDataCommand::setBlockElasticDistance)
.then(Commands.argument("keepTicks", IntegerArgumentType.integer(0))
.executes(context -> setBlockElasticDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks")))
.then(Commands.argument("reserved", StringArgumentType.string())
.executes(context -> setBlockElasticDistance(context,
IntegerArgumentType.getInteger(context, "keepTicks"),
StringArgumentType.getString(context, "reserved")))
)
)
)
)
);
LiteralArgumentBuilder<CommandSourceStack> $$$set = Commands.literal("set")
.then(Commands.argument("target", EntityArgument.entities())
// 实体拴绳设置
.then($$$set$holder)
// 方块拴绳设置
.then($$$set$pos)
);
LiteralArgumentBuilder<CommandSourceStack> $$$applayForces = Commands.literal("applyForces")
.then(Commands.argument("target", EntityArgument.entities())
.executes(LeashDataCommand::applyForces)
);
LiteralArgumentBuilder<CommandSourceStack> $$$get = Commands.literal("get")
.then(Commands.argument("target", EntityArgument.entities())
.executes(LeashDataCommand::getLeashData)
);
$$leashDataRoot
.requires(source -> source.hasPermission(2)) // 需要OP权限
// ==================== GET 命令 ====================
.then($$$get)
// ==================== ADD 命令 ====================
.then($$$add)
// ==================== REMOVE 命令 ====================
.then($$$remove)
// ==================== TRANSFER 命令 ====================
.then($$$transfer)
// ==================== SET 命令 ====================
.then($$$set)
// ==================== APPLY FORCES 命令 ====================
.then($$$applayForces);
if(SHOULD_USE_PREFIX){
literalArgumentBuilder.then($$leashDataRoot);
dispatcher.register(literalArgumentBuilder);
} else {
if (nodeList != null) {
nodeList.forEach(dispatcher::register);
}
}
}
}
public static final String SET_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "set.max_distance";
private static int setMaxDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setMaxDistance(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), "");
}
private static int setMaxDistance(CommandContext<CommandSourceStack> context, double maxDistance) throws CommandSyntaxException {
return setMaxDistance(context, maxDistance, "");
}
private static int setMaxDistance(CommandContext<CommandSourceStack> context, double maxDistance, String reserved) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
Entity holder = EntityArgument.getEntity(context, "holder");
for (Entity target : targets) {
}
return -1;
}
public static final String REMOVE_ALL_BLOCK_LEASHES = SLP_LEASH_MESSAGE_ + "remove.all_block_leashes";
private static int removeAllBlockLeashes(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return -1;
}
public static final String REMOVE_ALL_HOLDER_LEASHES = SLP_LEASH_MESSAGE_ + "remove.all_holder_leashes";
private static int removeAllHolderLeashes(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return -1;
}
public static final String TRANSFER_FROM_BLOCK = SLP_LEASH_MESSAGE_ + "transfer.from_block";
private static int transferFromBlock(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return transferFromBlock(context, "");
}
private static int transferFromBlock(CommandContext<CommandSourceStack> context, String reserved) throws CommandSyntaxException {
return -1;
}
public static final String SET_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "set.elastic_distance";
private static int setElasticDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setElasticDistance(context, 0 ,"");
}
private static int setElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks) throws CommandSyntaxException {
return setElasticDistance(context, keepTicks ,"");
}
private static int setElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks, String reserved) throws CommandSyntaxException {
return -1;
}
public static final String SET_BLOCK_MAX_DISTANCE = SLP_LEASH_MESSAGE_ + "set.block_max_distance";
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setBlockMaxDistance(context, 0 ,"");
}
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> context, int keepTicks) throws CommandSyntaxException {
return setBlockMaxDistance(context, keepTicks ,"");
}
private static int setBlockMaxDistance(CommandContext<CommandSourceStack> context, int keepTicks, String reserved) throws CommandSyntaxException {
return -1;
}
public static final String SET_BLOCK_ELASTIC_DISTANCE = SLP_LEASH_MESSAGE_ + "set.block_elastic_distance";
private static int setBlockElasticDistance(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return setBlockElasticDistance(context, 0 ,"");
}
private static int setBlockElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks) throws CommandSyntaxException {
return setBlockElasticDistance(context, keepTicks ,"");
}
private static int setBlockElasticDistance(CommandContext<CommandSourceStack> context, int keepTicks, String reserved) throws CommandSyntaxException {
return -1;
}
// ==================== 命令执行方法 ====================
private static int getLeashData(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
CommandSourceStack source = context.getSource();
for (Entity target : targets) {
Collection<ILeashData.LeashInfo> leashes = LeashDataAPI.QueryOperations.getAllLeashes(target);
source.sendSuccess(() -> Component.literal("=== Leash Data for " + target.getName().getString() + " ==="), false);
source.sendSuccess(() -> Component.literal("Total leashes: " + leashes.size()), false);
// TODO:翻译支持 HoverTip实现部分信息简化显示
for (ILeashData.LeashInfo leash : leashes) {
StringBuilder info = new StringBuilder();
leash.blockPosOpt().ifPresent(pos -> info.append("Block: ").append(pos.toShortString()).append(" "));
leash.holderUUIDOpt().ifPresent(uuid -> info.append("UUID: ").append(uuid).append(" "));
info.append("Max: ").append(leash.maxDistance()).append(" ");
info.append("Elastic: ").append(leash.elasticDistance()).append(" ");
info.append("Keep: ").append(leash.keepLeashTicks()).append("/").append(leash.maxKeepLeashTicks());
if (!leash.reserved().isEmpty()) {
info.append(" Reserved: ").append(leash.reserved());
}
source.sendSuccess(() -> Component.literal(info.toString()), false);
}
}
return targets.size();
}
private static int addLeash(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return addLeash(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance) throws CommandSyntaxException {
return addLeash(context, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance) throws CommandSyntaxException {
return addLeash(context, maxDistance, elasticDistance, 0, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks) throws CommandSyntaxException {
return addLeash(context, maxDistance, elasticDistance, keepTicks, "");
}
private static int addLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks, String reserved)
throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
Entity holder = EntityArgument.getEntity(context, "holder");
CommandSourceStack source = context.getSource();
List<Entity> successful = new ArrayList<>(), failed = new ArrayList<>();
for (Entity target : targets) {
if(LeashDataAPI.LeashOperations.attach(target, holder, maxDistance, elasticDistance, keepTicks, reserved)) {
successful.add(target);
} else failed.add(target);
}
// todo: source.sendSuccess(() -> Component.translatable(/*成功{},失败{}*/), true);
return successful.size();
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return addBlockLeash(context, CommonEventHandler.leashConfigManager.getMaxLeashLength(), CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context,
double maxDistance) throws CommandSyntaxException {
return addBlockLeash(context, maxDistance, CommonEventHandler.leashConfigManager.getElasticDistance(), 0, "");
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks) throws CommandSyntaxException {
return addBlockLeash(context, maxDistance, elasticDistance, keepTicks, "");
}
private static int addBlockLeash(CommandContext<CommandSourceStack> context,
double maxDistance, double elasticDistance, int keepTicks, String reserved)
throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
BlockPos pos = BlockPosArgument.getBlockPos(context, "pos");
CommandSourceStack source = context.getSource();
ServerLevel level = source.getLevel();
SuperLeashKnotEntity knotEntity = SuperLeashKnotEntity.get(level, pos)
.or(() -> {
if (SLPGameruleRegistry.getGameruleBoolValue(level, CreateSuperLeashKnotEntityIfAbsent.NAME_KEY))
return Optional.of(SuperLeashKnotEntity.createKnot(level, pos, true));
else return Optional.empty();
}).orElse(null);
if (knotEntity == null) {
// todo: source.sendFailure(Component.translatable(/*失败,目标上无拴绳结*/));
return -1;
}
List<Entity> successful = new ArrayList<>(), failed = new ArrayList<>();
for (Entity target : targets) {
if(LeashDataAPI.LeashOperations.attach(target, knotEntity, maxDistance, elasticDistance, keepTicks, reserved)) {
successful.add(target);
} else failed.add(target);
}
// todo: source.sendSuccess(() -> Component.translatable(/*成功{},失败{}*/), true);
return successful.size();
}
private static int removeLeash(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
Entity holder = EntityArgument.getEntity(context, "holder");
CommandSourceStack source = context.getSource();
int successCount = 0;
for (Entity target : targets) {
boolean success = LeashDataAPI.LeashOperations.detach(target, holder);
if (success) {
successCount++;
source.sendSuccess(() -> Component.literal("Removed leash from " + target.getName().getString() +
" held by " + holder.getName().getString()), false);
} else {
source.sendFailure(Component.literal("No leash found for " + holder.getName().getString() +
" on " + target.getName().getString()));
}
}
return successCount;
}
private static int removeBlockLeash(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
BlockPos pos = BlockPosArgument.getBlockPos(context, "pos");
CommandSourceStack source = context.getSource();
int successCount = 0;
for (Entity target : targets) {
boolean success = LeashDataAPI.LeashOperations.detach(target, pos);
if (success) {
successCount++;
source.sendSuccess(() -> Component.literal("Removed block leash from " + target.getName().getString() +
" at " + pos.toShortString()), false);
} else {
source.sendFailure(Component.literal("No block leash found at " + pos.toShortString() +
" on " + target.getName().getString()));
}
}
return successCount;
}
private static int removeAllLeashes(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
CommandSourceStack source = context.getSource();
for (Entity target : targets) {
LeashDataAPI.LeashOperations.detachAll(target);
source.sendSuccess(() -> Component.literal("Removed all leashes from " + target.getName().getString()), false);
}
return targets.size();
}
private static int transferLeash(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return transferLeash(context, "");
}
private static int transferLeash(CommandContext<CommandSourceStack> context, String reserved)
throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
Entity from = EntityArgument.getEntity(context, "from");
Entity to = EntityArgument.getEntity(context, "to");
CommandSourceStack source = context.getSource();
int successCount = 0;
for (Entity target : targets) {
boolean success = reserved.isEmpty() ?
LeashDataAPI.TransferOperations.transfer(target, from, to) :
LeashDataAPI.TransferOperations.transfer(target, from, to, reserved);
if (success) {
successCount++;
source.sendSuccess(() -> Component.literal("Transferred leash from " + from.getName().getString() +
" to " + to.getName().getString() + " for " + target.getName().getString()), false);
} else {
source.sendFailure(Component.literal("Failed to transfer leash for " + target.getName().getString()));
}
}
return successCount;
}
private static int applyForces(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Collection<? extends Entity> targets = EntityArgument.getEntities(context, "target");
CommandSourceStack source = context.getSource();
for (Entity target : targets) {
LeashDataAPI.PhysicsOperations.applyForces(target);
source.sendSuccess(() -> Component.literal("Applied leash forces to " + target.getName().getString()), false);
}
return targets.size();
}
}

View File

@ -34,13 +34,13 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
@ -83,7 +83,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
this.playSound(SoundEvents.LEASH_KNOT_BREAK);
List<Entity> entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this));
entities.forEach(entity ->
LeashUtil.getLeashData(entity)
LeashDataAPI.getLeashData(entity)
.map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this))
);
}
@ -120,6 +120,27 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
pLevel.addFreshEntity(superLeashKnotEntity1);
return superLeashKnotEntity1;
}
public static @NotNull Optional<SuperLeashKnotEntity> get(@NotNull Level level, @NotNull BlockPos pos) {
AABB searchArea = new AABB(pos).inflate(1.0D);
return level.getEntitiesOfClass(SuperLeashKnotEntity.class, searchArea)
.stream()
.filter(knot -> knot.getPos().equals(pos))
.findFirst();
}
/**
* 创建拴绳结请不用直接调用这个除非你知道自己在干上面
* @return 拴绳结
*/
public static @NotNull SuperLeashKnotEntity createKnot(@NotNull Level pLevel, @NotNull BlockPos pPos, boolean isEmpty) {
if(isEmpty) {
SuperLeashKnotEntity superLeashKnotEntity1 = new SuperLeashKnotEntity(pLevel, pPos);
pLevel.addFreshEntity(superLeashKnotEntity1);
return superLeashKnotEntity1;
}
throw new IllegalArgumentException("Cannot create Knot Entity of type " + SuperLeashKnotEntity.class.getSimpleName());
}
@Override
protected void recalculateBoundingBox() {
@ -169,7 +190,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
List<Entity> entities = LeashDataImpl.leashableInArea(player);
for(Entity entity : entities) {
if (LeashDataImpl.isLeashHolder(entity, player.getUUID()))
LeashUtil.getLeashData(entity)
LeashDataAPI.getLeashData(entity)
.ifPresent(i -> {
i.transferLeash(player.getUUID(), this);
isTransferLeash.set(true);
@ -182,7 +203,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
this.discard();
List<Entity> entities1 = LeashDataImpl.leashableInArea(this);
entities1.forEach(entity ->
LeashUtil.getLeashData(entity)
LeashDataAPI.getLeashData(entity)
.ifPresent(iLeashDataCapability -> {
iLeashDataCapability.removeLeash(this);
isRemoveLeashKnot.set(true);

View File

@ -24,12 +24,12 @@ public class SLPGamerules {
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 String RULE_KEY_PERFix_ = "gamerule." + GAMERULE_PREFIX.toLowerCase();
public static final String RULE_KEY_PERFiX_ = "gamerule." + GAMERULE_PREFIX.toLowerCase();
public static String getDescriptionKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName() + ".description";
return RULE_KEY_PERFiX_ + gameRuleClass.getSimpleName() + ".description";
}
public static String getDescriptionKey(String gameRuleName) {
return RULE_KEY_PERFix_ + gameRuleName + ".description";
return RULE_KEY_PERFiX_ + gameRuleName + ".description";
}
public static String getGameruleName(Class<?> clazz) {
return SLPGamerules.GAMERULE_PREFIX + clazz.getSimpleName();
@ -39,7 +39,7 @@ public class SLPGamerules {
}
public static String getNameKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName();
return RULE_KEY_PERFiX_ + gameRuleClass.getSimpleName();
}
}

View File

@ -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 CreateSuperLeashKnotEntityIfAbsent {
public static final boolean DEFAULT_VALUE = true;
public static final String ID = SLPGamerules.getGameruleName(CreateSuperLeashKnotEntityIfAbsent.class);
public static final String DESCRIPTION_KEY = SLPGamerules.getDescriptionKey(CreateSuperLeashKnotEntityIfAbsent.class);
public static final String NAME_KEY = SLPGamerules.getNameKey(CreateSuperLeashKnotEntityIfAbsent.class);
public static final GameRules.Category CATEGORY = GameRules.Category.PLAYER;
public static void register() {
GAMERULE_REGISTRY.registerGamerule(ID, CATEGORY, DEFAULT_VALUE);
}
}

View File

@ -29,15 +29,13 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraftforge.common.extensions.IForgeItem;
import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.SLPToolTier;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashUtil;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.List;
import java.util.Optional;
@ -83,7 +81,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
}
public static boolean canUse(ItemStack itemStack) {
return itemStack.getDamageValue() < 974;
return itemStack.getDamageValue() < 1200;
}
@Override
@ -106,11 +104,10 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
* @param newHolder 新实体
* @param player 明确持有玩家
* @param level 维度世界
* @param leashStack 拴绳物品实例
* @return 是否成功
*/
public static boolean bindToEntity(Entity newHolder, Player player, Level level, ItemStack leashStack) {
return bindToEntity(newHolder, player, level, player.getOnPos(), leashStack);
public static boolean bindToEntity(Entity newHolder, Player player, Level level) {
return bindToEntity(newHolder, player, level, player.getOnPos());
}
/**
* 右键蹲下绑定到另一实体上
@ -118,10 +115,9 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
* @param player 明确持有玩家
* @param level 维度世界
* @param pos 坐标一般是明确持有玩家的位置
* @param leashStack 拴绳物品实例
* @return 是否成功
*/
public static boolean bindToEntity(Entity newHolder, Player player, Level level, BlockPos pos, ItemStack leashStack) {
public static boolean bindToEntity(Entity newHolder, Player player, Level level, BlockPos pos) {
boolean isSuccess = false;
// 查找当前玩家持有的可拴生物
@ -131,7 +127,7 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
);
for (Entity e : list) {
Optional<ILeashData> leashDataOpt = LeashUtil.getLeashData(e);
Optional<ILeashData> leashDataOpt = LeashDataAPI.getLeashData(e);
if (leashDataOpt.map(i -> i.canBeAttachedTo(newHolder)).orElse(false)) {
leashDataOpt.ifPresent(i -> i.transferLeash(player.getUUID(), newHolder));
@ -182,16 +178,16 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
if (leashStack.isEmpty() || !canUse(leashStack)) {
return false;
}
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
knot.playPlacementSound();
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
knot.playPlacementSound();
SuperLeashKnotEntity finalKnot = knot;
LeashUtil.getLeashData(player).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) {
i.addLeash(finalKnot);
isSuccess.set(true);
}
});
SuperLeashKnotEntity finalKnot = knot;
LeashDataAPI.getLeashData(player).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) {
if (!level.isClientSide) i.addLeash(finalKnot);
isSuccess.set(true);
}
});
}
// 情况二把已有生物拴到 knot
else if (!list.isEmpty()) {
@ -202,9 +198,9 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
}
SuperLeashKnotEntity finalKnot = knot;
LeashUtil.getLeashData(e).ifPresent(i -> {
LeashDataAPI.getLeashData(e).ifPresent(i -> {
if (i.canBeAttachedTo(finalKnot)) {
i.transferLeash(uuid, finalKnot);
if (!level.isClientSide) i.transferLeash(uuid, finalKnot);
isSuccess.set(true);
}
});

View File

@ -0,0 +1,44 @@
/*
* 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.hook;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
public class LeashRenderHook {
public static boolean shouldRenderExtra(Mob mob, Frustum camera) {
SuperLeadRope.logger.debug("[SuperLeash] Checking entity: {} at position: {}, {}, {}", mob.getName().getString(), mob.getX(), mob.getY(), mob.getZ());
AtomicBoolean flag = new AtomicBoolean(false);
LeashDataAPI.getLeashData(mob).ifPresent(i -> {
i.getAllLeashes().forEach(j -> {
Optional<Integer> i1 = j.holderIdOpt();
if (i1.isPresent()) {
Entity entity = mob.level().getEntity(i1.get());
if (entity != null) {
flag.set(camera.isVisible(entity.getBoundingBoxForCulling()));
}
}
});
});
return flag.get();
}
}

View File

@ -16,6 +16,8 @@
package top.r3944realms.superleadrope.core.leash;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
@ -24,15 +26,16 @@ import net.minecraft.world.entity.player.Player;
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;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.Optional;
public class LeashInteractHandler {
//只有玩家可以互动触发其它的暂不支持考虑到0 Mixin)
@ -48,6 +51,7 @@ public class LeashInteractHandler {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
}
return;
}
if (hand == InteractionHand.OFF_HAND) {
@ -57,8 +61,8 @@ public class LeashInteractHandler {
if (!LeashDataImpl.isLeashable(target)) {
return;
}
LazyOptional<ILeashData> LeashCap = target.getCapability(CapabilityHandler.LEASH_DATA_CAP);
if (!LeashCap.isPresent()) {
Optional<ILeashData> LeashCap = LeashDataAPI.getLeashData(target);
if (LeashCap.isEmpty()) {
return;
}
@ -72,7 +76,7 @@ public class LeashInteractHandler {
) {
boolean isSuccess = SuperLeadRopeItem.bindToEntity(target, player, player.level(), player.getOnPos(), ItemStack.EMPTY);
boolean isSuccess = SuperLeadRopeItem.bindToEntity(target, player, player.level(), player.getOnPos());
if (isSuccess) {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
@ -103,7 +107,7 @@ public class LeashInteractHandler {
boolean success = iLeashDataCapability.addLeash(player);
if (success) {
if(!player.isCreative())
itemStack.hurtAndBreak(24, player, e->{});
itemStack.hurtAndBreak(24, player, e-> e.level().playSound(null, player.getOnPos(), SoundEvents.ITEM_BREAK, SoundSource.PLAYERS));
level.playSound(null, target.getOnPos(), SLPSoundEvents.LEAD_TIED.get(), SoundSource.PLAYERS);
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
@ -123,7 +127,7 @@ public class LeashInteractHandler {
}
} else {
if (flag) {
target.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashDataCapability -> {
LeashDataAPI.getLeashData(target).ifPresent(leashDataCapability -> {
if (leashDataCapability.hasLeash()){
int size = leashDataCapability.getAllLeashes().size();
if (player.isSecondaryUseActive())

View File

@ -18,7 +18,9 @@ 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.command.LeashDataCommand;
import top.r3944realms.superleadrope.content.command.MotionCommand;
import top.r3944realms.superleadrope.content.gamerule.server.CreateSuperLeashKnotEntityIfAbsent;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
@ -42,21 +44,24 @@ public enum SLPLangKeyValue {
"Eternal Potato", "永恒土豆", "永恆馬鈴薯", "不滅薯", true
),
EP_TOOLTIP_TITLE(EternalPotatoItem.getDescKey("title"), ModPartEnum.DESCRIPTION,
EP_TOOLTIP_TITLE(
EternalPotatoItem.getDescKey("title"), ModPartEnum.DESCRIPTION,
"§6Mythical Item §7- §6Eternal Potato",
"§6神话物品 §7- §6永恒土豆",
"§6神話物品 §7- §6永恒土豆",
"§6永恒土豆 §7- §6传奇之物"
),
EP_DESC_TOOLTIP(EternalPotatoItem.getDescKey("desc"), ModPartEnum.DESCRIPTION,
EP_DESC_TOOLTIP(
EternalPotatoItem.getDescKey("desc"), ModPartEnum.DESCRIPTION,
"§7Symbol of server-wide contract, cannot be discarded",
"§7象征全服契约不可丢弃",
"§7象徵全服契約不可丟棄",
"§7象征全服契约绝不可弃"
),
EP_BIND_OWNER(EternalPotatoItem.getDescKey("bind_owner"), ModPartEnum.DESCRIPTION,
EP_BIND_OWNER(
EternalPotatoItem.getDescKey("bind_owner"), ModPartEnum.DESCRIPTION,
"§bBound Owner: §f%s",
"§b绑定主人: §f%s",
"§b綁定主人: §f%s",
@ -70,42 +75,48 @@ public enum SLPLangKeyValue {
"§c尚未绑定主人"
),
EP_OBLIGATION_TOOLTIP(EternalPotatoItem.getDescKey("obligation"), ModPartEnum.DESCRIPTION,
EP_OBLIGATION_TOOLTIP(
EternalPotatoItem.getDescKey("obligation"), ModPartEnum.DESCRIPTION,
"§7Daily obligations remaining: §a%d §c(+%d§c overdue)",
"§7今日剩余义务: §a%d §c(+%d §c逾期未完成)",
"§7今日剩餘義務: §a%d §c(+%d §c逾期未完成)",
"§7今日责务尚余: §a%d §c(+%d §c逾期未尽)"
),
EP_PUNISH_TOOLTIP(EternalPotatoItem.getDescKey("punish"), ModPartEnum.DESCRIPTION,
EP_PUNISH_TOOLTIP(
EternalPotatoItem.getDescKey("punish"), ModPartEnum.DESCRIPTION,
"§cOverdue punishments: §4%d §7(will be applied), grace exceeded: §4%d",
"§c逾期未完成责务: §4%d §7(将会受罚),超出宽限数: §4%d",
"§c逾期未完成责務: §4%d §7(將會受罰),超出寬限數: §4%d",
"§c逾期责务尚未完成: §4%d §7(將受懲罰),超出寬限數: §4%d"
),
EP_OBLIGATION_INFO(EternalPotatoItem.getMsgKey("obligation_info"), ModPartEnum.MESSAGE,
EP_OBLIGATION_INFO(
EternalPotatoItem.getMsgKey("obligation_info"), ModPartEnum.MESSAGE,
"§e[Eternal Potato] §fThis is the server-wide shared person, remaining obligations today: §a%d§f.",
"§e[永恒土豆] §f这是全服共有之人今日义务剩余§a%d§f次。",
"§e[永恒土豆] §f這是全服共有之人今日義務剩餘§a%d§f次。",
"§e[永恒土豆] §f此为全服共享之人今日责务尚余§a%d§f次。"
),
EP_POTATO_HEAL(EternalPotatoItem.getMsgKey("potato_heal"), ModPartEnum.MESSAGE,
EP_POTATO_HEAL(
EternalPotatoItem.getMsgKey("potato_heal"), ModPartEnum.MESSAGE,
"§aThe power of the Eternal Potato comforts you, it won't disappear.",
"§a永恒土豆的力量抚慰了你但它不会消失。",
"§a永恆土豆的力量撫慰了你但它不會消失。",
"§a永恒土豆之力慰心永不消逝。"
),
EP_CANNOT_DROP(EternalPotatoItem.getMsgKey("cannot_drop"), ModPartEnum.MESSAGE,
EP_CANNOT_DROP(
EternalPotatoItem.getMsgKey("cannot_drop"), ModPartEnum.MESSAGE,
"§cThe Eternal Potato cannot be dropped! +%d punishments.",
"§c永恒土豆是不可丢弃的惩罚数加%d",
"§c永恆土豆不可丟棄懲罰數加%d",
"§c永恒土豆不可丟棄懲罰數增加%d"
),
EP_BIND_MSG(EternalPotatoItem.getMsgKey("bind_msg"), ModPartEnum.MESSAGE,
EP_BIND_MSG(
EternalPotatoItem.getMsgKey("bind_msg"), ModPartEnum.MESSAGE,
"§6Bound to you as the server-wide shared person.",
"§6已与你绑定成为全服共有之人。",
"§6已與你綁定成為全服共有之人。",
@ -114,21 +125,24 @@ public enum SLPLangKeyValue {
EP_OBLIGATION_DONE(EternalPotatoItem.getMsgKey("obligation_done"), ModPartEnum.MESSAGE,
EP_OBLIGATION_DONE(
EternalPotatoItem.getMsgKey("obligation_done"), ModPartEnum.MESSAGE,
"§eObligation completed, remaining: §a%d§e",
"§e义务完成一次剩余 §a%d §e次。",
"§e義務完成一次剩餘 §a%d §e次。",
"§e责务完成尚余 §a%d §e次。"
),
EP_OBLIGATION_FULL(EternalPotatoItem.getMsgKey("obligation_full"), ModPartEnum.MESSAGE,
EP_OBLIGATION_FULL(
EternalPotatoItem.getMsgKey("obligation_full"), ModPartEnum.MESSAGE,
"§aAll obligations completed today!",
"§a今日义务已全部完成",
"§a今日義務已全部完成",
"§a今日责务尽矣"
),
EP_PUNISH_MSG(EternalPotatoItem.getMsgKey("punish_msg"), ModPartEnum.MESSAGE,
EP_PUNISH_MSG(
EternalPotatoItem.getMsgKey("punish_msg"), ModPartEnum.MESSAGE,
"§cYesterday obligations incomplete, punished!",
"§c未完成昨日义务受到惩罚",
"§c未完成昨日義務受到懲罰",
@ -142,14 +156,16 @@ public enum SLPLangKeyValue {
"受罚倒数§a%d §f瞬"
),
EP_PICKUP_NOT_OWNER(EternalPotatoItem.getMsgKey("pickup_not_owner"), ModPartEnum.MESSAGE,
EP_PICKUP_NOT_OWNER(
EternalPotatoItem.getMsgKey("pickup_not_owner"), ModPartEnum.MESSAGE,
"§cYou are not the rightful owner and cannot pick this up!",
"§c非绑定主人无法拾取此物品",
"§c非綁定主人無法拾取此物品",
"§c非汝所主勿取"
),
EP_PUNISH_NOT_OWNER(EternalPotatoItem.getMsgKey("punish_not_owner"), ModPartEnum.MESSAGE,
EP_PUNISH_NOT_OWNER(
EternalPotatoItem.getMsgKey("punish_not_owner"), ModPartEnum.MESSAGE,
"§cYou are not the rightful owner, punished by lightning!",
"§c非绑定主人使用受到闪电惩罚",
"§c非綁定主人使用受到閃電懲罰",
@ -188,13 +204,29 @@ public enum SLPLangKeyValue {
SLPEntityTypes.getEntityNameKey("super_lead_knot"), ModPartEnum.ENTITY,
"Super Lead Knot", "超级拴绳结", "超級拴繩結", "神駒羈縻索結"
),
TELEPORT_WITH_LEASHED_ENTITIES_NAME(TeleportWithLeashedEntities.NAME_KEY, ModPartEnum.GAME_RULE,
TELEPORT_WITH_LEASHED_ENTITIES_NAME(
TeleportWithLeashedEntities.NAME_KEY, ModPartEnum.GAME_RULE,
"Teleport leashed player with player holder",
"被拴实体随玩家持有者传送",
"被拴实体随玩家持有者傳送",
"繫畜隨持者傳送"
),
TELEPORT_WITH_LEASHED_DESCRIPTION(TeleportWithLeashedEntities.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION,
CREATE_SUPER_LEASH_KNOT_ENTITY_IF_ABSENT_NAME(
CreateSuperLeashKnotEntityIfAbsent.NAME_KEY, ModPartEnum.NAME,
"Create Leash Fence Knot Entity if absent",
"如果缺失则创建超级拴绳结",
"如果缺失則創建超級拴繩結",
"若阙则创超级繫绳结"
),
CREATE_SUPER_LEASH_KNOT_ENTITY_IF_ABSENT_DESCRIPTION(
CreateSuperLeashKnotEntityIfAbsent.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION,
"Create LeashKnot Entity if it's absent on fence or other supported positions",
"如果在栅栏等支持处缺失超级拴绳结,则创建它",
"如果在柵欄等支持處缺失超級拴繩結,則創建它",
"若栅等支处阙超级繫绳结,则创之"
),
TELEPORT_WITH_LEASHED_DESCRIPTION(
TeleportWithLeashedEntities.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION,
"Holder will teleport with their leashed players ",
"传送时将被拴实体与持有者一起传送",
"將被拴实体將隨持有者一起傳送",
@ -221,6 +253,90 @@ public enum SLPLangKeyValue {
"§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"§b倍乘既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r"
),
MESSAGE_LEASHDATA_GET_TITLE(
LeashDataCommand.TITLE, ModPartEnum.COMMAND,
"=== Leash Data for %s ===",
"=== %s 的拴绳数据 ===",
"=== %s 的拴繩數據 ===",
"=== %s 之繫繩數據 ==="
),
MESSAGE_LEASHDATA_GET_TOTAL(
LeashDataCommand.TOTAL, ModPartEnum.COMMAND,
"Total leashes: %d",
"总拴绳数: %d",
"總拴繩數: %d",
"繫繩總數: %d"
),
MESSAGE_LEASHDATA_GET_BLOCK(
LeashDataCommand.BLOCK, ModPartEnum.COMMAND,
"§7Block: §e%s",
"§7方块: §e%s",
"§7方塊: §e%s",
"§7磚石: §e%s"
),
MESSAGE_LEASHDATA_GET_UUID(
LeashDataCommand.UUID, ModPartEnum.COMMAND,
"§7UUID: §b%s",
"§7UUID: §b%s",
"§7UUID: §b%s",
"§7UUID: §b%s"
),
MESSAGE_LEASHDATA_GET_MAX(
LeashDataCommand.MAX, ModPartEnum.COMMAND,
"§7Max: §a%.1f",
"§7最大距离: §a%.1f",
"§7最大距離: §a%.1f",
"§7極距: §a%.1f"
),
MESSAGE_LEASHDATA_GET_ELASTIC(
LeashDataCommand.ELASTIC, ModPartEnum.COMMAND,
"§7Elastic: §6%.1f",
"§7弹性距离: §6%.1f",
"§7彈性距離: §6%.1f",
"§7彈距: §6%.1f"
),
MESSAGE_LEASHDATA_GET_KEEP(
LeashDataCommand.KEEP, ModPartEnum.COMMAND,
"§7Keep: §c%d§7/§c%d",
"§7保持: §c%d§7/§c%d",
"§7保持: §c%d§7/§c%d",
"§7持時: §c%d§7/§c%d"
),
MESSAGE_LEASHDATA_GET_RESERVED(
LeashDataCommand.RESERVED, ModPartEnum.COMMAND,
"§7Reserved: §d%s",
"§7保留字段: §d%s",
"§7保留字段: §d%s",
"§7備註: §d%s"
),
MESSAGE_LEASHDATA_ADD_SUCCESS(
"command.leashdata.add.success", ModPartEnum.COMMAND,
"§bAdded leash successfully. §a%s §7→ §e%s",
"§b添加拴绳成功. §a%s §7→ §e%s",
"§b添加拴繩成功. §a%s §7→ §e%s",
"§b繫繩既添. §a%s §7→ §e%s"
),
MESSAGE_LEASHDATA_REMOVE_SUCCESS(
"command.leashdata.remove.success", ModPartEnum.COMMAND,
"§bRemoved leash successfully. §a%s §7- §e%s",
"§b移除拴绳成功. §a%s §7- §e%s",
"§b移除拴繩成功. §a%s §7- §e%s",
"§b繫繩既除. §a%s §7- §e%s"
),
MESSAGE_LEASHDATA_TRANSFER_SUCCESS(
"command.leashdata.transfer.success", ModPartEnum.COMMAND,
"§bTransferred leash successfully. §a%s §7→ §e%s §7→ §6%s",
"§b转移拴绳成功. §a%s §7→ §e%s §7→ §6%s",
"§b轉移拴繩成功. §a%s §7→ §e%s §7→ §6%s",
"§b繫繩既移. §a%s §7→ §e%s §7→ §6%s"
),
MESSAGE_LEASHDATA_SET_SUCCESS(
"command.leashdata.set.success", ModPartEnum.COMMAND,
"§bSet leash property successfully. §a%s §7: §e%s §7= §6%.1f",
"§b设置拴绳属性成功. §a%s §7: §e%s §7= §6%.1f",
"§b設置拴繩屬性成功. §a%s §7: §e%s §7= §6%.1f",
"§b繫繩性既定. §a%s §7: §e%s §7= §6%.1f"
);
;
private final Supplier<?> supplier;

View File

@ -21,7 +21,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
import java.util.function.Supplier;
@ -42,7 +42,7 @@ public record LeashDataSyncPacket(int entityId, CompoundTag leashData) {
if (level != null) {
Entity entity = level.getEntity(msg.entityId);
if (entity != null) {
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> {
LeashDataAPI.getLeashData(entity).ifPresent(cap -> {
// 只在数据确实变化时更新
CompoundTag current = cap.serializeNBT();
if (!current.equals(msg.leashData)) {

View File

@ -0,0 +1,315 @@
/*
* 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.util.capability;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import java.util.*;
/**
* 拴绳数据API - 提供统一的API接口操作拴绳数据能力
*/
@SuppressWarnings("unused")
public final class LeashDataAPI {
// ==================== 基础能力获取 ====================
public static Optional<ILeashData> getLeashData(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).resolve();
}
// ==================== 拴绳数据管理 API ====================
public static final class LeashOperations {
private LeashOperations() {}
// ---------------------- 添加拴绳 ----------------------
public static boolean attach(Entity entity, Entity holder) {
return getLeashData(entity).map(data -> data.addLeash(holder)).orElse(false);
}
public static boolean attach(Entity entity, Entity holder, String reserved) {
return getLeashData(entity).map(data -> data.addLeash(holder, reserved)).orElse(false);
}
public static boolean attach(Entity entity, Entity holder, double maxDistance) {
return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance)).orElse(false);
}
public static boolean attach(Entity entity, Entity holder, double maxDistance, double elasticDistance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance, elasticDistance, maxKeepTicks)).orElse(false);
}
public static boolean attach(Entity entity, Entity holder, double maxDistance, String reserved) {
return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance, reserved)).orElse(false);
}
public static boolean attach(Entity entity, Entity holder, double maxDistance, double elasticDistance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.addLeash(holder, maxDistance, elasticDistance, maxKeepTicks, reserved)).orElse(false);
}
public static void attachWithInfo(Entity entity, Entity holder, ILeashData.LeashInfo info) {
getLeashData(entity).ifPresent(data -> data.addLeash(holder, info));
}
// ---------------------- 延迟拴绳 ----------------------
public static void attachDelayed(Entity entity, Player holderPlayer) {
getLeashData(entity).ifPresent(data -> data.addDelayedLeash(holderPlayer));
}
public static void removeDelayed(Entity entity, UUID onceHolderPlayerUUID) {
getLeashData(entity).ifPresent(data -> data.removeDelayedLeash(onceHolderPlayerUUID));
}
// ---------------------- 移除拴绳 ----------------------
public static boolean detach(Entity entity, Entity holder) {
return getLeashData(entity).map(data -> data.removeLeash(holder)).orElse(false);
}
public static boolean detach(Entity entity, UUID holderUUID) {
return getLeashData(entity).map(data -> data.removeLeash(holderUUID)).orElse(false);
}
public static boolean detach(Entity entity, BlockPos knotPos) {
return getLeashData(entity).map(data -> data.removeLeash(knotPos)).orElse(false);
}
public static void detachAll(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::removeAllLeashes);
}
public static void detachAllHolders(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::removeAllHolderLeashes);
}
public static void detachAllKnots(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::removeAllKnotLeashes);
}
}
// ==================== 拴绳属性修改 API ====================
public static final class PropertyOperations {
private PropertyOperations() {}
// ---------------------- 设置最大距离 ----------------------
public static boolean setMaxDistance(Entity entity, Entity holder, double distance) {
return getLeashData(entity).map(data -> data.setMaxDistance(holder, distance)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, Entity holder, double distance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.setMaxDistance(holder, distance, maxKeepTicks)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, Entity holder, double distance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.setMaxDistance(holder, distance, maxKeepTicks, reserved)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, UUID holderUUID, double distance) {
return getLeashData(entity).map(data -> data.setMaxDistance(holderUUID, distance)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.setMaxDistance(holderUUID, distance, maxKeepTicks)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.setMaxDistance(holderUUID, distance, maxKeepTicks, reserved)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, BlockPos knotPos, double distance) {
return getLeashData(entity).map(data -> data.setMaxDistance(knotPos, distance)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.setMaxDistance(knotPos, distance, maxKeepTicks)).orElse(false);
}
public static boolean setMaxDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.setMaxDistance(knotPos, distance, maxKeepTicks, reserved)).orElse(false);
}
// ---------------------- 设置弹性距离 ----------------------
public static boolean setElasticDistance(Entity entity, Entity holder, double distance) {
return getLeashData(entity).map(data -> data.setElasticDistance(holder, distance)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, Entity holder, double distance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.setElasticDistance(holder, distance, maxKeepTicks)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, Entity holder, double distance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.setElasticDistance(holder, distance, maxKeepTicks, reserved)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, UUID holderUUID, double distance) {
return getLeashData(entity).map(data -> data.setElasticDistance(holderUUID, distance)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.setElasticDistance(holderUUID, distance, maxKeepTicks)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, UUID holderUUID, double distance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.setElasticDistance(holderUUID, distance, maxKeepTicks, reserved)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, BlockPos knotPos, double distance) {
return getLeashData(entity).map(data -> data.setElasticDistance(knotPos, distance)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks) {
return getLeashData(entity).map(data -> data.setElasticDistance(knotPos, distance, maxKeepTicks)).orElse(false);
}
public static boolean setElasticDistance(Entity entity, BlockPos knotPos, double distance, int maxKeepTicks, String reserved) {
return getLeashData(entity).map(data -> data.setElasticDistance(knotPos, distance, maxKeepTicks, reserved)).orElse(false);
}
}
// ==================== 物理应用 API ====================
public static final class PhysicsOperations {
private PhysicsOperations() {}
public static void applyForces(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::applyLeashForces);
}
}
// ==================== 拴绳转移 API ====================
public static final class TransferOperations {
private TransferOperations() {}
public static boolean transfer(Entity entity, Entity holder, Entity newHolder) {
return getLeashData(entity).map(data -> data.transferLeash(holder, newHolder)).orElse(false);
}
public static boolean transfer(Entity entity, Entity holder, Entity newHolder, String reserved) {
return getLeashData(entity).map(data -> data.transferLeash(holder, newHolder, reserved)).orElse(false);
}
public static boolean transfer(Entity entity, UUID holderUUID, Entity newHolder) {
return getLeashData(entity).map(data -> data.transferLeash(holderUUID, newHolder)).orElse(false);
}
public static boolean transfer(Entity entity, UUID holderUUID, Entity newHolder, String reserved) {
return getLeashData(entity).map(data -> data.transferLeash(holderUUID, newHolder, reserved)).orElse(false);
}
public static boolean transfer(Entity entity, BlockPos knotPos, Entity newHolder) {
return getLeashData(entity).map(data -> data.transferLeash(knotPos, newHolder)).orElse(false);
}
public static boolean transfer(Entity entity, BlockPos knotPos, Entity newHolder, String reserved) {
return getLeashData(entity).map(data -> data.transferLeash(knotPos, newHolder, reserved)).orElse(false);
}
}
// ==================== 查询操作 API ====================
public static final class QueryOperations {
private QueryOperations() {}
public static boolean hasLeash(Entity entity) {
return getLeashData(entity).map(ILeashData::hasLeash).orElse(false);
}
public static boolean hasKnotLeash(Entity entity) {
return getLeashData(entity).map(ILeashData::hasKnotLeash).orElse(false);
}
public static boolean hasHolderLeash(Entity entity) {
return getLeashData(entity).map(ILeashData::hasHolderLeash).orElse(false);
}
public static Collection<ILeashData.LeashInfo> getAllLeashes(Entity entity) {
return getLeashData(entity).map(ILeashData::getAllLeashes).orElse(Collections.emptyList());
}
public static boolean isLeashedBy(Entity entity, Entity holder) {
return getLeashData(entity).map(data -> data.isLeashedBy(holder)).orElse(false);
}
public static boolean isLeashedBy(Entity entity, UUID holderUUID) {
return getLeashData(entity).map(data -> data.isLeashedBy(holderUUID)).orElse(false);
}
public static boolean isLeashedBy(Entity entity, BlockPos knotPos) {
return getLeashData(entity).map(data -> data.isLeashedBy(knotPos)).orElse(false);
}
public static boolean isInDelayedLeash(Entity entity, UUID holderUUID) {
return getLeashData(entity).map(data -> data.isInDelayedLeash(holderUUID)).orElse(false);
}
public static Optional<ILeashData.LeashInfo> getLeashInfo(Entity entity, Entity holder) {
return getLeashData(entity).flatMap(data -> data.getLeashInfo(holder));
}
public static Optional<ILeashData.LeashInfo> getLeashInfo(Entity entity, UUID holderUUID) {
return getLeashData(entity).flatMap(data -> data.getLeashInfo(holderUUID));
}
public static Optional<ILeashData.LeashInfo> getLeashInfo(Entity entity, BlockPos knotPos) {
return getLeashData(entity).flatMap(data -> data.getLeashInfo(knotPos));
}
public static boolean canBeLeashed(Entity entity) {
return getLeashData(entity).map(ILeashData::canBeLeashed).orElse(false);
}
public static boolean canBeAttachedTo(Entity entity, Entity target) {
return getLeashData(entity).map(data -> data.canBeAttachedTo(target)).orElse(false);
}
}
// ==================== 占用和同步 API ====================
public static final class ManagementOperations {
private ManagementOperations() {}
public static Optional<UUID> occupyLeash(Entity entity) {
return getLeashData(entity).flatMap(ILeashData::occupyLeash);
}
public static void markForSync(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::markForSync);
}
public static void immediateSync(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::immediateSync);
}
public static void checkSync(Entity entity) {
getLeashData(entity).ifPresent(ILeashData::checkSync);
}
}
// ==================== 工具方法 ====================
public static final class Utils {
private Utils() {}
public static boolean hasLeashData(Entity entity) {
return getLeashData(entity).isPresent();
}
}
}

View File

@ -0,0 +1,273 @@
/*
* 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.util.capability;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@SuppressWarnings("unused")
public final class LeashStateAPI {
private LeashStateAPI() {
} // 防止实例化
public static Optional<ILeashState> getLeashState(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).resolve();
}
// ==================== 查询操作 ====================
public static final class Query {
private Query() {
}
public static Map<UUID, ILeashState.LeashState> getAllUUIDStates(Entity entity) {
return getLeashState(entity)
.map(ILeashState::getHolderLeashStates)
.orElse(Map.of());
}
public static Map<BlockPos, ILeashState.LeashState> getAllBlockPosStates(Entity entity) {
return getLeashState(entity)
.map(ILeashState::getKnotLeashStates)
.orElse(Map.of());
}
public static Optional<ILeashState.LeashState> getState(Entity entity, Entity holder) {
return getLeashState(entity).flatMap(state -> state.getLeashState(holder));
}
public static Optional<ILeashState.LeashState> getState(Entity entity, UUID holderUUID) {
return getLeashState(entity).flatMap(state -> state.getLeashState(holderUUID));
}
public static Optional<ILeashState.LeashState> getState(Entity entity, BlockPos knotPos) {
return getLeashState(entity).flatMap(state -> state.getLeashState(knotPos));
}
public static boolean hasState(Entity entity) {
return getLeashState(entity).isPresent();
}
public static boolean hasStateFor(Entity entity, Entity holder) {
return getState(entity, holder).isPresent();
}
public static boolean hasStateFor(Entity entity, UUID holderUUID) {
return getState(entity, holderUUID).isPresent();
}
public static boolean hasStateFor(Entity entity, BlockPos knotPos) {
return getState(entity, knotPos).isPresent();
}
}
// ==================== 偏移量操作 ====================
public static final class Offset {
private Offset() {
}
// ---------------------- 重置操作 ----------------------
public static void resetAll(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::resetAllLeashHolderLocationsOffset);
}
public static void resetFor(Entity entity, Entity holder) {
getLeashState(entity).ifPresent(state -> state.resetLeashHolderLocationOffset(holder));
}
public static void resetFor(Entity entity, UUID holderUUID) {
getLeashState(entity).ifPresent(state -> state.resetLeashHolderLocationOffset(holderUUID));
}
public static void resetFor(Entity entity, BlockPos knotPos) {
getLeashState(entity).ifPresent(state -> state.resetLeashHolderLocationOffset(knotPos));
}
// ---------------------- 设置操作 ----------------------
public static void setFor(Entity entity, Entity holder, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.setLeashHolderLocationOffset(holder, offset));
}
public static void setFor(Entity entity, UUID holderUUID, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.setLeashHolderLocationOffset(holderUUID, offset));
}
public static void setFor(Entity entity, BlockPos knotPos, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.setLeashHolderLocationOffset(knotPos, offset));
}
// ---------------------- 添加操作 ----------------------
public static void addTo(Entity entity, Entity holder, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.addLeashHolderLocationOffset(holder, offset));
}
public static void addTo(Entity entity, UUID holderUUID, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.addLeashHolderLocationOffset(holderUUID, offset));
}
public static void addTo(Entity entity, BlockPos knotPos, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.addLeashHolderLocationOffset(knotPos, offset));
}
// ---------------------- 移除操作 ----------------------
public static void removeFor(Entity entity, Entity holder) {
getLeashState(entity).ifPresent(state -> state.removeLeashHolderLocationOffset(holder));
}
public static void removeFor(Entity entity, UUID holderUUID) {
getLeashState(entity).ifPresent(state -> state.removeLeashHolderLocationOffset(holderUUID));
}
public static void removeFor(Entity entity, BlockPos knotPos) {
getLeashState(entity).ifPresent(state -> state.removeLeashHolderLocationOffset(knotPos));
}
public static void removeAll(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::removeAllLeashHolderLocationOffset);
}
public static void removeAllUUIDs(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::removeAllLeashHolderUUIDLocationOffset);
}
public static void removeAllBlockPoses(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::removeAllLeashHolderBlockPosLocationOffset);
}
}
// ==================== 应用实体偏移量操作 ====================
public static final class ApplyEntity {
private ApplyEntity() {
}
public static Optional<Vec3> getOffset(Entity entity) {
return getLeashState(entity).flatMap(ILeashState::getLeashApplyEntityLocationOffset);
}
public static Vec3 getDefaultOffset(Entity entity) {
return getLeashState(entity)
.map(ILeashState::getDefaultLeashApplyEntityLocationOffset)
.orElse(Vec3.ZERO);
}
public static void resetAll(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::resetAllLeashApplyEntityLocationsOffset);
}
public static void remove(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::removeLeashApplyEntityLocationOffset);
}
public static void set(Entity entity, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.setLeashApplyEntityLocationOffset(offset));
}
public static void add(Entity entity, Vec3 offset) {
getLeashState(entity).ifPresent(state -> state.addLeashApplyEntityLocationOffset(offset));
}
}
// ==================== 同步操作 ====================
public static final class Sync {
private Sync() {
}
public static void mark(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::markForSync);
}
public static void immediate(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::immediateSync);
}
public static void check(Entity entity) {
getLeashState(entity).ifPresent(ILeashState::checkSync);
}
}
// ==================== 高级操作 ====================
public static final class Operations {
private Operations() {
}
public static void attach(Entity leashed, Entity holder) {
getLeashState(leashed).ifPresent(state ->
state.addLeashHolderLocationOffset(holder,
CommonEventHandler.leashConfigManager.getEntityOffset(holder))
);
}
public static void detach(Entity leashed, Entity holder) {
Offset.removeFor(leashed, holder);
}
public static void detach(Entity leashed, UUID holderUUID) {
Offset.removeFor(leashed, holderUUID);
}
public static void detach(Entity leashed, BlockPos knotPos) {
Offset.removeFor(leashed, knotPos);
}
public static void transfer(Entity leashed, Entity oldHolder, Entity newHolder) {
getLeashState(leashed).ifPresent(state -> {
state.removeLeashHolderLocationOffset(oldHolder);
state.addLeashHolderLocationOffset(newHolder,
CommonEventHandler.leashConfigManager.getEntityOffset(newHolder));
});
}
public static void transfer(Entity leashed, UUID oldHolderUUID, Entity newHolder) {
getLeashState(leashed).ifPresent(state -> {
state.removeLeashHolderLocationOffset(oldHolderUUID);
state.addLeashHolderLocationOffset(newHolder,
CommonEventHandler.leashConfigManager.getEntityOffset(newHolder));
});
}
public static void transfer(Entity leashed, BlockPos oldKnotPos, Entity newHolder) {
getLeashState(leashed).ifPresent(state -> {
state.removeLeashHolderLocationOffset(oldKnotPos);
state.addLeashHolderLocationOffset(newHolder,
CommonEventHandler.leashConfigManager.getEntityOffset(newHolder));
});
}
public static void copy(Entity source, Entity target) {
getLeashState(source).ifPresent(sourceState ->
getLeashState(target).ifPresent(targetState ->
targetState.copy(sourceState, target)
)
);
}
}
}

View File

@ -1,36 +0,0 @@
/*
* 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.util.capability;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.inter.ILeashData;
import top.r3944realms.superleadrope.content.capability.inter.ILeashState;
import java.util.Objects;
import java.util.Optional;
public class LeashUtil {
public static Optional<ILeashData> getLeashData(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).resolve();
}
public static Optional<ILeashState> getLeashState(@NotNull Entity entity) {
Objects.requireNonNull(entity, "Entity cannot be null");
return entity.getCapability(CapabilityHandler.LEASH_STATE_CAP).resolve();
}
}

View File

@ -16,6 +16,7 @@
package top.r3944realms.superleadrope.util.riding;
import net.minecraft.world.entity.Entity;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.core.exception.RidingCycleException;
import top.r3944realms.superleadrope.util.model.RidingRelationship;
@ -55,7 +56,7 @@ public class RidingApplier {
if (entity == null) continue;
// ---------- 白名单保护 ----------
if (!RidingValidator.isInWhitelist(entity.getType())) {
if (!CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(entity)) {
// 不在白名单跳过本节点但保留其乘客挂回上层
if (vehicle != null) {
// 将当前节点的乘客挂回上层载具
@ -75,7 +76,7 @@ public class RidingApplier {
}
// 如果有指定的载具尝试上车
if (vehicle != null && RidingValidator.isInWhitelist(vehicle.getType())) {
if (vehicle != null && CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(vehicle)) {
if (RidingValidator.wouldCreateCycle(entity, vehicle)) {
throw new RidingCycleException(entityId, vehicleId);
}

View File

@ -17,6 +17,7 @@ package top.r3944realms.superleadrope.util.riding;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.core.exception.RidingCycleException;
import top.r3944realms.superleadrope.core.util.ImmutablePair;
import top.r3944realms.superleadrope.util.model.RidingRelationship;
@ -72,7 +73,7 @@ public class RidingSaver {
processedEntities.add(passengerId);
// 校验白名单
if (!RidingValidator.isInWhitelist(passenger.getType())) {
if (!CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(passenger.getType())) {
// 不在白名单直接截断
continue;
}
@ -105,7 +106,7 @@ public class RidingSaver {
if (relationship == null) return null;
// 如果当前根节点在白名单则直接处理子节点
if (RidingValidator.isInWhitelist(Objects.requireNonNull(getEntityType(relationship.getEntityId())))) {
if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(Objects.requireNonNull(getEntityType(relationship.getEntityId())))) {
RidingRelationship filtered = new RidingRelationship();
filtered.setEntityId(relationship.getEntityId());
filtered.setVehicleId(relationship.getVehicleId());
@ -114,7 +115,7 @@ public class RidingSaver {
} else {
// 根节点不在白名单尝试找到合法的子节点作为新的根
for (RidingRelationship child : relationship.getPassengers()) {
if (RidingValidator.isInWhitelist(Objects.requireNonNull(getEntityType(child.getEntityId())))) {
if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(Objects.requireNonNull(getEntityType(child.getEntityId())))) {
// 设置父节点为当前节点的父倒二叉逻辑
RidingRelationship newRoot = new RidingRelationship();
newRoot.setEntityId(child.getEntityId());

View File

@ -15,52 +15,12 @@
package top.r3944realms.superleadrope.util.riding;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import java.util.LinkedList;
import java.util.Queue;
public class RidingValidator {
/**
* 是否在配置白名单里
*/
@SuppressWarnings("deprecation")
public static boolean isInWhitelist(EntityType<?> type) {
String key = type.builtInRegistryHolder().key().location().toString();
String modid = key.split(":")[0];
for (String entry : LeashCommonConfig.COMMON.teleportWhitelist.get()) {
if (entry.startsWith("#")) {
String body = entry.substring(1);
// Case 1: #modid allow all entities from this mod
if (!body.contains(":")) {
if (modid.equals(body)) {
return true;
}
}
// Case 2: #modid:tag_name allow all entities under this tag
else {
ResourceLocation tagId = new ResourceLocation(body);
TagKey<EntityType<?>> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
if (type.builtInRegistryHolder().is(tag)) {
return true;
}
}
} else {
// Case 3: modid:entity_name allow a specific entity
if (entry.equals(key)) {
return true;
}
}
}
return false;
}
/**
* 检查骑乘是否会产生循环引用
*/

View File

@ -21,6 +21,7 @@ import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
@ -37,7 +38,7 @@ public class RindingLeash {
Entity current = root;
while (current != null) {
if (RidingValidator.isInWhitelist(current.getType())) {
if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(current.getType())) {
return current; // 找到白名单载具
}
current = current.getVehicle();
@ -60,7 +61,7 @@ public class RindingLeash {
Entity current = root;
while (current != null) {
if (RidingValidator.isInWhitelist(current.getType())) {
if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(current.getType())) {
return current; // 找到白名单载具
}
current = current.getVehicle();

View File

@ -1 +1,3 @@
{}
{
"morsb_patch":"coremods/morsb_patch.js"
}

View File

@ -0,0 +1,62 @@
/*
* 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/>.
*/
var Opcodes = Java.type("org.objectweb.asm.Opcodes");
var ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");
var VarInsnNode = Java.type("org.objectweb.asm.tree.VarInsnNode");
var MethodInsnNode = Java.type("org.objectweb.asm.tree.MethodInsnNode");
var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");
function initializeCoreMod() {
return {
"leash_render_patch": {
"target": {
"type": "METHOD",
"class": "net.minecraft.client.renderer.entity.MobRenderer",
"methodName": "m_5523_",
"methodDesc": "(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z"
},
"transformer": function(method) {
var insns = method.instructions;
for (var i = 0; i < insns.size(); i++) {
var insn = insns.get(i);
if (insn.getOpcode && insn.getOpcode() === Opcodes.ICONST_0) {
var next = insns.get(i + 1);
if (next && next.getOpcode() === Opcodes.IRETURN) {
// 插入调试日志和方法调用
insns.insertBefore(insn, ASMAPI.listOf(
new VarInsnNode(Opcodes.ALOAD, 1), // Mob
new VarInsnNode(Opcodes.ALOAD, 2), // Frustum
ASMAPI.buildMethodCall(
'your/package/LeashRenderHook',
null,
ASMAPI.MethodType.STATIC,
'shouldRenderExtraWithLog',
'(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/client/renderer/culling/Frustum;)Z',
ASMAPI.MethodCallMode.STATIC
)
));
// 移除原来的 ICONST_0
insns.remove(insn);
break;
}
}
}
return method;
}
}
};
}

View File

@ -13,12 +13,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.superleadrope.util.coremods;
package top.r3944realms.superleadropetest;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import top.r3944realms.superleadrope.util.capability.LeashDataAPI;
@OnlyIn(Dist.CLIENT)
public class InvokerMethod {
public class Placeholder {
}

View File

@ -0,0 +1,44 @@
/*
* 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.superleadropetest.asm;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ASMTest {
public static void main(String[] args) throws Exception {
Path jarPath = Paths.get("G:\\WhimsyMod\\SuperLeadRope\\build\\moddev\\artifacts\\forge-1.20.1-47.3.4-merged.jar");
try (JarFile jar = new JarFile(jarPath.toFile())) {
JarEntry entry = jar.getJarEntry("net/minecraft/client/renderer/entity/MobRenderer.class");
try (InputStream in = jar.getInputStream(entry)) {
byte[] classBytes = in.readAllBytes();
ClassReader reader = new ClassReader(classBytes);
ClassNode classNode = new ClassNode();
reader.accept(classNode, 0);
System.out.println("Methods in MobRenderer:");
classNode.methods.forEach(m -> System.out.println(" - " + m.name + m.desc));
}
}
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.superleadropetest.asm;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import java.lang.reflect.Method;
public class CoreModTransformerMemoryTest {
// 1 静态类替代 Minecraft
public static class TestMob {}
public static class TestFrustum {}
// Hook 模拟
public static class LeashRenderHook {
public static boolean shouldRenderExtra(TestMob mob, TestFrustum frustum) {
System.out.println("[Hook] shouldRenderExtra called with Mob=" + mob + ", Frustum=" + frustum);
return true;
}
}
public static void main(String[] args) throws Exception {
// 2 构造假的 shouldRender 方法
MethodNode methodNode = new MethodNode(Opcodes.ASM9, Opcodes.ACC_PUBLIC, "shouldRender",
"(Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestMob;" +
"Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestFrustum;)Z",
null, null);
InsnList insns = methodNode.instructions;
insns.clear(); // 清空原有指令
// 插入 Hook 调用
InsnList patch = new InsnList();
patch.add(new VarInsnNode(Opcodes.ALOAD, 1));
patch.add(new VarInsnNode(Opcodes.ALOAD, 2));
patch.add(new MethodInsnNode(
Opcodes.INVOKESTATIC,
"top/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$LeashRenderHook",
"shouldRenderExtra",
"(Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestMob;" +
"Ltop/r3944realms/superleadrope/core/test/CoreModTransformerMemoryTest$TestFrustum;)Z",
false
));
patch.add(new InsnNode(Opcodes.IRETURN));
insns.add(patch);
// 4 创建假的 ClassNode
ClassNode classNode = new ClassNode();
classNode.version = Opcodes.V1_8;
classNode.access = Opcodes.ACC_PUBLIC;
classNode.name = "FakeMobRenderer";
classNode.superName = "java/lang/Object";
classNode.methods.add(methodNode);
// 添加默认构造器 <init>
MethodNode constructor = new MethodNode(Opcodes.ASM9, Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
InsnList initInsns = constructor.instructions;
initInsns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
initInsns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false));
initInsns.add(new InsnNode(Opcodes.RETURN));
constructor.maxStack = 1;
constructor.maxLocals = 1;
classNode.methods.add(constructor);
// 5 写入字节码
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classNode.accept(cw);
byte[] bytes = cw.toByteArray();
// 6 自定义 ClassLoader直接返回 Class 对象
ClassLoader loader = new ClassLoader(CoreModTransformerMemoryTest.class.getClassLoader()) {
public Class<?> defineClassFromBytes(byte[] b) {
return defineClass("FakeMobRenderer", b, 0, b.length);
}
};
Class<?> clazz = ((Class<?>) loader.getClass().getMethod("defineClassFromBytes", byte[].class).invoke(loader, bytes));
// 7 实例化
Object rendererInstance = clazz.getDeclaredConstructor().newInstance();
Method shouldRender = clazz.getMethod("shouldRender", TestMob.class, TestFrustum.class);
boolean result = (boolean) shouldRender.invoke(rendererInstance, new TestMob(), new TestFrustum());
System.out.println("[Test] Result of shouldRender (after patch): " + result);
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.superleadropetest.config;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class OffsetReadTest {
private static final Pattern OFFSET_PATTERN = Pattern.compile(
"(?i)(?:vec3|vec3d|vector3|offset)\\s*\\(\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)\\s*\\)\\s*:\\s*\\[\\s*([^]]+?)\\s*]\\s*"
);
private static boolean isValidEntityRefFormat(String s) {
if (s.startsWith("#")) {
String body = s.substring(1);
// 支持 #modid 整个模组 #modid:tag_name 标签
return body.matches("[a-z0-9_]+(:[a-z0-9_/]+)?");
}
// 普通实体 ID: modid:entity_id
return s.matches("[a-z0-9_]+:[a-z0-9_/]+");
}
private static boolean isValidOffsetRefFormat(String s) {
// 匹配格式: function_name(x,y,z) : [entity_list]
Matcher matcher = OFFSET_PATTERN.matcher(s);
if (!matcher.matches()) {
return false;
}
// 检查坐标值是否有效
try {
// 组索引 现在坐标在组123实体列表在组4
Double.parseDouble(matcher.group(1));
Double.parseDouble(matcher.group(2));
Double.parseDouble(matcher.group(3));
// 检查实体列表格式
String entityList = matcher.group(4);
String[] entities = entityList.split(",");
for (String entity : entities) {
if (!isValidEntityRefFormat(entity.trim())) {
return false;
}
}
return true;
} catch (NumberFormatException e) {
return false;
}
}
private static void testCase(String testString, boolean expected) {
boolean result = isValidOffsetRefFormat(testString);
String status = result == expected ? "✓ PASS" : "✗ FAIL";
System.out.printf("%s: %s -> %s (expected: %s)%n",
status, testString, result, expected);
if (result) {
Matcher matcher = OFFSET_PATTERN.matcher(testString);
if (matcher.matches()) {
System.out.printf(" 解析结果: X=%s, Y=%s, Z=%s, Entities=%s%n%n",
matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4));
}
} else {
System.out.println();
}
}
public static void main(String[] args) {
System.out.println("=== 有效测试用例 ===");
// 基本格式测试
testCase("vec3(0,0.2,0) : [minecraft:bee]", true);
testCase("Vec3(0, 0.2, 0) : [minecraft:bee]", true);
testCase("VEC3(0,0.2,0) : [minecraft:bee]", true);
// 不同函数名测试
testCase("vec3d(0,0.2,0) : [minecraft:bee]", true);
testCase("Vector3(0,0.2,0) : [minecraft:bee]", true);
testCase("offset(0,0.2,0) : [minecraft:bee]", true);
// 空格兼容测试
testCase("vec3( 0 , 0.2 , 0 ) : [ minecraft:bee ]", true);
testCase("vec3(0,0.2,0) : [minecraft:bee] ", true);
testCase("vec3(0, 0.2, 0) : [ minecraft:bee ] ", true);
// 多实体测试
testCase("vec3(0,1.0,0) : [minecraft:horse, minecraft:donkey]", true);
testCase("vec3(0,0.5,0) : [#minecraft:boats, #minecraft:minecarts]", true);
// 标签和模组测试
testCase("vec3(0,0.5,0) : [#minecraft:boats]", true);
testCase("vec3(0,0.3,0) : [#minecraft]", true);
// 负数和小数测试
testCase("vec3(-1, 1.5, 2.8) : [minecraft:horse]", true);
testCase("vec3(0.0, -0.5, 1.23) : [minecraft:bee]", true);
System.out.println("=== 无效测试用例 ===");
// 错误函数名
testCase("vector(0,0.2,0) : [minecraft:bee]", false);
testCase("pos(0,0.2,0) : [minecraft:bee]", false);
// 格式错误
testCase("vec3(0,0.2,0) : minecraft:bee]", false); // 缺少左括号
testCase("vec3(0,0.2,0) : [minecraft:bee", false); // 缺少右括号
testCase("vec3(0,0.2) : [minecraft:bee]", false); // 缺少Z坐标
// 无效坐标
testCase("vec3(a,b,c) : [minecraft:bee]", false);
testCase("vec3(0,0.2,invalid) : [minecraft:bee]", false);
// 无效实体引用
testCase("vec3(0,0.2,0) : [invalid_entity]", false);
testCase("vec3(0,0.2,0) : [minecraft:bee, invalid]", false);
// 缺少冒号
testCase("vec3(0,0.2,0) [minecraft:bee]", false);
System.out.println("=== 测试完成 ===");
}
}