feather:
1.渲染由客户端世界Tick进行 2.优化了些细节实现(如拴绳交互) 3.提供可调配的实体配置(用于过滤拴绳拉动和拴绳传送) fix: 1.移除旧的技术实体实现,转为Tick计算力 2.修复C/S网络同步问题
This commit is contained in:
parent
bf0fb8665d
commit
ec373ee1cf
|
|
@ -4,5 +4,6 @@
|
|||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,net.minecraft.world.entity.Entity,level" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassCanBeRecord" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
16
README.MD
16
README.MD
|
|
@ -29,4 +29,18 @@ This project is licensed under the **GNU GPL v3** open source license.
|
|||
**相同许可证** - 使用相同的GPL v3协议
|
||||
|
||||
**Full Terms:** [View LICENSE file](LICENSE.GPL3) | [GPL v3 Details](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
**完整条款:** [查看LICENSE文件](LICENSE.GPL3) | [GPL v3详情](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
**完整条款:** [查看LICENSE文件](LICENSE.GPL3) | [GPL v3详情](https://www.gnu.org/licenses/gpl-3.0.html)
|
||||
# 🎵 Audio Resources / 音效资源声明
|
||||
|
||||
This mod includes supplemental audio assets from **Minecraft (by Mojang Studios / Microsoft)**,
|
||||
which are **not** covered by the GPL license of this project.
|
||||
本模组中包含部分来自 **Minecraft(Mojang Studios / Microsoft)** 的补充音效资源,
|
||||
这些资源 **不属于** 本项目 GPL 协议授权的范围。
|
||||
|
||||
- Copyright of these audio files belongs to **Mojang Studios / Microsoft**.
|
||||
这些音效文件的版权归 **Mojang Studios / Microsoft** 所有。
|
||||
- They are provided here solely for compatibility and gameplay purposes.
|
||||
这些音效仅为兼容性和游戏功能提供。
|
||||
- The **GPL v3 license applies only to the original code and assets of this mod**,
|
||||
and does **not** apply to the bundled Minecraft audio files.
|
||||
**GPL v3 协议仅适用于本模组的原创代码与资源**,不适用于其中包含的 Minecraft 音效文件。
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
// 1.20.1 2025-08-02T18:29:50.5044172 Languages: zh_tw
|
||||
d8984a2a501a148017582d1745d24136f24840a6 assets/superleadrope/lang/zh_tw.json
|
||||
// 1.20.1 2025-09-08T23:59:30.214676 Languages: zh_tw
|
||||
01bf653cea7c3be88445a248753b4cc75e5ac45e assets/superleadrope/lang/zh_tw.json
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// 1.20.1 2025-08-02T18:29:50.4900479 Languages: zh_cn
|
||||
1d742b4bdaf09cf0bb27b838c05b37d404f9d805 assets/superleadrope/lang/zh_cn.json
|
||||
// 1.20.1 2025-09-08T23:59:30.214676 Languages: zh_cn
|
||||
6c4c5affb9dae3253eae9155ee4fb58deb5c4b92 assets/superleadrope/lang/zh_cn.json
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// 1.20.1 2025-08-02T12:46:53.5980773 Item Models: superleadrope
|
||||
// 1.20.1 2025-09-05T21:41:43.7307014 Item Models: superleadrope
|
||||
c982e91b60c03a6460d1cc7b516628cf09a28417 assets/superleadrope/models/item/broken_super_lead_rope.json
|
||||
7b072a8cc70b53d54e37e5fa72d705bd07780943 assets/superleadrope/models/item/eternal_potato.json
|
||||
4fb737a5f8f15642212aa581a02a81cf649fc36f assets/superleadrope/models/item/super_lead_rope.json
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// 1.20.1 2025-08-02T18:29:50.5014197 Languages: lzh
|
||||
b9129899dc35bdeb8acf2451acab08ce358dfd8d assets/superleadrope/lang/lzh.json
|
||||
// 1.20.1 2025-09-08T23:59:30.214676 Languages: lzh
|
||||
e7d897e78d2a100c249948780b7f1c07ad94e9b9 assets/superleadrope/lang/lzh.json
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
// 1.20.1 2025-08-02T18:29:50.496049 Recipes
|
||||
// 1.20.1 2025-09-07T22:44:37.7874227 Recipes
|
||||
0e55db960b9b165ac2a40e2ec1f417a5affa88b9 data/superleadrope/advancements/recipes/tools/super_lead_rope.json
|
||||
4777e7678c6fa5b253dc58b84730eac7d331ed5a data/superleadrope/recipes/super_lead_rope.json
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
// 1.20.1 2025-07-29T13:13:42.0601667 Tags for minecraft:item mod id superleadrope
|
||||
// 1.20.1 2025-09-07T22:23:20.2618005 Tags for minecraft:item mod id superleadrope
|
||||
d0e9808a4ffe703b1a69a402d7b88980a775eb06 data/superleadrope/tags/items/lead.json
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// 1.20.1 2025-08-02T18:29:50.4980437 Languages: en_us
|
||||
4cf775c9c9c637391296805dbd65edea025bf959 assets/superleadrope/lang/en_us.json
|
||||
// 1.20.1 2025-09-08T23:59:30.214676 Languages: en_us
|
||||
9055e381f25bb65f46b6c4489800326812fd3659 assets/superleadrope/lang/en_us.json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
{
|
||||
"death.attack.eternal_potato_not_complete": "§c%1$s was not the rightful owner, struck by lightning!",
|
||||
"death.attack.eternal_potato_not_owner": "§c%1$s was not the rightful owner, struck by lightning!",
|
||||
"entity.superleadrope.super_lead_knot": "Super Lead Knot",
|
||||
"entity.superleadrope.super_leash": "Super Leash",
|
||||
"item.eternal_potato.msg.bind_msg": "§6Bound to you as the server-wide shared person.",
|
||||
"item.eternal_potato.msg.cannot_drop": "§cThe Eternal Potato cannot be dropped! +%d punishments.",
|
||||
"item.eternal_potato.msg.obligation_countdown": "Punish Countdown: §a%d §fseconds remaining",
|
||||
"item.eternal_potato.msg.obligation_done": "§eObligation completed, remaining: §a%d§e",
|
||||
"item.eternal_potato.msg.obligation_full": "§aAll obligations completed today!",
|
||||
"item.eternal_potato.msg.obligation_info": "§e[Eternal Potato] §fThis is the server-wide shared person, remaining obligations today: §a%d§f.",
|
||||
"item.eternal_potato.msg.pickup_not_owner": "§cYou are not the rightful owner and cannot pick this up!",
|
||||
"item.eternal_potato.msg.potato_heal": "§aThe power of the Eternal Potato comforts you, it won't disappear.",
|
||||
"item.eternal_potato.msg.punish_msg": "§cYesterday obligations incomplete, punished!",
|
||||
"item.eternal_potato.msg.punish_not_owner": "§cYou are not the rightful owner, punished by lightning!",
|
||||
"item.eternal_potato.tooltip.bind_owner": "§bBound Owner: §f%s",
|
||||
"item.eternal_potato.tooltip.desc": "§7Symbol of server-wide contract, cannot be discarded",
|
||||
"item.eternal_potato.tooltip.obligation": "§7Daily obligations remaining: §a%d §c(+%d§c overdue)",
|
||||
"item.eternal_potato.tooltip.punish": "§cOverdue punishments: §4%d §7(will be applied), grace exceeded: §4%d",
|
||||
"item.eternal_potato.tooltip.title": "§6Mythical Item §7- §6Eternal Potato",
|
||||
"item.eternal_potato.tooltip.unbound": "§cUnbound",
|
||||
"item.superleadrope.eternal_potato": "Eternal Potato",
|
||||
"item.superleadrope.super_lead_rope": "Super Lead Rope",
|
||||
"sound.superleadrope.subtitle.lead_break": "Lead Break",
|
||||
"sound.superleadrope.subtitle.lead_tied": "Lead Tied",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
{
|
||||
"death.attack.eternal_potato_not_complete": "§c%1$s 非汝所主,雷霆降身!",
|
||||
"death.attack.eternal_potato_not_owner": "§c%1$s 非汝所主,雷霆降身!",
|
||||
"entity.superleadrope.super_lead_knot": "神駒羈縻索結",
|
||||
"entity.superleadrope.super_leash": "神駒羈縻索",
|
||||
"item.eternal_potato.msg.bind_msg": "§6已与汝绑定,为全服共享之人。",
|
||||
"item.eternal_potato.msg.cannot_drop": "§c永恒土豆不可丟棄,懲罰數增加%d!",
|
||||
"item.eternal_potato.msg.obligation_countdown": "受罚倒数:§a%d §f瞬",
|
||||
"item.eternal_potato.msg.obligation_done": "§e责务完成,尚余 §a%d §e次。",
|
||||
"item.eternal_potato.msg.obligation_full": "§a今日责务尽矣!",
|
||||
"item.eternal_potato.msg.obligation_info": "§e[永恒土豆] §f此为全服共享之人,今日责务尚余:§a%d§f次。",
|
||||
"item.eternal_potato.msg.pickup_not_owner": "§c非汝所主,勿取!",
|
||||
"item.eternal_potato.msg.potato_heal": "§a永恒土豆之力慰心,永不消逝。",
|
||||
"item.eternal_potato.msg.punish_msg": "§c昨日之责未尽,受罚矣!",
|
||||
"item.eternal_potato.msg.punish_not_owner": "§c非汝所主,雷霆降身!",
|
||||
"item.eternal_potato.tooltip.bind_owner": "§b绑定主人: §f%s",
|
||||
"item.eternal_potato.tooltip.desc": "§7象征全服契约,绝不可弃",
|
||||
"item.eternal_potato.tooltip.obligation": "§7今日责务尚余: §a%d §c(+%d §c逾期未尽)",
|
||||
"item.eternal_potato.tooltip.punish": "§c逾期责务尚未完成: §4%d §7(將受懲罰),超出寬限數: §4%d",
|
||||
"item.eternal_potato.tooltip.title": "§6永恒土豆 §7- §6传奇之物",
|
||||
"item.eternal_potato.tooltip.unbound": "§c尚未绑定主人",
|
||||
"item.superleadrope.eternal_potato": "不滅薯",
|
||||
"item.superleadrope.super_lead_rope": "神駒羈縻索",
|
||||
"sound.superleadrope.subtitle.lead_break": "索絕",
|
||||
"sound.superleadrope.subtitle.lead_tied": "繫索",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
{
|
||||
"death.attack.eternal_potato_not_complete": "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
|
||||
"death.attack.eternal_potato_not_owner": "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
|
||||
"entity.superleadrope.super_lead_knot": "超级拴绳结",
|
||||
"entity.superleadrope.super_leash": "超级拴绳",
|
||||
"item.eternal_potato.msg.bind_msg": "§6已与你绑定,成为全服共有之人。",
|
||||
"item.eternal_potato.msg.cannot_drop": "§c永恒土豆是不可丢弃的,惩罚数加%d!",
|
||||
"item.eternal_potato.msg.obligation_countdown": "惩罚倒计时: §a%d §f秒",
|
||||
"item.eternal_potato.msg.obligation_done": "§e义务完成一次,剩余 §a%d §e次。",
|
||||
"item.eternal_potato.msg.obligation_full": "§a今日义务已全部完成!",
|
||||
"item.eternal_potato.msg.obligation_info": "§e[永恒土豆] §f这是全服共有之人,今日义务剩余:§a%d§f次。",
|
||||
"item.eternal_potato.msg.pickup_not_owner": "§c非绑定主人无法拾取此物品!",
|
||||
"item.eternal_potato.msg.potato_heal": "§a永恒土豆的力量抚慰了你,但它不会消失。",
|
||||
"item.eternal_potato.msg.punish_msg": "§c未完成昨日义务,受到惩罚!",
|
||||
"item.eternal_potato.msg.punish_not_owner": "§c非绑定主人使用,受到闪电惩罚!",
|
||||
"item.eternal_potato.tooltip.bind_owner": "§b绑定主人: §f%s",
|
||||
"item.eternal_potato.tooltip.desc": "§7象征全服契约,不可丢弃",
|
||||
"item.eternal_potato.tooltip.obligation": "§7今日剩余义务: §a%d §c(+%d §c逾期未完成)",
|
||||
"item.eternal_potato.tooltip.punish": "§c逾期未完成责务: §4%d §7(将会受罚),超出宽限数: §4%d",
|
||||
"item.eternal_potato.tooltip.title": "§6神话物品 §7- §6永恒土豆",
|
||||
"item.eternal_potato.tooltip.unbound": "§c未绑定主人",
|
||||
"item.superleadrope.eternal_potato": "永恒土豆",
|
||||
"item.superleadrope.super_lead_rope": "超级拴绳",
|
||||
"sound.superleadrope.subtitle.lead_break": "拴绳断裂",
|
||||
"sound.superleadrope.subtitle.lead_tied": "拴绳系上",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
{
|
||||
"death.attack.eternal_potato_not_complete": "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
|
||||
"death.attack.eternal_potato_not_owner": "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
|
||||
"entity.superleadrope.super_lead_knot": "超級拴繩結",
|
||||
"entity.superleadrope.super_leash": "超級拴繩",
|
||||
"item.eternal_potato.msg.bind_msg": "§6已與你綁定,成為全服共有之人。",
|
||||
"item.eternal_potato.msg.cannot_drop": "§c永恆土豆不可丟棄,懲罰數加%d!",
|
||||
"item.eternal_potato.msg.obligation_countdown": "懲罰倒計時: §a%d §f秒",
|
||||
"item.eternal_potato.msg.obligation_done": "§e義務完成一次,剩餘 §a%d §e次。",
|
||||
"item.eternal_potato.msg.obligation_full": "§a今日義務已全部完成!",
|
||||
"item.eternal_potato.msg.obligation_info": "§e[永恒土豆] §f這是全服共有之人,今日義務剩餘:§a%d§f次。",
|
||||
"item.eternal_potato.msg.pickup_not_owner": "§c非綁定主人無法拾取此物品!",
|
||||
"item.eternal_potato.msg.potato_heal": "§a永恆土豆的力量撫慰了你,但它不會消失。",
|
||||
"item.eternal_potato.msg.punish_msg": "§c未完成昨日義務,受到懲罰!",
|
||||
"item.eternal_potato.msg.punish_not_owner": "§c非綁定主人使用,受到閃電懲罰!",
|
||||
"item.eternal_potato.tooltip.bind_owner": "§b綁定主人: §f%s",
|
||||
"item.eternal_potato.tooltip.desc": "§7象徵全服契約,不可丟棄",
|
||||
"item.eternal_potato.tooltip.obligation": "§7今日剩餘義務: §a%d §c(+%d §c逾期未完成)",
|
||||
"item.eternal_potato.tooltip.punish": "§c逾期未完成责務: §4%d §7(將會受罰),超出寬限數: §4%d",
|
||||
"item.eternal_potato.tooltip.title": "§6神話物品 §7- §6永恒土豆",
|
||||
"item.eternal_potato.tooltip.unbound": "§c未綁定主人",
|
||||
"item.superleadrope.eternal_potato": "永恆馬鈴薯",
|
||||
"item.superleadrope.super_lead_rope": "超級拴繩",
|
||||
"sound.superleadrope.subtitle.lead_break": "拴繩斷裂",
|
||||
"sound.superleadrope.subtitle.lead_tied": "拴繩係上",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "superleadrope:item/eternal_potato"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_lead": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:lead"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "superleadrope:super_lead_rope"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_lead",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"superleadrope:super_lead_rope"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "equipment",
|
||||
"key": {
|
||||
"E": {
|
||||
"item": "minecraft:experience_bottle"
|
||||
},
|
||||
"I": {
|
||||
"item": "minecraft:string"
|
||||
},
|
||||
"L": {
|
||||
"item": "minecraft:lead"
|
||||
},
|
||||
"S": {
|
||||
"item": "minecraft:slime_ball"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"SL ",
|
||||
"LE ",
|
||||
" I"
|
||||
],
|
||||
"result": {
|
||||
"item": "superleadrope:super_lead_rope"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"values": [
|
||||
"minecraft:lead",
|
||||
"minecraft:slime_ball",
|
||||
"minecraft:string"
|
||||
]
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ 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.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.entity.vehicle.Minecart;
|
||||
|
|
@ -38,22 +39,40 @@ 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.item.ItemTossEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.event.level.LevelEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
|
||||
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.EternalPotatoItem;
|
||||
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
|
||||
import top.r3944realms.superleadrope.core.leash.LeashInteractHandler;
|
||||
import top.r3944realms.superleadrope.core.leash.LeashSyncManager;
|
||||
import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
|
||||
import top.r3944realms.superleadrope.core.register.SLPItems;
|
||||
import top.r3944realms.superleadrope.core.util.PotatoMode;
|
||||
import top.r3944realms.superleadrope.core.util.PotatoModeHelper;
|
||||
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
|
||||
import top.r3944realms.superleadrope.util.model.RidingRelationship;
|
||||
import top.r3944realms.superleadrope.util.riding.RidingApplier;
|
||||
import top.r3944realms.superleadrope.util.riding.RidingDismounts;
|
||||
import top.r3944realms.superleadrope.util.riding.RidingFinder;
|
||||
import top.r3944realms.superleadrope.util.riding.RidingSaver;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class CommonEventHandler {
|
||||
|
|
@ -65,7 +84,6 @@ public class CommonEventHandler {
|
|||
if (entity.level().isClientSide) return;
|
||||
if (entity instanceof LivingEntity || entity instanceof Boat || entity instanceof Minecart) {
|
||||
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager::track);
|
||||
entity.level().addFreshEntity(new SuperLeashEntity(entity.level(), entity));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +95,23 @@ public class CommonEventHandler {
|
|||
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(LeashSyncManager::untrack);
|
||||
}
|
||||
}
|
||||
@SubscribeEvent
|
||||
public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (!(event.getEntity() instanceof ServerPlayer player)) return; // 只处理服务端
|
||||
|
||||
for (ItemStack stack : player.getInventory().items) {
|
||||
if (stack.getItem() instanceof EternalPotatoItem) {
|
||||
stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
|
||||
// 物品已绑定主人时,恢复服务端上下文
|
||||
if (cap.getOwnerUUID() != null && cap.getOwnerUUID().equals(player.getUUID())) {
|
||||
cap.bindSyncContext(player); // 只绑定服务端玩家
|
||||
cap.syncToClient(player); // 由服务端发包给客户端
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) {
|
||||
Level level = event.getLevel();
|
||||
|
|
@ -95,76 +130,183 @@ public class CommonEventHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onServerStarting(ServerStartingEvent event) {
|
||||
PotatoMode mode = PotatoModeHelper.getCurrentMode();
|
||||
EternalPotatoFacade.init(mode, true); // 服务端
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onWorldLoad(LevelEvent.Load event) {
|
||||
if (event.getLevel() instanceof ServerLevel serverLevel) {
|
||||
// 只在主世界挂载 SavedData
|
||||
if (serverLevel.dimension() == Level.OVERWORLD) {
|
||||
EternalPotatoFacade.initSavedData(serverLevel);
|
||||
RidingSaver.setEntityProvider(serverLevel::getEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWorldUnload(LevelEvent.Unload event) {
|
||||
if (event.getLevel() instanceof ServerLevel serverLevel) {
|
||||
// 只在主世界卸载时清空
|
||||
if (serverLevel.dimension() == Level.OVERWORLD) {
|
||||
EternalPotatoFacade.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 服务器关闭
|
||||
@SubscribeEvent
|
||||
public static void onServerStopping(ServerStoppingEvent event) {
|
||||
EternalPotatoFacade.clear();
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onItemDrop(ItemTossEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ItemEntity entityItem = event.getEntity();
|
||||
ItemStack stack = entityItem.getItem();
|
||||
|
||||
if (!(stack.getItem() instanceof EternalPotatoItem)) return;
|
||||
if (player.level().isClientSide) return; // 只处理服务端
|
||||
|
||||
UUID uuid = EternalPotatoItem.getOrCreateItemUUID(stack);
|
||||
IEternalPotato cap = EternalPotatoFacade.getOrCreate(uuid);
|
||||
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
cap.bindSyncContext(serverPlayer); // 服务端绑定
|
||||
}
|
||||
|
||||
EternalPotatoItem.ensureItemInInventory(player, stack);
|
||||
|
||||
// 移除地面实体
|
||||
BlockPos spawnPos = entityItem.level().getSharedSpawnPos();
|
||||
entityItem.setPos(spawnPos.getX(), -394, spawnPos.getZ());
|
||||
|
||||
// 处罚逻辑
|
||||
if (cap.getOwnerUUID() != null
|
||||
&& cap.getOwnerUUID().equals(player.getUUID())
|
||||
&& !player.getAbilities().instabuild) {
|
||||
cap.beginInit();
|
||||
cap.setPendingPunishments(cap.getPendingPunishments() + 2);
|
||||
cap.endInit();
|
||||
player.sendSystemMessage(
|
||||
net.minecraft.network.chat.Component.translatable(SLPLangKeyValue.EP_CANNOT_DROP.getKey(), 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onItemPickup(@NotNull PlayerEvent.ItemPickupEvent event) {
|
||||
Player player = event.getEntity();
|
||||
ItemStack stack = event.getStack();
|
||||
|
||||
if (player.level().isClientSide) return; // 客户端不处理
|
||||
if (!(stack.getItem() instanceof EternalPotatoItem)) return;
|
||||
if (player.getAbilities().instabuild) return;
|
||||
|
||||
UUID uuid = EternalPotatoItem.getOrCreateItemUUID(stack);
|
||||
IEternalPotato cap = EternalPotatoFacade.getOrCreate(uuid);
|
||||
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
cap.bindSyncContext(serverPlayer); // 服务端绑定
|
||||
}
|
||||
|
||||
// 如果玩家不是主人,显示提示
|
||||
if (cap.getOwnerUUID() != null && !cap.getOwnerUUID().equals(player.getUUID())) {
|
||||
player.displayClientMessage(
|
||||
net.minecraft.network.chat.Component.translatable(SLPLangKeyValue.EP_PICKUP_NOT_OWNER.getKey()),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onEntityTeleport(EntityTeleportEvent event) {
|
||||
Entity telEntity = event.getEntity();
|
||||
Vec3 targetPos = event.getTarget();
|
||||
Level level = telEntity.level();
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
List<Entity> entities = LeashDataImpl.leashableInArea(telEntity);
|
||||
// 为每个实体创建状态快照
|
||||
entities.forEach(beLeashedEntity -> {
|
||||
// 保存原来的旋转角度
|
||||
|
||||
Pose originalPose = beLeashedEntity.getPose();
|
||||
boolean originalIsSprinting = beLeashedEntity.isSprinting();
|
||||
float originalYaw = beLeashedEntity.getYRot();
|
||||
float originalPitch = beLeashedEntity.getXRot();
|
||||
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
|
||||
AtomicReference<ILeashDataCapability.LeashInfo> originalLeashInfo = new AtomicReference<>();
|
||||
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP)
|
||||
.ifPresent(cap -> {
|
||||
originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null));
|
||||
cap.removeLeash(telEntity);
|
||||
});
|
||||
if (!(level instanceof ServerLevel serverLevel)) return;
|
||||
|
||||
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)
|
||||
);
|
||||
// 获取范围内可被拴住实体
|
||||
List<Entity> entities = LeashDataImpl.leashableInArea(telEntity);
|
||||
|
||||
for (Entity beLeashedEntity : entities) {
|
||||
// --- 保存状态快照 ---
|
||||
Pose originalPose = beLeashedEntity.getPose();
|
||||
boolean originalIsSprinting = beLeashedEntity.isSprinting();
|
||||
float originalYaw = beLeashedEntity.getYRot();
|
||||
float originalPitch = beLeashedEntity.getXRot();
|
||||
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
|
||||
|
||||
AtomicReference<ILeashDataCapability.LeashInfo> originalLeashInfo = new AtomicReference<>();
|
||||
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(cap -> {
|
||||
originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null));
|
||||
cap.removeLeash(telEntity);
|
||||
});
|
||||
|
||||
// --- 保存骑乘关系(可修改列表) ---
|
||||
RidingRelationship originalRidingRelationship = RidingSaver.save(beLeashedEntity, true);
|
||||
|
||||
// --- 解除骑乘 ---
|
||||
List<Entity> allPassengers = RidingFinder.getEntityFromRidingShip(originalRidingRelationship, serverLevel::getEntity);
|
||||
RidingDismounts.dismountEntities(allPassengers);
|
||||
|
||||
// --- 传送实体及乘客 ---
|
||||
for (Entity entity : allPassengers) {
|
||||
if (entity.level() != serverLevel) {
|
||||
entity.teleportTo(serverLevel, targetPos.x, targetPos.y, targetPos.z,
|
||||
Collections.emptySet(), originalYaw, originalPitch);
|
||||
} else if (entity instanceof ServerPlayer player) {
|
||||
player.connection.teleport(targetPos.x, targetPos.y, targetPos.z,
|
||||
originalYaw, originalPitch, Collections.emptySet());
|
||||
} else {
|
||||
entity.teleportTo(serverLevel, targetPos.x, targetPos.y, targetPos.z,
|
||||
Collections.emptySet(), originalYaw, originalPitch);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 恢复状态 ---
|
||||
for (Entity entity : allPassengers) {
|
||||
entity.setDeltaMovement(originalDeltaMovement);
|
||||
entity.setSprinting(originalIsSprinting);
|
||||
entity.setPose(originalPose);
|
||||
}
|
||||
|
||||
// --- 恢复拴绳 ---
|
||||
ILeashDataCapability.LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get())
|
||||
.map(info -> info.transferHolder(telEntity))
|
||||
.orElse(ILeashDataCapability.LeashInfo.EMPTY);
|
||||
|
||||
beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(
|
||||
cap -> cap.addLeash(telEntity, leashInfo)
|
||||
);
|
||||
|
||||
// --- 重新应用骑乘关系,仅保留白名单根载具 ---
|
||||
RidingRelationship filteredRelationship = RidingSaver.filterByWhitelistRoot(originalRidingRelationship);
|
||||
RidingApplier.applyRidingRelationship(filteredRelationship, serverLevel::getEntity);
|
||||
}
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onPlayerClone(PlayerEvent.Clone event) {
|
||||
CapabilityRemainder.onPlayerClone(event);
|
||||
}
|
||||
|
||||
private static int tickCounter = 0;
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.END) {
|
||||
LeashSyncManager.forEach((i -> ((LeashDataImpl) i).checkSync()));
|
||||
tickCounter++;
|
||||
|
||||
// 每10 tick标记为脏(needsSync)
|
||||
if (tickCounter % 10 == 0) {
|
||||
LeashSyncManager.forEach(ILeashDataCapability::markForSync);
|
||||
}
|
||||
|
||||
// 定期同步检查
|
||||
LeashSyncManager.forEach(ILeashDataCapability::checkSync);
|
||||
|
||||
// 应用物理拉力/效果
|
||||
LeashSyncManager.forEach(ILeashDataCapability::applyLeashForces);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,15 +16,18 @@
|
|||
package top.r3944realms.superleadrope;
|
||||
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.r3944realms.superleadrope.config.LeashCommonConfig;
|
||||
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.network.NetworkHandler;
|
||||
|
||||
import top.r3944realms.superleadrope.util.file.ConfigUtil;
|
||||
|
||||
@Mod(value = SuperLeadRope.MOD_ID)
|
||||
public class SuperLeadRope {
|
||||
|
|
@ -36,5 +39,13 @@ public class SuperLeadRope {
|
|||
SLPEntityTypes.register(eventBus);
|
||||
SLPSoundEvents.register(eventBus);
|
||||
NetworkHandler.register();
|
||||
initialize();
|
||||
}
|
||||
public static void initialize() {
|
||||
logger.info("Initializing SuperLeadRope");
|
||||
String c = "common";
|
||||
ConfigUtil.createFile(new String[]{c});
|
||||
ModLoadingContext modLoadingContext = ModLoadingContext.get();
|
||||
ConfigUtil.registerConfig(modLoadingContext, ModConfig.Type.COMMON, LeashCommonConfig.SPEC, c, "leash");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import net.minecraft.client.renderer.item.ItemProperties;
|
|||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
|
|
@ -27,15 +28,23 @@ import top.r3944realms.superleadrope.SuperLeadRope;
|
|||
import top.r3944realms.superleadrope.client.model.SuperLeashKnotModel;
|
||||
import top.r3944realms.superleadrope.client.model.geom.SLPModelLayers;
|
||||
import top.r3944realms.superleadrope.client.renderer.entity.SuperLeashKnotRenderer;
|
||||
import top.r3944realms.superleadrope.client.renderer.entity.SuperLeashRenderer;
|
||||
import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
|
||||
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
|
||||
import top.r3944realms.superleadrope.core.register.SLPItems;
|
||||
import top.r3944realms.superleadrope.core.util.PotatoMode;
|
||||
|
||||
import static top.r3944realms.superleadrope.core.util.PotatoModeHelper.getCurrentMode;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class ClientEventHandler {
|
||||
// @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, value = Dist.CLIENT, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE)
|
||||
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, value = Dist.CLIENT, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE)
|
||||
public static class Game {
|
||||
// 未使用-注释
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedOut(ClientPlayerNetworkEvent.LoggingOut event) {
|
||||
// 清理客户端缓存数据
|
||||
EternalPotatoFacade.clear();
|
||||
}
|
||||
}
|
||||
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, value = Dist.CLIENT, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD)
|
||||
public static class Mod {
|
||||
|
|
@ -50,6 +59,8 @@ public class ClientEventHandler {
|
|||
return itemStack.getDamageValue() > 1024 - 50 ? 1.0F : 0.0F; // 损坏 → 返回 1.0(触发 override)
|
||||
}
|
||||
);
|
||||
PotatoMode mode = getCurrentMode();
|
||||
EternalPotatoFacade.init(mode, false); // 客户端
|
||||
});
|
||||
}
|
||||
@SubscribeEvent
|
||||
|
|
@ -59,7 +70,6 @@ public class ClientEventHandler {
|
|||
@SubscribeEvent
|
||||
public static void onRegisterRenderer (EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerEntityRenderer(SLPEntityTypes.SUPER_LEAD_KNOT.get(), SuperLeashKnotRenderer::new);
|
||||
event.registerEntityRenderer(SLPEntityTypes.SUPER_LEASH.get(), SuperLeashRenderer::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.client.renderer;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.event.RenderLevelStageEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.joml.Matrix4f;
|
||||
import top.r3944realms.superleadrope.SuperLeadRope;
|
||||
import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver;
|
||||
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, value = Dist.CLIENT)
|
||||
public class LeashRenderHandler {
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@SubscribeEvent
|
||||
public static void onRenderLevelStage(RenderLevelStageEvent event) {
|
||||
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_ENTITIES) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderAllCustomLeashes(event.getPoseStack(), event.getPartialTick());
|
||||
}
|
||||
|
||||
private static void renderAllCustomLeashes(PoseStack poseStack, float partialTick) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
Level level = minecraft.level;
|
||||
Entity cameraEntity = minecraft.getCameraEntity();
|
||||
|
||||
if (level == null || cameraEntity == null) return;
|
||||
|
||||
MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource();
|
||||
|
||||
for (Entity entity : level.getEntitiesOfClass(Entity.class,
|
||||
cameraEntity.getBoundingBox().inflate(50))) {
|
||||
|
||||
entity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(leashData -> {
|
||||
for (ILeashDataCapability.LeashInfo leashInfo : leashData.getAllLeashes()) {
|
||||
renderLeashFromInfo(entity, leashInfo, poseStack, bufferSource, partialTick);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void renderLeashFromInfo(Entity entity, ILeashDataCapability.LeashInfo leashInfo,
|
||||
PoseStack poseStack, MultiBufferSource bufferSource,
|
||||
float partialTick) {
|
||||
try {
|
||||
Optional<Entity> holderOpt = getHolderFromLeashInfo((ClientLevel) entity.level(), leashInfo);
|
||||
if (holderOpt.isEmpty()) return;
|
||||
|
||||
Entity holder = holderOpt.get();
|
||||
|
||||
Optional<SuperLeashRenderState> stateOpt = SuperLeashStateResolver.resolve(
|
||||
holder, entity, leashInfo, partialTick
|
||||
);
|
||||
|
||||
stateOpt.ifPresent(state -> {
|
||||
// ✅ 每个渲染都创建自己的 PoseStack,避免污染全局
|
||||
PoseStack localStack = new PoseStack();
|
||||
localStack.mulPoseMatrix(poseStack.last().pose());
|
||||
|
||||
renderSingleLeash(state, localStack, bufferSource, partialTick);
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
SuperLeadRope.logger.error("Error rendering leash: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Entity> getHolderFromLeashInfo(ClientLevel level, ILeashDataCapability.LeashInfo leashInfo) {
|
||||
// 根据LeashInfo类型获取持有者
|
||||
if (leashInfo.blockPosOpt().isPresent()) {
|
||||
// 拴绳结持有者
|
||||
BlockPos pos = leashInfo.blockPosOpt().get();
|
||||
SuperLeashKnotEntity knot = SuperLeashKnotEntity.getOrCreateKnot(level, pos);
|
||||
return Optional.of(knot);
|
||||
} else if (leashInfo.holderUUIDOpt().isPresent()) {
|
||||
// 实体持有者
|
||||
UUID holderUUID = leashInfo.holderUUIDOpt().get();
|
||||
// 在客户端,我们需要通过ID查找实体
|
||||
if (leashInfo.holderIdOpt().isPresent()) {
|
||||
int holderId = leashInfo.holderIdOpt().get();
|
||||
Entity holder = level.getEntity(holderId);
|
||||
if (holder != null) {
|
||||
return Optional.of(holder);
|
||||
}
|
||||
}
|
||||
// 备用方案:通过UUID查找(可能效率较低)
|
||||
for (Entity entity : level.entitiesForRendering()) {
|
||||
if (entity.getUUID().equals(holderUUID)) {
|
||||
return Optional.of(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static void renderSingleLeash(SuperLeashRenderState state, PoseStack poseStack,
|
||||
MultiBufferSource bufferSource, float partialTick) {
|
||||
poseStack.pushPose();
|
||||
try {
|
||||
// 相机位置平移
|
||||
Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
|
||||
poseStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
|
||||
|
||||
// 获取缓冲区
|
||||
VertexConsumer consumer = bufferSource.getBuffer(SLPRenderType.leashType());
|
||||
|
||||
// 绘制拴绳
|
||||
renderLeashAsTriangleStrip(state, consumer, poseStack, partialTick);
|
||||
|
||||
} finally {
|
||||
// ✅ 保证无论发生什么都会弹栈
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderLeashAsTriangleStrip(SuperLeashRenderState state,
|
||||
VertexConsumer consumer,
|
||||
PoseStack poseStack,
|
||||
float partialTick) {
|
||||
Vec3 start = state.startPos();
|
||||
Vec3 end = state.endPos();
|
||||
double length = start.distanceTo(end);
|
||||
|
||||
if (length < 0.001) return;
|
||||
|
||||
Vec3 direction = end.subtract(start).normalize();
|
||||
|
||||
// 计算垂直向量用于厚度
|
||||
Vec3 perpendicular = calculatePerpendicularVector(direction);
|
||||
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
int color = state.color();
|
||||
float r = ((color >> 16) & 0xFF) / 255.0f;
|
||||
float g = ((color >> 8) & 0xFF) / 255.0f;
|
||||
float b = (color & 0xFF) / 255.0f;
|
||||
float a = state.isCritical() ? 0.8f : 1.0f;
|
||||
|
||||
// 分段渲染(使用三角形带)
|
||||
final int SEGMENTS = 12;
|
||||
for (int i = 0; i <= SEGMENTS; i++) {
|
||||
float progress = (float) i / SEGMENTS;
|
||||
|
||||
// 计算当前段中心位置(包含摆动效果)
|
||||
Vec3 center = start.add(direction.scale(length * progress));
|
||||
Vec3 swingOffset = state.getSwingOffset(progress, partialTick);
|
||||
center = center.add(swingOffset);
|
||||
|
||||
// 应用张力拉伸效果
|
||||
if (state.stretchRatio() > 1.0f) {
|
||||
float stretchFactor = (float)Math.sin(progress * Math.PI) * 0.2f * (state.stretchRatio() - 1.0f);
|
||||
center = center.add(direction.scale(stretchFactor * length));
|
||||
}
|
||||
|
||||
// 计算当前厚度
|
||||
float currentThickness = state.thickness() * (1 - progress * 0.3f);
|
||||
|
||||
// 添加两个顶点形成带子
|
||||
Vec3 top = center.add(perpendicular.scale(currentThickness));
|
||||
Vec3 bottom = center.subtract(perpendicular.scale(currentThickness));
|
||||
|
||||
consumer.vertex(pose, (float)top.x, (float)top.y, (float)top.z)
|
||||
.color(r, g, b, a)
|
||||
.uv(0.0F, 0.0F) // 固定UV,随便填
|
||||
.uv2(0xF000F0) // 光照,全亮
|
||||
.normal(0.0F, 1.0F, 0.0F) // 法线,朝上
|
||||
.endVertex();
|
||||
|
||||
consumer.vertex(pose, (float)bottom.x, (float)bottom.y, (float)bottom.z)
|
||||
.color(r, g, b, a)
|
||||
.uv(1.0F, 0.0F) // 对应另一边UV
|
||||
.uv2(0xF000F0)
|
||||
.normal(0.0F, 1.0F, 0.0F)
|
||||
.endVertex();
|
||||
}
|
||||
}
|
||||
|
||||
private static Vec3 calculatePerpendicularVector(Vec3 direction) {
|
||||
// 计算一个与方向向量垂直的向量
|
||||
Vec3 perpendicular = new Vec3(-direction.z, 0, direction.x).normalize();
|
||||
if (perpendicular.lengthSqr() < 1.0E-7D) {
|
||||
// 如果方向是垂直的,使用另一个计算方法
|
||||
perpendicular = new Vec3(0, -direction.z, direction.y).normalize();
|
||||
}
|
||||
return perpendicular;
|
||||
}
|
||||
|
||||
// 距离和视野检查(性能优化)
|
||||
private static boolean shouldRenderLeash(Entity entity, Entity holder) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
Entity cameraEntity = minecraft.getCameraEntity();
|
||||
|
||||
if (cameraEntity == null) return false;
|
||||
|
||||
// 距离检查(只渲染50格内)
|
||||
double distanceToEntity = entity.distanceToSqr(cameraEntity);
|
||||
double distanceToHolder = holder.distanceToSqr(cameraEntity);
|
||||
return !(distanceToEntity > 50 * 50) || !(distanceToHolder > 50 * 50);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,35 +15,44 @@
|
|||
|
||||
package top.r3944realms.superleadrope.client.renderer;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
public class SLPRenderType extends RenderType {
|
||||
public SLPRenderType(String name, VertexFormat format, VertexFormat.Mode mode, int bufferSize, boolean affectsCrumbling, boolean sortOnUpload, Runnable setupState, Runnable clearState) {
|
||||
super(name, format, mode, bufferSize, affectsCrumbling, sortOnUpload, setupState, clearState);
|
||||
}
|
||||
static RenderType SUPER_LEASH;
|
||||
public static final VertexFormat POSITION_COLOR_LIGHTMAP_NORMAL;
|
||||
public static RenderType leashType() {
|
||||
return SUPER_LEASH;
|
||||
}
|
||||
static {
|
||||
POSITION_COLOR_LIGHTMAP_NORMAL = new VertexFormat(
|
||||
ImmutableMap.<String, VertexFormatElement>builder()
|
||||
.put("Position", DefaultVertexFormat.ELEMENT_POSITION)
|
||||
.put("Color", DefaultVertexFormat.ELEMENT_COLOR)
|
||||
.put("UV2", DefaultVertexFormat.ELEMENT_UV2) // 光照
|
||||
.put("Normal", DefaultVertexFormat.ELEMENT_NORMAL) // 法线
|
||||
.put("Padding", DefaultVertexFormat.ELEMENT_PADDING)
|
||||
.build()
|
||||
);
|
||||
SUPER_LEASH = RenderType.create("super_leash",
|
||||
DefaultVertexFormat.POSITION_COLOR_LIGHTMAP,
|
||||
VertexFormat.Mode.LINE_STRIP,
|
||||
POSITION_COLOR_LIGHTMAP_NORMAL, // 注意:需要 NORMAL(法线)信息!
|
||||
VertexFormat.Mode.TRIANGLE_STRIP, // 改为三角形带模式
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
false, // not used for crumbling
|
||||
false, // sortOnUpload
|
||||
CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_LINES_SHADER)
|
||||
.setLineState(new LineStateShard(OptionalDouble.empty()))
|
||||
.setLayeringState(VIEW_OFFSET_Z_LAYERING)
|
||||
.setShaderState(RENDERTYPE_LEASH_SHADER) // 使用实体着色器
|
||||
.setTextureState(NO_TEXTURE) // 无纹理
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setOutputState(ITEM_ENTITY_TARGET)
|
||||
.setWriteMaskState(COLOR_DEPTH_WRITE)
|
||||
.setCullState(NO_CULL)
|
||||
.createCompositeState(false));
|
||||
.setLightmapState(LIGHTMAP) // 启用光照
|
||||
.setCullState(NO_CULL) // 双面渲染
|
||||
.createCompositeState(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.client.renderer.entity;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
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.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.superleadrope.client.renderer.SLPRenderType;
|
||||
import top.r3944realms.superleadrope.client.renderer.resolver.SuperLeashStateResolver;
|
||||
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.entity.SuperLeashEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SuperLeashRenderer extends EntityRenderer<SuperLeashEntity> {
|
||||
// 渲染参数
|
||||
private static final int SEGMENTS = 24; // 绳子分段数
|
||||
private static final float BASE_THICKNESS = 0.08f; // 基础线宽
|
||||
private static final float CRITICAL_PULSE_SPEED = 0.2f; // 临界状态脉冲速度
|
||||
|
||||
public SuperLeashRenderer(EntityRendererProvider.Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(@NotNull SuperLeashEntity entity, float entityYaw, float partialTick,
|
||||
@NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight) {
|
||||
Entity controlled = entity.getControlled();
|
||||
if (!LeashDataImpl.isLeashable(controlled)) return;
|
||||
|
||||
// 创建渲染状态
|
||||
List<SuperLeashRenderState> superLeashRenderStates = SuperLeashStateResolver.resolveAll(
|
||||
controlled,
|
||||
(LeashDataImpl) controlled.getCapability(CapabilityHandler.LEASH_DATA_CAP).orElseThrow(NullPointerException::new),
|
||||
partialTick
|
||||
);
|
||||
|
||||
// 渲染绳子
|
||||
superLeashRenderStates.forEach(s -> renderLeash(s, poseStack, buffer, packedLight, partialTick));
|
||||
}
|
||||
|
||||
private void renderLeash(SuperLeashRenderState state, PoseStack poseStack,
|
||||
MultiBufferSource buffer, int packedLight, float partialTicks) {
|
||||
poseStack.pushPose();
|
||||
|
||||
VertexConsumer vertexBuilder = buffer.getBuffer(SLPRenderType.leashType());
|
||||
Vec3 start = state.startPos();
|
||||
Vec3 end = state.endPos();
|
||||
|
||||
// 计算绳子总长度和方向
|
||||
double length = start.distanceTo(end);
|
||||
Vec3 direction = end.subtract(start).normalize();
|
||||
|
||||
// 临界状态闪烁效果
|
||||
float alpha = state.isCritical() ?
|
||||
(Mth.sin(partialTicks * CRITICAL_PULSE_SPEED) * 0.5f + 0.5f) :
|
||||
1.0f;
|
||||
|
||||
// 分段渲染绳子
|
||||
for (int i = 0; i <= SEGMENTS; i++) {
|
||||
float progress = (float)i / SEGMENTS;
|
||||
|
||||
// 计算当前段位置和摆动偏移
|
||||
Vec3 segmentPos = start.add(direction.scale(length * progress));
|
||||
Vec3 swingOffset = state.getSwingOffset(progress, partialTicks);
|
||||
|
||||
// 应用张力拉伸效果
|
||||
if (state.stretchRatio() > 1.0f) {
|
||||
float stretchFactor = (float)Math.sin(progress * Math.PI) * 0.2f * (state.stretchRatio() - 1.0f);
|
||||
segmentPos = segmentPos.add(direction.scale(stretchFactor * length));
|
||||
}
|
||||
|
||||
// 添加摆动偏移
|
||||
segmentPos = segmentPos.add(swingOffset);
|
||||
|
||||
// 计算颜色渐变 (从正常色到紧张色)
|
||||
int color = interpolateColor(
|
||||
SuperLeashRenderState.COLOR_NORMAL,
|
||||
state.isCritical() ? SuperLeashRenderState.COLOR_CRITICAL : SuperLeashRenderState.COLOR_TENSION,
|
||||
state.tension());
|
||||
|
||||
// 添加顶点
|
||||
addVertex(vertexBuilder, poseStack, segmentPos,
|
||||
state.thickness() * (1 - progress * 0.3f), // 末端稍细
|
||||
color, alpha, packedLight);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
private void addVertex(VertexConsumer builder, PoseStack poseStack, Vec3 pos,
|
||||
float thickness, int color, float alpha, int packedLight) {
|
||||
// 解包颜色
|
||||
float r = ((color >> 16) & 0xFF) / 255.0f;
|
||||
float g = ((color >> 8) & 0xFF) / 255.0f;
|
||||
float b = (color & 0xFF) / 255.0f;
|
||||
|
||||
// 转换到相机空间
|
||||
Vec3 viewPos = pos.subtract(this.entityRenderDispatcher.camera.getPosition());
|
||||
|
||||
// 添加顶点 (使用LINE_STRIP渲染)
|
||||
builder.vertex(poseStack.last().pose(), (float)viewPos.x, (float)viewPos.y, (float)viewPos.z)
|
||||
.color(r, g, b, alpha)
|
||||
.uv2(packedLight)
|
||||
.overlayCoords(OverlayTexture.NO_OVERLAY)
|
||||
.normal(poseStack.last().normal(), 0, 1, 0)
|
||||
.endVertex();
|
||||
}
|
||||
|
||||
private int interpolateColor(int color1, int color2, float ratio) {
|
||||
int r1 = (color1 >> 16) & 0xFF;
|
||||
int g1 = (color1 >> 8) & 0xFF;
|
||||
int b1 = color1 & 0xFF;
|
||||
|
||||
int r2 = (color2 >> 16) & 0xFF;
|
||||
int g2 = (color2 >> 8) & 0xFF;
|
||||
int b2 = color2 & 0xFF;
|
||||
|
||||
int r = (int)(r1 + (r2 - r1) * ratio);
|
||||
int g = (int)(g1 + (g2 - g1) * ratio);
|
||||
int b = (int)(b1 + (b2 - b1) * ratio);
|
||||
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ResourceLocation getTextureLocation(@NotNull SuperLeashEntity entity) {
|
||||
return new ResourceLocation("unknown"); // 使用自定义渲染类型,不需要纹理
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ import net.minecraft.world.level.Level;
|
|||
import net.minecraft.world.phys.Vec3;
|
||||
import top.r3944realms.superleadrope.SuperLeadRope;
|
||||
import top.r3944realms.superleadrope.client.renderer.state.SuperLeashRenderState;
|
||||
import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
|
||||
|
||||
|
|
@ -173,7 +173,6 @@ public class SuperLeashStateResolver {
|
|||
/**
|
||||
* 获取实体所有拴绳的渲染状态
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<SuperLeashRenderState> resolveAll(
|
||||
Entity leashedEntity,
|
||||
LeashDataImpl leashData,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.config;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LeashCommonConfig {
|
||||
public static ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
|
||||
public static ForgeConfigSpec SPEC;
|
||||
public static final Common COMMON;
|
||||
static {
|
||||
BUILDER.comment("Leash Common Config");
|
||||
COMMON = new Common(BUILDER);
|
||||
SPEC = BUILDER.build();
|
||||
}
|
||||
public static class Common {
|
||||
public final ForgeConfigSpec.ConfigValue<List<? extends String>> teleportWhitelist;
|
||||
|
||||
public Common(ForgeConfigSpec.Builder builder) {
|
||||
builder.push("leash");
|
||||
|
||||
teleportWhitelist = builder
|
||||
.comment("Entity teleport whitelist.",
|
||||
"Use `#modid` to allow teleporting to all entities from a mod.",
|
||||
"Use `modid:entity_name` to allow teleporting to a specific entity.")
|
||||
.defineListAllowEmpty(
|
||||
List.of("allowedTeleportEntities"),
|
||||
List.of("#minecraft", "modernlife:bicycle", "modernlife:motorboat"),
|
||||
o -> o instanceof String s && isValidFormat(s)
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
private static boolean isValidFormat(String s) {
|
||||
if (s.startsWith("#")) {
|
||||
return s.length() > 1 && s.substring(1).matches("[a-z0-9_]+");
|
||||
}
|
||||
return s.matches("[a-z0-9_]+:[a-z0-9_/]+");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content;
|
||||
|
||||
import net.minecraft.world.damagesource.DamageEffects;
|
||||
import net.minecraft.world.damagesource.DamageScaling;
|
||||
import net.minecraft.world.damagesource.DamageType;
|
||||
import net.minecraft.world.damagesource.DeathMessageType;
|
||||
|
||||
public class SLPDamageTypes {
|
||||
// 非绑定主人使用惩罚
|
||||
public static final DamageType ETERNAL_POTATO_NOT_OWNER =
|
||||
new DamageType(
|
||||
"eternal_potato_not_owner",
|
||||
DamageScaling.ALWAYS,
|
||||
0f,
|
||||
DamageEffects.HURT,
|
||||
DeathMessageType.DEFAULT
|
||||
);
|
||||
public static final DamageType ETERNAL_POTATO_NOT_COMPLETE =
|
||||
new DamageType(
|
||||
"eternal_potato_not_complete",
|
||||
DamageScaling.ALWAYS,
|
||||
0f,
|
||||
DamageEffects.HURT,
|
||||
DeathMessageType.DEFAULT
|
||||
);
|
||||
}
|
||||
|
|
@ -13,24 +13,21 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.datagen.data;
|
||||
package top.r3944realms.superleadrope.content;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import top.r3944realms.superleadrope.SuperLeadRope;
|
||||
|
||||
public final class SLPItemTags {
|
||||
|
||||
|
||||
private SLPItemTags() {
|
||||
public class SLPTags {
|
||||
public static class Items {
|
||||
public static final TagKey<Item> LEAD = tag("lead");
|
||||
private static TagKey<net.minecraft.world.item.Item> tag(String name)
|
||||
{
|
||||
return TagKey.create(Registries.ITEM, new ResourceLocation(SuperLeadRope.MOD_ID, name));
|
||||
}
|
||||
}
|
||||
|
||||
private static TagKey<Item> bind(String name) {
|
||||
return TagKey.create(Registries.ITEM, new ResourceLocation(name));
|
||||
}
|
||||
|
||||
public static TagKey<Item> create(final ResourceLocation name) {
|
||||
return TagKey.create(Registries.ITEM, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content;
|
||||
|
||||
import net.minecraft.world.item.Tier;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public enum SLPToolTier implements Tier {
|
||||
STRING(24, 1.0F, 0.0F, 15, () -> Ingredient.of(SLPTags.Items.LEAD))
|
||||
;
|
||||
private final int uses;
|
||||
private final float speed;
|
||||
private final float attackDamageBonus;
|
||||
private final int enchantmentValue;
|
||||
private final Supplier<Ingredient> repairIngredient;
|
||||
SLPToolTier(int uses, float speed, float attackDamageBonus, int enchantmentValue, Supplier<Ingredient> repairIngredient) {
|
||||
this.uses = uses;
|
||||
this.speed = speed;
|
||||
this.attackDamageBonus = attackDamageBonus;
|
||||
this.enchantmentValue = enchantmentValue;
|
||||
this.repairIngredient = repairIngredient;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getUses() {
|
||||
return this.uses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSpeed() {
|
||||
return this.speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAttackDamageBonus() {
|
||||
return this.attackDamageBonus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLevel() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getEnchantmentValue() {
|
||||
return this.enchantmentValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Ingredient getRepairIngredient() {
|
||||
return this.repairIngredient.get();
|
||||
}
|
||||
}
|
||||
|
|
@ -16,18 +16,25 @@
|
|||
package top.r3944realms.superleadrope.content.capability;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.capabilities.CapabilityToken;
|
||||
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
import top.r3944realms.superleadrope.content.capability.provider.EternalPotatoProvider;
|
||||
import top.r3944realms.superleadrope.content.capability.provider.LeashDataProvider;
|
||||
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
|
||||
|
||||
public class CapabilityHandler {
|
||||
public static final Capability<ILeashDataCapability> LEASH_DATA_CAP = CapabilityManager.get(new CapabilityToken<>(){});
|
||||
|
||||
public static Capability<IEternalPotato> ETERNAL_POTATO_CAP = CapabilityManager.get(new CapabilityToken<>() {});
|
||||
public static void registerCapability(RegisterCapabilitiesEvent event) {
|
||||
event.register(ILeashDataCapability.class);
|
||||
event.register(IEternalPotato.class);
|
||||
}
|
||||
|
||||
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
|
||||
|
|
@ -36,6 +43,9 @@ public class CapabilityHandler {
|
|||
(LeashDataImpl.isLeashable(entity))//只对活体 船 矿车添加CAP
|
||||
) {
|
||||
event.addCapability(LeashDataProvider.LEASH_DATA_REL, new LeashDataProvider(entity));
|
||||
} else if (object instanceof ItemStack stack && stack.getItem() instanceof EternalPotatoItem) {
|
||||
event.addCapability(EternalPotatoProvider.ETERNAL_POTATO_DATA_REL, new EternalPotatoProvider(stack));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content.capability.impi;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
|
||||
import top.r3944realms.superleadrope.core.punishment.IObligationCompletion;
|
||||
import top.r3944realms.superleadrope.core.punishment.PunishmentDefinition;
|
||||
import top.r3944realms.superleadrope.core.register.ObligationCompletionRegistry;
|
||||
import top.r3944realms.superleadrope.network.NetworkHandler;
|
||||
import top.r3944realms.superleadrope.network.toClient.EternalPotatoSyncCapPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EternalPotatoImpl implements IEternalPotato {
|
||||
private ItemStackSync itemStackSync;
|
||||
private Player boundPlayer;
|
||||
private UUID ownerUUID;
|
||||
private volatile UUID itemUUID;
|
||||
private String ownerName;
|
||||
private boolean initializing = false;
|
||||
private boolean dirtyDuringInit = false;
|
||||
/**
|
||||
* 每日任务次数
|
||||
*/
|
||||
private int dailyObligations = 0;
|
||||
private int gracePeriod = 0;
|
||||
/**
|
||||
* 累计未完成触发的惩罚次数
|
||||
*/
|
||||
private int pendingPunishments = 0;
|
||||
private String lastReset = "";
|
||||
private String lastPunishDate = ""; // yyyy-MM-dd
|
||||
private PunishmentDefinition punishment = PunishmentDefinition.DEFAULT;
|
||||
|
||||
private IObligationCompletion completionRule = IObligationCompletion.NONE;
|
||||
// NBT Keys
|
||||
public static final String TAG_LAST_PUNISH_DATE = "last_punish_date";
|
||||
public static final String TAG_PENDING_PUNISHMENTS = "pending_punishments";
|
||||
public static final String TAG_GRACE_PERIOD = "grace_period";
|
||||
public static final String TAG_OWNER_UUID = "owner_uuid";
|
||||
public static final String TAG_ITEM_UUID = "item_uuid";
|
||||
public static final String TAG_OWNER_NAME = "owner_name";
|
||||
public static final String TAG_OBLIGATIONS = "obligations";
|
||||
public static final String TAG_LAST_RESET = "last_reset";
|
||||
public static final String TAG_PUNISHMENT_TYPE = "punishment_type";
|
||||
public static final String TAG_PUNISHMENT_STRENGTH = "punishment_strength";
|
||||
public static final String TAG_PUNISHMENT_AFFECT_OTHERS = "punishment_affect_others";
|
||||
public static final String TAG_COMPLETION_ID = "completion_id";
|
||||
|
||||
@Override
|
||||
public void bindItemStackSync(ItemStackSync callback) {
|
||||
this.itemStackSync = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始初始化(禁止立即发包)
|
||||
*/
|
||||
public void beginInit() {
|
||||
initializing = true;
|
||||
dirtyDuringInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束初始化(如果期间有改动,就统一发一次包)
|
||||
*/
|
||||
public void endInit() {
|
||||
initializing = false;
|
||||
if (dirtyDuringInit) {
|
||||
// 同步到玩家
|
||||
if (boundPlayer != null) {
|
||||
syncToClient(boundPlayer);
|
||||
}
|
||||
|
||||
// 写回 ItemStack NBT
|
||||
if (itemStackSync != null) {
|
||||
itemStackSync.markDirtyForItem();
|
||||
}
|
||||
|
||||
// 重置脏标记
|
||||
dirtyDuringInit = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void bindSyncContext(Player player) {
|
||||
if (isNetworkSyncNonRequired()) return;
|
||||
this.boundPlayer = player;
|
||||
}
|
||||
public Player getBoundPlayer() {
|
||||
return boundPlayer;
|
||||
}
|
||||
|
||||
private void markDirty() {
|
||||
if (initializing) {
|
||||
dirtyDuringInit = true;
|
||||
} else {
|
||||
if (boundPlayer != null) {
|
||||
syncToClient(boundPlayer);
|
||||
}
|
||||
if (itemStackSync != null) {
|
||||
itemStackSync.markDirtyForItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemUUID(UUID uuid) {
|
||||
if (uuid == null) throw new IllegalArgumentException("Item UUID cannot be null");
|
||||
this.itemUUID = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getItemUUID() {
|
||||
return itemUUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(UUID uuid, String name) {
|
||||
if (uuid == null) throw new IllegalArgumentException("Owner UUID cannot be null");
|
||||
if (name == null) name = "Unknown";
|
||||
this.ownerUUID = uuid;
|
||||
this.ownerName = name;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getOwnerUUID() {
|
||||
return ownerUUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerName() {
|
||||
return ownerName != null ? ownerName : "Unknown";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDailyObligations(int count) {
|
||||
if (count < 0) count = 0;
|
||||
this.dailyObligations = count;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDailyObligations() {
|
||||
return dailyObligations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingPunishments() {
|
||||
return pendingPunishments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingPunishments(int count) {
|
||||
if (count < 0) count = 0;
|
||||
this.pendingPunishments = count;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGracePunishments() {
|
||||
return gracePeriod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGracePunishments(int count) {
|
||||
if (count < 0) count = 0;
|
||||
this.gracePeriod = count;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncToClient(Player player) {
|
||||
if (isNetworkSyncNonRequired() || player.level().isClientSide) return;
|
||||
|
||||
EternalPotatoSyncCapPacket packet =
|
||||
new EternalPotatoSyncCapPacket(
|
||||
itemUUID, ownerUUID, ownerName,
|
||||
dailyObligations,pendingPunishments, gracePeriod,
|
||||
lastReset, lastPunishDate, punishment, completionRule
|
||||
);
|
||||
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
|
||||
if (server != null) {
|
||||
for (ServerPlayer p : server.getPlayerList().getPlayers()) {
|
||||
NetworkHandler.sendToPlayer(packet, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastReset(String date) {
|
||||
this.lastReset = date;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastReset() { return lastReset; }
|
||||
|
||||
@Override
|
||||
public PunishmentDefinition getPunishment() { return punishment; }
|
||||
|
||||
@Override
|
||||
public void setPunishment(PunishmentDefinition definition) {
|
||||
this.punishment = definition != null ? definition : PunishmentDefinition.DEFAULT;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastPunishDate(String date) {
|
||||
this.lastPunishDate = date;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastPunishDate() {
|
||||
return lastPunishDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IObligationCompletion getCompletionRule() {
|
||||
return completionRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompletionRule(IObligationCompletion completion) {
|
||||
this.completionRule = completion != null ? completion : IObligationCompletion.NONE;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeNBT() {
|
||||
CompoundTag tag = new CompoundTag();
|
||||
if (ownerUUID != null) tag.putUUID(TAG_OWNER_UUID, ownerUUID);
|
||||
if (itemUUID != null) tag.putUUID(TAG_ITEM_UUID, itemUUID);
|
||||
if (ownerName != null) tag.putString(TAG_OWNER_NAME, ownerName);
|
||||
tag.putInt(TAG_OBLIGATIONS, dailyObligations);
|
||||
tag.putInt(TAG_GRACE_PERIOD, gracePeriod);
|
||||
if (lastReset != null) tag.putString(TAG_LAST_RESET, lastReset);
|
||||
|
||||
// 序列化 PunishmentDefinition record
|
||||
if (punishment != null && punishment != PunishmentDefinition.DEFAULT) {
|
||||
tag.putString(TAG_PUNISHMENT_TYPE, punishment.type().name());
|
||||
tag.putFloat(TAG_PUNISHMENT_STRENGTH, punishment.strength());
|
||||
tag.putBoolean(TAG_PUNISHMENT_AFFECT_OTHERS, punishment.affectOthers());
|
||||
}
|
||||
|
||||
// 序列化任务完成规则
|
||||
if (completionRule != null && completionRule != IObligationCompletion.NONE) {
|
||||
tag.putString(TAG_COMPLETION_ID, completionRule.getId());
|
||||
}
|
||||
if (lastPunishDate != null) tag.putString(TAG_LAST_PUNISH_DATE, lastPunishDate);
|
||||
tag.putInt(TAG_PENDING_PUNISHMENTS, pendingPunishments);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(CompoundTag nbt) {
|
||||
if (nbt == null) return; // 卫语句防空
|
||||
if (nbt.hasUUID(TAG_OWNER_UUID)) ownerUUID = nbt.getUUID(TAG_OWNER_UUID);
|
||||
if (nbt.contains(TAG_ITEM_UUID)) itemUUID = nbt.getUUID(TAG_ITEM_UUID);
|
||||
if (nbt.contains(TAG_OWNER_NAME)) ownerName = nbt.getString(TAG_OWNER_NAME);
|
||||
if (nbt.contains(TAG_GRACE_PERIOD)) gracePeriod = Math.max(0, nbt.getInt(TAG_GRACE_PERIOD));
|
||||
if (nbt.contains(TAG_OBLIGATIONS)) dailyObligations = Math.max(0, nbt.getInt(TAG_OBLIGATIONS));
|
||||
if (nbt.contains(TAG_PENDING_PUNISHMENTS)) pendingPunishments = Math.max(0, nbt.getInt(TAG_PENDING_PUNISHMENTS));
|
||||
if (nbt.contains(TAG_LAST_RESET)) lastReset = nbt.getString(TAG_LAST_RESET);
|
||||
if (nbt.contains(TAG_LAST_PUNISH_DATE)) lastPunishDate = nbt.getString(TAG_LAST_PUNISH_DATE);
|
||||
|
||||
// PunishmentDefinition 反序列化
|
||||
if (nbt.contains(TAG_PUNISHMENT_TYPE) && nbt.contains(TAG_PUNISHMENT_STRENGTH) && nbt.contains(TAG_PUNISHMENT_AFFECT_OTHERS)) {
|
||||
try {
|
||||
PunishmentDefinition.Type type = PunishmentDefinition.Type.valueOf(nbt.getString(TAG_PUNISHMENT_TYPE));
|
||||
float strength = nbt.getFloat(TAG_PUNISHMENT_STRENGTH);
|
||||
boolean affectOthers = nbt.getBoolean(TAG_PUNISHMENT_AFFECT_OTHERS);
|
||||
punishment = new PunishmentDefinition(type, strength, affectOthers);
|
||||
} catch (IllegalArgumentException e) {
|
||||
punishment = PunishmentDefinition.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
// 完成规则反序列化
|
||||
if (nbt.contains(TAG_COMPLETION_ID)) {
|
||||
String id = nbt.getString(TAG_COMPLETION_ID);
|
||||
IObligationCompletion rule = ObligationCompletionRegistry.byId(id);
|
||||
completionRule = rule != null ? rule : IObligationCompletion.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -13,20 +13,18 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content.capability;
|
||||
package top.r3944realms.superleadrope.content.capability.impi;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
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.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.animal.horse.Llama;
|
||||
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;
|
||||
|
|
@ -37,17 +35,19 @@ import net.minecraftforge.network.PacketDistributor;
|
|||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.superleadrope.SuperLeadRope;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
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 top.r3944realms.superleadrope.util.riding.RindingLeash;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 预期行为
|
||||
|
|
@ -90,48 +90,49 @@ public class LeashDataImpl implements ILeashDataCapability {
|
|||
private final Map<UUID, LeashInfo> leashHolders = new ConcurrentHashMap<>();
|
||||
// 引入解决 绳结不保存导致第二进入持有者不存在的问题
|
||||
private final Map<BlockPos, LeashInfo> leashKnots = new ConcurrentHashMap<>();
|
||||
private CompoundTag lastSyncedData = new CompoundTag();
|
||||
// private CompoundTag lastSyncedData = new CompoundTag();
|
||||
|
||||
public LeashDataImpl(Entity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
private void markForSync() {
|
||||
@Override
|
||||
public void markForSync() {
|
||||
if (!entity.level().isClientSide) {
|
||||
needsSync = true;
|
||||
immediateSync();
|
||||
immediateSync(); // 立即同步一次
|
||||
}
|
||||
}
|
||||
private void immediateSync() {
|
||||
NetworkHandler.INSTANCE.send(
|
||||
PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity),
|
||||
new LeashDataSyncPacket(entity.getId(), serializeNBT())
|
||||
|
||||
/** 立即同步,无视时间间隔 */
|
||||
@Override
|
||||
public void immediateSync() {
|
||||
syncNow();
|
||||
}
|
||||
|
||||
/** 定期调用,每 tick 或每几秒检测 */
|
||||
@Override
|
||||
public void checkSync() {
|
||||
if (!needsSync || entity.level().isClientSide) return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
// 每隔 2 秒同步一次
|
||||
if (now - lastSyncTime > 2000) {
|
||||
syncNow();
|
||||
}
|
||||
}
|
||||
|
||||
/** 内部统一同步方法,避免重复逻辑 */
|
||||
private void syncNow() {
|
||||
CompoundTag currentData = serializeNBT();
|
||||
|
||||
NetworkHandler.sendToPlayer(
|
||||
new LeashDataSyncPacket(entity.getId(), currentData),
|
||||
entity,
|
||||
PacketDistributor.TRACKING_ENTITY_AND_SELF
|
||||
);
|
||||
lastSyncTime = System.currentTimeMillis();
|
||||
needsSync = false;
|
||||
}
|
||||
public void sync() {
|
||||
if (!needsSync || entity.level().isClientSide) return;
|
||||
|
||||
CompoundTag currentData = serializeNBT();
|
||||
if (!currentData.equals(lastSyncedData)) {
|
||||
NetworkHandler.INSTANCE.send(
|
||||
PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity),
|
||||
new LeashDataSyncPacket(entity.getId(), currentData)
|
||||
);
|
||||
lastSyncTime = System.currentTimeMillis();
|
||||
lastSyncedData = currentData;
|
||||
needsSync = false;
|
||||
}
|
||||
}
|
||||
// 定期同步检查
|
||||
public void checkSync() {
|
||||
if (!needsSync) return;
|
||||
|
||||
// 距离上次同步超过0.1秒才同步
|
||||
if (System.currentTimeMillis() - lastSyncTime > 100) {
|
||||
sync();
|
||||
}
|
||||
}
|
||||
|
||||
// 添加拴绳(支持自定义最大长度和弹性距离)
|
||||
@Override
|
||||
|
|
@ -366,34 +367,25 @@ public class LeashDataImpl implements ILeashDataCapability {
|
|||
}
|
||||
}
|
||||
|
||||
// 应用合力
|
||||
if (!combinedForce.equals(Vec3.ZERO)) {
|
||||
if(entity instanceof ServerPlayer serverPlayer) { //对于玩家发包交给客户端处理移动
|
||||
NetworkHandler.sendToPlayer(
|
||||
new UpdatePlayerMovementPacket(
|
||||
UpdatePlayerMovementPacket.Operation.ADD,
|
||||
combinedForce
|
||||
), serverPlayer
|
||||
);
|
||||
return; //后面的逻辑肯定与该分支无关,直接返回
|
||||
boolean hasForce = !combinedForce.equals(Vec3.ZERO);
|
||||
Entity finalApplyEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
|
||||
if (hasForce) {
|
||||
|
||||
if (finalApplyEntity instanceof ServerPlayer player) {
|
||||
RindingLeash.applyForceToPlayer(player, combinedForce);
|
||||
return;
|
||||
} else {
|
||||
entity.setDeltaMovement(entity.getDeltaMovement().add(combinedForce));
|
||||
entity.hurtMarked = true;
|
||||
finalApplyEntity.setDeltaMovement(finalApplyEntity.getDeltaMovement().add(combinedForce));
|
||||
finalApplyEntity.hurtMarked = true;
|
||||
}
|
||||
|
||||
// 有拴绳时:禁用移动控制
|
||||
if (entity instanceof Animal mob) {
|
||||
mob.goalSelector.disableControlFlag(Goal.Flag.MOVE);
|
||||
entity.resetFallDistance();
|
||||
}
|
||||
RindingLeash.protectAnimalMovement(finalApplyEntity, true);
|
||||
} else {
|
||||
// 无拴绳时:恢复移动控制
|
||||
if (entity instanceof Animal mob) {
|
||||
mob.goalSelector.enableControlFlag(Goal.Flag.MOVE);
|
||||
}
|
||||
RindingLeash.protectAnimalMovement(finalApplyEntity, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为UUID拴绳计算力
|
||||
*/
|
||||
|
|
@ -403,7 +395,8 @@ public class LeashDataImpl implements ILeashDataCapability {
|
|||
if (uuidHolder != null) {
|
||||
return calculateLeashForce(uuidHolder, entry);
|
||||
} else {
|
||||
SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found.", uuid);
|
||||
SuperLeadRope.logger.error("Could not apply leash forces for {}, because it is not found(it will be removed from list).", uuid);
|
||||
leashHolders.remove(uuid);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -608,9 +601,10 @@ public class LeashDataImpl implements ILeashDataCapability {
|
|||
// 获取所有拴绳信息
|
||||
@Override
|
||||
public Collection<LeashInfo> getAllLeashes() {
|
||||
Collection<LeashInfo> values = leashHolders.values();
|
||||
values.addAll(leashKnots.values());
|
||||
return values;
|
||||
return Stream.concat(
|
||||
leashHolders.values().stream(),
|
||||
leashKnots.values().stream()
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -785,20 +779,12 @@ public class LeashDataImpl implements ILeashDataCapability {
|
|||
}
|
||||
public static boolean isLeashHolder(@NotNull Entity pEntity, UUID pHolderUUID) {
|
||||
AtomicBoolean isTarget = new AtomicBoolean(false);
|
||||
pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> {
|
||||
if (i instanceof LeashDataImpl li) {
|
||||
isTarget.set(li.isLeashedBy(pHolderUUID));
|
||||
}
|
||||
});
|
||||
pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> isTarget.set(i.isLeashedBy(pHolderUUID)));
|
||||
return isTarget.get();
|
||||
}
|
||||
public static boolean isLeashHolder(@NotNull Entity pEntity, BlockPos pKnotPos) {
|
||||
AtomicBoolean isTarget = new AtomicBoolean(false);
|
||||
pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> {
|
||||
if (i instanceof LeashDataImpl li) {
|
||||
isTarget.set(li.isLeashedBy(pKnotPos));
|
||||
}
|
||||
});
|
||||
pEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> isTarget.set(i.isLeashedBy(pKnotPos)));
|
||||
return isTarget.get();
|
||||
}
|
||||
public static boolean isLeashHolder(@NotNull Entity pEntity, Entity pTestHolder) {
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content.capability.inter;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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;
|
||||
|
||||
public interface IEternalPotato {
|
||||
interface ItemStackSync {
|
||||
void markDirtyForItem();
|
||||
}
|
||||
void bindItemStackSync(ItemStackSync callback);
|
||||
void beginInit();
|
||||
void endInit();
|
||||
|
||||
void setItemUUID(UUID uuid);
|
||||
UUID getItemUUID();
|
||||
void setOwner(UUID uuid, String name);
|
||||
UUID getOwnerUUID();
|
||||
String getOwnerName();
|
||||
|
||||
void setDailyObligations(int count);
|
||||
int getDailyObligations();
|
||||
|
||||
int getPendingPunishments();
|
||||
void setPendingPunishments(int count);
|
||||
/**
|
||||
* 计算今日总任务次数 = 未完成惩罚次数 + 当日剩余次数
|
||||
*/
|
||||
default int getFinalTaskCount() {
|
||||
return getPendingPunishments() + getDailyObligations();
|
||||
}
|
||||
default boolean isNetworkSyncNonRequired() {
|
||||
return !EternalPotatoFacade.isServer();
|
||||
}
|
||||
default boolean isGlobalEffect() {
|
||||
// 根据当前惩罚、任务规则决定
|
||||
return getPunishment() != null && getPunishment().affectOthers();
|
||||
}
|
||||
/**
|
||||
* 是否在宽限期内
|
||||
*/
|
||||
default boolean isWithinGracePeriod() {
|
||||
return getPendingPunishments() <= getGracePunishments();
|
||||
}
|
||||
/**
|
||||
* 宽限惩罚数
|
||||
*/
|
||||
int getGracePunishments();
|
||||
void setGracePunishments(int count);
|
||||
void syncToClient(Player player);
|
||||
void bindSyncContext(Player player);
|
||||
Player getBoundPlayer();
|
||||
void setLastReset(String date);
|
||||
String getLastReset();
|
||||
|
||||
PunishmentDefinition getPunishment();
|
||||
void setPunishment(PunishmentDefinition definition);
|
||||
|
||||
void setLastPunishDate(String date);
|
||||
String getLastPunishDate();
|
||||
|
||||
IObligationCompletion getCompletionRule();
|
||||
void setCompletionRule(IObligationCompletion completion);
|
||||
|
||||
CompoundTag serializeNBT();
|
||||
void deserializeNBT(CompoundTag nbt);
|
||||
}
|
||||
|
|
@ -70,7 +70,9 @@ public interface ILeashDataCapability extends INBTSerializable<CompoundTag> {
|
|||
|
||||
boolean canBeLeashed();
|
||||
boolean canBeAttachedTo(Entity pEntity);
|
||||
|
||||
void markForSync();
|
||||
void immediateSync();
|
||||
void checkSync();
|
||||
|
||||
record LeashInfo(
|
||||
Optional<BlockPos> blockPosOpt,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content.capability.provider;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.superleadrope.SuperLeadRope;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
|
||||
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
|
||||
import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EternalPotatoProvider implements ICapabilitySerializable<CompoundTag> {
|
||||
|
||||
public static final ResourceLocation ETERNAL_POTATO_DATA_REL =
|
||||
new ResourceLocation(SuperLeadRope.MOD_ID, "eternal_potato_data");
|
||||
|
||||
private final UUID uuid;
|
||||
private volatile IEternalPotato instance;
|
||||
private final LazyOptional<IEternalPotato> optional;
|
||||
|
||||
// 新增引用 ItemStack
|
||||
private final ItemStack stack;
|
||||
|
||||
public EternalPotatoProvider(ItemStack stack) {
|
||||
this.stack = stack;
|
||||
this.uuid = EternalPotatoItem.getOrCreateItemUUID(stack);
|
||||
this.instance = null;
|
||||
this.optional = LazyOptional.of(this::resolveInstance);
|
||||
}
|
||||
|
||||
private IEternalPotato resolveInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (this) {
|
||||
if (instance == null) {
|
||||
instance = EternalPotatoFacade.getOrCreate(uuid);
|
||||
|
||||
// 绑定写回回调,只绑定一次
|
||||
instance.bindItemStackSync(() -> {
|
||||
if (!stack.isEmpty()) {
|
||||
stack.setTag(instance.serializeNBT());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
|
||||
return CapabilityHandler.ETERNAL_POTATO_CAP.orEmpty(cap, optional);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeNBT() {
|
||||
return resolveInstance() != null ? resolveInstance().serializeNBT() : new CompoundTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(CompoundTag compoundTag) {
|
||||
if (compoundTag == null) return;
|
||||
|
||||
// 只在首次 attach 时初始化 UUID
|
||||
if (instance == null) {
|
||||
UUID loaded = compoundTag.hasUUID("item_uuid")
|
||||
? compoundTag.getUUID("item_uuid")
|
||||
: UUID.randomUUID();
|
||||
|
||||
IEternalPotato shared = EternalPotatoFacade.getOrCreate(loaded);
|
||||
|
||||
// 不再调用 shared.deserializeNBT(compoundTag),由 Manager 统一负责持久化
|
||||
this.instance = shared;
|
||||
|
||||
// 绑定写回回调
|
||||
shared.bindItemStackSync(() -> {
|
||||
if (!stack.isEmpty()) {
|
||||
stack.setTag(shared.serializeNBT());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content.capability;
|
||||
package top.r3944realms.superleadrope.content.capability.provider;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
|
@ -25,6 +25,8 @@ import net.minecraftforge.common.util.LazyOptional;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import top.r3944realms.superleadrope.SuperLeadRope;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
|
||||
public class LeashDataProvider implements ICapabilitySerializable<CompoundTag> {
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.content.entity;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
|
||||
|
||||
public class SuperLeashEntity extends Entity {
|
||||
private Entity controlled;
|
||||
public SuperLeashEntity(EntityType<? extends SuperLeashEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
}
|
||||
public SuperLeashEntity(Level level, Entity controlled) {
|
||||
super(SLPEntityTypes.SUPER_LEASH.get(), level);
|
||||
if (!LeashDataImpl.isLeashable(controlled)) {
|
||||
throw new IllegalArgumentException("Controlled entity " + controlled.getClass().getName() + "is not a leashable entity");
|
||||
}
|
||||
this.controlled = controlled;
|
||||
}
|
||||
|
||||
public Entity getControlled() {
|
||||
return controlled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (controlled == null || !controlled.isAlive()) {
|
||||
this.discard();
|
||||
}
|
||||
this.setPos(controlled.getX(), controlled.getY(), controlled.getZ());
|
||||
if(!level().isClientSide) controlled.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(ILeashDataCapability::applyLeashForces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kill() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readAdditionalSaveData(@NotNull CompoundTag compoundTag) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAdditionalSaveData(@NotNull CompoundTag compoundTag) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
package top.r3944realms.superleadrope.content.entity;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
|
|
@ -25,6 +27,7 @@ import net.minecraft.world.entity.Entity;
|
|||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
|
@ -32,7 +35,7 @@ import net.minecraft.world.level.gameevent.GameEvent;
|
|||
import net.minecraft.world.phys.AABB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.core.register.SLPEntityTypes;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
|
@ -71,8 +74,12 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
|
|||
return false;
|
||||
} else {
|
||||
if (!this.isRemoved() && !this.level().isClientSide) {
|
||||
if (source.getEntity() instanceof ServerPlayer player && player.gameMode.getGameModeForPlayer() == GameType.ADVENTURE) {
|
||||
return false;
|
||||
}
|
||||
this.kill();
|
||||
this.markHurt();
|
||||
this.playSound(SoundEvents.LEASH_KNOT_BREAK);
|
||||
List<Entity> entities = LeashDataImpl.leashableInArea(this.level(), pos.getCenter(), entity -> LeashDataImpl.isLeashHolder(entity, this));
|
||||
entities.forEach(entity -> entity
|
||||
.getCapability(CapabilityHandler.LEASH_DATA_CAP)
|
||||
|
|
@ -169,9 +176,11 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
|
|||
}
|
||||
AtomicBoolean isRemoveLeashKnot = new AtomicBoolean(false);
|
||||
if (!isTransferLeash.get()) {
|
||||
this.discard();
|
||||
if (player.getAbilities().instabuild) {
|
||||
entities.forEach(
|
||||
if (((ServerPlayer) player).gameMode.getGameModeForPlayer() != GameType.ADVENTURE) {
|
||||
this.playSound(SoundEvents.LEASH_KNOT_BREAK);
|
||||
this.discard();
|
||||
List<Entity> entities1 = LeashDataImpl.leashableInArea(this);
|
||||
entities1.forEach(
|
||||
entity -> entity
|
||||
.getCapability(CapabilityHandler.LEASH_DATA_CAP)
|
||||
.ifPresent(iLeashDataCapability -> {
|
||||
|
|
@ -180,6 +189,8 @@ public class SuperLeashKnotEntity extends LeashFenceKnotEntity {
|
|||
}
|
||||
));
|
||||
}
|
||||
} else {
|
||||
this.playSound(SoundEvents.LEASH_KNOT_PLACE);
|
||||
}
|
||||
if (isTransferLeash.get() || isRemoveLeashKnot.get()) {
|
||||
this.gameEvent(GameEvent.BLOCK_ATTACH, player);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package top.r3944realms.superleadrope.content.item;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.superleadrope.content.SLPDamageTypes;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.provider.EternalPotatoProvider;
|
||||
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EternalPotatoItem extends Item {
|
||||
|
||||
private static final String ITEM_UUID_TAG = "item_uuid";
|
||||
|
||||
public EternalPotatoItem(Properties properties) {
|
||||
super(properties.rarity(Rarity.EPIC).stacksTo(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICapabilityProvider initCapabilities(ItemStack stack, CompoundTag nbt) {
|
||||
return new EternalPotatoProvider(stack);
|
||||
}
|
||||
|
||||
/** 获取或生成 ItemStack 的唯一 UUID */
|
||||
public static UUID getOrCreateItemUUID(ItemStack stack) {
|
||||
CompoundTag tag = stack.getOrCreateTag();
|
||||
if (tag.hasUUID(ITEM_UUID_TAG)) {
|
||||
return tag.getUUID(ITEM_UUID_TAG);
|
||||
} else {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
tag.putUUID(ITEM_UUID_TAG, uuid);
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
/** 使用契约物品完成义务 */
|
||||
@Override
|
||||
public @NotNull InteractionResult useOn(UseOnContext context) {
|
||||
Player player = context.getPlayer();
|
||||
if (player == null || context.getLevel().isClientSide) return InteractionResult.PASS;
|
||||
|
||||
ItemStack stack = context.getItemInHand();
|
||||
|
||||
stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
|
||||
if (cap.getCompletionRule().isCompleted((ServerPlayer) player, stack)) {
|
||||
cap.getCompletionRule().onCompleted((ServerPlayer) player, stack);
|
||||
cap.beginInit();
|
||||
cap.setPendingPunishments(Math.max(0, cap.getPendingPunishments() - 1));
|
||||
cap.endInit();
|
||||
}
|
||||
});
|
||||
|
||||
return InteractionResult.sidedSuccess(player.level().isClientSide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(@NotNull ItemStack stack, Level world, @NotNull List<Component> tooltip, @NotNull TooltipFlag flag) {
|
||||
stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
|
||||
tooltip.add(Component.translatable(SLPLangKeyValue.EP_TOOLTIP_TITLE.getKey()));
|
||||
tooltip.add(Component.translatable(SLPLangKeyValue.EP_DESC_TOOLTIP.getKey()));
|
||||
if (cap.getOwnerUUID() != null) {
|
||||
tooltip.add(Component.translatable(SLPLangKeyValue.EP_OBLIGATION_TOOLTIP.getKey(), cap.getFinalTaskCount(), cap.getPendingPunishments()));
|
||||
if (cap.getPendingPunishments() > cap.getGracePunishments())
|
||||
tooltip.add(Component.translatable(SLPLangKeyValue.EP_PUNISH_TOOLTIP.getKey(), cap.getPendingPunishments(), cap.getGracePunishments()));
|
||||
tooltip.add(Component.translatable(SLPLangKeyValue.EP_BIND_OWNER.getKey(), cap.getOwnerName()));
|
||||
} else {
|
||||
tooltip.add(Component.translatable(SLPLangKeyValue.EP_UNBOUND.getKey()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InteractionResultHolder<ItemStack> use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) {
|
||||
ItemStack stack = player.getItemInHand(hand);
|
||||
|
||||
if (level.isClientSide) {
|
||||
// 客户端只返回结果,不修改任何数据
|
||||
return InteractionResultHolder.consume(stack);
|
||||
}
|
||||
|
||||
// 服务端逻辑
|
||||
if (player.getCooldowns().isOnCooldown(this)) return InteractionResultHolder.fail(stack);
|
||||
|
||||
|
||||
stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
|
||||
// 首次绑定
|
||||
if (cap.getOwnerUUID() == null) {
|
||||
cap.beginInit();
|
||||
if (player instanceof ServerPlayer serverPlayer) cap.bindSyncContext(serverPlayer);
|
||||
cap.setOwner(player.getUUID(), player.getDisplayName().getString());
|
||||
cap.setDailyObligations(99);
|
||||
cap.setPendingPunishments(0);
|
||||
cap.endInit();
|
||||
player.displayClientMessage(Component.translatable(SLPLangKeyValue.EP_BIND_MSG.getKey()), true);
|
||||
cap.syncToClient(player); // 只在服务端发包
|
||||
return;
|
||||
}
|
||||
|
||||
// 已绑定 → 只在服务端更新
|
||||
if (cap.getBoundPlayer() == null || !cap.getBoundPlayer().getUUID().equals(player.getUUID())) {
|
||||
if (player instanceof ServerPlayer serverPlayer) cap.bindSyncContext(serverPlayer);
|
||||
assert player instanceof ServerPlayer;
|
||||
cap.syncToClient(player);
|
||||
}
|
||||
|
||||
boolean isOwner = cap.getOwnerUUID().equals(player.getUUID());
|
||||
if (!isOwner) {
|
||||
cap.getPunishment().execute(
|
||||
(ServerPlayer) player,
|
||||
new DamageSource(Holder.direct(SLPDamageTypes.ETERNAL_POTATO_NOT_OWNER), player),
|
||||
Component.translatable(SLPLangKeyValue.EP_PUNISH_NOT_OWNER.getKey())
|
||||
);
|
||||
} else {
|
||||
player.startUsingItem(hand);
|
||||
player.heal(50.0F);
|
||||
player.getFoodData().eat(18, 8);
|
||||
player.displayClientMessage(Component.translatable(SLPLangKeyValue.EP_OBLIGATION_INFO.getKey(), cap.getDailyObligations()), true);
|
||||
player.getCooldowns().addCooldown(this, 20 * 3);
|
||||
}
|
||||
});
|
||||
|
||||
return InteractionResultHolder.sidedSuccess(stack, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getUseDuration(@NotNull ItemStack stack) {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UseAnim getUseAnimation(@NotNull ItemStack stack) {
|
||||
return UseAnim.EAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack finishUsingItem(@NotNull ItemStack stack, @NotNull Level level, @NotNull LivingEntity entity) {
|
||||
if (entity instanceof Player player && !level.isClientSide) {
|
||||
player.heal(50.0F);
|
||||
player.getFoodData().eat(18, 8);
|
||||
player.displayClientMessage(Component.translatable(SLPLangKeyValue.EP_POTATO_HEAL.getKey()), true);
|
||||
player.getCooldowns().addCooldown(this, 20 * 3);
|
||||
}
|
||||
return stack.copy();
|
||||
}
|
||||
|
||||
public static String getDescKey(String name) {
|
||||
return "item.eternal_potato.tooltip." + name;
|
||||
}
|
||||
|
||||
public static String getMsgKey(String name) {
|
||||
return "item.eternal_potato.msg." + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeHurtBy(@NotNull DamageSource source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canFitInsideContainerItems() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFoil(@NotNull ItemStack stack) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inventoryTick(@NotNull ItemStack stack, @NotNull Level level, @NotNull Entity entity, int slot, boolean selected) {
|
||||
super.inventoryTick(stack, level, entity, slot, selected);
|
||||
|
||||
// 延迟初始化客户端逻辑
|
||||
// if (level.isClientSide) { // 只在客户端执行
|
||||
// // 直接通过 ItemStack 的 UUID 调用 Manager
|
||||
// UUID uuid = EternalPotatoItem.getOrCreateItemUUID(stack);
|
||||
// EternalPotatoFacade.getOrCreate(uuid);
|
||||
// }
|
||||
}
|
||||
|
||||
public static void ensureItemInInventory(Player player, ItemStack stack) {
|
||||
if (player.getInventory().add(stack)) return;
|
||||
|
||||
List<ItemStack> nonEmptySlots = new ArrayList<>();
|
||||
for (ItemStack s : player.getInventory().items) {
|
||||
if (!s.isEmpty() && s != stack) nonEmptySlots.add(s);
|
||||
}
|
||||
|
||||
if (!nonEmptySlots.isEmpty()) {
|
||||
ItemStack toDrop = nonEmptySlots.get(player.getRandom().nextInt(nonEmptySlots.size()));
|
||||
player.drop(toDrop.copy(), true, false);
|
||||
player.getInventory().removeItem(toDrop);
|
||||
|
||||
player.getInventory().add(stack);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack currentHelmet = player.getInventory().armor.get(3);
|
||||
if (!currentHelmet.isEmpty()) {
|
||||
Objects.requireNonNull(player.drop(currentHelmet.copy(), true, false))
|
||||
.setPos(player.getX(), player.getY() + 1, player.getZ());
|
||||
}
|
||||
player.getInventory().armor.set(3, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeDepleted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import net.minecraft.world.InteractionResultHolder;
|
|||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.LeadItem;
|
||||
import net.minecraft.world.item.TieredItem;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
|
@ -30,8 +30,9 @@ import net.minecraft.world.level.gameevent.GameEvent;
|
|||
import net.minecraftforge.common.extensions.IForgeItem;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.superleadrope.content.SLPToolTier;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
|
||||
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
|
||||
|
|
@ -50,7 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
// 6. 不可消耗的(但是有耐久,需要修复, 通过消耗原版拴绳恢复,每次多重绑定就会消耗1点耐久)
|
||||
// 实现拴生物,在生物的interact方法里去写相关逻辑
|
||||
// (尝试0 mixin 实现 加强拴绳逻辑)
|
||||
public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
|
||||
public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
|
||||
// 配置常量
|
||||
// 【手动调节,可以通过附魔获取更远抛掷和抛掷距离 - x1.3】//TODO:将可抛掷实现留到下次编写
|
||||
// 可以做个大于一定距离时远距离使用时抛出拴绳的实体,击中生物才栓中的
|
||||
|
|
@ -59,12 +60,13 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
|
|||
|
||||
|
||||
public SuperLeadRopeItem(@NotNull Properties pProperties) {
|
||||
super(
|
||||
super(SLPToolTier.STRING,
|
||||
pProperties
|
||||
.durability(1024)
|
||||
.setNoRepair()
|
||||
);
|
||||
}
|
||||
|
||||
//通过按键 可抛掷启用/关闭实现(不会影响use逻辑)
|
||||
@Override
|
||||
public @NotNull InteractionResultHolder<ItemStack> use(@NotNull Level pLevel, @NotNull Player pPlayer, @NotNull InteractionHand pUsedHand) {
|
||||
|
|
@ -122,17 +124,11 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
|
|||
for(Entity e : list) {
|
||||
AtomicBoolean canBeAttachedTo = new AtomicBoolean(false);
|
||||
LazyOptional<ILeashDataCapability> iLeashDataCapability = e.getCapability(CapabilityHandler.LEASH_DATA_CAP);
|
||||
iLeashDataCapability.ifPresent(i -> {
|
||||
if (i instanceof LeashDataImpl li) {
|
||||
canBeAttachedTo.set(li.canBeAttachedTo(newHolder));
|
||||
}
|
||||
});
|
||||
iLeashDataCapability.ifPresent(i -> canBeAttachedTo.set(i.canBeAttachedTo(newHolder)));
|
||||
if(canBeAttachedTo.get()) {//canBeAttachedTo
|
||||
iLeashDataCapability.ifPresent(i -> {
|
||||
if(i instanceof LeashDataImpl li) {
|
||||
li.transferLeash(player.getUUID(), newHolder, leashStack);
|
||||
i.transferLeash(player.getUUID(), newHolder, leashStack);
|
||||
isSuccess.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -179,10 +175,8 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
|
|||
|
||||
SuperLeashKnotEntity finalKnot1 = knot;
|
||||
player.getCapability(CapabilityHandler.LEASH_DATA_CAP).ifPresent(i -> {
|
||||
if(i instanceof LeashDataImpl li) {
|
||||
li.addLeash(finalKnot1, leashStack, 8D);
|
||||
i.addLeash(finalKnot1, leashStack, 8D);
|
||||
isSuccess.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
|
@ -195,17 +189,11 @@ public class SuperLeadRopeItem extends LeadItem implements IForgeItem {
|
|||
AtomicBoolean canBeAttachedTo = new AtomicBoolean(false);
|
||||
SuperLeashKnotEntity finalKnot = knot;
|
||||
LazyOptional<ILeashDataCapability> iLeashDataCapability = e.getCapability(CapabilityHandler.LEASH_DATA_CAP);
|
||||
iLeashDataCapability.ifPresent(i -> {
|
||||
if (i instanceof LeashDataImpl li) {
|
||||
canBeAttachedTo.set(li.canBeAttachedTo(finalKnot));
|
||||
}
|
||||
});
|
||||
iLeashDataCapability.ifPresent(i -> canBeAttachedTo.set(i.canBeAttachedTo(finalKnot)));
|
||||
if(canBeAttachedTo.get()) {//canBeAttachedTo
|
||||
iLeashDataCapability.ifPresent(i -> {
|
||||
if(i instanceof LeashDataImpl li) {
|
||||
li.transferLeash(uuid, finalKnot);
|
||||
i.transferLeash(uuid, finalKnot);
|
||||
isSuccess.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.core.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RidingCycleException extends IllegalStateException {
|
||||
private final UUID entityId;
|
||||
private final UUID vehicleId;
|
||||
|
||||
public RidingCycleException(UUID entityId, UUID vehicleId) {
|
||||
super(String.format("Cyclic riding reference detected. " +
|
||||
"Entity %s cannot be added as passenger to vehicle %s " +
|
||||
"as it would create a circular dependency.",
|
||||
entityId, vehicleId));
|
||||
this.entityId = entityId;
|
||||
this.vehicleId = vehicleId;
|
||||
}
|
||||
|
||||
public UUID getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
public UUID getVehicleId() {
|
||||
return vehicleId;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ import net.minecraft.world.level.gameevent.GameEvent;
|
|||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
|
||||
import top.r3944realms.superleadrope.content.capability.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.ILeashDataCapability;
|
||||
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
|
||||
import top.r3944realms.superleadrope.core.register.SLPItems;
|
||||
|
|
@ -39,6 +39,13 @@ public class LeashInteractHandler {
|
|||
|
||||
// ===== 卫语句 =====
|
||||
if (level.isClientSide) {
|
||||
if (hand == InteractionHand.MAIN_HAND &&
|
||||
(player.getItemInHand(InteractionHand.MAIN_HAND).is(SLPItems.SUPER_LEAD_ROPE.get()) ||
|
||||
player.getItemInHand(InteractionHand.OFF_HAND).is(SLPItems.SUPER_LEAD_ROPE.get()))
|
||||
) {
|
||||
event.setCanceled(true);
|
||||
event.setCancellationResult(InteractionResult.CONSUME);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (hand == InteractionHand.OFF_HAND) {
|
||||
|
|
@ -69,12 +76,14 @@ public class LeashInteractHandler {
|
|||
event.setCancellationResult(InteractionResult.CONSUME);
|
||||
}
|
||||
} else {
|
||||
if(LeashDataImpl.isLeashHolder(target, player)) {
|
||||
if (LeashDataImpl.isLeashHolder(target, player)) {
|
||||
LeashCap.ifPresent(
|
||||
iLeashDataCapability -> iLeashDataCapability.removeLeash(player.getUUID())
|
||||
);
|
||||
target.gameEvent(GameEvent.ENTITY_INTERACT, player);
|
||||
target.playSound(SLPSoundEvents.LEAD_UNTIED.get());
|
||||
event.setCanceled(true);
|
||||
event.setCancellationResult(InteractionResult.CONSUME);
|
||||
return;
|
||||
}
|
||||
ItemStack itemStack;
|
||||
|
|
@ -92,8 +101,10 @@ public class LeashInteractHandler {
|
|||
boolean success = iLeashDataCapability.addLeash(player, itemStack, 12);
|
||||
if (success) {
|
||||
if(!player.isCreative())
|
||||
itemStack.hurtAndBreak(50, player, e->{});
|
||||
itemStack.hurtAndBreak(24, player, e->{});
|
||||
target.playSound(SLPSoundEvents.LEAD_TIED.get());
|
||||
event.setCanceled(true);
|
||||
event.setCancellationResult(InteractionResult.CONSUME);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -101,6 +112,5 @@ public class LeashInteractHandler {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.core.potato;
|
||||
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
|
||||
import top.r3944realms.superleadrope.core.util.PotatoMode;
|
||||
import top.r3944realms.superleadrope.core.util.PotatoModeHelper;
|
||||
import top.r3944realms.superleadrope.network.NetworkHandler;
|
||||
import top.r3944realms.superleadrope.network.toClient.PacketEternalPotatoRemovePacket;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* 外观类:统一入口
|
||||
* 调用方只管 Facade,不关心底层是 Local 还是 Synced。
|
||||
*/
|
||||
public class EternalPotatoFacade {
|
||||
private static IEternalPotatoManager manager;
|
||||
private static PotatoSavedData savedData;
|
||||
// 全局监听器
|
||||
private static final List<IEternalPotatoChangeListener> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,12 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.core.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;
|
||||
|
||||
/**
|
||||
* 单人世界 & 局域网主机使用
|
||||
* <p>
|
||||
* 因为客户端和服务端在同一 JVM,可以共用同一个 Map<UUID, Data>。
|
||||
* <p>
|
||||
* 特点:不需要发网络包,直接访问。
|
||||
*/
|
||||
class LocalEternalPotatoManager implements IEternalPotatoManager {
|
||||
private final Map<UUID, EternalPotatoImpl> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 专用服务器 + 远程客户端使用
|
||||
* <p>
|
||||
* 服务端:维护权威数据,变更时发包
|
||||
* <p>
|
||||
* 客户端:本地缓存,收到服务端同步包时更新
|
||||
*/
|
||||
class SyncedEternalPotatoManager implements IEternalPotatoManager {
|
||||
private final Map<UUID, EternalPotatoImpl> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<UUID, Integer> 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);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, IObligationCompletion> 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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, IObligationCompletion> 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<String, IObligationCompletion> getAll() {
|
||||
return Collections.unmodifiableMap(REGISTRY);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<EntityType<SuperLeashEntity>> SUPER_LEASH = ENTITY_TYPES.register(
|
||||
"super_leash",
|
||||
() -> EntityType.Builder.<SuperLeashEntity>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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Item> ETERNAL_POTATO =
|
||||
ITEMS.register("eternal_potato",
|
||||
() -> new EternalPotatoItem(
|
||||
new Item.Properties()
|
||||
.stacksTo(1) // 只能有一颗
|
||||
.fireResistant() // 防火
|
||||
));
|
||||
public static void register(IEventBus bus) {
|
||||
ITEMS.register(bus);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.core.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record ImmutablePair<F, S>(F first, S second) {
|
||||
|
||||
public static <F, S> ImmutablePair<F, S> 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 + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<HolderLookup.Provider> lookupProvider = event.getLookupProvider();
|
||||
LanguageGenerator(event, LanguageEnum.English);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<FinishedRecipe> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <MSG> void sendAllPlayer(MSG message){
|
||||
INSTANCE.send(PacketDistributor.ALL.noArg(), message);
|
||||
}
|
||||
|
||||
public static <MSG> void sendToPlayer(MSG message, ServerPlayer player){
|
||||
INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);
|
||||
}
|
||||
public static <MSG, T> void sendToPlayer(MSG message, T entity, PacketDistributor<T> packetDistributor){
|
||||
INSTANCE.send(packetDistributor.with(() -> entity), message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<NetworkEvent.Context> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
// 客户端收到移除请求
|
||||
EternalPotatoFacade.remove(msg.itemUUID());
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.utils.coremods;
|
||||
package top.r3944realms.superleadrope.util.coremods;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.utils.lang;
|
||||
package top.r3944realms.superleadrope.util.lang;
|
||||
|
||||
public enum LanguageEnum {
|
||||
English("en_us"),
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.utils.lang;
|
||||
package top.r3944realms.superleadrope.util.lang;
|
||||
|
||||
public enum ModPartEnum {
|
||||
DEFAULT,
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Super Lead rope mod
|
||||
* Copyright (C) 2025 R3944Realms
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package top.r3944realms.superleadrope.util.model;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 骑乘关系数据结构
|
||||
*/
|
||||
public class RidingRelationship {
|
||||
private UUID entityId;
|
||||
private UUID vehicleId;
|
||||
private List<RidingRelationship> passengers;
|
||||
|
||||
public RidingRelationship() {
|
||||
this.passengers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public RidingRelationship(List<RidingRelationship> 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<RidingRelationship> getPassengers() {
|
||||
return Collections.unmodifiableList(passengers);
|
||||
}
|
||||
|
||||
public void setPassengers(List<RidingRelationship> 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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<UUID, Entity> entityProvider) {
|
||||
if (relationship == null || entityProvider == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int appliedCount = 0;
|
||||
Queue<RidingRelationship> 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<RidingRelationship> relationships,
|
||||
Function<UUID, Entity> 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<UUID, Entity> entityProvider) {
|
||||
RidingRelationship relationship = RidingSerializer.deserialize(json);
|
||||
return applyRidingRelationship(relationship, entityProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Entity> queue = new LinkedList<>();
|
||||
queue.offer(entity);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Entity current = queue.poll();
|
||||
|
||||
// 让当前实体的所有乘客下车
|
||||
List<Entity> 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<Entity> queue = new LinkedList<>();
|
||||
queue.offer(entity);
|
||||
|
||||
while (!queue.isEmpty() && iteration < maxIterations) {
|
||||
Entity current = queue.poll();
|
||||
iteration++;
|
||||
|
||||
// 让当前实体下车(如果是乘客)
|
||||
if (current.isPassenger()) {
|
||||
current.stopRiding();
|
||||
}
|
||||
|
||||
// 处理当前实体的乘客
|
||||
List<Entity> passengers = new ArrayList<>(current.getPassengers());
|
||||
for (Entity passenger : passengers) {
|
||||
passenger.stopRiding();
|
||||
queue.offer(passenger);
|
||||
}
|
||||
}
|
||||
|
||||
return queue.isEmpty(); // 如果队列为空表示全部解除成功
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量解除多个实体的骑乘关系
|
||||
*/
|
||||
public static void dismountEntities(Collection<Entity> entities) {
|
||||
if (entities == null || entities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Entity> processed = new HashSet<>();
|
||||
Queue<Entity> 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<Entity> passengers = new ArrayList<>(current.getPassengers());
|
||||
for (Entity passenger : passengers) {
|
||||
if (!processed.contains(passenger)) {
|
||||
queue.offer(passenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据骑乘关系数据结构解除骑乘
|
||||
*/
|
||||
public static void dismountByRelationship(RidingRelationship relationship,
|
||||
Function<UUID, Entity> entityProvider) {
|
||||
if (relationship == null || entityProvider == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用栈进行深度优先遍历解除
|
||||
Deque<RidingRelationship> 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<RidingRelationship> 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<Entity> allPassengers = RidingFinder.getAllPassengers(entity, false);
|
||||
for (Entity passenger : allPassengers) {
|
||||
if (passenger.isPassenger()) {
|
||||
passenger.stopRiding();
|
||||
}
|
||||
}
|
||||
|
||||
// 再次检查并清理(确保完全解除)
|
||||
if (!entity.getPassengers().isEmpty()) {
|
||||
entity.ejectPassengers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Entity> getEntityFromRidingShip(RidingRelationship ship,
|
||||
Function<UUID, Entity> entityProvider) {
|
||||
List<Entity> ret = new ArrayList<>();
|
||||
Queue<RidingRelationship> queue = new LinkedList<>();
|
||||
queue.offer(ship);
|
||||
while (!queue.isEmpty()) {
|
||||
RidingRelationship poll = queue.poll();
|
||||
ret.add(entityProvider.apply(ship.getEntityId()));
|
||||
List<RidingRelationship> 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<Entity> getAllPassengers(@Nullable Entity entity) {
|
||||
return getAllPassengers(entity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有乘客(包括嵌套乘客)
|
||||
*/
|
||||
public static List<Entity> 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<Entity> result = new ArrayList<>();
|
||||
Queue<Entity> queue = new LinkedList<>();
|
||||
queue.offer(rootEntity);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Entity current = queue.poll();
|
||||
result.add(current); // 把当前实体加入列表
|
||||
|
||||
List<Entity> passengers = current.getPassengers();
|
||||
if (!passengers.isEmpty()) {
|
||||
queue.addAll(passengers);
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<ImmutablePair<Entity, RidingRelationship>> queue = new LinkedList<>();
|
||||
queue.offer(ImmutablePair.of(rootEntity, rootRelationship));
|
||||
|
||||
Set<UUID> processedEntities = new HashSet<>();
|
||||
processedEntities.add(rootEntity.getUUID());
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
ImmutablePair<Entity, RidingRelationship> current = queue.poll();
|
||||
Entity currentEntity = current.first();
|
||||
RidingRelationship currentRelation = current.second();
|
||||
|
||||
List<Entity> 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<RidingRelationship> filterPassengers(List<RidingRelationship> passengers) {
|
||||
if (passengers == null || passengers.isEmpty()) return new ArrayList<>();
|
||||
List<RidingRelationship> filtered = new ArrayList<>();
|
||||
for (RidingRelationship passenger : passengers) {
|
||||
RidingRelationship childFiltered = filterByWhitelistRoot(passenger);
|
||||
if (childFiltered != null && childFiltered.getEntityId() != null) {
|
||||
filtered.add(childFiltered);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// 传入一个实体提供器 Function<UUID, Entity>,通常在服务器侧就是 level::getEntity
|
||||
private static Function<UUID, Entity> entityProvider;
|
||||
|
||||
public static void setEntityProvider(Function<UUID, Entity> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Entity> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 205 B |
Loading…
Reference in New Issue
Block a user