listeners = new CopyOnWriteArrayList<>();
+ public static void addListener(IEternalPotatoChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ public static void removeListener(IEternalPotatoChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ // 内部方法,用于通知变化
+ static void notifyChange(UUID uuid, IEternalPotato potato) {
+ listeners.forEach(l -> l.onPotatoChanged(uuid, potato));
+ }
+
+ public static IEternalPotatoManager getManager() {
+ return manager;
+ }
+ public static PotatoSavedData getSavedData() {
+ return savedData;
+ }
+ public static void initSavedData(ServerLevel serverLevel) {
+ savedData = PotatoSavedData.create(serverLevel);
+ }
+ /**
+ * 初始化(进入世界时调用)
+ * @param mode 当前运行模式
+ * @param isServer 是否在服务端
+ */
+ public static void init(PotatoMode mode, boolean isServer) {
+ if (manager != null) {
+ return; // 已经有 manager,说明重复初始化,直接返回
+ }
+ switch (mode) {
+ case INTEGRATED -> manager = new LocalEternalPotatoManager();
+ case DEDICATED, REMOTE_CLIENT -> manager = new SyncedEternalPotatoManager(isServer);
+ }
+ }
+
+ public static IEternalPotato getOrCreate(UUID uuid) {
+ if (manager == null) throw new IllegalStateException("EternalPotatoFacade not initialized!");
+ IEternalPotato potato = manager.getOrCreate(uuid);
+
+ // 绑定统一回调
+ potato.bindItemStackSync(() -> {
+ if (savedData != null) savedData.setDirty(); // 标记 SavedData 脏
+ notifyChange(uuid, potato); // 通知全局监听器
+ });
+
+ return potato;
+ }
+
+ public static void remove(UUID uuid) {
+ if (manager != null) manager.remove(uuid);
+
+ // Synced 模式才发包
+ if (PotatoModeHelper.getCurrentMode().isSynced() && ServerLifecycleHooks.getCurrentServer() != null) {
+ ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers().forEach(player -> {
+ PacketEternalPotatoRemovePacket packet = new PacketEternalPotatoRemovePacket(uuid);
+ NetworkHandler.sendToPlayer(packet, player);
+ });
+ }
+ }
+
+ public static void clear() {
+ if (manager != null) manager.clear();
+ }
+
+ public static boolean isServer() {
+ return (manager instanceof SyncedEternalPotatoManager synced) && synced.isServer();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/config/CommonConfig.java b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoChangeListener.java
similarity index 73%
rename from src/main/java/top/r3944realms/superleadrope/config/CommonConfig.java
rename to src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoChangeListener.java
index c3e398a..2e93285 100644
--- a/src/main/java/top/r3944realms/superleadrope/config/CommonConfig.java
+++ b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoChangeListener.java
@@ -13,7 +13,12 @@
* along with this program. If not, see .
*/
-package top.r3944realms.superleadrope.config;
+package top.r3944realms.superleadrope.core.potato;
-public class CommonConfig {
+import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
+
+import java.util.UUID;
+
+public interface IEternalPotatoChangeListener {
+ void onPotatoChanged(UUID uuid, IEternalPotato potato);
}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java
new file mode 100644
index 0000000..a1ef90e
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java
@@ -0,0 +1,31 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.potato;
+
+import net.minecraft.nbt.CompoundTag;
+import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
+
+import java.util.UUID;
+
+public interface IEternalPotatoManager {
+ IEternalPotato getOrCreate(UUID uuid);
+
+ void remove(UUID uuid);
+
+ void clear();
+ CompoundTag saveAll();
+ void loadAll(CompoundTag tag);
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java b/src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java
new file mode 100644
index 0000000..9faf477
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java
@@ -0,0 +1,76 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.potato;
+
+import net.minecraft.nbt.CompoundTag;
+import top.r3944realms.superleadrope.SuperLeadRope;
+import top.r3944realms.superleadrope.content.capability.impi.EternalPotatoImpl;
+import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 单人世界 & 局域网主机使用
+ *
+ * 因为客户端和服务端在同一 JVM,可以共用同一个 Map。
+ *
+ * 特点:不需要发网络包,直接访问。
+ */
+class LocalEternalPotatoManager implements IEternalPotatoManager {
+ private final Map LOCAL_DATA = new ConcurrentHashMap<>();
+
+ public IEternalPotato getOrCreate(UUID uuid) {
+ return LOCAL_DATA.computeIfAbsent(uuid, k -> {
+ EternalPotatoImpl impl = new EternalPotatoImpl();
+ impl.setItemUUID(uuid);
+ return impl;
+ });
+ }
+
+ @Override
+ public void remove(UUID uuid) {
+ LOCAL_DATA.remove(uuid);
+ }
+
+ public void clear() {
+ LOCAL_DATA.clear();
+ }
+
+ @Override
+ public CompoundTag saveAll() {
+ CompoundTag root = new CompoundTag();
+ LOCAL_DATA.forEach((uuid, impl) -> root.put(uuid.toString(), impl.serializeNBT()));
+ return root;
+ }
+
+ @Override
+ public void loadAll(CompoundTag tag) {
+ LOCAL_DATA.clear();
+ for (String key : tag.getAllKeys()) {
+ try {
+ UUID uuid = UUID.fromString(key);
+ EternalPotatoImpl impl = new EternalPotatoImpl();
+ impl.deserializeNBT(tag.getCompound(key));
+ impl.setItemUUID(uuid);
+ LOCAL_DATA.put(uuid, impl);
+ } catch (IllegalArgumentException e) {
+ SuperLeadRope.logger.error("Could not load UUID: {}", key, e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java b/src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java
new file mode 100644
index 0000000..4263597
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java
@@ -0,0 +1,51 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.potato;
+
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.saveddata.SavedData;
+import org.jetbrains.annotations.NotNull;
+
+public class PotatoSavedData extends SavedData {
+ public static final String DATA_NAME = "eternal_potato";
+
+
+ @Override
+ public @NotNull CompoundTag save(CompoundTag tag) {
+ // 把所有数据塞进 tag
+ tag.put(DATA_NAME, EternalPotatoFacade.getManager().saveAll());
+ return tag;
+ }
+
+ public static PotatoSavedData load(CompoundTag tag) {
+ IEternalPotatoManager manager = EternalPotatoFacade.getManager();
+ PotatoSavedData data = new PotatoSavedData();
+ if (tag.contains(DATA_NAME)) {
+ manager.loadAll(tag.getCompound(DATA_NAME));
+ }
+ return data;
+ }
+
+ // 工厂方法(Forge 推荐写法)
+ public static PotatoSavedData create(ServerLevel level) {
+ return level.getDataStorage().computeIfAbsent(
+ PotatoSavedData::load,
+ PotatoSavedData::new,
+ DATA_NAME
+ );
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java b/src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java
new file mode 100644
index 0000000..b4fbbcf
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java
@@ -0,0 +1,89 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.potato;
+
+import net.minecraft.nbt.CompoundTag;
+import top.r3944realms.superleadrope.SuperLeadRope;
+import top.r3944realms.superleadrope.content.capability.impi.EternalPotatoImpl;
+import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 专用服务器 + 远程客户端使用
+ *
+ * 服务端:维护权威数据,变更时发包
+ *
+ * 客户端:本地缓存,收到服务端同步包时更新
+ */
+class SyncedEternalPotatoManager implements IEternalPotatoManager {
+ private final Map GLOBAL_DATA = new ConcurrentHashMap<>();
+ private final boolean isServer;
+ public SyncedEternalPotatoManager(boolean isServer) {
+ this.isServer = isServer;
+ }
+ public IEternalPotato getOrCreate(UUID uuid) {
+ return GLOBAL_DATA.computeIfAbsent(uuid, k -> {
+ EternalPotatoImpl impl = new EternalPotatoImpl();
+ impl.setItemUUID(uuid);
+ return impl;
+ });
+ }
+
+ // 可选:移除数据,防止内存泄漏
+ public void remove(UUID uuid) {
+ GLOBAL_DATA.remove(uuid);
+ }
+
+ @Override
+ public void clear() {
+ GLOBAL_DATA.clear();
+ }
+
+ @Override
+ public CompoundTag saveAll() {
+ if (!isServer) {
+ return new CompoundTag(); // 客户端不存盘
+ }
+ CompoundTag root = new CompoundTag();
+ GLOBAL_DATA.forEach((uuid, impl) -> {
+ root.put(uuid.toString(), impl.serializeNBT());
+ });
+ return root;
+ }
+
+ @Override
+ public void loadAll(CompoundTag tag) {
+ GLOBAL_DATA.clear();
+ for (String key : tag.getAllKeys()) {
+ try {
+ UUID uuid = UUID.fromString(key);
+ EternalPotatoImpl impl = new EternalPotatoImpl();
+ impl.deserializeNBT(tag.getCompound(key));
+ impl.setItemUUID(uuid);
+ GLOBAL_DATA.put(uuid, impl);
+ } catch (IllegalArgumentException e) {
+ SuperLeadRope.logger.error("Could not load UUID: {}", key, e);
+ }
+ }
+ }
+
+ public boolean isServer() {
+ return isServer;
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/punishment/DailyPunishmentHandler.java b/src/main/java/top/r3944realms/superleadrope/core/punishment/DailyPunishmentHandler.java
new file mode 100644
index 0000000..531ffee
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/punishment/DailyPunishmentHandler.java
@@ -0,0 +1,127 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.punishment;
+
+import net.minecraft.core.Holder;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextColor;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraftforge.server.ServerLifecycleHooks;
+import top.r3944realms.superleadrope.content.SLPDamageTypes;
+import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
+import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DailyPunishmentHandler {
+
+ private static long lastProcessedDay = -1;
+ private static final int COUNTDOWN_SECONDS = 10;
+
+ /**
+ * 玩家倒计时任务存储
+ */
+ private static final Map countdownMap = new ConcurrentHashMap<>();
+
+
+ public static void onServerTick() {
+
+ var server = ServerLifecycleHooks.getCurrentServer();
+ if (server == null) return;
+
+ for (ServerLevel level : server.getAllLevels()) {
+ long currentDay = level.getDayTime() / 24000L; // 每 24000 tick 为一天
+
+ if (currentDay != lastProcessedDay) {
+ lastProcessedDay = currentDay;
+
+ for (ServerPlayer player : level.getPlayers(p -> !(p.isCreative() || p.isSpectator()))) {
+ player.getInventory().items.stream()
+ .filter(stack -> !stack.isEmpty())
+ .forEach(stack -> stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
+
+ // 将上日未完成每日任务加入 pendingPunishments
+ int dailyObl = cap.getDailyObligations();
+ if (dailyObl > 0) {
+ cap.beginInit();
+ cap.setPendingPunishments(cap.getPendingPunishments() + dailyObl);
+ cap.setDailyObligations(0);
+ cap.endInit();
+ }
+
+ // 超过可宽恕次数,开始倒计时惩罚
+ if (cap.getPendingPunishments() > cap.getGracePunishments()) {
+ UUID playerId = player.getUUID();
+ if (!countdownMap.containsKey(playerId)) {
+ countdownMap.put(playerId, COUNTDOWN_SECONDS * 20); // 10秒 = 200 tick
+ }
+ }
+ }));
+ }
+ }
+
+ // 每 tick 更新倒计时
+ countdownMap.forEach((uuid, ticksLeft) -> {
+ ServerPlayer player = (ServerPlayer) level.getPlayerByUUID(uuid);
+ if (player == null) return;
+
+ player.getInventory().items.stream()
+ .filter(stack -> !stack.isEmpty())
+ .forEach(stack -> stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
+ // 如果 pending 已经低于宽恕值或者是非生存/冒险模式下的玩家,中途取消倒计时
+ if (cap.getPendingPunishments() <= cap.getGracePunishments() || player.isCreative() || player.isSpectator()) {
+ countdownMap.remove(uuid);
+ return;
+ }
+
+ int newTicksLeft = ticksLeft - 1;
+ countdownMap.put(uuid, newTicksLeft);
+ int secondsLeft = (newTicksLeft + 19) / 20; // 转成秒
+
+ // 颜色渐变:红->黄->绿
+ int color;
+ if (secondsLeft > 6) color = 0xFF5555; // 红
+ else if (secondsLeft > 3) color = 0xFFFF55; // 黄
+ else color = 0x55FF55; // 绿
+
+ player.displayClientMessage(
+ Component.translatable(SLPLangKeyValue.EP_OBLIGATION_COUNTDOWN.getKey(), secondsLeft)
+ .withStyle(style -> style.withColor(TextColor.fromRgb(color))),
+ true
+ );
+
+ if (newTicksLeft <= 0) {
+ // 执行惩罚
+ PunishmentDefinition punishment = cap.getPunishment();
+ if (punishment != null) {
+ punishment.execute(player,
+ new DamageSource(Holder.direct(SLPDamageTypes.ETERNAL_POTATO_NOT_COMPLETE), player));
+ }
+ // 扣除 2 次 pendingPunishments
+ cap.beginInit();
+ cap.setPendingPunishments(Math.max(0, cap.getPendingPunishments() - 2));
+ cap.endInit();
+ countdownMap.remove(uuid);
+ }
+ }));
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java b/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java
new file mode 100644
index 0000000..aca02ba
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java
@@ -0,0 +1,77 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.punishment;
+
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.item.ItemStack;
+import top.r3944realms.superleadrope.core.register.ObligationCompletionRegistry;
+
+import java.util.Map;
+
+/**
+ * 判定单次任务是否完成
+ */
+public interface IObligationCompletion {
+ /**
+ * 判断某次操作是否算作完成义务
+ *
+ * @param player 执行玩家
+ * @param stack 操作的物品
+ * @return true = 完成一次义务
+ */
+ boolean isCompleted(ServerPlayer player, ItemStack stack);
+
+ /**
+ * 当义务完成时执行(比如减少计数、提示)
+ *
+ * @param player 执行玩家
+ * @param stack 操作的物品
+ */
+ void onCompleted(ServerPlayer player, ItemStack stack);
+ /**
+ * 获取注册 ID
+ */
+ default String getId() {
+ for (Map.Entry entry : ObligationCompletionRegistry.getAll().entrySet()) {
+ if (entry.getValue() == this) return entry.getKey();
+ }
+ return "none";
+ }
+ // --- 网络序列化 ---
+ default void toNetwork(FriendlyByteBuf buf) {
+ buf.writeUtf(this.getId());
+ }
+
+ static IObligationCompletion fromNetwork(FriendlyByteBuf buf) {
+ String id = buf.readUtf();
+ return ObligationCompletionRegistry.byId(id); // 如果没找到,返回 NONE
+ }
+ /**
+ * 一个便捷的静态空实现(默认永不完成)
+ */
+ IObligationCompletion NONE = new IObligationCompletion() {
+ @Override
+ public boolean isCompleted(ServerPlayer player, ItemStack stack) {
+ return false;
+ }
+
+ @Override
+ public void onCompleted(ServerPlayer player, ItemStack stack) {
+ // no-op
+ }
+ };
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/punishment/PunishmentDefinition.java b/src/main/java/top/r3944realms/superleadrope/core/punishment/PunishmentDefinition.java
new file mode 100644
index 0000000..8f9a953
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/punishment/PunishmentDefinition.java
@@ -0,0 +1,82 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.punishment;
+
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LightningBolt;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * 定义物品的惩罚方式
+ *
+ * @param strength 威力(爆炸用 / 效果用)
+ * @param affectOthers 是否影响其他实体
+ */
+public record PunishmentDefinition(PunishmentDefinition.Type type, float strength,
+ boolean affectOthers) {
+
+ public static final PunishmentDefinition DEFAULT = new PunishmentDefinition(Type.LIGHTNING, 0, false);
+
+ public enum Type {
+ LIGHTNING, // 雷劈
+ EXPLOSION, // 爆炸
+ EFFECT // 给予负面效果
+ }
+ /** 序列化到网络 */
+ public void toNetwork(FriendlyByteBuf buf) {
+ buf.writeEnum(this.type);
+ buf.writeFloat(this.strength);
+ buf.writeBoolean(this.affectOthers);
+ }
+
+ /** 从网络反序列化 */
+ public static PunishmentDefinition fromNetwork(FriendlyByteBuf buf) {
+ Type type = buf.readEnum(Type.class);
+ float strength = buf.readFloat();
+ boolean affectOthers = buf.readBoolean();
+ return new PunishmentDefinition(type, strength, affectOthers);
+ }
+ /**
+ * 执行惩罚
+ */
+ public void execute(ServerPlayer target, DamageSource cause) {
+ execute(target, cause, null);
+ }
+ public void execute(ServerPlayer target, DamageSource cause,@Nullable Component actionMessage) {
+ ServerLevel level = (ServerLevel) target.level();
+ switch (type) {
+ case LIGHTNING -> {
+ LightningBolt bolt = new LightningBolt(EntityType.LIGHTNING_BOLT, level);
+ bolt.setPos(target.getX(), target.getY(), target.getZ());
+ bolt.setVisualOnly(true);
+ if(actionMessage != null) target.displayClientMessage(actionMessage, true);
+ level.addFreshEntity(bolt);
+ target.hurt(cause, Float.MAX_VALUE);
+ }
+ case EXPLOSION -> {
+
+ }
+ case EFFECT -> {
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java b/src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java
new file mode 100644
index 0000000..9f32984
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java
@@ -0,0 +1,58 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.register;
+
+import top.r3944realms.superleadrope.core.punishment.IObligationCompletion;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * IObligationCompletion 注册与反序列化管理器
+ */
+public class ObligationCompletionRegistry {
+
+ /** ID -> IObligationCompletion 实例 */
+ private static final Map REGISTRY = new HashMap<>();
+
+ /**
+ * 注册一个 IObligationCompletion 实例
+ * @param id 唯一 ID
+ * @param completion 实例
+ */
+ public static void register(String id, IObligationCompletion completion) {
+ if (id == null || id.isEmpty()) throw new IllegalArgumentException("ID cannot be null or empty");
+ if (completion == null) throw new IllegalArgumentException("Completion cannot be null");
+ REGISTRY.put(id, completion);
+ }
+
+ /**
+ * 根据 ID 获取 IObligationCompletion 实例
+ * @param id ID
+ * @return 实例,如果未注册则返回 NONE
+ */
+ public static IObligationCompletion byId(String id) {
+ return REGISTRY.getOrDefault(id, IObligationCompletion.NONE);
+ }
+
+ /**
+ * 获取只读注册表(用于调试或枚举)
+ */
+ public static Map getAll() {
+ return Collections.unmodifiableMap(REGISTRY);
+ }
+}
\ No newline at end of file
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 9268ea3..0542a8b 100644
--- a/src/main/java/top/r3944realms/superleadrope/core/register/SLPEntityTypes.java
+++ b/src/main/java/top/r3944realms/superleadrope/core/register/SLPEntityTypes.java
@@ -22,7 +22,6 @@ import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import top.r3944realms.superleadrope.SuperLeadRope;
-import top.r3944realms.superleadrope.content.entity.SuperLeashEntity;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
public class SLPEntityTypes {
@@ -37,18 +36,6 @@ public class SLPEntityTypes {
.updateInterval(Integer.MAX_VALUE)
.build("super_lead_knot")
);
- public static RegistryObject> SUPER_LEASH = ENTITY_TYPES.register(
- "super_leash",
- () -> EntityType.Builder.of(SuperLeashEntity::new, MobCategory.MISC)
- .sized(0.01f, 0.01f)
- .noSummon()
- .noSave()
- .clientTrackingRange(0)
- .updateInterval(1)
- .fireImmune()
- .canSpawnFarFromPlayer()
- .build("super_leash")
- );
public static String getEntityNameKey(String entityName) {
return "entity." + SuperLeadRope.MOD_ID + "." + entityName;
}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/register/SLPItems.java b/src/main/java/top/r3944realms/superleadrope/core/register/SLPItems.java
index f875a24..c3590f4 100644
--- a/src/main/java/top/r3944realms/superleadrope/core/register/SLPItems.java
+++ b/src/main/java/top/r3944realms/superleadrope/core/register/SLPItems.java
@@ -21,6 +21,7 @@ import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import top.r3944realms.superleadrope.SuperLeadRope;
+import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
public class SLPItems {
@@ -29,6 +30,13 @@ public class SLPItems {
"super_lead_rope",
() -> new SuperLeadRopeItem(new Item.Properties())
);
+ public static final RegistryObject- ETERNAL_POTATO =
+ ITEMS.register("eternal_potato",
+ () -> new EternalPotatoItem(
+ new Item.Properties()
+ .stacksTo(1) // 只能有一颗
+ .fireResistant() // 防火
+ ));
public static void register(IEventBus bus) {
ITEMS.register(bus);
}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/util/ImmutablePair.java b/src/main/java/top/r3944realms/superleadrope/core/util/ImmutablePair.java
new file mode 100644
index 0000000..21d153a
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/util/ImmutablePair.java
@@ -0,0 +1,47 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.util;
+
+import java.util.Objects;
+
+public record ImmutablePair(F first, S second) {
+
+ public static ImmutablePair of(F first, S second) {
+ return new ImmutablePair<>(first, second);
+ }
+
+ /**
+ * 重写equals方法
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ImmutablePair, ?> immutablePair = (ImmutablePair, ?>) o;
+
+ if (!Objects.equals(first, immutablePair.first)) return false;
+ return Objects.equals(second, immutablePair.second);
+ }
+
+ /**
+ * 重写toString方法便于调试
+ */
+ @Override
+ public String toString() {
+ return "Pair{" + first + ", " + second + "}";
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/core/util/PotatoMode.java b/src/main/java/top/r3944realms/superleadrope/core/util/PotatoMode.java
new file mode 100644
index 0000000..b3fcedd
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/util/PotatoMode.java
@@ -0,0 +1,35 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.util;
+
+public enum PotatoMode {
+ /**
+ * 单人 or 局域网主机
+ */
+ INTEGRATED,
+ /**
+ * 专用服务器
+ */
+ DEDICATED,
+ /**
+ * 远程连接的客户端
+ */
+ REMOTE_CLIENT;
+ public boolean isSynced() {
+ // Synced 模式:DEDICATED 服务端 + REMOTE_CLIENT 客户端
+ return this == DEDICATED || this == REMOTE_CLIENT;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/core/util/PotatoModeHelper.java b/src/main/java/top/r3944realms/superleadrope/core/util/PotatoModeHelper.java
new file mode 100644
index 0000000..c749fb4
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/core/util/PotatoModeHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Super Lead rope mod
+ * Copyright (C) 2025 R3944Realms
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package top.r3944realms.superleadrope.core.util;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.server.MinecraftServer;
+import net.minecraftforge.fml.loading.FMLEnvironment;
+import net.minecraftforge.server.ServerLifecycleHooks;
+
+public class PotatoModeHelper {
+
+ public static PotatoMode getCurrentMode() {
+ if (FMLEnvironment.dist.isClient()) {
+ Minecraft mc = Minecraft.getInstance();
+ if (mc.hasSingleplayerServer()) {
+ return PotatoMode.INTEGRATED;
+ }
+ return PotatoMode.REMOTE_CLIENT;
+ } else {
+ MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
+ if (server != null && server.isDedicatedServer()) {
+ return PotatoMode.DEDICATED;
+ }
+ // 如果不是专用服,那就是集成服服务端部分
+ return PotatoMode.INTEGRATED;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/SLPDataGenEvent.java b/src/main/java/top/r3944realms/superleadrope/datagen/SLPDataGenEvent.java
index 97ce933..a66efd9 100644
--- a/src/main/java/top/r3944realms/superleadrope/datagen/SLPDataGenEvent.java
+++ b/src/main/java/top/r3944realms/superleadrope/datagen/SLPDataGenEvent.java
@@ -25,16 +25,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.datagen.provider.*;
-import top.r3944realms.superleadrope.utils.lang.LanguageEnum;
+import top.r3944realms.superleadrope.util.lang.LanguageEnum;
-import java.io.IOException;
import java.util.concurrent.CompletableFuture;
@Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class SLPDataGenEvent {
static Logger logger = LoggerFactory.getLogger(SLPDataGenEvent.class);
@SubscribeEvent
- public static void gatherData(GatherDataEvent event) throws IOException {
+ public static void gatherData(GatherDataEvent event) {
logger.info("GatherDataEvent thread: {}", Thread.currentThread().getName());
CompletableFuture lookupProvider = event.getLookupProvider();
LanguageGenerator(event, LanguageEnum.English);
diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java b/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java
index b1dc790..829fad7 100644
--- a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java
+++ b/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPLangKeyValue.java
@@ -18,11 +18,12 @@ 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.item.EternalPotatoItem;
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
-import top.r3944realms.superleadrope.utils.lang.LanguageEnum;
-import top.r3944realms.superleadrope.utils.lang.ModPartEnum;
+import top.r3944realms.superleadrope.util.lang.LanguageEnum;
+import top.r3944realms.superleadrope.util.lang.ModPartEnum;
import javax.annotation.Nullable;
import java.util.function.Supplier;
@@ -34,6 +35,138 @@ public enum SLPLangKeyValue {
"Super Lead Rope", "超级拴绳", "超級拴繩","神駒羈縻索"
),
+ ITEM_ETERNAL_POTATO(
+ SLPItems.ETERNAL_POTATO, ModPartEnum.ITEM,
+ "Eternal Potato", "永恒土豆", "永恆馬鈴薯", "不滅薯", true
+ ),
+
+ 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,
+ "§7Symbol of server-wide contract, cannot be discarded",
+ "§7象征全服契约,不可丢弃",
+ "§7象徵全服契約,不可丟棄",
+ "§7象征全服契约,绝不可弃"
+ ),
+
+ EP_BIND_OWNER(EternalPotatoItem.getDescKey("bind_owner"), ModPartEnum.DESCRIPTION,
+ "§bBound Owner: §f%s",
+ "§b绑定主人: §f%s",
+ "§b綁定主人: §f%s",
+ "§b绑定主人: §f%s"
+ ),
+
+ EP_UNBOUND(EternalPotatoItem.getDescKey("unbound"), ModPartEnum.DESCRIPTION,
+ "§cUnbound",
+ "§c未绑定主人",
+ "§c未綁定主人",
+ "§c尚未绑定主人"
+ ),
+
+ 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,
+ "§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,
+ "§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,
+ "§aThe power of the Eternal Potato comforts you, it won't disappear.",
+ "§a永恒土豆的力量抚慰了你,但它不会消失。",
+ "§a永恆土豆的力量撫慰了你,但它不會消失。",
+ "§a永恒土豆之力慰心,永不消逝。"
+ ),
+
+ 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,
+ "§6Bound to you as the server-wide shared person.",
+ "§6已与你绑定,成为全服共有之人。",
+ "§6已與你綁定,成為全服共有之人。",
+ "§6已与汝绑定,为全服共享之人。"
+ ),
+
+
+
+ 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,
+ "§aAll obligations completed today!",
+ "§a今日义务已全部完成!",
+ "§a今日義務已全部完成!",
+ "§a今日责务尽矣!"
+ ),
+
+ EP_PUNISH_MSG(EternalPotatoItem.getMsgKey("punish_msg"), ModPartEnum.MESSAGE,
+ "§cYesterday obligations incomplete, punished!",
+ "§c未完成昨日义务,受到惩罚!",
+ "§c未完成昨日義務,受到懲罰!",
+ "§c昨日之责未尽,受罚矣!"
+ ),
+
+ EP_OBLIGATION_COUNTDOWN(EternalPotatoItem.getMsgKey("obligation_countdown"), ModPartEnum.MESSAGE,
+ "Punish Countdown: §a%d §fseconds remaining",
+ "惩罚倒计时: §a%d §f秒",
+ "懲罰倒計時: §a%d §f秒",
+ "受罚倒数:§a%d §f瞬"
+ ),
+
+ 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,
+ "§cYou are not the rightful owner, punished by lightning!",
+ "§c非绑定主人使用,受到闪电惩罚!",
+ "§c非綁定主人使用,受到閃電懲罰!",
+ "§c非汝所主,雷霆降身!"
+ ),
+ EP_PUNISH_NOT_OWNER_DEATH_MSG(
+ "death.attack.eternal_potato_not_owner", ModPartEnum.MESSAGE,
+ "§c%1$s was not the rightful owner, struck by lightning!",
+ "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
+ "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
+ "§c%1$s 非汝所主,雷霆降身!"
+ ),
+ EP_PUNISH_NOT_COMPETE_DEATH_MSG(
+ "death.attack.eternal_potato_not_complete", ModPartEnum.MESSAGE,
+ "§c%1$s was not the rightful owner, struck by lightning!",
+ "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
+ "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
+ "§c%1$s 非汝所主,雷霆降身!"
+ ),
SOUND_SUBTITLE_SUPER_LEAD_BREAK(
SLPSoundEvents.getSubTitleTranslateKey("lead_break"), ModPartEnum.SOUND,
"Lead Break", "拴绳断裂", "拴繩斷裂", "索絕"
@@ -54,13 +187,6 @@ public enum SLPLangKeyValue {
"Super Lead Knot", "超级拴绳结", "超級拴繩結", "神駒羈縻索結"
),
- ENTITY_SUPER_LEASH(
- SLPEntityTypes.getEntityNameKey("super_leash"), ModPartEnum.ENTITY,
- "Super Leash", "超级拴绳", "超級拴繩","神駒羈縻索"
- ),
-
-
-
;
private final Supplier> supplier;
private String key;
diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemRecipeProvider.java b/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemRecipeProvider.java
index a8c9ad9..fa766ab 100644
--- a/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemRecipeProvider.java
+++ b/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemRecipeProvider.java
@@ -17,8 +17,12 @@ package top.r3944realms.superleadrope.datagen.provider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.FinishedRecipe;
+import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeProvider;
+import net.minecraft.data.recipes.ShapedRecipeBuilder;
+import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
+import top.r3944realms.superleadrope.core.register.SLPItems;
import java.util.function.Consumer;
@@ -29,6 +33,15 @@ public class SLPItemRecipeProvider extends RecipeProvider {
@Override
protected void buildRecipes(@NotNull Consumer consumer) {
-
+ ShapedRecipeBuilder.shaped(RecipeCategory.TOOLS, SLPItems.SUPER_LEAD_ROPE.get())
+ .pattern("SL ")
+ .pattern("LE ")
+ .pattern(" I")
+ .define('S', Items.SLIME_BALL)
+ .define('L', Items.LEAD)
+ .define('E', Items.EXPERIENCE_BOTTLE)
+ .define('I', Items.STRING)
+ .unlockedBy("has_lead", has(Items.LEAD))
+ .save(consumer);
}
}
diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemTagProvider.java b/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemTagProvider.java
index 01cb025..16e4eb6 100644
--- a/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemTagProvider.java
+++ b/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPItemTagProvider.java
@@ -18,10 +18,12 @@ package top.r3944realms.superleadrope.datagen.provider;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.PackOutput;
import net.minecraft.data.tags.ItemTagsProvider;
+import net.minecraft.world.item.Items;
import net.minecraftforge.common.data.ExistingFileHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.SuperLeadRope;
+import top.r3944realms.superleadrope.content.SLPTags;
import java.util.concurrent.CompletableFuture;
@@ -35,7 +37,9 @@ public class SLPItemTagProvider extends ItemTagsProvider {
@Override
protected void addTags(HolderLookup.@NotNull Provider provider) {
-
-
+ tag(SLPTags.Items.LEAD)
+ .add(Items.LEAD)
+ .add(Items.SLIME_BALL)
+ .add(Items.STRING);
}
}
diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPLanguageProvider.java b/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPLanguageProvider.java
index 2338574..ee9310f 100644
--- a/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPLanguageProvider.java
+++ b/src/main/java/top/r3944realms/superleadrope/datagen/provider/SLPLanguageProvider.java
@@ -19,7 +19,7 @@ import net.minecraft.data.PackOutput;
import net.minecraftforge.common.data.LanguageProvider;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
-import top.r3944realms.superleadrope.utils.lang.LanguageEnum;
+import top.r3944realms.superleadrope.util.lang.LanguageEnum;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java b/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java
index 4e4cb0a..b4f81ec 100644
--- a/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java
+++ b/src/main/java/top/r3944realms/superleadrope/network/NetworkHandler.java
@@ -22,9 +22,12 @@ import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
import top.r3944realms.superleadrope.SuperLeadRope;
+import top.r3944realms.superleadrope.network.toClient.EternalPotatoSyncCapPacket;
import top.r3944realms.superleadrope.network.toClient.LeashDataSyncPacket;
+import top.r3944realms.superleadrope.network.toClient.PacketEternalPotatoRemovePacket;
import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
+
public class NetworkHandler {
private static final String PROTOCOL_VERSION = "1";
private static int cid = 0;
@@ -45,12 +48,21 @@ public class NetworkHandler {
.encoder(UpdatePlayerMovementPacket::encode)
.consumerNetworkThread(UpdatePlayerMovementPacket::handle)
.add();
+ INSTANCE.messageBuilder(EternalPotatoSyncCapPacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT)
+ .decoder(EternalPotatoSyncCapPacket::decode)
+ .encoder(EternalPotatoSyncCapPacket::encode)
+ .consumerNetworkThread(EternalPotatoSyncCapPacket::handle)
+ .add();
+ INSTANCE.messageBuilder(PacketEternalPotatoRemovePacket.class, cid++, NetworkDirection.PLAY_TO_CLIENT)
+ .decoder(PacketEternalPotatoRemovePacket::decode)
+ .encoder(PacketEternalPotatoRemovePacket::encode)
+ .consumerNetworkThread(PacketEternalPotatoRemovePacket::handle)
+ .add();
}
- public static void sendAllPlayer(MSG message){
- INSTANCE.send(PacketDistributor.ALL.noArg(), message);
- }
-
public static void sendToPlayer(MSG message, ServerPlayer player){
INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);
}
+ public static void sendToPlayer(MSG message, T entity, PacketDistributor packetDistributor){
+ INSTANCE.send(packetDistributor.with(() -> entity), message);
+ }
}
diff --git a/src/main/java/top/r3944realms/superleadrope/network/toClient/EternalPotatoSyncCapPacket.java b/src/main/java/top/r3944realms/superleadrope/network/toClient/EternalPotatoSyncCapPacket.java
new file mode 100644
index 0000000..69fb0c1
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/EternalPotatoSyncCapPacket.java
@@ -0,0 +1,95 @@
+package top.r3944realms.superleadrope.network.toClient;
+
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraftforge.network.NetworkEvent;
+import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
+import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
+import top.r3944realms.superleadrope.core.punishment.IObligationCompletion;
+import top.r3944realms.superleadrope.core.punishment.PunishmentDefinition;
+
+import java.util.UUID;
+import java.util.function.Supplier;
+
+public record EternalPotatoSyncCapPacket(
+ UUID itemUUID,
+ UUID ownerUUID,
+ String ownerName,
+ int dailyObligations,
+ int pendingPunishments,
+ int gracePunishments,
+ String lastReset,
+ String lastPunishDate,
+ PunishmentDefinition punishment,
+ IObligationCompletion completionRule
+) {
+
+ // 编码
+ public static void encode(EternalPotatoSyncCapPacket msg, FriendlyByteBuf buf) {
+ buf.writeUUID(msg.itemUUID);
+
+ buf.writeBoolean(msg.ownerUUID != null);
+ if (msg.ownerUUID != null) buf.writeUUID(msg.ownerUUID);
+
+ buf.writeUtf(msg.ownerName != null ? msg.ownerName : "");
+ buf.writeInt(msg.dailyObligations);
+ buf.writeInt(msg.pendingPunishments);
+ buf.writeInt(msg.gracePunishments);
+ buf.writeUtf(msg.lastReset != null ? msg.lastReset : "");
+ buf.writeUtf(msg.lastPunishDate != null ? msg.lastPunishDate : "");
+
+ buf.writeBoolean(msg.punishment != null);
+ if (msg.punishment != null) {
+ msg.punishment.toNetwork(buf);
+ }
+
+ buf.writeBoolean(msg.completionRule != null);
+ if (msg.completionRule != null) {
+ msg.completionRule.toNetwork(buf);
+ }
+ }
+
+ // 解码
+ public static EternalPotatoSyncCapPacket decode(FriendlyByteBuf buf) {
+ UUID itemUUID = buf.readUUID();
+ UUID ownerUUID = buf.readBoolean() ? buf.readUUID() : null;
+ String name = buf.readUtf();
+ int daily = buf.readInt();
+ int pending = buf.readInt();
+ int grace = buf.readInt();
+ String lastReset = buf.readUtf();
+ String lastPunishDate = buf.readUtf();
+
+ PunishmentDefinition punishment = null;
+ if (buf.readBoolean()) {
+ punishment = PunishmentDefinition.fromNetwork(buf);
+ }
+
+ IObligationCompletion completionRule = null;
+ if (buf.readBoolean()) {
+ completionRule = IObligationCompletion.fromNetwork(buf);
+ }
+
+ return new EternalPotatoSyncCapPacket(itemUUID, ownerUUID, name, daily, pending, grace,
+ lastReset, lastPunishDate, punishment, completionRule);
+ }
+
+ // 处理
+ public static void handle(EternalPotatoSyncCapPacket msg, Supplier ctx) {
+ ctx.get().enqueueWork(() -> {
+ // 获取全局能力实例
+ IEternalPotato cap = EternalPotatoFacade.getOrCreate(msg.itemUUID);
+ // 更新数据
+ cap.beginInit();
+ cap.setOwner(msg.ownerUUID, msg.ownerName);
+ cap.setDailyObligations(msg.dailyObligations);
+ cap.setPendingPunishments(msg.pendingPunishments);
+ cap.setGracePunishments(msg.gracePunishments);
+ cap.setLastReset(msg.lastReset);
+ cap.setLastPunishDate(msg.lastPunishDate);
+ if (msg.punishment != null) cap.setPunishment(msg.punishment);
+ if (msg.completionRule != null) cap.setCompletionRule(msg.completionRule);
+ cap.endInit();
+ });
+ ctx.get().setPacketHandled(true);
+ }
+}
\ No newline at end of file
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 090a27e..4038d93 100644
--- a/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java
+++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/LeashDataSyncPacket.java
@@ -19,9 +19,7 @@ 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;
diff --git a/src/main/java/top/r3944realms/superleadrope/network/toClient/PacketEternalPotatoRemovePacket.java b/src/main/java/top/r3944realms/superleadrope/network/toClient/PacketEternalPotatoRemovePacket.java
new file mode 100644
index 0000000..9e29774
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/PacketEternalPotatoRemovePacket.java
@@ -0,0 +1,41 @@
+/*
+ * 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.network.FriendlyByteBuf;
+import net.minecraftforge.network.NetworkEvent;
+import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
+
+import java.util.UUID;
+import java.util.function.Supplier;
+
+public record PacketEternalPotatoRemovePacket(UUID itemUUID) {
+ public static void encode(PacketEternalPotatoRemovePacket msg, FriendlyByteBuf buf) {
+ buf.writeUUID(msg.itemUUID());
+ }
+
+ public static PacketEternalPotatoRemovePacket decode(FriendlyByteBuf buf) {
+ return new PacketEternalPotatoRemovePacket(buf.readUUID());
+ }
+
+ public static void handle(PacketEternalPotatoRemovePacket msg, Supplier ctx) {
+ ctx.get().enqueueWork(() -> {
+ // 客户端收到移除请求
+ EternalPotatoFacade.remove(msg.itemUUID());
+ });
+ ctx.get().setPacketHandled(true);
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/utils/coremods/InvokerMethod.java b/src/main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java
similarity index 94%
rename from src/main/java/top/r3944realms/superleadrope/utils/coremods/InvokerMethod.java
rename to src/main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java
index a978a78..cb322d8 100644
--- a/src/main/java/top/r3944realms/superleadrope/utils/coremods/InvokerMethod.java
+++ b/src/main/java/top/r3944realms/superleadrope/util/coremods/InvokerMethod.java
@@ -13,7 +13,7 @@
* along with this program. If not, see .
*/
-package top.r3944realms.superleadrope.utils.coremods;
+package top.r3944realms.superleadrope.util.coremods;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
diff --git a/src/main/java/top/r3944realms/superleadrope/util/file/ConfigUtil.java b/src/main/java/top/r3944realms/superleadrope/util/file/ConfigUtil.java
new file mode 100644
index 0000000..29aac66
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/file/ConfigUtil.java
@@ -0,0 +1,63 @@
+/*
+ * 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.util.file;
+
+import net.minecraftforge.common.ForgeConfigSpec;
+import net.minecraftforge.fml.ModLoadingContext;
+import net.minecraftforge.fml.config.ModConfig;
+import net.minecraftforge.fml.loading.FMLPaths;
+import top.r3944realms.superleadrope.SuperLeadRope;
+
+import java.io.File;
+import java.util.Optional;
+
+public class ConfigUtil {
+ public static void createFile(String[] children) {//初始化配置文件目录
+ File configFile = new File(FMLPaths.CONFIGDIR.get().toFile(), SuperLeadRope.MOD_ID);
+ if (!configFile.exists()) {
+ boolean mkdirSuccess = configFile.mkdirs();
+ if (!mkdirSuccess) {
+ SuperLeadRope.logger.error("failed to create config directory for whimsicality");
+ throw new RuntimeException("failed to create config directory for " + SuperLeadRope.MOD_ID);
+ } else {
+ for (String child : children) {
+ File file = new File(configFile, child);
+ if (!file.exists()) {
+ boolean mkdirChildrenSuccess = file.mkdirs();
+ if (!mkdirChildrenSuccess) {
+ SuperLeadRope.logger.error("failed to create {} directory for +" + SuperLeadRope.MOD_ID, child);
+ throw new RuntimeException("failed to create " + child + " directory for" +SuperLeadRope.MOD_ID);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static void registerConfig (
+ ModLoadingContext context,
+ ModConfig.Type type,
+ ForgeConfigSpec configSpec,
+ String folderName,
+ String fileName
+ ) {
+ context.registerConfig(
+ type,
+ configSpec,
+ SuperLeadRope.MOD_ID + "/" + Optional.ofNullable(folderName).map(i-> i + "/").orElse("") + fileName + ".toml"
+ );
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/utils/lang/LanguageEnum.java b/src/main/java/top/r3944realms/superleadrope/util/lang/LanguageEnum.java
similarity index 95%
rename from src/main/java/top/r3944realms/superleadrope/utils/lang/LanguageEnum.java
rename to src/main/java/top/r3944realms/superleadrope/util/lang/LanguageEnum.java
index 1c0866a..121004f 100644
--- a/src/main/java/top/r3944realms/superleadrope/utils/lang/LanguageEnum.java
+++ b/src/main/java/top/r3944realms/superleadrope/util/lang/LanguageEnum.java
@@ -13,7 +13,7 @@
* along with this program. If not, see .
*/
-package top.r3944realms.superleadrope.utils.lang;
+package top.r3944realms.superleadrope.util.lang;
public enum LanguageEnum {
English("en_us"),
diff --git a/src/main/java/top/r3944realms/superleadrope/utils/lang/ModPartEnum.java b/src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java
similarity index 95%
rename from src/main/java/top/r3944realms/superleadrope/utils/lang/ModPartEnum.java
rename to src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java
index e41a367..b68e212 100644
--- a/src/main/java/top/r3944realms/superleadrope/utils/lang/ModPartEnum.java
+++ b/src/main/java/top/r3944realms/superleadrope/util/lang/ModPartEnum.java
@@ -13,7 +13,7 @@
* along with this program. If not, see .
*/
-package top.r3944realms.superleadrope.utils.lang;
+package top.r3944realms.superleadrope.util.lang;
public enum ModPartEnum {
DEFAULT,
diff --git a/src/main/java/top/r3944realms/superleadrope/util/model/RidingRelationship.java b/src/main/java/top/r3944realms/superleadrope/util/model/RidingRelationship.java
new file mode 100644
index 0000000..9f0a7dd
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/model/RidingRelationship.java
@@ -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 .
+ */
+
+package top.r3944realms.superleadrope.util.model;
+
+import java.util.*;
+
+/**
+ * 骑乘关系数据结构
+ */
+public class RidingRelationship {
+ private UUID entityId;
+ private UUID vehicleId;
+ private List passengers;
+
+ public RidingRelationship() {
+ this.passengers = new ArrayList<>();
+ }
+
+ public RidingRelationship(List passengers, UUID vehicleId, UUID entityId) {
+ this.passengers = passengers != null ? passengers : new ArrayList<>();
+ this.vehicleId = vehicleId;
+ this.entityId = entityId;
+ }
+
+ public UUID getEntityId() {
+ return entityId;
+ }
+
+ public void setEntityId(UUID entityId) {
+ this.entityId = entityId;
+ }
+
+ public List getPassengers() {
+ return Collections.unmodifiableList(passengers);
+ }
+
+ public void setPassengers(List passengers) {
+ this.passengers = passengers != null ? passengers : new ArrayList<>();
+ }
+
+ public void addPassenger(RidingRelationship passenger) {
+ this.passengers.add(passenger);
+ }
+
+ public UUID getVehicleId() {
+ return vehicleId;
+ }
+
+ public void setVehicleId(UUID vehicleId) {
+ this.vehicleId = vehicleId;
+ }
+
+ /**
+ * 获取所有嵌套乘客的数量
+ */
+ public int getTotalPassengerCount() {
+ int count = passengers.size();
+ for (RidingRelationship passenger : passengers) {
+ count += passenger.getTotalPassengerCount();
+ }
+ return count;
+ }
+
+ /**
+ * 检查是否包含特定实体
+ */
+ public boolean containsEntity(UUID entityId) {
+ if (Objects.equals(this.entityId, entityId)) {
+ return true;
+ }
+ for (RidingRelationship passenger : passengers) {
+ if (passenger.containsEntity(entityId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "RidingRelationship{" +
+ "entityId=" + entityId +
+ ", vehicleId=" + vehicleId +
+ ", passengers=" + passengers.size() +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java
new file mode 100644
index 0000000..21daee5
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java
@@ -0,0 +1,122 @@
+/*
+ * 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.util.riding;
+
+import net.minecraft.world.entity.Entity;
+import top.r3944realms.superleadrope.SuperLeadRope;
+import top.r3944realms.superleadrope.core.exception.RidingCycleException;
+import top.r3944realms.superleadrope.util.model.RidingRelationship;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.function.Function;
+
+public class RidingApplier {
+ /**
+ * 应用骑乘关系(在服务器端调用)
+ * @param relationship 骑乘关系
+ * @param entityProvider 实体提供器(根据UUID获取实体)
+ * @return 应用成功的实体数量
+ */
+ public static int applyRidingRelationship(RidingRelationship relationship,
+ Function entityProvider) {
+ if (relationship == null || entityProvider == null) {
+ return 0;
+ }
+
+ int appliedCount = 0;
+ Queue queue = new LinkedList<>();
+ queue.offer(relationship);
+
+ while (!queue.isEmpty()) {
+ RidingRelationship current = queue.poll();
+ UUID entityId = current.getEntityId();
+ UUID vehicleId = current.getVehicleId();
+
+ // 获取实体和载具
+ Entity entity = entityProvider.apply(entityId);
+ Entity vehicle = vehicleId != null ? entityProvider.apply(vehicleId) : null;
+
+ if (entity == null) continue;
+
+ // ---------- 白名单保护 ----------
+ if (!RidingValidator.isInWhitelist(entity.getType())) {
+ // 不在白名单,跳过本节点,但保留其乘客挂回上层
+ if (vehicle != null) {
+ // 将当前节点的乘客挂回上层载具
+ for (RidingRelationship child : current.getPassengers()) {
+ child.setVehicleId(vehicle.getUUID());
+ queue.offer(child);
+ }
+ }
+ continue; // 跳过本实体的骑乘操作
+ }
+
+ appliedCount++;
+
+ // 如果实体已经有载具,先下车
+ if (entity.getVehicle() != null) {
+ entity.stopRiding();
+ }
+
+ // 如果有指定的载具,尝试上车
+ if (vehicle != null && RidingValidator.isInWhitelist(vehicle.getType())) {
+ if (RidingValidator.wouldCreateCycle(entity, vehicle)) {
+ throw new RidingCycleException(entityId, vehicleId);
+ }
+ boolean success = entity.startRiding(vehicle, true);
+ if (!success) {
+ SuperLeadRope.logger.error("Failed to mount entity {} to vehicle {}", entityId, vehicleId);
+ }
+ }
+
+ // 处理子乘客
+ queue.addAll(current.getPassengers());
+ }
+
+ return appliedCount;
+ }
+ /**
+ * 批量应用骑乘关系(适用于世界加载时)
+ */
+ public static void applyRidingRelationships(Collection relationships,
+ Function entityProvider) {
+ if (relationships == null || relationships.isEmpty()) {
+ return;
+ }
+
+ for (RidingRelationship relationship : relationships) {
+ try {
+ applyRidingRelationship(relationship, entityProvider);
+ } catch (RidingCycleException e) {
+ // 记录循环引用错误,但继续处理其他关系
+ SuperLeadRope.logger.warn("Cyclic riding reference detected and skipped: {}", e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 从JSON字符串应用骑乘关系
+ */
+ public static int applyRidingRelationshipFromJson(String json,
+ Function entityProvider) {
+ RidingRelationship relationship = RidingSerializer.deserialize(json);
+ return applyRidingRelationship(relationship, entityProvider);
+ }
+
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingDismounts.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingDismounts.java
new file mode 100644
index 0000000..e695d15
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingDismounts.java
@@ -0,0 +1,208 @@
+/*
+ * 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.util.riding;
+
+import net.minecraft.world.entity.Entity;
+import top.r3944realms.superleadrope.util.model.RidingRelationship;
+
+import java.util.*;
+import java.util.function.Function;
+
+public class RidingDismounts {
+ /**
+ * 解除单个实体的骑乘关系
+ */
+ public static void dismountEntity(Entity entity) {
+ if (entity == null) {
+ return;
+ }
+
+ // 如果实体正在骑乘,先下车
+ if (entity.isPassenger()) {
+ entity.stopRiding();
+ }
+
+ // 让所有乘客下车
+ dismountAllPassengers(entity);
+ }
+
+ /**
+ * 解除实体及其所有乘客的骑乘关系(非递归)
+ */
+ public static void dismountAllPassengers(Entity entity) {
+ if (entity == null) {
+ return;
+ }
+
+ // 使用队列进行广度优先遍历
+ Queue queue = new LinkedList<>();
+ queue.offer(entity);
+
+ while (!queue.isEmpty()) {
+ Entity current = queue.poll();
+
+ // 让当前实体的所有乘客下车
+ List passengers = new ArrayList<>(current.getPassengers());
+ for (Entity passenger : passengers) {
+ passenger.stopRiding();
+ queue.offer(passenger);
+ }
+ }
+ }
+
+ /**
+ * 解除根实体的骑乘关系(包括从载具下车)
+ */
+ public static void dismountRootEntity(Entity entity) {
+ if (entity == null) {
+ return;
+ }
+
+ // 找到根载具
+ Entity rootVehicle = RidingFinder.findRootVehicle(entity);
+ if (rootVehicle != null) {
+ // 让根载具的所有乘客下车
+ dismountAllPassengers(rootVehicle);
+
+ // 根载具本身也下车(如果有载具的话)
+ if (rootVehicle.isPassenger()) {
+ rootVehicle.stopRiding();
+ }
+ }
+ }
+
+ /**
+ * 安全解除骑乘关系(带超时保护)
+ */
+ public static boolean safeDismountAll(Entity entity, int maxIterations) {
+ if (entity == null) {
+ return true;
+ }
+
+ int iteration = 0;
+ Queue queue = new LinkedList<>();
+ queue.offer(entity);
+
+ while (!queue.isEmpty() && iteration < maxIterations) {
+ Entity current = queue.poll();
+ iteration++;
+
+ // 让当前实体下车(如果是乘客)
+ if (current.isPassenger()) {
+ current.stopRiding();
+ }
+
+ // 处理当前实体的乘客
+ List passengers = new ArrayList<>(current.getPassengers());
+ for (Entity passenger : passengers) {
+ passenger.stopRiding();
+ queue.offer(passenger);
+ }
+ }
+
+ return queue.isEmpty(); // 如果队列为空表示全部解除成功
+ }
+
+ /**
+ * 批量解除多个实体的骑乘关系
+ */
+ public static void dismountEntities(Collection entities) {
+ if (entities == null || entities.isEmpty()) {
+ return;
+ }
+
+ Set processed = new HashSet<>();
+ Queue queue = new LinkedList<>(entities);
+
+ while (!queue.isEmpty()) {
+ Entity current = queue.poll();
+
+ if (current != null && !processed.contains(current)) {
+ processed.add(current);
+
+ // 让当前实体下车
+ if (current.isPassenger()) {
+ current.stopRiding();
+ }
+
+ // 处理乘客
+ List passengers = new ArrayList<>(current.getPassengers());
+ for (Entity passenger : passengers) {
+ if (!processed.contains(passenger)) {
+ queue.offer(passenger);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 根据骑乘关系数据结构解除骑乘
+ */
+ public static void dismountByRelationship(RidingRelationship relationship,
+ Function entityProvider) {
+ if (relationship == null || entityProvider == null) {
+ return;
+ }
+
+ // 使用栈进行深度优先遍历解除
+ Deque stack = new ArrayDeque<>();
+ stack.push(relationship);
+
+ while (!stack.isEmpty()) {
+ RidingRelationship current = stack.pop();
+
+ // 解除当前实体的骑乘
+ Entity entity = entityProvider.apply(current.getEntityId());
+ if (entity != null && entity.isPassenger()) {
+ entity.stopRiding();
+ }
+
+ // 将子乘客加入栈中(后进先出,深度优先)
+ List passengers = current.getPassengers();
+ for (int i = passengers.size() - 1; i >= 0; i--) {
+ stack.push(passengers.get(i));
+ }
+ }
+ }
+
+ /**
+ * 立即解除所有骑乘关系(强制方式)
+ */
+ public static void forceDismountAll(Entity entity) {
+ if (entity == null) {
+ return;
+ }
+
+ // 先让自己下车
+ if (entity.isPassenger()) {
+ entity.stopRiding();
+ }
+
+ // 使用广度优先让所有乘客下车
+ List allPassengers = RidingFinder.getAllPassengers(entity, false);
+ for (Entity passenger : allPassengers) {
+ if (passenger.isPassenger()) {
+ passenger.stopRiding();
+ }
+ }
+
+ // 再次检查并清理(确保完全解除)
+ if (!entity.getPassengers().isEmpty()) {
+ entity.ejectPassengers();
+ }
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingFinder.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingFinder.java
new file mode 100644
index 0000000..2ae5f5e
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingFinder.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.riding;
+
+import net.minecraft.world.entity.Entity;
+import top.r3944realms.superleadrope.util.model.RidingRelationship;
+
+import javax.annotation.Nullable;
+import java.util.*;
+import java.util.function.Function;
+
+public class RidingFinder {
+ /**
+ * 从JSON字符串应用骑乘关系
+ */
+ public static List getEntityFromRidingShip(RidingRelationship ship,
+ Function entityProvider) {
+ List ret = new ArrayList<>();
+ Queue queue = new LinkedList<>();
+ queue.offer(ship);
+ while (!queue.isEmpty()) {
+ RidingRelationship poll = queue.poll();
+ ret.add(entityProvider.apply(ship.getEntityId()));
+ List passengers = poll.getPassengers();
+ if (!passengers.isEmpty()) {
+ queue.addAll(passengers);
+ }
+ }
+ return ret;
+ }
+ /**
+ * 查找根载具
+ */
+ @Nullable
+ public static Entity findRootVehicle(@Nullable Entity entity) {
+ if (entity == null) {
+ return null;
+ }
+
+ Entity current = entity;
+ while (current.getVehicle() != null) {
+ current = current.getVehicle();
+ // 安全保护,防止意外循环
+ if (current == entity) {
+ break;
+ }
+ }
+ return current;
+ }
+
+ /**
+ * 获取所有乘客(包括嵌套乘客)
+ */
+ public static List getAllPassengers(@Nullable Entity entity) {
+ return getAllPassengers(entity, true);
+ }
+
+ /**
+ * 获取所有乘客(包括嵌套乘客)
+ */
+ public static List getAllPassengers(@Nullable Entity entity, boolean findRoot) {
+ if (entity == null) {
+ return Collections.emptyList();
+ }
+
+ Entity rootEntity = findRoot ? findRootVehicle(entity) : entity;
+ if (rootEntity == null) {
+ return Collections.emptyList();
+ }
+
+ List result = new ArrayList<>();
+ Queue queue = new LinkedList<>();
+ queue.offer(rootEntity);
+
+ while (!queue.isEmpty()) {
+ Entity current = queue.poll();
+ result.add(current); // 把当前实体加入列表
+
+ List passengers = current.getPassengers();
+ if (!passengers.isEmpty()) {
+ queue.addAll(passengers);
+ }
+ }
+
+ return Collections.unmodifiableList(result);
+ }
+
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java
new file mode 100644
index 0000000..184a9e3
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java
@@ -0,0 +1,160 @@
+/*
+ * 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.util.riding;
+
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import top.r3944realms.superleadrope.core.exception.RidingCycleException;
+import top.r3944realms.superleadrope.core.util.ImmutablePair;
+import top.r3944realms.superleadrope.util.model.RidingRelationship;
+
+import javax.annotation.Nullable;
+import java.util.*;
+import java.util.function.Function;
+
+public class RidingSaver {
+ /**
+ * 保存骑乘关系
+ */
+ public static RidingRelationship save(@Nullable Entity entity) {
+ return save(entity, true);
+ }
+
+ /**
+ * 保存骑乘关系
+ */
+ public static RidingRelationship save(@Nullable Entity entity, boolean findRoot) {
+ if (entity == null) {
+ return new RidingRelationship(Collections.emptyList(), null, null);
+ }
+
+ Entity rootEntity = findRoot ? RidingFinder.findRootVehicle(entity) : entity;
+ if (rootEntity == null) {
+ return new RidingRelationship(Collections.emptyList(), null, null);
+ }
+
+ RidingRelationship rootRelationship = new RidingRelationship();
+ rootRelationship.setEntityId(rootEntity.getUUID());
+ rootRelationship.setVehicleId(null);
+ rootRelationship.setPassengers(new ArrayList<>());
+
+ Queue> queue = new LinkedList<>();
+ queue.offer(ImmutablePair.of(rootEntity, rootRelationship));
+
+ Set processedEntities = new HashSet<>();
+ processedEntities.add(rootEntity.getUUID());
+
+ while (!queue.isEmpty()) {
+ ImmutablePair current = queue.poll();
+ Entity currentEntity = current.first();
+ RidingRelationship currentRelation = current.second();
+
+ List passengers = currentEntity.getPassengers();
+
+ if (!passengers.isEmpty()) {
+ for (Entity passenger : passengers) {
+ UUID passengerId = passenger.getUUID();
+
+ if (!processedEntities.contains(passengerId)) {
+ processedEntities.add(passengerId);
+
+ // ✅ 校验白名单
+ if (!RidingValidator.isInWhitelist(passenger.getType())) {
+ // ❌ 不在白名单,直接截断
+ continue;
+ }
+
+ // ⬇ 构建子关系
+ RidingRelationship passengerRelation = new RidingRelationship();
+ passengerRelation.setEntityId(passengerId);
+ passengerRelation.setVehicleId(currentEntity.getUUID());
+ passengerRelation.setPassengers(new ArrayList<>());
+
+ currentRelation.addPassenger(passengerRelation);
+ queue.offer(ImmutablePair.of(passenger, passengerRelation));
+ } else {
+ throw new RidingCycleException(
+ passengerId,
+ currentEntity.getUUID()
+ );
+ }
+ }
+ }
+ }
+
+ return rootRelationship;
+ }
+ /**
+ * 过滤骑乘关系,只保留白名单根节点及其合法子树
+ * 如果根节点不在白名单,则回退到第一个合法父节点
+ */
+ public static RidingRelationship filterByWhitelistRoot(RidingRelationship relationship) {
+ if (relationship == null) return null;
+
+ // 如果当前根节点在白名单,则直接处理子节点
+ if (RidingValidator.isInWhitelist(Objects.requireNonNull(getEntityType(relationship.getEntityId())))) {
+ RidingRelationship filtered = new RidingRelationship();
+ filtered.setEntityId(relationship.getEntityId());
+ filtered.setVehicleId(relationship.getVehicleId());
+ filtered.setPassengers(filterPassengers(relationship.getPassengers()));
+ return filtered;
+ } else {
+ // 根节点不在白名单,尝试找到合法的子节点作为新的根
+ for (RidingRelationship child : relationship.getPassengers()) {
+ if (RidingValidator.isInWhitelist(Objects.requireNonNull(getEntityType(child.getEntityId())))) {
+ // 设置父节点为当前节点的父(倒二叉逻辑)
+ RidingRelationship newRoot = new RidingRelationship();
+ newRoot.setEntityId(child.getEntityId());
+ newRoot.setVehicleId(relationship.getVehicleId());
+ newRoot.setPassengers(filterPassengers(child.getPassengers()));
+ return newRoot;
+ }
+ }
+ }
+
+ // 如果整个子树都不在白名单,返回空关系
+ return new RidingRelationship(new ArrayList<>(), null, null);
+ }
+
+ private static List filterPassengers(List passengers) {
+ if (passengers == null || passengers.isEmpty()) return new ArrayList<>();
+ List filtered = new ArrayList<>();
+ for (RidingRelationship passenger : passengers) {
+ RidingRelationship childFiltered = filterByWhitelistRoot(passenger);
+ if (childFiltered != null && childFiltered.getEntityId() != null) {
+ filtered.add(childFiltered);
+ }
+ }
+ return filtered;
+ }
+
+ // 传入一个实体提供器 Function,通常在服务器侧就是 level::getEntity
+ private static Function entityProvider;
+
+ public static void setEntityProvider(Function provider) {
+ entityProvider = provider;
+ }
+
+ /**
+ * 根据UUID获取EntityType
+ */
+ private static EntityType> getEntityType(UUID entityId) {
+ if (entityProvider == null) return null;
+ Entity entity = entityProvider.apply(entityId);
+ if (entity == null) return null;
+ return entity.getType();
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSerializer.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSerializer.java
new file mode 100644
index 0000000..bea0368
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingSerializer.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util.riding;
+
+import com.google.gson.Gson;
+import top.r3944realms.superleadrope.util.model.RidingRelationship;
+
+public class RidingSerializer {
+ private static final Gson GSON = new Gson();
+ /**
+ * 序列化骑乘关系
+ */
+ public static String serialize(RidingRelationship relationship) {
+ return GSON.toJson(relationship);
+ }
+
+ /**
+ * 反序列化骑乘关系
+ */
+ public static RidingRelationship deserialize(String json) {
+ return GSON.fromJson(json, RidingRelationship.class);
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java
new file mode 100644
index 0000000..a914696
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java
@@ -0,0 +1,78 @@
+/*
+ * 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.util.riding;
+
+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 {
+ /**
+ * 是否在配置白名单里
+ */
+ public static boolean isInWhitelist(EntityType> type) {
+ //noinspection deprecation
+ String key = type.builtInRegistryHolder().key().location().toString();
+ String modid = key.split(":")[0];
+ for (String entry : LeashCommonConfig.COMMON.teleportWhitelist.get()) {
+ if (entry.startsWith("#")) {
+ if (modid.equals(entry.substring(1))) {
+ return true;
+ }
+ } else if (entry.equals(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * 检查骑乘是否会产生循环引用
+ */
+ public static boolean wouldCreateCycle(Entity entity, Entity vehicle) {
+ // 如果实体就是载具本身,直接产生循环
+ if (entity == vehicle) {
+ return true;
+ }
+
+ // 检查载具是否已经是实体的乘客(直接或间接)
+ return isIndirectPassenger(vehicle, entity);
+ }
+
+ /**
+ * 检查target是否是entity的间接乘客
+ */
+ public static boolean isIndirectPassenger(Entity target, Entity entity) {
+ Queue queue = new LinkedList<>();
+ queue.offer(entity);
+
+ while (!queue.isEmpty()) {
+ Entity current = queue.poll();
+ if (current == target) {
+ return true;
+ }
+
+ // 检查当前实体的所有乘客
+ for (Entity passenger : current.getPassengers()) {
+ queue.offer(passenger);
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java b/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java
new file mode 100644
index 0000000..511ee12
--- /dev/null
+++ b/src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.riding;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+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.network.NetworkHandler;
+import top.r3944realms.superleadrope.network.toClient.UpdatePlayerMovementPacket;
+
+public class RindingLeash {
+ /**
+ * 获取乘坐链中第一个在白名单的载具,如果没有则返回null
+ */
+ @Nullable
+ public static Entity getSafeWhitelistRoot(Entity entity) {
+ if (entity == null) return null;
+
+ Entity root = RidingFinder.findRootVehicle(entity);
+ if (root == null) return null;
+
+ Entity current = root;
+ while (current != null) {
+ if (RidingValidator.isInWhitelist(current.getType())) {
+ return current; // 找到白名单载具
+ }
+ current = current.getVehicle();
+ }
+ return null; // 整条链条没有白名单载具
+ }
+
+ /**
+ * 获取最终可作用的载具,用于拴绳合力应用。
+ * 当链条中没有白名单载具时解除骑乘并返回自身
+ * 仅在拴绳合力不为零时调用
+ */
+ public static Entity getFinalEntityForLeashIfForce(Entity entity, boolean hasForce) {
+ if (!hasForce || entity == null) {
+ return entity; // 没有力时,直接返回原实体,不做处理
+ }
+
+ Entity root = RidingFinder.findRootVehicle(entity);
+ if (root == null) return entity;
+
+ Entity current = root;
+ while (current != null) {
+ if (RidingValidator.isInWhitelist(current.getType())) {
+ return current; // 找到白名单载具
+ }
+ current = current.getVehicle();
+ }
+
+ // 没有白名单载具,解除骑乘
+ RidingDismounts.dismountRootEntity(entity);
+ return entity; // 返回自身作为最终应用对象
+ }
+
+
+ /**
+ * 给动物应用拴绳力前的移动控制保护
+ */
+ public static void protectAnimalMovement(Entity entity, boolean hasLeash) {
+ if (entity instanceof Animal mob) {
+ if (hasLeash) {
+ mob.goalSelector.disableControlFlag(Goal.Flag.MOVE);
+ entity.resetFallDistance();
+ } else {
+ mob.goalSelector.enableControlFlag(Goal.Flag.MOVE);
+ }
+ }
+ }
+
+ /**
+ * 给玩家应用拴绳力前的发包处理
+ */
+ public static void applyForceToPlayer(ServerPlayer player, Vec3 force) {
+ NetworkHandler.sendToPlayer(
+ new UpdatePlayerMovementPacket(
+ UpdatePlayerMovementPacket.Operation.ADD,
+ force
+ ), player
+ );
+ }
+
+}
diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml
index 779a22e..349c1b5 100644
--- a/src/main/resources/META-INF/mods.toml
+++ b/src/main/resources/META-INF/mods.toml
@@ -27,7 +27,7 @@ displayName="${mod_name}" #mandatory
# A file name (in the root of the mod JAR) containing a logo for display
logoFile="superleadrope_logo.png" #optional
# A text field displayed in the mod UI
-#credits="" #optional
+credits="Leisuretimedock"
# A text field displayed in the mod UI
authors="${mod_authors}" #optional
# Display Test controls the display for your mod in the server connection screen
@@ -38,7 +38,15 @@ authors="${mod_authors}" #optional
# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself.
#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional)
# The description text for the mod (multi line!) (#mandatory)
-description='''${mod_description}'''
+description='''
+${mod_description}
+
+Audio Notice / 音效声明:
+This mod includes supplemental audio assets from Minecraft (Mojang Studios/Microsoft).
+They are not covered by the GPL license of this project, and remain the property of Mojang Studios/Microsoft.
+本模组包含部分来自 Minecraft (Mojang Studios/Microsoft) 的音效资源,
+这些资源不属于 GPL 授权范围,版权归 Mojang Studios/Microsoft 所有
+'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies.${mod_id}]] #optional
diff --git a/src/main/resources/assets/superleadrope/textures/item/eternal_potato.png b/src/main/resources/assets/superleadrope/textures/item/eternal_potato.png
new file mode 100644
index 0000000..bbb4988
Binary files /dev/null and b/src/main/resources/assets/superleadrope/textures/item/eternal_potato.png differ