diff --git a/build.gradle b/build.gradle index c68ae09..f9ed9be 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,37 @@ repositories { maven { url = "https://libraries.minecraft.net/" } maven { url = "https://neoforged.forgecdn.net/releases" } 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 { @@ -101,7 +132,15 @@ configurations { } 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') + + } // ========== 编译配置 ========== diff --git a/gradle.properties b/gradle.properties index 129dda1..57079bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,8 @@ neoForge.parchment.mappingsVersion=2023.09.03 # enable ProGuard enableProguard=false +# Jei Version +jei_version=15.20.0.112 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact diff --git a/libs/curtain-1.20.1-1.3.2.jar b/libs/curtain-1.20.1-1.3.2.jar new file mode 100644 index 0000000..65caf13 Binary files /dev/null and b/libs/curtain-1.20.1-1.3.2.jar differ diff --git a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java index a4a0622..44abc2e 100644 --- a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java @@ -16,17 +16,28 @@ package top.r3944realms.superleadrope; 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.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.Minecart; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.BuildCreativeModeTabContentsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.EntityJoinLevelEvent; 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.PlayerInteractEvent; 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.CapabilityRemainder; 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.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem; import top.r3944realms.superleadrope.core.leash.LeashInteractHandler; import top.r3944realms.superleadrope.core.leash.LeashSyncManager; +import top.r3944realms.superleadrope.core.register.SLPItems; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; public class CommonEventHandler { @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 - public static void onPlayerHitOnBlock (PlayerInteractEvent.RightClickBlock event) { + public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) { Level level = event.getLevel(); if (level.isClientSide) { - return ; + return; } BlockPos blockPos = event.getHitVec().getBlockPos(); BlockState blockState = level.getBlockState(blockPos); + Player player = event.getEntity(); + ItemStack itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND); 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 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 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 public static void onPlayerClone(PlayerEvent.Clone event) { CapabilityRemainder.onPlayerClone(event); @@ -88,7 +170,7 @@ public class CommonEventHandler { @SubscribeEvent public static void onEntityInteract (PlayerInteractEvent.EntityInteract event) { - LeashInteractHandler.onEntityInteract(event); //处理实体互动 + LeashInteractHandler.onEntityInteract(event.getLevel(), event.getHand(), event.getTarget(), event.getEntity(), event); //处理实体互动 } @SubscribeEvent @@ -107,6 +189,12 @@ public class CommonEventHandler { public static void registerCapability(RegisterCapabilitiesEvent event) { CapabilityHandler.registerCapability(event); } + @SubscribeEvent + public static void onCreativeTab (BuildCreativeModeTabContentsEvent event) { + if (event.getTabKey() == CreativeModeTabs.TOOLS_AND_UTILITIES) { + event.accept(SLPItems.SUPER_LEAD_ROPE); + } + } } } diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashKnotRenderer.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashKnotRenderer.java index a66daa0..d1748f1 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashKnotRenderer.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashKnotRenderer.java @@ -22,6 +22,7 @@ import net.minecraft.client.renderer.entity.EntityRenderer; import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.AABB; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.jetbrains.annotations.NotNull; @@ -41,18 +42,30 @@ public class SuperLeashKnotRenderer extends EntityRenderer @Override public void render(@NotNull SuperLeashKnotEntity entity, float entityYaw, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight) { - // 根据实体的实际 hitbox 调整缩放 - float scaleX = entity.getBbWidth() / 0.5f; // 默认 0.5f,对应 scale=1 - float scaleY = entity.getBbHeight() / 0.5f; // 默认 0.5f,对应 scale=1 - float scaleZ = scaleX; // 宽度对称 + AABB box = entity.getBoundingBox(); + float boxWidth = (float) box.getXsize(); + float boxHeight = (float) box.getYsize(); - 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); VertexConsumer vertexConsumer = buffer.getBuffer(this.model.renderType(KNOT_LOCATION)); + this.model.renderToBuffer(poseStack, vertexConsumer, packedLight, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F); diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java index 864ad87..b945e0f 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java @@ -147,7 +147,7 @@ public class SuperLeashRenderer extends EntityRenderer { } @Override - public ResourceLocation getTextureLocation(@NotNull SuperLeashEntity entity) { - return null; // 使用自定义渲染类型,不需要纹理 + public @NotNull ResourceLocation getTextureLocation(@NotNull SuperLeashEntity entity) { + return new ResourceLocation("unknown"); // 使用自定义渲染类型,不需要纹理 } } diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java index 341b22b..162f1ea 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java @@ -19,9 +19,14 @@ import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.LivingEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.*; +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.Minecart; 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.network.NetworkHandler; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; +import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; @@ -84,6 +91,7 @@ public class LeashDataImpl implements ILeashDataCapability { // 引入解决 绳结不保存导致第二进入持有者不存在的问题 private final Map leashKnots = new ConcurrentHashMap<>(); private CompoundTag lastSyncedData = new CompoundTag(); + public LeashDataImpl(Entity entity) { this.entity = entity; } @@ -155,6 +163,11 @@ public class LeashDataImpl implements ILeashDataCapability { return true; } + @Override + public boolean addLeash(Entity holder, LeashInfo leashInfo) { + return addLeash(holder, ItemStack.EMPTY, leashInfo.maxDistance(), leashInfo.elasticDistance(), leashInfo.maxKeepLeashTicks()); + } + @Override public boolean setMaxDistance(Entity holder, double newMaxDistance) { return holder instanceof SuperLeashKnotEntity superLeashKnotEntity ? @@ -331,32 +344,82 @@ public class LeashDataImpl implements ILeashDataCapability { return true; } - // 计算拴绳拉力(防抖动逻辑) + /** + * 计算拴绳拉力(防抖动逻辑) + */ @Override public void applyLeashForces() { - for (Map.Entry uuidLeashInfoEntry : leashHolders.entrySet()) { - internalUUIDApplyLeashForces(uuidLeashInfoEntry); + Vec3 combinedForce = Vec3.ZERO; // 初始化合力向量 + + // 计算所有拴绳的合力 + for (Map.Entry entry : leashHolders.entrySet()) { + Vec3 force = calculateLeashForceForUUID(entry); + if (force != null) { + combinedForce = combinedForce.add(force); + } } - for (Map.Entry blockPosLeashInfoEntry : leashKnots.entrySet()) { - internalBlockPosApplyLeashForce(blockPosLeashInfoEntry); + + for (Map.Entry 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 entry) { + /** + * 为UUID拴绳计算力 + */ + private Vec3 calculateLeashForceForUUID(Map.Entry entry) { UUID uuid = entry.getKey(); Entity uuidHolder = ((ServerLevel) entity.level()).getEntity(uuid); if (uuidHolder != null) { - internalApplyLeashForces(uuidHolder, entry); + return calculateLeashForce(uuidHolder, entry); } else { SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found.", uuid); + return null; } } - private void internalBlockPosApplyLeashForce(Map.Entry entry) { + /** + * 为方块位置拴绳计算力 + */ + private Vec3 calculateLeashForceForBlockPos(Map.Entry entry) { SuperLeashKnotEntity orCreateKnot = SuperLeashKnotEntity.getOrCreateKnot(entity.level(), entry.getKey()); - internalApplyLeashForces(orCreateKnot, entry); + return calculateLeashForce(orCreateKnot, entry); } - private void internalApplyLeashForces(Entity holder, Map.Entry entry) { + + /** + * 计算单个拴绳的力 + */ + private Vec3 calculateLeashForce(Entity holder, Map.Entry entry) { Vec3 holderPos = holder.position().add(0, holder.getBbHeight() * 0.7, 0); LeashInfo info = entry.getValue(); Vec3 entityPos = entity.position().add(info.attachOffset()); @@ -365,41 +428,49 @@ public class LeashDataImpl implements ILeashDataCapability { // 1. 检查是否超出断裂距离 if (distance > extremeSnapDist) { - // 如果还有剩余缓冲Tick,施加更强拉力并减少计数 if (info.keepLeashTicks() > 0) { - // 计算临界拉力(距离越远,拉力越强) + // 计算临界拉力 Vec3 pullForce = calculateCriticalPullForce(holderPos, entityPos, distance, info); - entity.setDeltaMovement(entity.getDeltaMovement().add(pullForce)); - entity.hurtMarked = true; entry.setValue(info.decrementKeepLeashTicks()); - return; + return pullForce; } - // 否则立即断裂 + // 断裂 removeLeash(holder); - return; + return null; } - // 2. 正常弹性拉力逻辑(保持不变) + // 2. 正常弹性拉力逻辑 + Vec3 pullForce = Vec3.ZERO; if (distance > info.elasticDistance()) { - Vec3 pullForce = calculatePullForce(holderPos, entityPos, distance, info); - entity.setDeltaMovement(entity.getDeltaMovement().add(pullForce)); - entity.hurtMarked = true; + 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(如果回到安全距离) + // 3. 重置缓冲Tick if (distance <= info.maxDistance() && info.keepLeashTicks() < info.maxKeepLeashTicks()) { entry.setValue(info.resetKeepLeashTicks()); } + + return pullForce; } + + // 计算正常拉力(保持不变) @Contract("_, _, _, _ -> new") private @NotNull Vec3 calculatePullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) { Vec3 pullDirection = holderPos.subtract(entityPos).normalize(); double pullStrength = 0.2; - // 增强拉力(如果超出maxDistance但未达断裂距离) if (distance > info.maxDistance()) { - double excessRatio = (distance - info.maxDistance()) / (info.maxDistance()); - pullStrength += excessRatio * 0.8; // 最高1.0倍基础拉力 + double excessRatio = (distance - info.maxDistance()) / info.maxDistance(); + pullStrength += excessRatio * 0.8; } Vec3 pullForce = pullDirection.scale( @@ -412,18 +483,17 @@ public class LeashDataImpl implements ILeashDataCapability { pullForce.z * AXIS_SPECIFIC_ELASTICITY.z ); } + + // 计算临界拉力(保持不变) private @NotNull Vec3 calculateCriticalPullForce(@NotNull Vec3 holderPos, Vec3 entityPos, double distance, @NotNull LeashInfo info) { Vec3 pullDirection = holderPos.subtract(entityPos).normalize(); - - // 非线性增强拉力(距离越远,拉力越强) - double excessRatio = (distance - info.maxDistance()) / (info.maxDistance()); - double pullStrength = 1.0 + excessRatio * 2.0; // 基础1.0 + 额外增强(最高3.0倍) + double excessRatio = (distance - info.maxDistance()) / info.maxDistance(); + double pullStrength = 1.0 + excessRatio * 2.0; Vec3 pullForce = pullDirection.scale( (distance - info.elasticDistance()) * pullStrength * SPRING_DAMPENING ); - // 应用轴向弹性系数(减少Y轴抖动) return new Vec3( pullForce.x * AXIS_SPECIFIC_ELASTICITY.x, pullForce.y * AXIS_SPECIFIC_ELASTICITY.y, @@ -702,11 +772,14 @@ public class LeashDataImpl implements ILeashDataCapability { public static @NotNull List leashableInArea(Entity entity, Predicate filter) { return leashableInArea(entity, filter, 1024D); } + public static @NotNull List leashableInArea(Entity holder) { + return leashableInArea(holder, i -> isLeashHolder(i, holder), 1024D); + } public boolean canBeAttachedTo(Entity pEntity) { if(pEntity == entity) { return false; } else { - Optional leashInfo = getLeashInfo(pEntity.getUUID()); + Optional leashInfo = getLeashInfo(pEntity); return leashInfo.isEmpty() && (entity.distanceTo(pEntity) <= LEASH_ELASTIC_DIST * LEASH_EXTREME_SNAP_DIST_FACTOR) && canBeLeashed();//距离最大,则不可以被固定或转移 } } diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java index 4391d52..9982465 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java @@ -31,6 +31,7 @@ public interface ILeashDataCapability extends INBTSerializable { // 原LeashData的方法接口 boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance); boolean addLeash(Entity holder, ItemStack leashStack, double maxDistance, double elasticDistance, int maxKeepLeashTicks); + boolean addLeash(Entity holder, LeashInfo leashInfo); boolean setMaxDistance(Entity holder, double newMaxDistance); boolean setMaxDistance(Entity holder,double newMaxDistance, int newMaxKeepLeashTicks); @@ -82,6 +83,10 @@ public interface ILeashDataCapability extends INBTSerializable { int keepLeashTicks, // 新增:保持拴绳的剩余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( Entity entity, String reserved, diff --git a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java index 074fb4c..be8af01 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java +++ b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java @@ -52,6 +52,11 @@ public class SuperLeashEntity extends Entity { if(!level().isClientSide) controlled.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(ILeashDataCapability::applyLeashForces); } + @Override + public void kill() { + + } + @Override protected void defineSynchedData() { diff --git a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java index 8c5af5f..8a63037 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java +++ b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java @@ -20,6 +20,7 @@ import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagKey; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.decoration.LeashFenceKnotEntity; @@ -57,11 +58,31 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { super(pEntityType, pLevel); } + public SuperLeashKnotEntity(Level pLevel, BlockPos pPos) { this(SLPEntityTypes.SUPER_LEAD_KNOT.get(), pLevel); 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 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 public boolean survives() { @@ -73,7 +94,15 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { int j = pPos.getY(); 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)) { return superLeashKnotEntity; } @@ -87,6 +116,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { @Override protected void recalculateBoundingBox() { updateDimensionsBasedOnBlock(); + setPosRaw(this.pos.getX() + 0.5, this.pos.getY() + 0.20, this.pos.getZ() + 0.5); double halfWidth = currentWidth / 2.0f; this.setBoundingBox(new AABB( this.getX() - halfWidth, @@ -104,7 +134,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { // 根据方块类型调整尺寸 if (state.is(BlockTags.WALLS)) { // 墙类方块 - 更窄更高 - currentWidth = 0.25f; + currentWidth = 0.75f; currentHeight = 0.75f; } else { // 默认栅栏尺寸 @@ -113,13 +143,6 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { } //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) { for(TagKey tagKey : SUPPORTED_BLOCK) { @@ -135,7 +158,7 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { return InteractionResult.SUCCESS; } AtomicBoolean isTransferLeash = new AtomicBoolean(false); - List entities = LeashDataImpl.leashableInArea(player, ing -> true); + List entities = LeashDataImpl.leashableInArea(player); for(Entity entity : entities) { if (LeashDataImpl.isLeashHolder(entity, player.getUUID())) entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> { @@ -143,10 +166,22 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity { 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() ) { + } + if (isTransferLeash.get() || isRemoveLeashKnot.get()) { this.gameEvent(GameEvent.BLOCK_ATTACH, player); } return InteractionResult.CONSUME; diff --git a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java index b95499f..83f7285 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java +++ b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java @@ -51,7 +51,6 @@ import java.util.concurrent.atomic.AtomicBoolean; // 实现拴生物,在生物的interact方法里去写相关逻辑 // (尝试0 mixin 实现 加强拴绳逻辑) public class SuperLeadRopeItem extends LeadItem implements IForgeItem { - // 配置常量 // 【手动调节,可以通过附魔获取更远抛掷和抛掷距离 - x1.3】//TODO:将可抛掷实现留到下次编写 // 可以做个大于一定距离时远距离使用时抛出拴绳的实体,击中生物才栓中的 @@ -92,12 +91,32 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem { if(SuperLeashKnotEntity.isSupportBlock(state)) { Player player = context.getPlayer(); 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; } - 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); List list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, player.getUUID())); for(Entity e : list) { @@ -118,22 +137,43 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem { } } if(!isSuccess.get()) { - return InteractionResult.PASS; + return false; } else { level.gameEvent(GameEvent.ENTITY_INTERACT, pos, GameEvent.Context.of(player)); 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; AtomicBoolean isSuccess = new AtomicBoolean(false); UUID uuid = player.getUUID(); List list = LeashDataImpl.leashableInArea(level, pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, uuid)); - if(list.isEmpty()) {//拴自己 to new knot - if (leashStack.isEmpty() || !canUse(leashStack)) return InteractionResult.PASS; + if (shouldBindSelf && list.isEmpty()) {//拴自己 to new knot + if (leashStack.isEmpty() || !canUse(leashStack)) return false; knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);; knot.playPlacementSound(); @@ -146,10 +186,10 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem { }); } - else { + else if (!list.isEmpty()) { for(Entity e : list) { if(knot == null) { - knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);; + knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos); knot.playPlacementSound(); } AtomicBoolean canBeAttachedTo = new AtomicBoolean(false); @@ -171,12 +211,9 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem { } } if (isSuccess.get()) { - if(!player.isCreative()) { - leashStack.hurtAndBreak(50, player, e->{}); - } level.gameEvent(GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of(player)); - return InteractionResult.SUCCESS; - } else - return InteractionResult.PASS; + return true; + } + return false; } } diff --git a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java index 4d4b042..195b7a0 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java @@ -17,9 +17,11 @@ package top.r3944realms.superleadrope.core.leash; import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; 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.PlayerInteractEvent; @@ -32,18 +34,16 @@ import top.r3944realms.superleadrope.core.register.SLPSoundEvents; public class LeashInteractHandler { //只有玩家可以互动触发(其它的暂不支持(考虑到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: 主手和副手都会触发一次该事件 // ===== 卫语句 ===== - if (event.getLevel().isClientSide) { + if (level.isClientSide) { return; } - InteractionHand hand = event.getHand(); if (hand == InteractionHand.OFF_HAND) { return; } - Entity target = event.getTarget(); if (!LeashDataImpl.isLeashable(target)) { return; @@ -52,7 +52,6 @@ public class LeashInteractHandler { if (!LeashCap.isPresent()) { return; } - Player player = event.getEntity(); ItemStack mainHandItem = player.getItemInHand(InteractionHand.MAIN_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 { if(LeashDataImpl.isLeashHolder(target, player)) { LeashCap.ifPresent( @@ -83,18 +86,18 @@ public class LeashInteractHandler { itemStack = ItemStack.EMPTY; } if (!itemStack.isEmpty()) { - if (itemStack.getItem() == SLPItems.SUPER_LEAD_ROPE.get()) {} - LeashCap.ifPresent(iLeashDataCapability -> { - if (iLeashDataCapability.canBeAttachedTo(player)) { - boolean success = iLeashDataCapability.addLeash(player, itemStack, 12); - if (success) { - if(!player.isCreative()) - itemStack.hurtAndBreak(50, player, e->{}); - target.playSound(SLPSoundEvents.LEAD_TIED.get()); - } - } - }); - + if (itemStack.getItem() == SLPItems.SUPER_LEAD_ROPE.get()) { + LeashCap.ifPresent(iLeashDataCapability -> { + if (iLeashDataCapability.canBeAttachedTo(player)) { + boolean success = iLeashDataCapability.addLeash(player, itemStack, 12); + if (success) { + if(!player.isCreative()) + itemStack.hurtAndBreak(50, player, e->{}); + target.playSound(SLPSoundEvents.LEAD_TIED.get()); + } + } + }); + } } } diff --git a/src/main/java/top/r3944realms/superleadrope/core/register/SLPEntityTypes.java b/src/main/java/top/r3944realms/superleadrope/core/register/SLPEntityTypes.java index 5cb0fad..9268ea3 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/register/SLPEntityTypes.java +++ b/src/main/java/top/r3944realms/superleadrope/core/register/SLPEntityTypes.java @@ -31,10 +31,10 @@ public class SLPEntityTypes { public static RegistryObject> SUPER_LEAD_KNOT = ENTITY_TYPES.register( "super_lead_knot", () -> EntityType.Builder.of(SuperLeashKnotEntity::new, MobCategory.MISC) - .sized(1.0F, 1.0F) - .clientTrackingRange(4) - .updateInterval(20) .noSave() + .sized(0.375F, 0.5F) + .clientTrackingRange(10) + .updateInterval(Integer.MAX_VALUE) .build("super_lead_knot") ); public static RegistryObject> SUPER_LEASH = ENTITY_TYPES.register( diff --git a/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java b/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java index 1bec9ed..4e4cb0a 100644 --- a/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java @@ -23,6 +23,7 @@ import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.simple.SimpleChannel; import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket; +import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; @@ -39,6 +40,11 @@ public class NetworkHandler { .encoder(LeashDataSyncPacket::encode) .consumerNetworkThread(LeashDataSyncPacket::handle) .add(); + INSTANCE.messageBuilder(UpdatePlayerMovementPacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(UpdatePlayerMovementPacket::decode) + .encoder(UpdatePlayerMovementPacket::encode) + .consumerNetworkThread(UpdatePlayerMovementPacket::handle) + .add(); } public static void sendAllPlayer(MSG message){ INSTANCE.send(PacketDistributor.ALL.noArg(), message); diff --git a/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java b/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java index db2f505..090a27e 100644 --- a/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java +++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java @@ -19,20 +19,15 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.Packet; import net.minecraft.world.entity.Entity; +import net.minecraftforge.network.ICustomPacket; import net.minecraftforge.network.NetworkEvent; import top.r3944realms.superleadrope.content.capability.CapabilityHandler; import java.util.function.Supplier; -public class LeashDataSyncPacket { - private final int entityId; - private final CompoundTag leashData; - - public LeashDataSyncPacket(int entityId, CompoundTag leashData) { - this.entityId = entityId; - this.leashData = leashData; - } +public record LeashDataSyncPacket(int entityId, CompoundTag leashData) { public static void encode(LeashDataSyncPacket msg, FriendlyByteBuf buffer) { buffer.writeInt(msg.entityId); diff --git a/src/main/java/top/r3944realms/superleadrope/network/toClient/UpdatePlayerMovementPacket.java b/src/main/java/top/r3944realms/superleadrope/network/toClient/UpdatePlayerMovementPacket.java new file mode 100644 index 0000000..83d17fb --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/UpdatePlayerMovementPacket.java @@ -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 . + */ + +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 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 + } +}