feat:优化逻辑与修复之前的BUG

This commit is contained in:
叁玖领域 2025-09-05 12:53:31 +08:00
parent 7e68e31888
commit 1137416bb5
16 changed files with 472 additions and 106 deletions

View File

@ -42,6 +42,37 @@ repositories {
maven { url = "https://libraries.minecraft.net/" } maven { url = "https://libraries.minecraft.net/" }
maven { url = "https://neoforged.forgecdn.net/releases" } maven { url = "https://neoforged.forgecdn.net/releases" }
maven { url = "https://neoforged.forgecdn.net/mojang-meta" } maven { url = "https://neoforged.forgecdn.net/mojang-meta" }
maven {
url "https://cursemaven.com"
content {
includeGroup "curse.maven"
}
}
maven {
// location of the maven that hosts JEI files before January 2023
name = "Progwml6's maven"
url = "https://dvs1.progwml6.com/files/maven/"
}
maven {
// location of the maven that hosts JEI files since January 2023
name = "Jared's maven"
url = "https://maven.blamejared.com/"
}
maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "https://modmaven.dev"
}
maven {
name 'luck-repo'
url 'https://repo.lucko.me/'
content {
includeModule 'me.lucko', 'spark-api'
}
}
flatDir {
dir "libs"
}
} }
legacyForge { legacyForge {
@ -101,7 +132,15 @@ configurations {
} }
dependencies { dependencies {
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' annotationProcessor ('org.spongepowered:mixin:0.8.5:processor')
modRuntimeOnly("curse.maven:debug-utils-forge-783008:5337491")
modRuntimeOnly("blank:curtain-1.20.1:1.3.2")
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge-api:${jei_version}")
modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modRuntimeOnly("curse.maven:spark-361579:4738952")
compileOnly ('me.lucko:spark-api:0.1-SNAPSHOT')
} }
// ========== ========== // ========== ==========

View File

@ -10,6 +10,8 @@ neoForge.parchment.mappingsVersion=2023.09.03
# enable ProGuard # enable ProGuard
enableProguard=false enableProguard=false
# Jei Version
jei_version=15.20.0.112
## Environment Properties ## Environment Properties
# The Minecraft version must agree with the Forge version to get a valid artifact # The Minecraft version must agree with the Forge version to get a valid artifact

Binary file not shown.

View File

@ -16,17 +16,28 @@
package top.r3944realms.superleadrope; package top.r3944realms.superleadrope;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.Minecart; import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent; import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.EntityJoinLevelEvent; import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.EntityLeaveLevelEvent; import net.minecraftforge.event.entity.EntityLeaveLevelEvent;
import net.minecraftforge.event.entity.EntityTeleportEvent;
import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
@ -34,11 +45,16 @@ import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.CapabilityRemainder; import top.r3944realms.superleadrope.content.capability.CapabilityRemainder;
import top.r3944realms.superleadrope.content.capability.LeashDataImpl; import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
import top.r3944realms.superleadrope.content.entity.SuperLeashEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashEntity;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem; import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.leash.LeashInteractHandler; import top.r3944realms.superleadrope.core.leash.LeashInteractHandler;
import top.r3944realms.superleadrope.core.leash.LeashSyncManager; import top.r3944realms.superleadrope.core.leash.LeashSyncManager;
import top.r3944realms.superleadrope.core.register.SLPItems;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class CommonEventHandler { public class CommonEventHandler {
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE) @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE)
@ -62,18 +78,84 @@ public class CommonEventHandler {
} }
} }
@SubscribeEvent @SubscribeEvent
public static void onPlayerHitOnBlock (PlayerInteractEvent.RightClickBlock event) { public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) {
Level level = event.getLevel(); Level level = event.getLevel();
if (level.isClientSide) { if (level.isClientSide) {
return ; return;
} }
BlockPos blockPos = event.getHitVec().getBlockPos(); BlockPos blockPos = event.getHitVec().getBlockPos();
BlockState blockState = level.getBlockState(blockPos); BlockState blockState = level.getBlockState(blockPos);
Player player = event.getEntity();
ItemStack itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
if (SuperLeashKnotEntity.isSupportBlock(blockState)) { if (SuperLeashKnotEntity.isSupportBlock(blockState)) {
SuperLeadRopeItem.bindToBlock(event.getEntity(), level, blockPos, event.getItemStack()); boolean shouldConsume = SuperLeadRopeItem.bindToBlock(player, level, blockPos, event.getItemStack(), itemInHand.is(SLPItems.SUPER_LEAD_ROPE.get()));
if (shouldConsume) {
event.setCancellationResult(InteractionResult.CONSUME);
event.setCanceled(true);
}
} }
} }
@SubscribeEvent
public static void onEntityTeleport(EntityTeleportEvent event) {
Entity telEntity = event.getEntity();
Vec3 targetPos = event.getTarget();
Level level = telEntity.level();
if (level instanceof ServerLevel serverLevel) {
List<Entity> entities = LeashDataImpl.leashableInArea(telEntity);
// 为每个实体创建状态快照
entities.forEach(beLeashedEntity -> {
// 保存原来的旋转角度
Pose originalPose = beLeashedEntity.getPose();
boolean originalIsSprinting = beLeashedEntity.isSprinting();
float originalYaw = beLeashedEntity.getYRot();
float originalPitch = beLeashedEntity.getXRot();
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
AtomicReference<ILeashDataCapability.LeashInfo> originalLeashInfo = new AtomicReference<>();
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP)
.ifPresent(cap -> {
originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null));
cap.removeLeash(telEntity);
});
if (beLeashedEntity.level() == telEntity.level()) {
// 使用空集合表示所有值都是绝对的
if (beLeashedEntity instanceof ServerPlayer serverPlayer) {
serverPlayer.connection.teleport(
targetPos.x, targetPos.y, targetPos.z,
originalYaw, originalPitch,
Collections.emptySet() // 所有值都是绝对的
);
} else {
beLeashedEntity.teleportTo(
serverLevel,
targetPos.x,
targetPos.y,
targetPos.z,
Collections.emptySet(),
originalYaw, originalPitch
);
}
} else {
beLeashedEntity.teleportTo(
serverLevel,
targetPos.x,
targetPos.y,
targetPos.z,
Collections.emptySet(),
originalYaw, originalPitch
);
}
beLeashedEntity.setDeltaMovement(originalDeltaMovement);
beLeashedEntity.setSprinting(originalIsSprinting);
beLeashedEntity.setPose(originalPose);
ILeashDataCapability.LeashInfo leashInfoOrDefault = Optional.ofNullable(originalLeashInfo.get()).map(i -> i.transferHolder(telEntity)).orElse(ILeashDataCapability.LeashInfo.EMPTY);
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(
cap -> cap.addLeash(telEntity, leashInfoOrDefault)
);
});
}
}
@SubscribeEvent @SubscribeEvent
public static void onPlayerClone(PlayerEvent.Clone event) { public static void onPlayerClone(PlayerEvent.Clone event) {
CapabilityRemainder.onPlayerClone(event); CapabilityRemainder.onPlayerClone(event);
@ -88,7 +170,7 @@ public class CommonEventHandler {
@SubscribeEvent @SubscribeEvent
public static void onEntityInteract (PlayerInteractEvent.EntityInteract event) { public static void onEntityInteract (PlayerInteractEvent.EntityInteract event) {
LeashInteractHandler.onEntityInteract(event); //处理实体互动 LeashInteractHandler.onEntityInteract(event.getLevel(), event.getHand(), event.getTarget(), event.getEntity(), event); //处理实体互动
} }
@SubscribeEvent @SubscribeEvent
@ -107,6 +189,12 @@ public class CommonEventHandler {
public static void registerCapability(RegisterCapabilitiesEvent event) { public static void registerCapability(RegisterCapabilitiesEvent event) {
CapabilityHandler.registerCapability(event); CapabilityHandler.registerCapability(event);
} }
@SubscribeEvent
public static void onCreativeTab (BuildCreativeModeTabContentsEvent event) {
if (event.getTabKey() == CreativeModeTabs.TOOLS_AND_UTILITIES) {
event.accept(SLPItems.SUPER_LEAD_ROPE);
}
}
} }
} }

View File

@ -22,6 +22,7 @@ import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -41,18 +42,30 @@ public class SuperLeashKnotRenderer extends EntityRenderer<SuperLeashKnotEntity>
@Override @Override
public void render(@NotNull SuperLeashKnotEntity entity, float entityYaw, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight) { public void render(@NotNull SuperLeashKnotEntity entity, float entityYaw, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight) {
// 根据实体的实际 hitbox 调整缩放 AABB box = entity.getBoundingBox();
float scaleX = entity.getBbWidth() / 0.5f; // 默认 0.5f对应 scale=1 float boxWidth = (float) box.getXsize();
float scaleY = entity.getBbHeight() / 0.5f; // 默认 0.5f对应 scale=1 float boxHeight = (float) box.getYsize();
float scaleZ = scaleX; // 宽度对称
poseStack.scale(-1.5F * scaleX, -1.5F * scaleY, 1.5F * scaleZ); // 模型原始尺寸像素 方块
float modelWidthBlocks = 6.0F / 16.0F;
float modelHeightBlocks = 8.0F / 16.0F;
// 位置微调避免浮空 // 缩放比例
poseStack.translate(0.0D, 0.15D * scaleY, 0.0D); float scaleX = boxWidth / modelWidthBlocks;
float scaleY = boxHeight / modelHeightBlocks;
float scaleZ = scaleX;
poseStack.pushPose();
// 先缩放
poseStack.scale(scaleX, scaleY, scaleZ);
// 再平移把模型抬到碰撞箱底部
poseStack.translate(0.0D, boxHeight / scaleY, 0.0D);
this.model.setupAnim(entity, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F); this.model.setupAnim(entity, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F);
VertexConsumer vertexConsumer = buffer.getBuffer(this.model.renderType(KNOT_LOCATION)); VertexConsumer vertexConsumer = buffer.getBuffer(this.model.renderType(KNOT_LOCATION));
this.model.renderToBuffer(poseStack, vertexConsumer, packedLight, OverlayTexture.NO_OVERLAY, this.model.renderToBuffer(poseStack, vertexConsumer, packedLight, OverlayTexture.NO_OVERLAY,
1.0F, 1.0F, 1.0F, 1.0F); 1.0F, 1.0F, 1.0F, 1.0F);

View File

@ -147,7 +147,7 @@ public class SuperLeashRenderer extends EntityRenderer<SuperLeashEntity> {
} }
@Override @Override
public ResourceLocation getTextureLocation(@NotNull SuperLeashEntity entity) { public @NotNull ResourceLocation getTextureLocation(@NotNull SuperLeashEntity entity) {
return null; // 使用自定义渲染类型不需要纹理 return new ResourceLocation("unknown"); // 使用自定义渲染类型不需要纹理
} }
} }

View File

@ -19,9 +19,14 @@ import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag; import net.minecraft.nbt.ListTag;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.*;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.horse.Llama;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.Minecart; import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -36,9 +41,11 @@ import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapabili
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.network.NetworkHandler; import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -84,6 +91,7 @@ public class LeashDataImpl implements ILeashDataCapability {
// 引入解决 绳结不保存导致第二进入持有者不存在的问题 // 引入解决 绳结不保存导致第二进入持有者不存在的问题
private final Map<BlockPos, LeashInfo> leashKnots = new ConcurrentHashMap<>(); private final Map<BlockPos, LeashInfo> leashKnots = new ConcurrentHashMap<>();
private CompoundTag lastSyncedData = new CompoundTag(); private CompoundTag lastSyncedData = new CompoundTag();
public LeashDataImpl(Entity entity) { public LeashDataImpl(Entity entity) {
this.entity = entity; this.entity = entity;
} }
@ -155,6 +163,11 @@ public class LeashDataImpl implements ILeashDataCapability {
return true; return true;
} }
@Override
public boolean addLeash(Entity holder, LeashInfo leashInfo) {
return addLeash(holder, ItemStack.EMPTY, leashInfo.maxDistance(), leashInfo.elasticDistance(), leashInfo.maxKeepLeashTicks());
}
@Override @Override
public boolean setMaxDistance(Entity holder, double newMaxDistance) { public boolean setMaxDistance(Entity holder, double newMaxDistance) {
return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ?
@ -331,32 +344,82 @@ public class LeashDataImpl implements ILeashDataCapability {
return true; return true;
} }
// 计算拴绳拉力防抖动逻辑 /**
* 计算拴绳拉力防抖动逻辑
*/
@Override @Override
public void applyLeashForces() { public void applyLeashForces() {
for (Map.Entry<UUID, LeashInfo> uuidLeashInfoEntry : leashHolders.entrySet()) { Vec3 combinedForce = Vec3.ZERO; // 初始化合力向量
internalUUIDApplyLeashForces(uuidLeashInfoEntry);
// 计算所有拴绳的合力
for (Map.Entry<UUID, LeashInfo> entry : leashHolders.entrySet()) {
Vec3 force = calculateLeashForceForUUID(entry);
if (force != null) {
combinedForce = combinedForce.add(force);
}
} }
for (Map.Entry<BlockPos, LeashInfo> blockPosLeashInfoEntry : leashKnots.entrySet()) {
internalBlockPosApplyLeashForce(blockPosLeashInfoEntry); for (Map.Entry<BlockPos, LeashInfo> entry : leashKnots.entrySet()) {
Vec3 force = calculateLeashForceForBlockPos(entry);
if (force != null) {
combinedForce = combinedForce.add(force);
}
}
// 应用合力
if (!combinedForce.equals(Vec3.ZERO)) {
if(entity instanceof ServerPlayer serverPlayer) { //对于玩家发包交给客户端处理移动
NetworkHandler.sendToPlayer(
new UpdatePlayerMovementPacket(
UpdatePlayerMovementPacket.Operation.ADD,
combinedForce
), serverPlayer
);
return; //后面的逻辑肯定与该分支无关直接返回
} else {
entity.setDeltaMovement(entity.getDeltaMovement().add(combinedForce));
entity.hurtMarked = true;
}
// 有拴绳时禁用移动控制
if (entity instanceof Animal mob) {
mob.goalSelector.disableControlFlag(Goal.Flag.MOVE);
entity.resetFallDistance();
}
} else {
// 无拴绳时恢复移动控制
if (entity instanceof Animal mob) {
mob.goalSelector.enableControlFlag(Goal.Flag.MOVE);
}
} }
} }
private void internalUUIDApplyLeashForces(Map.Entry<UUID, LeashInfo> entry) { /**
* 为UUID拴绳计算力
*/
private Vec3 calculateLeashForceForUUID(Map.Entry<UUID, LeashInfo> entry) {
UUID uuid = entry.getKey(); UUID uuid = entry.getKey();
Entity uuidHolder = ((ServerLevel) entity.level()).getEntity(uuid); Entity uuidHolder = ((ServerLevel) entity.level()).getEntity(uuid);
if (uuidHolder != null) { if (uuidHolder != null) {
internalApplyLeashForces(uuidHolder, entry); return calculateLeashForce(uuidHolder, entry);
} else { } else {
SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found.", uuid); SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found.", uuid);
return null;
} }
} }
private void internalBlockPosApplyLeashForce(Map.Entry<BlockPos, LeashInfo> entry) { /**
* 为方块位置拴绳计算力
*/
private Vec3 calculateLeashForceForBlockPos(Map.Entry<BlockPos, LeashInfo> entry) {
SuperLeashKnotEntity orCreateKnot = SuperLeashKnotEntity.getOrCreateKnot(entity.level(), entry.getKey()); SuperLeashKnotEntity orCreateKnot = SuperLeashKnotEntity.getOrCreateKnot(entity.level(), entry.getKey());
internalApplyLeashForces(orCreateKnot, entry); return calculateLeashForce(orCreateKnot, entry);
} }
private void internalApplyLeashForces(Entity holder, Map.Entry<?, LeashInfo> entry) {
/**
* 计算单个拴绳的力
*/
private Vec3 calculateLeashForce(Entity holder, Map.Entry<?, LeashInfo> entry) {
Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0); Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0);
LeashInfo info = entry.getValue(); LeashInfo info = entry.getValue();
Vec3 entityPos = entity.position().add(info.attachOffset()); Vec3 entityPos = entity.position().add(info.attachOffset());
@ -365,41 +428,49 @@ public class LeashDataImpl implements ILeashDataCapability {
// 1. 检查是否超出断裂距离 // 1. 检查是否超出断裂距离
if (distance > extremeSnapDist) { if (distance > extremeSnapDist) {
// 如果还有剩余缓冲Tick施加更强拉力并减少计数
if (info.keepLeashTicks() > 0) { if (info.keepLeashTicks() > 0) {
// 计算临界拉力距离越远拉力越强 // 计算临界拉力
Vec3 pullForce = calculateCriticalPullForce(holderPos, entityPos, distance, info); Vec3 pullForce = calculateCriticalPullForce(holderPos, entityPos, distance, info);
entity.setDeltaMovement(entity.getDeltaMovement().add(pullForce));
entity.hurtMarked = true;
entry.setValue(info.decrementKeepLeashTicks()); entry.setValue(info.decrementKeepLeashTicks());
return; return pullForce;
} }
// 否则立即断裂 // 断裂
removeLeash(holder); removeLeash(holder);
return; return null;
} }
// 2. 正常弹性拉力逻辑保持不变 // 2. 正常弹性拉力逻辑
Vec3 pullForce = Vec3.ZERO;
if (distance > info.elasticDistance()) { if (distance > info.elasticDistance()) {
Vec3 pullForce = calculatePullForce(holderPos, entityPos, distance, info); pullForce = calculatePullForce(holderPos, entityPos, distance, info);
entity.setDeltaMovement(entity.getDeltaMovement().add(pullForce));
entity.hurtMarked = true; // 生物添加跟随逻辑保持不变
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如果回到安全距离 // 3. 重置缓冲Tick
if (distance <= info.maxDistance() && info.keepLeashTicks() < info.maxKeepLeashTicks()) { if (distance <= info.maxDistance() && info.keepLeashTicks() < info.maxKeepLeashTicks()) {
entry.setValue(info.resetKeepLeashTicks()); entry.setValue(info.resetKeepLeashTicks());
} }
return pullForce;
} }
// 计算正常拉力保持不变
@Contract("_, _, _, _ -> new") @Contract("_, _, _, _ -> new")
private @NotNull Vec3 calculatePullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) { private @NotNull Vec3 calculatePullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) {
Vec3 pullDirection = holderPos.subtract(entityPos).normalize(); Vec3 pullDirection = holderPos.subtract(entityPos).normalize();
double pullStrength = 0.2; double pullStrength = 0.2;
// 增强拉力如果超出maxDistance但未达断裂距离
if (distance > info.maxDistance()) { if (distance > info.maxDistance()) {
double excessRatio = (distance - info.maxDistance()) / (info.maxDistance()); double excessRatio = (distance - info.maxDistance()) / info.maxDistance();
pullStrength += excessRatio * 0.8; // 最高1.0倍基础拉力 pullStrength += excessRatio * 0.8;
} }
Vec3 pullForce = pullDirection.scale( Vec3 pullForce = pullDirection.scale(
@ -412,18 +483,17 @@ public class LeashDataImpl implements ILeashDataCapability {
pullForce.z * AXIS_SPECIFIC_ELASTICITY.z pullForce.z * AXIS_SPECIFIC_ELASTICITY.z
); );
} }
// 计算临界拉力保持不变
private @NotNull Vec3 calculateCriticalPullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) { private @NotNull Vec3 calculateCriticalPullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) {
Vec3 pullDirection = holderPos.subtract(entityPos).normalize(); Vec3 pullDirection = holderPos.subtract(entityPos).normalize();
double excessRatio = (distance - info.maxDistance()) / info.maxDistance();
// 非线性增强拉力距离越远拉力越强 double pullStrength = 1.0 + excessRatio * 2.0;
double excessRatio = (distance - info.maxDistance()) / (info.maxDistance());
double pullStrength = 1.0 + excessRatio * 2.0; // 基础1.0 + 额外增强最高3.0倍
Vec3 pullForce = pullDirection.scale( Vec3 pullForce = pullDirection.scale(
(distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING (distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING
); );
// 应用轴向弹性系数减少Y轴抖动
return new Vec3( return new Vec3(
pullForce.x * AXIS_SPECIFIC_ELASTICITY.x, pullForce.x * AXIS_SPECIFIC_ELASTICITY.x,
pullForce.y * AXIS_SPECIFIC_ELASTICITY.y, pullForce.y * AXIS_SPECIFIC_ELASTICITY.y,
@ -702,11 +772,14 @@ public class LeashDataImpl implements ILeashDataCapability {
public static @NotNull List<Entity> leashableInArea(Entity entity, Predicate<Entity> filter) { public static @NotNull List<Entity> leashableInArea(Entity entity, Predicate<Entity> filter) {
return leashableInArea(entity, filter, 1024D); return leashableInArea(entity, filter, 1024D);
} }
public static @NotNull List<Entity> leashableInArea(Entity holder) {
return leashableInArea(holder, i -> isLeashHolder(i, holder), 1024D);
}
public boolean canBeAttachedTo(Entity pEntity) { public boolean canBeAttachedTo(Entity pEntity) {
if(pEntity == entity) { if(pEntity == entity) {
return false; return false;
} else { } else {
Optional<LeashInfo> leashInfo = getLeashInfo(pEntity.getUUID()); Optional<LeashInfo> leashInfo = getLeashInfo(pEntity);
return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= LEASH_ELASTIC_DIST * LEASH_EXTREME_SNAP_DIST_FACTOR) && canBeLeashed();//距离最大,则不可以被固定或转移 return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= LEASH_ELASTIC_DIST * LEASH_EXTREME_SNAP_DIST_FACTOR) && canBeLeashed();//距离最大,则不可以被固定或转移
} }
} }

View File

@ -31,6 +31,7 @@ public interface ILeashDataCapability extends INBTSerializable<CompoundTag> {
// 原LeashData的方法接口 // 原LeashData的方法接口
boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance); boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance);
boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks); boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks);
boolean addLeash(Entity holder, LeashInfo leashInfo);
boolean setMaxDistance(Entity holder, double newMaxDistance); boolean setMaxDistance(Entity holder, double newMaxDistance);
boolean setMaxDistance(Entity holder,double newMaxDistance, int newMaxKeepLeashTicks); boolean setMaxDistance(Entity holder,double newMaxDistance, int newMaxKeepLeashTicks);
@ -82,6 +83,10 @@ public interface ILeashDataCapability extends INBTSerializable<CompoundTag> {
int keepLeashTicks, // 新增保持拴绳的剩余Tick数 int keepLeashTicks, // 新增保持拴绳的剩余Tick数
int maxKeepLeashTicks // 新增最大保持Tick数可配置 int maxKeepLeashTicks // 新增最大保持Tick数可配置
) { ) {
public static final LeashInfo EMPTY = new LeashInfo(
Optional.empty(), Optional.empty(), Optional.empty(),
"", Vec3.ZERO, 12.0D, 6.0D, 0, 0
);
public static LeashInfo CreateLeashInfo( public static LeashInfo CreateLeashInfo(
Entity entity, Entity entity,
String reserved, String reserved,

View File

@ -52,6 +52,11 @@ public class SuperLeashEntity extends Entity {
if(!level().isClientSide) controlled.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(ILeashDataCapability::applyLeashForces); if(!level().isClientSide) controlled.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(ILeashDataCapability::applyLeashForces);
} }
@Override
public void kill() {
}
@Override @Override
protected void defineSynchedData() { protected void defineSynchedData() {

View File

@ -20,6 +20,7 @@ import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity; import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
@ -57,11 +58,31 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
super(pEntityType, pLevel); super(pEntityType, pLevel);
} }
public SuperLeashKnotEntity(Level pLevel, BlockPos pPos) { public SuperLeashKnotEntity(Level pLevel, BlockPos pPos) {
this(SLPEntityTypes.SUPER_LEAD_KNOT.get(), pLevel); this(SLPEntityTypes.SUPER_LEAD_KNOT.get(), pLevel);
this.setPos(pPos.getX(), pPos.getY(), pPos.getZ()); this.setPos(pPos.getX(), pPos.getY(), pPos.getZ());
} }
@Override
public boolean hurt(@NotNull DamageSource source, float amount) {
if (this.isInvulnerableTo(source)) {
return false;
} else {
if (!this.isRemoved() && !this.level().isClientSide) {
this.kill();
this.markHurt();
List<Entity> entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this));
entities.forEach(entity -> entity
.getCapability(CapabilityHandler.LEASH_DATA_CAP)
.map(iLeashDataCapability -> iLeashDataCapability.removeLeash(this))
);
}
return true;
}
}
@Override @Override
public boolean survives() { public boolean survives() {
@ -73,7 +94,15 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
int j = pPos.getY(); int j = pPos.getY();
int k = pPos.getZ(); int k = pPos.getZ();
for(SuperLeashKnotEntity superLeashKnotEntity : pLevel.getEntitiesOfClass(SuperLeashKnotEntity.class, new AABB((double)i - 1.0D, (double)j - 1.0D, (double)k - 1.0D, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D))) { for(SuperLeashKnotEntity superLeashKnotEntity :
pLevel.getEntitiesOfClass(
SuperLeashKnotEntity.class,
new AABB((double)i - 1.0D,
(double)j - 1.0D,
(double)k - 1.0D,
(double)i + 1.0D,
(double)j + 1.0D,
(double)k + 1.0D))) {
if (superLeashKnotEntity.getPos().equals(pPos)) { if (superLeashKnotEntity.getPos().equals(pPos)) {
return superLeashKnotEntity; return superLeashKnotEntity;
} }
@ -87,6 +116,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
@Override @Override
protected void recalculateBoundingBox() { protected void recalculateBoundingBox() {
updateDimensionsBasedOnBlock(); updateDimensionsBasedOnBlock();
setPosRaw(this.pos.getX() + 0.5, this.pos.getY() + 0.20, this.pos.getZ() + 0.5);
double halfWidth = currentWidth / 2.0f; double halfWidth = currentWidth / 2.0f;
this.setBoundingBox(new AABB( this.setBoundingBox(new AABB(
this.getX() - halfWidth, this.getX() - halfWidth,
@ -104,7 +134,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
// 根据方块类型调整尺寸 // 根据方块类型调整尺寸
if (state.is(BlockTags.WALLS)) { if (state.is(BlockTags.WALLS)) {
// 墙类方块 - 更窄更高 // 墙类方块 - 更窄更高
currentWidth = 0.25f; currentWidth = 0.75f;
currentHeight = 0.75f; currentHeight = 0.75f;
} else { } else {
// 默认栅栏尺寸 // 默认栅栏尺寸
@ -113,13 +143,6 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
} }
//TODO: 未来扩展可配置化大小 //TODO: 未来扩展可配置化大小
} }
@Override
public void setPos(double x, double y, double z) {
super.setPos(x, y, z);
// 确保位置与方块中心对齐
BlockPos pos = this.getPos();
this.setPosRaw(pos.getX() + 0.5, pos.getY() + 0.375, pos.getZ() + 0.5);
}
public static boolean isSupportBlock(BlockState state) { public static boolean isSupportBlock(BlockState state) {
for(TagKey<Block> tagKey : SUPPORTED_BLOCK) { for(TagKey<Block> tagKey : SUPPORTED_BLOCK) {
@ -135,7 +158,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }
AtomicBoolean isTransferLeash = new AtomicBoolean(false); AtomicBoolean isTransferLeash = new AtomicBoolean(false);
List<Entity> entities = LeashDataImpl.leashableInArea(player, ing -> true); List<Entity> entities = LeashDataImpl.leashableInArea(player);
for(Entity entity : entities) { for(Entity entity : entities) {
if (LeashDataImpl.isLeashHolder(entity, player.getUUID())) if (LeashDataImpl.isLeashHolder(entity, player.getUUID()))
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> { entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> {
@ -143,10 +166,22 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
isTransferLeash.set(true); isTransferLeash.set(true);
}); });
}
AtomicBoolean isRemoveLeashKnot = new AtomicBoolean(false);
if (!isTransferLeash.get()) {
this.discard();
if (player.getAbilities().instabuild) {
entities.forEach(
entity -> entity
.getCapability(CapabilityHandler.LEASH_DATA_CAP)
.ifPresent(iLeashDataCapability -> {
iLeashDataCapability.removeLeash(this);
isRemoveLeashKnot.set(true);
}
));
} }
}
if (isTransferLeash.get() || isRemoveLeashKnot.get()) {
if (isTransferLeash.get() ) {
this.gameEvent(GameEvent.BLOCK_ATTACH, player); this.gameEvent(GameEvent.BLOCK_ATTACH, player);
} }
return InteractionResult.CONSUME; return InteractionResult.CONSUME;

View File

@ -51,7 +51,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
// 实现拴生物在生物的interact方法里去写相关逻辑 // 实现拴生物在生物的interact方法里去写相关逻辑
// 尝试0 mixin 实现 加强拴绳逻辑 // 尝试0 mixin 实现 加强拴绳逻辑
public class SuperLeadRopeItem extends LeadItem implements IForgeItem { public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
// 配置常量 // 配置常量
// 手动调节,可以通过附魔获取更远抛掷和抛掷距离 - x1.3//TODO:将可抛掷实现留到下次编写 // 手动调节,可以通过附魔获取更远抛掷和抛掷距离 - x1.3//TODO:将可抛掷实现留到下次编写
// 可以做个大于一定距离时远距离使用时抛出拴绳的实体击中生物才栓中的 // 可以做个大于一定距离时远距离使用时抛出拴绳的实体击中生物才栓中的
@ -92,12 +91,32 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
if(SuperLeashKnotEntity.isSupportBlock(state)) { if(SuperLeashKnotEntity.isSupportBlock(state)) {
Player player = context.getPlayer(); Player player = context.getPlayer();
if(!level.isClientSide && player != null) { if(!level.isClientSide && player != null) {
return bindToBlock(player, level, pos, itemStack); return bindToBlock(player, level, pos, itemStack, false) ? InteractionResult.CONSUME : InteractionResult.PASS;
} }
} }
return InteractionResult.PASS; return InteractionResult.PASS;
} }
public static InteractionResult bindToEntity (Entity newHolder, Player player, Level level, BlockPos pos, ItemStack leashStack) { /**
* 右键蹲下绑定到另一实体上
* @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);
}
/**
* 右键蹲下绑定到另一实体上
* @param newHolder 新实体
* @param player 明确持有玩家
* @param level 维度世界
* @param pos 坐标一般是明确持有玩家的位置
* @param leashStack 拴绳物品实例
* @return 是否成功
*/
public static boolean bindToEntity(Entity newHolder, Player player, Level level, BlockPos pos, ItemStack leashStack) {
AtomicBoolean isSuccess = new AtomicBoolean(false); AtomicBoolean isSuccess = new AtomicBoolean(false);
List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, player.getUUID())); List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, player.getUUID()));
for(Entity e : list) { for(Entity e : list) {
@ -118,22 +137,43 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
} }
} }
if(!isSuccess.get()) { if(!isSuccess.get()) {
return InteractionResult.PASS; return false;
} }
else { else {
level.gameEvent(GameEvent.ENTITY_INTERACT, pos, GameEvent.Context.of(player)); level.gameEvent(GameEvent.ENTITY_INTERACT, pos, GameEvent.Context.of(player));
newHolder.playSound(SLPSoundEvents.LEAD_TIED.get()); newHolder.playSound(SLPSoundEvents.LEAD_TIED.get());
return InteractionResult.CONSUME; return true;
} }
} }
public static InteractionResult bindToBlock(Player player, Level level, BlockPos pos, ItemStack leashStack) {
/**
* 右键蹲下绑定到支持方块上
* @param player 明确持有玩家
* @param level 维度世界
* @param leashStack 拴绳物品实例
* @param shouldBindSelf 是否应该触发拴自己逻辑检查
* @return 是否成功
*/
public static boolean bindToBlock(Player player, Level level, ItemStack leashStack, boolean shouldBindSelf) {
return bindToBlock(player, level, player.getOnPos(), leashStack, shouldBindSelf);
}
/**
* 右键蹲下绑定到支持方块上
* @param player 明确持有玩家
* @param level 维度世界
* @param pos 坐标一般是明确持有玩家的位置
* @param leashStack 拴绳物品实例
* @param shouldBindSelf 是否应该触发拴自己逻辑检查
* @return 是否成功
*/
public static boolean bindToBlock(Player player, Level level, BlockPos pos, ItemStack leashStack, boolean shouldBindSelf) {
//实现个加强绳结实体 //实现个加强绳结实体
SuperLeashKnotEntity knot = null; SuperLeashKnotEntity knot = null;
AtomicBoolean isSuccess = new AtomicBoolean(false); AtomicBoolean isSuccess = new AtomicBoolean(false);
UUID uuid = player.getUUID(); UUID uuid = player.getUUID();
List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, uuid)); List<Entity> list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, uuid));
if(list.isEmpty()) {//拴自己 to new knot if (shouldBindSelf && list.isEmpty()) {//拴自己 to new knot
if (leashStack.isEmpty() || !canUse(leashStack)) return InteractionResult.PASS; if (leashStack.isEmpty() || !canUse(leashStack)) return false;
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);; knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);;
knot.playPlacementSound(); knot.playPlacementSound();
@ -146,10 +186,10 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
}); });
} }
else { else if (!list.isEmpty()) {
for(Entity e : list) { for(Entity e : list) {
if(knot == null) { if(knot == null) {
knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);; knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
knot.playPlacementSound(); knot.playPlacementSound();
} }
AtomicBoolean canBeAttachedTo = new AtomicBoolean(false); AtomicBoolean canBeAttachedTo = new AtomicBoolean(false);
@ -171,12 +211,9 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
} }
} }
if (isSuccess.get()) { if (isSuccess.get()) {
if(!player.isCreative()) {
leashStack.hurtAndBreak(50, player, e->{});
}
level.gameEvent(GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of(player)); level.gameEvent(GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of(player));
return InteractionResult.SUCCESS; return true;
} else }
return InteractionResult.PASS; return false;
} }
} }

View File

@ -17,9 +17,11 @@ package top.r3944realms.superleadrope.core.leash;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent;
@ -32,18 +34,16 @@ import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
public class LeashInteractHandler { public class LeashInteractHandler {
//只有玩家可以互动触发其它的暂不支持考虑到0 Mixin) //只有玩家可以互动触发其它的暂不支持考虑到0 Mixin)
public static void onEntityInteract (PlayerInteractEvent.EntityInteract event) { public static void onEntityInteract (Level level, InteractionHand hand, Entity target , Player player, PlayerInteractEvent.EntityInteract event) {
//WARNING: 主手和副手都会触发一次该事件 //WARNING: 主手和副手都会触发一次该事件
// ===== 卫语句 ===== // ===== 卫语句 =====
if (event.getLevel().isClientSide) { if (level.isClientSide) {
return; return;
} }
InteractionHand hand = event.getHand();
if (hand == InteractionHand.OFF_HAND) { if (hand == InteractionHand.OFF_HAND) {
return; return;
} }
Entity target = event.getTarget();
if (!LeashDataImpl.isLeashable(target)) { if (!LeashDataImpl.isLeashable(target)) {
return; return;
@ -52,7 +52,6 @@ public class LeashInteractHandler {
if (!LeashCap.isPresent()) { if (!LeashCap.isPresent()) {
return; return;
} }
Player player = event.getEntity();
ItemStack mainHandItem = player.getItemInHand(InteractionHand.MAIN_HAND); ItemStack mainHandItem = player.getItemInHand(InteractionHand.MAIN_HAND);
ItemStack offHandItem = player.getItemInHand(InteractionHand.OFF_HAND); ItemStack offHandItem = player.getItemInHand(InteractionHand.OFF_HAND);
@ -64,7 +63,11 @@ public class LeashInteractHandler {
) { ) {
SuperLeadRopeItem.bindToEntity(target, player, player.level(), target.getOnPos(), ItemStack.EMPTY); boolean isSuccess = SuperLeadRopeItem.bindToEntity(target, player, player.level(), player.getOnPos(), ItemStack.EMPTY);
if (isSuccess) {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.CONSUME);
}
} else { } else {
if(LeashDataImpl.isLeashHolder(target, player)) { if(LeashDataImpl.isLeashHolder(target, player)) {
LeashCap.ifPresent( LeashCap.ifPresent(
@ -83,18 +86,18 @@ public class LeashInteractHandler {
itemStack = ItemStack.EMPTY; itemStack = ItemStack.EMPTY;
} }
if (!itemStack.isEmpty()) { if (!itemStack.isEmpty()) {
if (itemStack.getItem() == SLPItems.SUPER_LEAD_ROPE.get()) {} if (itemStack.getItem() == SLPItems.SUPER_LEAD_ROPE.get()) {
LeashCap.ifPresent(iLeashDataCapability -> { LeashCap.ifPresent(iLeashDataCapability -> {
if (iLeashDataCapability.canBeAttachedTo(player)) { if (iLeashDataCapability.canBeAttachedTo(player)) {
boolean success = iLeashDataCapability.addLeash(player, itemStack, 12); boolean success = iLeashDataCapability.addLeash(player, itemStack, 12);
if (success) { if (success) {
if(!player.isCreative()) if(!player.isCreative())
itemStack.hurtAndBreak(50, player, e->{}); itemStack.hurtAndBreak(50, player, e->{});
target.playSound(SLPSoundEvents.LEAD_TIED.get()); target.playSound(SLPSoundEvents.LEAD_TIED.get());
} }
} }
}); });
}
} }
} }

View File

@ -31,10 +31,10 @@ public class SLPEntityTypes {
public static RegistryObject<EntityType<SuperLeashKnotEntity>> SUPER_LEAD_KNOT = ENTITY_TYPES.register( public static RegistryObject<EntityType<SuperLeashKnotEntity>> SUPER_LEAD_KNOT = ENTITY_TYPES.register(
"super_lead_knot", "super_lead_knot",
() -> EntityType.Builder.<SuperLeashKnotEntity>of(SuperLeashKnotEntity::new, MobCategory.MISC) () -> EntityType.Builder.<SuperLeashKnotEntity>of(SuperLeashKnotEntity::new, MobCategory.MISC)
.sized(1.0F, 1.0F)
.clientTrackingRange(4)
.updateInterval(20)
.noSave() .noSave()
.sized(0.375F, 0.5F)
.clientTrackingRange(10)
.updateInterval(Integer.MAX_VALUE)
.build("super_lead_knot") .build("super_lead_knot")
); );
public static RegistryObject<EntityType<SuperLeashEntity>> SUPER_LEASH = ENTITY_TYPES.register( public static RegistryObject<EntityType<SuperLeashEntity>> SUPER_LEASH = ENTITY_TYPES.register(

View File

@ -23,6 +23,7 @@ import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.simple.SimpleChannel;
import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
public class NetworkHandler { public class NetworkHandler {
private static final String PROTOCOL_VERSION = "1"; private static final String PROTOCOL_VERSION = "1";
@ -39,6 +40,11 @@ public class NetworkHandler {
.encoder(LeashDataSyncPacket::encode) .encoder(LeashDataSyncPacket::encode)
.consumerNetworkThread(LeashDataSyncPacket::handle) .consumerNetworkThread(LeashDataSyncPacket::handle)
.add(); .add();
INSTANCE.messageBuilder(UpdatePlayerMovementPacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT)
.decoder(UpdatePlayerMovementPacket::decode)
.encoder(UpdatePlayerMovementPacket::encode)
.consumerNetworkThread(UpdatePlayerMovementPacket::handle)
.add();
} }
public static <MSG> void sendAllPlayer(MSG message){ public static <MSG> void sendAllPlayer(MSG message){
INSTANCE.send(PacketDistributor.ALL.noArg(), message); INSTANCE.send(PacketDistributor.ALL.noArg(), message);

View File

@ -19,20 +19,15 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.ICustomPacket;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import java.util.function.Supplier; import java.util.function.Supplier;
public class LeashDataSyncPacket { public record LeashDataSyncPacket(int entityId, CompoundTag leashData) {
private final int entityId;
private final CompoundTag leashData;
public LeashDataSyncPacket(int entityId, CompoundTag leashData) {
this.entityId = entityId;
this.leashData = leashData;
}
public static void encode(LeashDataSyncPacket msg, FriendlyByteBuf buffer) { public static void encode(LeashDataSyncPacket msg, FriendlyByteBuf buffer) {
buffer.writeInt(msg.entityId); buffer.writeInt(msg.entityId);

View File

@ -0,0 +1,65 @@
/*
* 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.network.toClient;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier;
public record UpdatePlayerMovementPacket(Operation operation, double x, double y, double z) {
public static void encode(UpdatePlayerMovementPacket packet, FriendlyByteBuf buffer) {
buffer.writeEnum(packet.operation());
buffer.writeDouble(packet.x());
buffer.writeDouble(packet.y());
buffer.writeDouble(packet.z());
}
public UpdatePlayerMovementPacket(Operation operation, Vec3 vec) {
this(operation, vec.x, vec.y, vec.z);
}
public static UpdatePlayerMovementPacket decode(FriendlyByteBuf buffer) {
return new UpdatePlayerMovementPacket(
buffer.readEnum(Operation.class),
buffer.readDouble(),
buffer.readDouble(),
buffer.readDouble()
);
}
public static void handle(UpdatePlayerMovementPacket packet, Supplier<NetworkEvent.Context> ctx) {
NetworkEvent.Context context = ctx.get();
context.enqueueWork(() -> {
LocalPlayer player = Minecraft.getInstance().player;
assert player != null;
switch (packet.operation) {
case ADD -> player.addDeltaMovement(new Vec3(packet.x, packet.y, packet.z));
case SET -> player.setDeltaMovement(new Vec3(packet.x, packet.y, packet.z));
case MULTIPLY -> player.addDeltaMovement(player.getDeltaMovement().multiply(packet.x, packet.y, packet.z));
}
}
);
context.setPacketHandled(true);
}
public enum Operation {
SET,
ADD,
MULTIPLY
}
}