From ec373ee1cf06e09d455f2802081f3048406b6f08 Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Tue, 9 Sep 2025 00:00:33 +0800 Subject: [PATCH] =?UTF-8?q?feather:=201.=E6=B8=B2=E6=9F=93=E7=94=B1?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E4=B8=96=E7=95=8CTick=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=202.=E4=BC=98=E5=8C=96=E4=BA=86=E4=BA=9B=E7=BB=86?= =?UTF-8?q?=E8=8A=82=E5=AE=9E=E7=8E=B0(=E5=A6=82=E6=8B=B4=E7=BB=B3?= =?UTF-8?q?=E4=BA=A4=E4=BA=92)=203.=E6=8F=90=E4=BE=9B=E5=8F=AF=E8=B0=83?= =?UTF-8?q?=E9=85=8D=E7=9A=84=E5=AE=9E=E4=BD=93=E9=85=8D=E7=BD=AE=EF=BC=88?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E8=BF=87=E6=BB=A4=E6=8B=B4=E7=BB=B3=E6=8B=89?= =?UTF-8?q?=E5=8A=A8=E5=92=8C=E6=8B=B4=E7=BB=B3=E4=BC=A0=E9=80=81=EF=BC=89?= =?UTF-8?q?=20fix:=201.=E7=A7=BB=E9=99=A4=E6=97=A7=E7=9A=84=E6=8A=80?= =?UTF-8?q?=E6=9C=AF=E5=AE=9E=E4=BD=93=E5=AE=9E=E7=8E=B0=EF=BC=8C=E8=BD=AC?= =?UTF-8?q?=E4=B8=BATick=E8=AE=A1=E7=AE=97=E5=8A=9B=202.=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?C/S=E7=BD=91=E7=BB=9C=E5=90=8C=E6=AD=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/inspectionProfiles/Project_Default.xml | 1 + README.MD | 16 +- .../1de3d2ee724999f84a11b20b51c37030049be277 | 4 +- .../2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac | 4 +- .../31f65342fc4da49cb4df4f994bdb8a2aa9453b5d | 3 +- .../82018c5420b46ddbb7071e62df09fdecd98133e6 | 4 +- .../9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e | 4 +- .../9fc560db70239f9631ea2b54518b8d5ada1a7b88 | 3 +- .../c622617f6fabf890a00b9275cd5f643584a8a2c8 | 4 +- .../assets/superleadrope/lang/en_us.json | 20 +- .../assets/superleadrope/lang/lzh.json | 20 +- .../assets/superleadrope/lang/zh_cn.json | 20 +- .../assets/superleadrope/lang/zh_tw.json | 20 +- .../models/item/eternal_potato.json | 6 + .../recipes/tools/super_lead_rope.json | 35 ++ .../recipes/super_lead_rope.json | 27 ++ .../data/superleadrope/tags/items/lead.json | 7 + .../superleadrope/CommonEventHandler.java | 256 +++++++++++---- .../superleadrope/SuperLeadRope.java | 13 +- .../client/ClientEventHandler.java | 16 +- .../client/renderer/LeashRenderHandler.java | 234 ++++++++++++++ .../client/renderer/SLPRenderType.java | 35 +- .../renderer/entity/SuperLeashRenderer.java | 153 --------- .../resolver/SuperLeashStateResolver.java | 3 +- .../config/LeashCommonConfig.java | 57 ++++ .../superleadrope/content/SLPDamageTypes.java | 41 +++ .../SLPItemTags.java => content/SLPTags.java} | 21 +- .../superleadrope/content/SLPToolTier.java | 71 ++++ .../content/capability/CapabilityHandler.java | 12 +- .../capability/impi/EternalPotatoImpl.java | 306 ++++++++++++++++++ .../capability/{ => impi}/LeashDataImpl.java | 134 ++++---- .../capability/inter/IEternalPotato.java | 86 +++++ .../inter/ILeashDataCapability.java | 4 +- .../provider/EternalPotatoProvider.java | 106 ++++++ .../{ => provider}/LeashDataProvider.java | 4 +- .../content/entity/SuperLeashEntity.java | 74 ----- .../content/entity/SuperLeashKnotEntity.java | 19 +- .../content/item/EternalPotatoItem.java | 245 ++++++++++++++ .../content/item/SuperLeadRopeItem.java | 34 +- .../core/exception/RidingCycleException.java | 40 +++ .../core/leash/LeashInteractHandler.java | 18 +- .../core/potato/EternalPotatoFacade.java | 108 +++++++ .../potato/IEternalPotatoChangeListener.java} | 9 +- .../core/potato/IEternalPotatoManager.java | 31 ++ .../potato/LocalEternalPotatoManager.java | 76 +++++ .../core/potato/PotatoSavedData.java | 51 +++ .../potato/SyncedEternalPotatoManager.java | 89 +++++ .../punishment/DailyPunishmentHandler.java | 127 ++++++++ .../punishment/IObligationCompletion.java | 77 +++++ .../core/punishment/PunishmentDefinition.java | 82 +++++ .../ObligationCompletionRegistry.java | 58 ++++ .../core/register/SLPEntityTypes.java | 13 - .../superleadrope/core/register/SLPItems.java | 8 + .../core/util/ImmutablePair.java | 47 +++ .../superleadrope/core/util/PotatoMode.java | 35 ++ .../core/util/PotatoModeHelper.java | 41 +++ .../datagen/SLPDataGenEvent.java | 5 +- .../datagen/data/SLPLangKeyValue.java | 144 ++++++++- .../provider/SLPItemRecipeProvider.java | 15 +- .../datagen/provider/SLPItemTagProvider.java | 8 +- .../datagen/provider/SLPLanguageProvider.java | 2 +- .../superleadrope/network/NetworkHandler.java | 20 +- .../toClient/EternalPotatoSyncCapPacket.java | 95 ++++++ .../network/toClient/LeashDataSyncPacket.java | 2 - .../PacketEternalPotatoRemovePacket.java | 41 +++ .../coremods/InvokerMethod.java | 2 +- .../superleadrope/util/file/ConfigUtil.java | 63 ++++ .../{utils => util}/lang/LanguageEnum.java | 2 +- .../{utils => util}/lang/ModPartEnum.java | 2 +- .../util/model/RidingRelationship.java | 100 ++++++ .../util/riding/RidingApplier.java | 122 +++++++ .../util/riding/RidingDismounts.java | 208 ++++++++++++ .../util/riding/RidingFinder.java | 101 ++++++ .../util/riding/RidingSaver.java | 160 +++++++++ .../util/riding/RidingSerializer.java | 36 +++ .../util/riding/RidingValidator.java | 78 +++++ .../util/riding/RindingLeash.java | 101 ++++++ src/main/resources/META-INF/mods.toml | 12 +- .../textures/item/eternal_potato.png | Bin 0 -> 205 bytes 79 files changed, 3870 insertions(+), 481 deletions(-) create mode 100644 src/generated/resources/assets/superleadrope/models/item/eternal_potato.json create mode 100644 src/generated/resources/data/superleadrope/advancements/recipes/tools/super_lead_rope.json create mode 100644 src/generated/resources/data/superleadrope/recipes/super_lead_rope.json create mode 100644 src/generated/resources/data/superleadrope/tags/items/lead.json create mode 100644 src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java delete mode 100644 src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java create mode 100644 src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/SLPDamageTypes.java rename src/main/java/top/r3944realms/superleadrope/{datagen/data/SLPItemTags.java => content/SLPTags.java} (68%) create mode 100644 src/main/java/top/r3944realms/superleadrope/content/SLPToolTier.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java rename src/main/java/top/r3944realms/superleadrope/content/capability/{ => impi}/LeashDataImpl.java (91%) create mode 100644 src/main/java/top/r3944realms/superleadrope/content/capability/inter/IEternalPotato.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/capability/provider/EternalPotatoProvider.java rename src/main/java/top/r3944realms/superleadrope/content/capability/{ => provider}/LeashDataProvider.java (90%) delete mode 100644 src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java create mode 100644 src/main/java/top/r3944realms/superleadrope/content/item/EternalPotatoItem.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/exception/RidingCycleException.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/potato/EternalPotatoFacade.java rename src/main/java/top/r3944realms/superleadrope/{config/CommonConfig.java => core/potato/IEternalPotatoChangeListener.java} (73%) create mode 100644 src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/punishment/DailyPunishmentHandler.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/punishment/IObligationCompletion.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/punishment/PunishmentDefinition.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/register/ObligationCompletionRegistry.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/util/ImmutablePair.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/util/PotatoMode.java create mode 100644 src/main/java/top/r3944realms/superleadrope/core/util/PotatoModeHelper.java create mode 100644 src/main/java/top/r3944realms/superleadrope/network/toClient/EternalPotatoSyncCapPacket.java create mode 100644 src/main/java/top/r3944realms/superleadrope/network/toClient/PacketEternalPotatoRemovePacket.java rename src/main/java/top/r3944realms/superleadrope/{utils => util}/coremods/InvokerMethod.java (94%) create mode 100644 src/main/java/top/r3944realms/superleadrope/util/file/ConfigUtil.java rename src/main/java/top/r3944realms/superleadrope/{utils => util}/lang/LanguageEnum.java (95%) rename src/main/java/top/r3944realms/superleadrope/{utils => util}/lang/ModPartEnum.java (95%) create mode 100644 src/main/java/top/r3944realms/superleadrope/util/model/RidingRelationship.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RidingApplier.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RidingDismounts.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RidingFinder.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RidingSaver.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RidingSerializer.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RidingValidator.java create mode 100644 src/main/java/top/r3944realms/superleadrope/util/riding/RindingLeash.java create mode 100644 src/main/resources/assets/superleadrope/textures/item/eternal_potato.png diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index fdea32d..e9fc1e9 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,5 +4,6 @@ + \ No newline at end of file diff --git a/README.MD b/README.MD index 445c371..b02a5fd 100644 --- a/README.MD +++ b/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) \ No newline at end of file +**完整条款:** [查看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 音效文件。 \ No newline at end of file diff --git a/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 b/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 index c5822cb..e5d1426 100644 --- a/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 +++ b/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 @@ -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 diff --git a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac index 81ccea2..10254a5 100644 --- a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac +++ b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac @@ -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 diff --git a/src/generated/resources/.cache/31f65342fc4da49cb4df4f994bdb8a2aa9453b5d b/src/generated/resources/.cache/31f65342fc4da49cb4df4f994bdb8a2aa9453b5d index bbd41ea..0834274 100644 --- a/src/generated/resources/.cache/31f65342fc4da49cb4df4f994bdb8a2aa9453b5d +++ b/src/generated/resources/.cache/31f65342fc4da49cb4df4f994bdb8a2aa9453b5d @@ -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 diff --git a/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 b/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 index f74d9d2..85ca492 100644 --- a/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 +++ b/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 @@ -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 diff --git a/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e b/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e index 869f26c..ee4f5e1 100644 --- a/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e +++ b/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e @@ -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 diff --git a/src/generated/resources/.cache/9fc560db70239f9631ea2b54518b8d5ada1a7b88 b/src/generated/resources/.cache/9fc560db70239f9631ea2b54518b8d5ada1a7b88 index b84ae29..0b031ae 100644 --- a/src/generated/resources/.cache/9fc560db70239f9631ea2b54518b8d5ada1a7b88 +++ b/src/generated/resources/.cache/9fc560db70239f9631ea2b54518b8d5ada1a7b88 @@ -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 diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 44f902e..16bf93b 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -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 diff --git a/src/generated/resources/assets/superleadrope/lang/en_us.json b/src/generated/resources/assets/superleadrope/lang/en_us.json index 611bbab..04bd09f 100644 --- a/src/generated/resources/assets/superleadrope/lang/en_us.json +++ b/src/generated/resources/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", diff --git a/src/generated/resources/assets/superleadrope/lang/lzh.json b/src/generated/resources/assets/superleadrope/lang/lzh.json index 2dfddfa..c02cf9b 100644 --- a/src/generated/resources/assets/superleadrope/lang/lzh.json +++ b/src/generated/resources/assets/superleadrope/lang/lzh.json @@ -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": "繫索", diff --git a/src/generated/resources/assets/superleadrope/lang/zh_cn.json b/src/generated/resources/assets/superleadrope/lang/zh_cn.json index 93e271c..3ccd6d5 100644 --- a/src/generated/resources/assets/superleadrope/lang/zh_cn.json +++ b/src/generated/resources/assets/superleadrope/lang/zh_cn.json @@ -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": "拴绳系上", diff --git a/src/generated/resources/assets/superleadrope/lang/zh_tw.json b/src/generated/resources/assets/superleadrope/lang/zh_tw.json index c76a457..d1fc2cc 100644 --- a/src/generated/resources/assets/superleadrope/lang/zh_tw.json +++ b/src/generated/resources/assets/superleadrope/lang/zh_tw.json @@ -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": "拴繩係上", diff --git a/src/generated/resources/assets/superleadrope/models/item/eternal_potato.json b/src/generated/resources/assets/superleadrope/models/item/eternal_potato.json new file mode 100644 index 0000000..4b60646 --- /dev/null +++ b/src/generated/resources/assets/superleadrope/models/item/eternal_potato.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "superleadrope:item/eternal_potato" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/superleadrope/advancements/recipes/tools/super_lead_rope.json b/src/generated/resources/data/superleadrope/advancements/recipes/tools/super_lead_rope.json new file mode 100644 index 0000000..e0ad0d3 --- /dev/null +++ b/src/generated/resources/data/superleadrope/advancements/recipes/tools/super_lead_rope.json @@ -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 +} \ No newline at end of file diff --git a/src/generated/resources/data/superleadrope/recipes/super_lead_rope.json b/src/generated/resources/data/superleadrope/recipes/super_lead_rope.json new file mode 100644 index 0000000..71c88f8 --- /dev/null +++ b/src/generated/resources/data/superleadrope/recipes/super_lead_rope.json @@ -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 +} \ No newline at end of file diff --git a/src/generated/resources/data/superleadrope/tags/items/lead.json b/src/generated/resources/data/superleadrope/tags/items/lead.json new file mode 100644 index 0000000..922d09b --- /dev/null +++ b/src/generated/resources/data/superleadrope/tags/items/lead.json @@ -0,0 +1,7 @@ +{ + "values": [ + "minecraft:lead", + "minecraft:slime_ball", + "minecraft:string" + ] +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java index 44abc2e..d95e9fe 100644 --- a/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/CommonEventHandler.java @@ -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 entities = LeashDataImpl.leashableInArea(telEntity); - // 为每个实体创建状态快照 - entities.forEach(beLeashedEntity -> { - // 保存原来的旋转角度 - Pose originalPose = beLeashedEntity.getPose(); - boolean originalIsSprinting = beLeashedEntity.isSprinting(); - float originalYaw = beLeashedEntity.getYRot(); - float originalPitch = beLeashedEntity.getXRot(); - Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement(); - AtomicReference originalLeashInfo = new AtomicReference<>(); - beLeashedEntity.getCapability(CapabilityHandler.LEASH_DATA_CAP) - .ifPresent(cap -> { - originalLeashInfo.set(cap.getLeashInfo(telEntity).orElse(null)); - cap.removeLeash(telEntity); - }); + if (!(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 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 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 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); } } diff --git a/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java b/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java index 126fc07..5b9f8cc 100644 --- a/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java +++ b/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java @@ -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"); } } diff --git a/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java b/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java index 91d9c48..13bf104 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/client/ClientEventHandler.java @@ -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); } } } diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java new file mode 100644 index 0000000..ee0e208 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/LeashRenderHandler.java @@ -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 . + */ + +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 holderOpt = getHolderFromLeashInfo((ClientLevel) entity.level(), leashInfo); + if (holderOpt.isEmpty()) return; + + Entity holder = holderOpt.get(); + + Optional 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 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); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java index b27e1af..a1c54f1 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/SLPRenderType.java @@ -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.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) + ); } } diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java deleted file mode 100644 index b945e0f..0000000 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/entity/SuperLeashRenderer.java +++ /dev/null @@ -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 . - */ - -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 { - // 渲染参数 - 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 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"); // 使用自定义渲染类型,不需要纹理 - } -} diff --git a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java index 505308c..500a1f4 100644 --- a/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java +++ b/src/main/java/top/r3944realms/superleadrope/client/renderer/resolver/SuperLeashStateResolver.java @@ -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 resolveAll( Entity leashedEntity, LeashDataImpl leashData, diff --git a/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java b/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java new file mode 100644 index 0000000..88d7bae --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/config/LeashCommonConfig.java @@ -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 . + */ + +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> 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_/]+"); + } + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/SLPDamageTypes.java b/src/main/java/top/r3944realms/superleadrope/content/SLPDamageTypes.java new file mode 100644 index 0000000..fa88264 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/SLPDamageTypes.java @@ -0,0 +1,41 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.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 + ); +} diff --git a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPItemTags.java b/src/main/java/top/r3944realms/superleadrope/content/SLPTags.java similarity index 68% rename from src/main/java/top/r3944realms/superleadrope/datagen/data/SLPItemTags.java rename to src/main/java/top/r3944realms/superleadrope/content/SLPTags.java index 3444beb..76ff589 100644 --- a/src/main/java/top/r3944realms/superleadrope/datagen/data/SLPItemTags.java +++ b/src/main/java/top/r3944realms/superleadrope/content/SLPTags.java @@ -13,24 +13,21 @@ * along with this program. If not, see . */ -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 LEAD = tag("lead"); + private static TagKey tag(String name) + { + return TagKey.create(Registries.ITEM, new ResourceLocation(SuperLeadRope.MOD_ID, name)); + } } - private static TagKey bind(String name) { - return TagKey.create(Registries.ITEM, new ResourceLocation(name)); - } - - public static TagKey create(final ResourceLocation name) { - return TagKey.create(Registries.ITEM, name); - } } diff --git a/src/main/java/top/r3944realms/superleadrope/content/SLPToolTier.java b/src/main/java/top/r3944realms/superleadrope/content/SLPToolTier.java new file mode 100644 index 0000000..b6a4f3e --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/SLPToolTier.java @@ -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 . + */ + +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 repairIngredient; + SLPToolTier(int uses, float speed, float attackDamageBonus, int enchantmentValue, Supplier 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(); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityHandler.java b/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityHandler.java index eb5615e..fe55d73 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/CapabilityHandler.java @@ -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 LEASH_DATA_CAP = CapabilityManager.get(new CapabilityToken<>(){}); - + public static Capability 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)); } } + } diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java new file mode 100644 index 0000000..27d6203 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/EternalPotatoImpl.java @@ -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 . + */ + +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; + } + } +} + diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java similarity index 91% rename from src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java rename to src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java index 162f1ea..29a8db2 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java @@ -13,20 +13,18 @@ * along with this program. If not, see . */ -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 leashHolders = new ConcurrentHashMap<>(); // 引入解决 绳结不保存导致第二进入持有者不存在的问题 private final Map 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 getAllLeashes() { - Collection 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) { diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/IEternalPotato.java b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/IEternalPotato.java new file mode 100644 index 0000000..e7a8ac2 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/IEternalPotato.java @@ -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 . + */ + +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); +} diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java index 9982465..d333196 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/inter/ILeashDataCapability.java @@ -70,7 +70,9 @@ public interface ILeashDataCapability extends INBTSerializable { boolean canBeLeashed(); boolean canBeAttachedTo(Entity pEntity); - + void markForSync(); + void immediateSync(); + void checkSync(); record LeashInfo( Optional blockPosOpt, diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/provider/EternalPotatoProvider.java b/src/main/java/top/r3944realms/superleadrope/content/capability/provider/EternalPotatoProvider.java new file mode 100644 index 0000000..3534094 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/provider/EternalPotatoProvider.java @@ -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 . + */ + +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 { + + 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 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 LazyOptional getCapability(@NotNull Capability 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()); + } + }); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataProvider.java b/src/main/java/top/r3944realms/superleadrope/content/capability/provider/LeashDataProvider.java similarity index 90% rename from src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataProvider.java rename to src/main/java/top/r3944realms/superleadrope/content/capability/provider/LeashDataProvider.java index 5e2a7c4..dab9933 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/LeashDataProvider.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/provider/LeashDataProvider.java @@ -13,7 +13,7 @@ * along with this program. If not, see . */ -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 { diff --git a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java deleted file mode 100644 index be8af01..0000000 --- a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashEntity.java +++ /dev/null @@ -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 . - */ - -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 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) { - - } -} diff --git a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java index 8a63037..ad7903c 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java +++ b/src/main/java/top/r3944realms/superleadrope/content/entity/SuperLeashKnotEntity.java @@ -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 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 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); diff --git a/src/main/java/top/r3944realms/superleadrope/content/item/EternalPotatoItem.java b/src/main/java/top/r3944realms/superleadrope/content/item/EternalPotatoItem.java new file mode 100644 index 0000000..dad67f9 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/content/item/EternalPotatoItem.java @@ -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 . + */ + + +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 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 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java index 83f7285..cdcce2a 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java +++ b/src/main/java/top/r3944realms/superleadrope/content/item/SuperLeadRopeItem.java @@ -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 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 = 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 = 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); - } }); } } diff --git a/src/main/java/top/r3944realms/superleadrope/core/exception/RidingCycleException.java b/src/main/java/top/r3944realms/superleadrope/core/exception/RidingCycleException.java new file mode 100644 index 0000000..0290f7e --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/exception/RidingCycleException.java @@ -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 . + */ + +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; + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java index 195b7a0..9ba2cb3 100644 --- a/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java +++ b/src/main/java/top/r3944realms/superleadrope/core/leash/LeashInteractHandler.java @@ -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 { } } - } } diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/EternalPotatoFacade.java b/src/main/java/top/r3944realms/superleadrope/core/potato/EternalPotatoFacade.java new file mode 100644 index 0000000..56d1721 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/potato/EternalPotatoFacade.java @@ -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 . + */ + +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 listeners = new CopyOnWriteArrayList<>(); + public static void addListener(IEternalPotatoChangeListener listener) { + listeners.add(listener); + } + + public static void removeListener(IEternalPotatoChangeListener listener) { + listeners.remove(listener); + } + + // 内部方法,用于通知变化 + static void notifyChange(UUID uuid, IEternalPotato potato) { + listeners.forEach(l -> l.onPotatoChanged(uuid, potato)); + } + + public static IEternalPotatoManager getManager() { + return manager; + } + public static PotatoSavedData getSavedData() { + return savedData; + } + public static void initSavedData(ServerLevel serverLevel) { + savedData = PotatoSavedData.create(serverLevel); + } + /** + * 初始化(进入世界时调用) + * @param mode 当前运行模式 + * @param isServer 是否在服务端 + */ + public static void init(PotatoMode mode, boolean isServer) { + if (manager != null) { + return; // 已经有 manager,说明重复初始化,直接返回 + } + switch (mode) { + case INTEGRATED -> manager = new LocalEternalPotatoManager(); + case DEDICATED, REMOTE_CLIENT -> manager = new SyncedEternalPotatoManager(isServer); + } + } + + public static IEternalPotato getOrCreate(UUID uuid) { + if (manager == null) throw new IllegalStateException("EternalPotatoFacade not initialized!"); + IEternalPotato potato = manager.getOrCreate(uuid); + + // 绑定统一回调 + potato.bindItemStackSync(() -> { + if (savedData != null) savedData.setDirty(); // 标记 SavedData 脏 + notifyChange(uuid, potato); // 通知全局监听器 + }); + + return potato; + } + + public static void remove(UUID uuid) { + if (manager != null) manager.remove(uuid); + + // Synced 模式才发包 + if (PotatoModeHelper.getCurrentMode().isSynced() && ServerLifecycleHooks.getCurrentServer() != null) { + ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers().forEach(player -> { + PacketEternalPotatoRemovePacket packet = new PacketEternalPotatoRemovePacket(uuid); + NetworkHandler.sendToPlayer(packet, player); + }); + } + } + + public static void clear() { + if (manager != null) manager.clear(); + } + + public static boolean isServer() { + return (manager instanceof SyncedEternalPotatoManager synced) && synced.isServer(); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/config/CommonConfig.java b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoChangeListener.java similarity index 73% rename from src/main/java/top/r3944realms/superleadrope/config/CommonConfig.java rename to src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoChangeListener.java index c3e398a..2e93285 100644 --- a/src/main/java/top/r3944realms/superleadrope/config/CommonConfig.java +++ b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoChangeListener.java @@ -13,7 +13,12 @@ * along with this program. If not, see . */ -package top.r3944realms.superleadrope.config; +package top.r3944realms.superleadrope.core.potato; -public class CommonConfig { +import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato; + +import java.util.UUID; + +public interface IEternalPotatoChangeListener { + void onPotatoChanged(UUID uuid, IEternalPotato potato); } diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java new file mode 100644 index 0000000..a1ef90e --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/potato/IEternalPotatoManager.java @@ -0,0 +1,31 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.core.potato; + +import net.minecraft.nbt.CompoundTag; +import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato; + +import java.util.UUID; + +public interface IEternalPotatoManager { + IEternalPotato getOrCreate(UUID uuid); + + void remove(UUID uuid); + + void clear(); + CompoundTag saveAll(); + void loadAll(CompoundTag tag); +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java b/src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java new file mode 100644 index 0000000..9faf477 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/potato/LocalEternalPotatoManager.java @@ -0,0 +1,76 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.core.potato; + +import net.minecraft.nbt.CompoundTag; +import top.r3944realms.superleadrope.SuperLeadRope; +import top.r3944realms.superleadrope.content.capability.impi.EternalPotatoImpl; +import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 单人世界 & 局域网主机使用 + *

+ * 因为客户端和服务端在同一 JVM,可以共用同一个 Map。 + *

+ * 特点:不需要发网络包,直接访问。 + */ +class LocalEternalPotatoManager implements IEternalPotatoManager { + private final Map LOCAL_DATA = new ConcurrentHashMap<>(); + + public IEternalPotato getOrCreate(UUID uuid) { + return LOCAL_DATA.computeIfAbsent(uuid, k -> { + EternalPotatoImpl impl = new EternalPotatoImpl(); + impl.setItemUUID(uuid); + return impl; + }); + } + + @Override + public void remove(UUID uuid) { + LOCAL_DATA.remove(uuid); + } + + public void clear() { + LOCAL_DATA.clear(); + } + + @Override + public CompoundTag saveAll() { + CompoundTag root = new CompoundTag(); + LOCAL_DATA.forEach((uuid, impl) -> root.put(uuid.toString(), impl.serializeNBT())); + return root; + } + + @Override + public void loadAll(CompoundTag tag) { + LOCAL_DATA.clear(); + for (String key : tag.getAllKeys()) { + try { + UUID uuid = UUID.fromString(key); + EternalPotatoImpl impl = new EternalPotatoImpl(); + impl.deserializeNBT(tag.getCompound(key)); + impl.setItemUUID(uuid); + LOCAL_DATA.put(uuid, impl); + } catch (IllegalArgumentException e) { + SuperLeadRope.logger.error("Could not load UUID: {}", key, e); + } + } + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java b/src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java new file mode 100644 index 0000000..4263597 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/potato/PotatoSavedData.java @@ -0,0 +1,51 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.core.potato; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; +import org.jetbrains.annotations.NotNull; + +public class PotatoSavedData extends SavedData { + public static final String DATA_NAME = "eternal_potato"; + + + @Override + public @NotNull CompoundTag save(CompoundTag tag) { + // 把所有数据塞进 tag + tag.put(DATA_NAME, EternalPotatoFacade.getManager().saveAll()); + return tag; + } + + public static PotatoSavedData load(CompoundTag tag) { + IEternalPotatoManager manager = EternalPotatoFacade.getManager(); + PotatoSavedData data = new PotatoSavedData(); + if (tag.contains(DATA_NAME)) { + manager.loadAll(tag.getCompound(DATA_NAME)); + } + return data; + } + + // 工厂方法(Forge 推荐写法) + public static PotatoSavedData create(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + PotatoSavedData::load, + PotatoSavedData::new, + DATA_NAME + ); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java b/src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java new file mode 100644 index 0000000..b4fbbcf --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/core/potato/SyncedEternalPotatoManager.java @@ -0,0 +1,89 @@ +/* + * Super Lead rope mod + * Copyright (C) 2025 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.core.potato; + +import net.minecraft.nbt.CompoundTag; +import top.r3944realms.superleadrope.SuperLeadRope; +import top.r3944realms.superleadrope.content.capability.impi.EternalPotatoImpl; +import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 专用服务器 + 远程客户端使用 + *

+ * 服务端:维护权威数据,变更时发包 + *

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