版本更新到1.0.0

1. 对WayStone做进一步适配(显然WS自己有问题)
2. 添加了禁拴效果及其对应的药水
3. 添加了彩蛋内容(暂无非作弊获取方式)
4. 添加了最大拴绳拉力阈值,在配置中调整
This commit is contained in:
叁玖领域 2025-12-11 18:27:07 +08:00
parent 186103e0c5
commit ad275c4d37
46 changed files with 1384 additions and 52 deletions

View File

@ -167,6 +167,7 @@ dependencies {
modImplementation("curse.maven:bendy-lib-623373:4550371")
modImplementation("curse.maven:waystones-245755:6856603")
modImplementation("curse.maven:balm-531761:7087245")
// modImplementation("curse.maven:kaleidoscope-doll-1233277:7201584")
// modRuntimeOnly("curse.maven:luckperms-431733:4738950")
modImplementation("software.bernie.geckolib:geckolib-forge-${minecraft_version}:${geckolib_version}")
implementation("com.eliotlash.mclib:mclib:20")

View File

@ -59,7 +59,7 @@ mod_name=Super Lead Rope
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=GPLv3
# The mod version. See https://semver.org/
mod_version=0.0.0.9
mod_version=1.0.0
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html

1
res/doll.bbmodel Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-04T18:55:51.5054614 Languages: zh_tw
ff7556f7a8bd62a8c4cc776e45947835ffc19689 assets/superleadrope/lang/zh_tw.json
// 1.20.1 2025-12-11T17:31:55.2360112 Languages: zh_tw
d1253ab3534285cc6a4d6c0e5495720f48edd62f assets/superleadrope/lang/zh_tw.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-04T18:55:51.5004568 Languages: zh_cn
f5128cf6058db0e79f91aece8de7f5bdd8e04193 assets/superleadrope/lang/zh_cn.json
// 1.20.1 2025-12-11T17:31:55.2360112 Languages: zh_cn
7f4db04637eb656488388de206cad9070a53ae48 assets/superleadrope/lang/zh_cn.json

View File

@ -1,4 +1,5 @@
// 1.20.1 2025-09-05T21:41:43.7307014 Item Models: superleadrope
// 1.20.1 2025-12-11T18:08:58.6978167 Item Models: superleadrope
c982e91b60c03a6460d1cc7b516628cf09a28417 assets/superleadrope/models/item/broken_super_lead_rope.json
50782bef004ff1c69c09aa397a8ee92b71a8b858 assets/superleadrope/models/item/doll.json
7b072a8cc70b53d54e37e5fa72d705bd07780943 assets/superleadrope/models/item/eternal_potato.json
4fb737a5f8f15642212aa581a02a81cf649fc36f assets/superleadrope/models/item/super_lead_rope.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-12-11T17:31:55.2360112 Block States: superleadrope
6e9e15398c0d85d14941797931eb3f7c886a800a assets/superleadrope/blockstates/doll.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-12-11T17:31:55.2360112 Loot Tables
c31110372e9281cf7264482c2c89c8927fb67fe2 data/superleadrope/loot_tables/blocks/doll.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-04T18:55:51.5034621 Languages: lzh
8dde976f77c0ca836ed66627ea2af29b20e5f7c5 assets/superleadrope/lang/lzh.json
// 1.20.1 2025-12-11T17:31:55.2360112 Languages: lzh
c03cdd0fb0e9a7bcf525fc0e18fe31e2b906f8f0 assets/superleadrope/lang/lzh.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-04T18:55:51.5024633 Languages: en_us
b1f5523a28ab74acc8bd5b0ec167bf254e0b0dea assets/superleadrope/lang/en_us.json
// 1.20.1 2025-12-11T17:31:55.2360112 Languages: en_us
d1d1d2a67f7602340c0f0e0dcf1ea1b93fdc23f4 assets/superleadrope/lang/en_us.json

View File

@ -0,0 +1,34 @@
{
"variants": {
"facing=east,waterlogged=false": {
"model": "superleadrope:block/doll",
"y": 90
},
"facing=east,waterlogged=true": {
"model": "superleadrope:block/doll",
"y": 90
},
"facing=north,waterlogged=false": {
"model": "superleadrope:block/doll"
},
"facing=north,waterlogged=true": {
"model": "superleadrope:block/doll"
},
"facing=south,waterlogged=false": {
"model": "superleadrope:block/doll",
"y": 180
},
"facing=south,waterlogged=true": {
"model": "superleadrope:block/doll",
"y": 180
},
"facing=west,waterlogged=false": {
"model": "superleadrope:block/doll",
"y": 270
},
"facing=west,waterlogged=true": {
"model": "superleadrope:block/doll",
"y": 270
}
}
}

View File

@ -1,6 +1,8 @@
{
"block.superleadrope.doll": "Doll",
"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!",
"effect.superleadrope.no_super_leash": "No Super Leash",
"entity.superleadrope.super_lead_knot": "Super Lead Knot",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "Create Leash Fence Knot Entity if absent",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "Create LeashKnot Entity if it's absent on fence or other supported positions",
@ -22,6 +24,9 @@
"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.minecraft.lingering_potion.effect.no_super_leash": "Splash No Super Leash Potion",
"item.minecraft.potion.effect.no_super_leash": "No Super Leash Potion",
"item.minecraft.splash_potion.effect.no_super_leash": "Splash No Super Leash Potion",
"item.superleadrope.eternal_potato": "Eternal Potato",
"item.superleadrope.super_lead_rope": "Super Lead Rope",
"sound.superleadrope.subtitle.lead_break": "Lead Break",
@ -105,5 +110,6 @@
"superleadrope.command.motion.message.multiply.successful": "§bMultiply Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§bSet Successfully.§a%s§7:§f[§eVec§7:§a(§f%.2f§7,§f%.2f§7,§f%.2f§7)§f]§r",
"superleadrope.command.none": "<None>",
"superleadrope.command.state": "State"
"superleadrope.command.state": "State",
"tooltip.superleadrope.author": "Author : R3944Realms"
}

View File

@ -1,6 +1,8 @@
{
"block.superleadrope.doll": "戲像",
"death.attack.eternal_potato_not_complete": "§c%1$s 非汝所主,雷霆降身!",
"death.attack.eternal_potato_not_owner": "§c%1$s 非汝所主,雷霆降身!",
"effect.superleadrope.no_super_leash": "禁系之效",
"entity.superleadrope.super_lead_knot": "神駒羈縻索結",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "若阙则创超级繫绳结",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "若栅等支处阙超级繫绳结,则创之",
@ -22,6 +24,9 @@
"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.minecraft.lingering_potion.effect.no_super_leash": "缠绵禁系汤",
"item.minecraft.potion.effect.no_super_leash": "禁系汤剂",
"item.minecraft.splash_potion.effect.no_super_leash": "飞溅禁系汤",
"item.superleadrope.eternal_potato": "不滅薯",
"item.superleadrope.super_lead_rope": "神駒羈縻索",
"sound.superleadrope.subtitle.lead_break": "索絕",
@ -105,5 +110,6 @@
"superleadrope.command.motion.message.multiply.successful": "§b倍乘既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§b定值既成.§a%s§7:§f[§e速勢§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.none": "無",
"superleadrope.command.state": "狀"
"superleadrope.command.state": "狀",
"tooltip.superleadrope.author": "作者 : R3944Realms"
}

View File

@ -1,6 +1,8 @@
{
"block.superleadrope.doll": "玩偶",
"death.attack.eternal_potato_not_complete": "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
"death.attack.eternal_potato_not_owner": "§c%1$s 因使用非自己绑定物品,受到闪电惩罚!",
"effect.superleadrope.no_super_leash": "禁拴",
"entity.superleadrope.super_lead_knot": "超级拴绳结",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "如果缺失则创建超级拴绳结",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "如果在栅栏等支持处缺失超级拴绳结,则创建它",
@ -22,6 +24,9 @@
"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.minecraft.lingering_potion.effect.no_super_leash": "滞留型禁拴药水",
"item.minecraft.potion.effect.no_super_leash": "禁拴药水",
"item.minecraft.splash_potion.effect.no_super_leash": "喷溅型禁拴药水",
"item.superleadrope.eternal_potato": "永恒土豆",
"item.superleadrope.super_lead_rope": "超级拴绳",
"sound.superleadrope.subtitle.lead_break": "拴绳断裂",
@ -105,5 +110,6 @@
"superleadrope.command.motion.message.multiply.successful": "§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§b设置成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.none": "无",
"superleadrope.command.state": "状态"
"superleadrope.command.state": "状态",
"tooltip.superleadrope.author": "作者 : R3944Realms"
}

View File

@ -1,6 +1,8 @@
{
"block.superleadrope.doll": "玩偶",
"death.attack.eternal_potato_not_complete": "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
"death.attack.eternal_potato_not_owner": "§c%1$s 因使用非自己綁定物品,受到閃電懲罰!",
"effect.superleadrope.no_super_leash": "禁拴",
"entity.superleadrope.super_lead_knot": "超級拴繩結",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent": "如果缺失則創建超級拴繩結",
"gamerule.SLP.CreateSuperLeashKnotEntityIfAbsent.description": "如果在柵欄等支持處缺失超級拴繩結,則創建它",
@ -22,6 +24,9 @@
"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.minecraft.lingering_potion.effect.no_super_leash": "滯留型禁拴藥水",
"item.minecraft.potion.effect.no_super_leash": "禁拴藥水",
"item.minecraft.splash_potion.effect.no_super_leash": "噴濺型禁拴藥水",
"item.superleadrope.eternal_potato": "永恆馬鈴薯",
"item.superleadrope.super_lead_rope": "超級拴繩",
"sound.superleadrope.subtitle.lead_break": "拴繩斷裂",
@ -105,5 +110,6 @@
"superleadrope.command.motion.message.multiply.successful": "§b倍乘成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.motion.message.setter.successful": "§b設置成功.§a%s§7:§f[§e加速§7:(§a%.2f§7,§a%.2f§7,§a%.2f§7)§f]§r",
"superleadrope.command.none": "無",
"superleadrope.command.state": "狀態"
"superleadrope.command.state": "狀態",
"tooltip.superleadrope.author": "作者 : R3944Realms"
}

View File

@ -0,0 +1,3 @@
{
"parent": "superleadrope:block/doll"
}

View File

@ -0,0 +1,21 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"name": "superleadrope:doll"
}
],
"rolls": 1.0
}
],
"random_sequence": "superleadrope:blocks/doll"
}

View File

@ -74,6 +74,7 @@ import top.r3944realms.superleadrope.core.leash.LeashSyncManager;
import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.register.SLPPotionRecipeRegistry;
import top.r3944realms.superleadrope.core.util.PotatoMode;
import top.r3944realms.superleadrope.core.util.PotatoModeHelper;
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
@ -499,6 +500,7 @@ public class CommonEventHandler {
public static void onFMLCommonInit(FMLCommonSetupEvent event) {
event.enqueueWork(Mod::checkAndSet);
event.enqueueWork(SLPGameruleRegistry::register);
event.enqueueWork(SLPPotionRecipeRegistry::init);
}
/**

View File

@ -25,9 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.r3944realms.superleadrope.compat.WayStoneCompat;
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.core.register.*;
import top.r3944realms.superleadrope.network.NetworkHandler;
import top.r3944realms.superleadrope.util.file.ConfigUtil;
@ -53,8 +51,11 @@ public class SuperLeadRope {
public SuperLeadRope() {
IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus();
SLPItems.register(eventBus);
SLPBlocks.register(eventBus);
SLPEntityTypes.register(eventBus);
SLPSoundEvents.register(eventBus);
SLPEffects.register(eventBus);
SLPPotions.register(eventBus);
NetworkHandler.register();
initialize();

View File

@ -1,26 +1,137 @@
package top.r3944realms.superleadrope.compat;
import com.ibm.icu.impl.Pair;
import net.blay09.mods.waystones.api.WaystoneTeleportEvent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.ModList;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.api.event.SuperLeadRopeEvent;
import top.r3944realms.superleadrope.api.type.capabilty.LeashInfo;
import top.r3944realms.superleadrope.api.type.util.ILeashHelper;
import top.r3944realms.superleadrope.api.workspace.Services;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import top.r3944realms.superleadrope.core.leash.LeashSyncManager;
import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry;
import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI;
import top.r3944realms.superleadrope.util.capability.LeashStateInnerAPI;
import top.r3944realms.superleadrope.util.entity.TeleportUtil;
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.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
public class WayStoneCompat {
public final static boolean isModLoaded = ModList.get().isLoaded("waystones");
public final static Map<UUID, Set<Pair<Entity, OriginalState>>> tempLeashMap = new ConcurrentHashMap<>();
public final static Map<UUID, UUID> uuidMap = new ConcurrentHashMap<>();
public record OriginalState(Pose pose, boolean isSprinting, float yaw, float pitch, Vec3 deltaMovement, LeashInfo leashInfo, RidingRelationship ridingRelationship) {}
public static void init() {
if (isModLoaded) {
MinecraftForge.EVENT_BUS.addListener(WayStoneCompat::onWayStoneTeleport);
MinecraftForge.EVENT_BUS.addListener(WayStoneCompat::onWayStoneTeleport$Pre);
MinecraftForge.EVENT_BUS.addListener(WayStoneCompat::onWayStoneTeleport$Post);
}
}
public static void onWayStoneTeleport(WaystoneTeleportEvent.@NotNull Pre event) {
Entity entity = event.getContext().getEntity();
ILeashHelper.IHolder holderHelper = Services.WORK_SPACE.getLeashHelper().getHolderHelper(entity);
public static void onWayStoneTeleport$Pre(WaystoneTeleportEvent.@NotNull Pre event) {
Entity telEntity = event.getContext().getEntity();
ILeashHelper.IHolder holderHelper = Services.WORK_SPACE.getLeashHelper().getHolderHelper(telEntity);
Set<Entity> allLeashedEntities = holderHelper.getAllLeashedEntities();
allLeashedEntities.forEach(event::addAdditionalEntity);
if(!SLPGameruleRegistry.getGameruleBoolValue(telEntity.level(), TeleportWithLeashedEntities.ID)) {
holderHelper.getAllLeashedEntities();
return;
}
ServerLevel level = event.getContext().getDestination().getLevel();
Vec3 destination = event.getContext().getDestination().getLocation();
Set<Pair<Entity, OriginalState>> set = new HashSet<>();
for (Entity beLeashedEntity : allLeashedEntities) {
// --- 保存状态快照 ---
if (MinecraftForge.EVENT_BUS.post(new SuperLeadRopeEvent.teleportWithHolder(beLeashedEntity, telEntity, beLeashedEntity.level(), level, beLeashedEntity.position(), destination)))
continue;
Pose originalPose = beLeashedEntity.getPose();
boolean originalIsSprinting = beLeashedEntity.isSprinting();
float originalYaw = beLeashedEntity.getYRot();
float originalPitch = beLeashedEntity.getXRot();
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
AtomicReference<LeashInfo> originalLeashInfo = new AtomicReference<>();
LeashDataInnerAPI.getLeashData(beLeashedEntity).ifPresent(data -> {
originalLeashInfo.set(data.getLeashInfo(telEntity).orElse(null));
data.removeLeash(telEntity);
});
// --- 保存骑乘关系可修改列表 ---
RidingRelationship originalRidingRelationship = RidingSaver.save(beLeashedEntity, true);
// --- 解除骑乘 ---
List<Entity> allPassengers = RidingFinder.getEntityFromRidingShip(originalRidingRelationship, level::getEntity);
RidingDismounts.dismountEntities(allPassengers);
set.add(Pair.of(beLeashedEntity, new OriginalState(
originalPose,
originalIsSprinting,
originalYaw,
originalPitch,
originalDeltaMovement,
originalLeashInfo.get(),
originalRidingRelationship
)
)
);
}
tempLeashMap.put(telEntity.getUUID(), set);
}
public static void onWayStoneTeleport$Post(WaystoneTeleportEvent.@NotNull Post event) {
Entity telEntity = event.getContext().getEntity();
ServerLevel serverLevel = event.getContext().getDestination().getLevel();
Vec3 destination = event.getContext().getDestination().getLocation();
Set<Pair<Entity, OriginalState>> set = tempLeashMap.get(telEntity.getUUID());
Set<UUID> shouldBeRemoved = new HashSet<>();
if (set != null) {
HashSet<Pair<Entity, OriginalState>> newSet = new HashSet<>();
// --- 传送实体及乘客 ---
for (Pair<Entity, OriginalState> entityPair : set) {
Entity beLeashedEntity = entityPair.first;
Entity newEntity = TeleportUtil.teleportEntity(beLeashedEntity, serverLevel, destination, beLeashedEntity.getDirection());
if (!beLeashedEntity.getUUID().equals(newEntity.getUUID())){
uuidMap.put(beLeashedEntity.getUUID(), newEntity.getUUID());
}
newSet.add(Pair.of(newEntity, entityPair.second));
}
for (Pair<Entity, OriginalState> entityPair : newSet) {
// --- 恢复状态 ---
Entity beLeashedEntity = entityPair.first;
OriginalState originalState = entityPair.second;
LeashStateInnerAPI.getLeashState(beLeashedEntity).ifPresent(LeashSyncManager.State::track);
LeashDataInnerAPI.getLeashData(beLeashedEntity).ifPresent(LeashSyncManager.Data::track);
beLeashedEntity.setDeltaMovement(originalState.deltaMovement);
beLeashedEntity.setPose(originalState.pose);
beLeashedEntity.setSprinting(originalState.isSprinting);
// --- 将holder替换 ---
LeashInfo leashInfo = Optional.ofNullable(originalState.leashInfo)
.orElse(LeashInfo.EMPTY);
if (leashInfo.holderUUIDOpt().isPresent() && uuidMap.containsKey(leashInfo.holderUUIDOpt().get())) {
leashInfo.transferHolder(beLeashedEntity);
shouldBeRemoved.add(leashInfo.holderUUIDOpt().get());
}
LeashDataInnerAPI.LeashOperations.attachWithInfo(beLeashedEntity, telEntity, leashInfo);
// --- 重新应用骑乘关系仅保留白名单根载具 ---
uuidMap.forEach((oldUUID, newUUID) -> {
int andReplaceAll = originalState.ridingRelationship.findAndReplaceAll(oldUUID, newUUID);
if (andReplaceAll != 0) shouldBeRemoved.add(oldUUID);
});
RidingRelationship filteredRelationship = RidingSaver.filterByWhitelistRoot(originalState.ridingRelationship);
RidingApplier.applyRidingRelationship(filteredRelationship, serverLevel::getEntity);
}
shouldBeRemoved.forEach(uuidMap::remove);
tempLeashMap.remove(telEntity.getUUID());
}
}
}

View File

@ -55,6 +55,7 @@ public class LeashCommonConfig {
*/
// Leash settings
public final ForgeConfigSpec.DoubleValue maxLeashLength;
public final ForgeConfigSpec.DoubleValue maxMovement;
/**
* The Elastic distance scale.
*/
@ -155,7 +156,9 @@ public class LeashCommonConfig {
maxLeashLength = builder
.comment("Maximum leash distance (in blocks) for any entity")
.defineInRange("maxLeashLength", 6.0, LeashConfigManager.MAX_DISTANCE_MIN_VALUE, LeashConfigManager.MAX_DISTANCE_MAX_VALUE);
maxMovement = builder
.comment("Defines the maximum acceleration in standard coordinate directions (X/Y/Z axes)")
.defineInRange("maxMovement", 100.0, 10.0, Double.MAX_VALUE);
elasticDistanceScale = builder
.comment("Default elastic distance for the Super Lead rope")
.defineInRange("elasticDistanceScale", 1.0, LeashConfigManager.ELASTIC_DISTANCE_MIN_VALUE, LeashConfigManager.ELASTIC_DISTANCE_MAX_VALUE);
@ -200,6 +203,8 @@ public class LeashCommonConfig {
.defineInRange("mobSpringFactor", 0.5, 0.05, 2.0);
builder.pop();
// ===== Leash State Offsets =====
builder.push("LeashStateSettings");
defaultApplyEntityLocationOffset = builder

View File

@ -88,6 +88,7 @@ public class LeashConfigManager {
private volatile double mobSpringFactor = 0.5;
private volatile double maxLeashLength = 12.0;
private volatile double maxMovement = 100.0;
private volatile double elasticDistanceScale = 1.0;
private volatile double extremeSnapFactor = 2.0;
private volatile double springDampening = 0.7;
@ -318,6 +319,7 @@ public class LeashConfigManager {
* @return the max leash length
*/
public double getMaxLeashLength() { return maxLeashLength; }
public double getMaxMovement() { return maxMovement; }
/**
* Gets elastic distance scale.
@ -399,6 +401,7 @@ public class LeashConfigManager {
commandPrefixEnabledCache = LeashCommonConfig.COMMON.enableSLPModCommandPrefix.get();
maxLeashLength = LeashCommonConfig.COMMON.maxLeashLength.get();
maxMovement = LeashCommonConfig.COMMON.maxMovement.get();
elasticDistanceScale = LeashCommonConfig.COMMON.elasticDistanceScale.get();
extremeSnapFactor = LeashCommonConfig.COMMON.extremeSnapFactor.get();
springDampening = LeashCommonConfig.COMMON.springDampening.get();
@ -489,6 +492,7 @@ public class LeashConfigManager {
tag.putDouble("mob_spring_factor", mobSpringFactor);
tag.putDouble("max_leash_length", maxLeashLength);
tag.putDouble("max_movement", maxMovement);
tag.putDouble("elastic_distance_scale", elasticDistanceScale);
tag.putDouble("extreme_snap_factor", extremeSnapFactor);
tag.putDouble("spring_dampening", springDampening);
@ -524,6 +528,7 @@ public class LeashConfigManager {
// ========== 更新物理参数 ==========
LeashCommonConfig.COMMON.maxLeashLength.set(maxLeashLength);
LeashCommonConfig.COMMON.maxMovement.set(maxMovement);
LeashCommonConfig.COMMON.elasticDistanceScale.set(elasticDistanceScale);
LeashCommonConfig.COMMON.extremeSnapFactor.set(extremeSnapFactor);
LeashCommonConfig.COMMON.springDampening.set(springDampening);
@ -641,6 +646,9 @@ public class LeashConfigManager {
if (tag.contains("max_leash_length", Tag.TAG_DOUBLE)) {
maxLeashLength = tag.getDouble("max_leash_length");
}
if (tag.contains("max_movement", Tag.TAG_DOUBLE)) {
maxLeashLength = tag.getDouble("max_movement");
}
if (tag.contains("elastic_distance_scale", Tag.TAG_DOUBLE)) {
elasticDistanceScale = tag.getDouble("elastic_distance_scale");
}
@ -696,6 +704,7 @@ public class LeashConfigManager {
hash = fnv1aHashLong(hash, Double.doubleToLongBits(playerSpringFactor));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(mobSpringFactor));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(maxLeashLength));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(maxMovement));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(elasticDistanceScale));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(extremeSnapFactor));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(springDampening));

View File

@ -0,0 +1,144 @@
package top.r3944realms.superleadrope.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("deprecation")
public class DollBlock extends HorizontalDirectionalBlock implements SimpleWaterloggedBlock {
private static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private static final VoxelShape DOLL_SHAPE = Block.box(2.0d, 0.0d, 2.0d, 14.0d, 12.0d, 14.0d);
private static final double PARTICLE_OFFSET_RANGE = 0.25;
private static final double PARTICLE_HEIGHT_OFFSET = 1.0;
private static final double PARTICLE_HEIGHT_VARIANCE = 0.2;
private static final float NOTE_COLOR_DIVISOR = 24.0F;
private static final int MAX_NOTE_COLORS = 4;
private static final float BASE_VOLUME = 1.0f;
private static final float PITCH_VARIANCE = 0.5f;
private static final float BASE_PITCH = 0.75f;
public DollBlock() {
super(BlockBehaviour.Properties.of()
.instrument(NoteBlockInstrument.BASEDRUM)
.sound(SoundType.WOOL)
.strength(0f, 10f)
.noOcclusion());
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.SOUTH)
.setValue(WATERLOGGED, false));
}
@Override
public @NotNull BlockState updateShape(@NotNull BlockState currentState, @NotNull Direction direction, @NotNull BlockState neighborState,
@NotNull LevelAccessor level, @NotNull BlockPos currentPos, @NotNull BlockPos neighborPos) {
if (currentState.getValue(WATERLOGGED)) {
level.scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
return super.updateShape(currentState, direction, neighborState, level, currentPos, neighborPos);
}
@Override
public @NotNull FluidState getFluidState(@NotNull BlockState blockState) {
return blockState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(blockState);
}
@Override
public @NotNull InteractionResult use(@NotNull BlockState blockState, @NotNull Level level, @NotNull BlockPos blockPos, @NotNull Player player,
@NotNull InteractionHand hand, @NotNull BlockHitResult hitResult) {
if (level instanceof ServerLevel serverLevel) {
// 播放粒子效果
spawnNoteParticles(serverLevel, blockPos);
// 播放音效
playDollSound(serverLevel, blockPos);
}
return InteractionResult.SUCCESS;
}
/**
* 在玩偶位置生成音符粒子效果
*/
private void spawnNoteParticles(ServerLevel serverLevel, BlockPos blockPos) {
Vec3 particlePosition = calculateParticlePosition(serverLevel, blockPos);
float noteColor = calculateNoteColor(serverLevel);
serverLevel.sendParticles(ParticleTypes.NOTE,
particlePosition.x(), particlePosition.y(), particlePosition.z(),
0, noteColor, 0, 0, 1);
}
/**
* 计算粒子生成位置添加随机偏移
*/
private @NotNull Vec3 calculateParticlePosition(@NotNull ServerLevel serverLevel, BlockPos blockPos) {
return Vec3.atBottomCenterOf(blockPos).add(
(serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2,
PARTICLE_HEIGHT_OFFSET + serverLevel.getRandom().nextFloat() * PARTICLE_HEIGHT_VARIANCE,
(serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2
);
}
/**
* 计算音符粒子的颜色
*/
private float calculateNoteColor(@NotNull ServerLevel serverLevel) {
return serverLevel.getRandom().nextInt(MAX_NOTE_COLORS) / NOTE_COLOR_DIVISOR;
}
/**
* 播放玩偶音效
*/
private void playDollSound(@NotNull ServerLevel serverLevel, BlockPos blockPos) {
float pitch = BASE_PITCH + serverLevel.random.nextFloat() * PITCH_VARIANCE;
serverLevel.playSound(null, blockPos, SoundEvents.NOTE_BLOCK_BASEDRUM.get(),
SoundSource.BLOCKS, BASE_VOLUME, pitch);
}
@Override
public @Nullable BlockState getStateForPlacement(@NotNull BlockPlaceContext context) {
FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos());
boolean isWaterlogged = fluidState.getType() == Fluids.WATER;
return this.defaultBlockState()
.setValue(FACING, context.getHorizontalDirection().getOpposite())
.setValue(WATERLOGGED, isWaterlogged);
}
@Override
public @NotNull VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter level, @NotNull BlockPos blockPos, @NotNull CollisionContext context) {
return DOLL_SHAPE;
}
@Override
protected void createBlockStateDefinition(StateDefinition.@NotNull Builder<Block, BlockState> builder) {
builder.add(FACING, WATERLOGGED);
}
}

View File

@ -64,6 +64,7 @@ import java.util.stream.Stream;
/**
* 预期行为
* <pre>
* <table border="1">
* <thead>
* <tr>
@ -90,6 +91,7 @@ import java.util.stream.Stream;
* </tr>
* </tbody>
* </table>
* </pre>
*/
@SuppressWarnings("DuplicatedCode")
public class LeashDataImpl implements ILeashData {
@ -804,7 +806,24 @@ public class LeashDataImpl implements ILeashData {
boolean hasForce = !combinedForce.equals(Vec3.ZERO);
Entity targetEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
if(targetEntity instanceof LocalPlayer && hasForce){
entity.addDeltaMovement(combinedForce);
entity.addDeltaMovement(limitMovement(combinedForce));
}
}
public Vec3 limitMovement(@NotNull Vec3 movement) {
double maxMovement = CommonEventHandler.leashConfigManager.getMaxMovement();
return new Vec3(
clamp(movement.x, maxMovement),
clamp(movement.y, maxMovement),
clamp(movement.z, maxMovement)
);
}
private double clamp(double value, double max) {
if (value > 0) {
return Math.min(value, max);
} else {
return Math.max(value, -max);
}
}
@ -842,6 +861,7 @@ public class LeashDataImpl implements ILeashData {
boolean hasForce = !combinedForce.equals(Vec3.ZERO);
Entity targetEntity = RindingLeash.getFinalEntityForLeashIfForce(entity, hasForce);
if (targetEntity != null && hasForce) {
combinedForce = limitMovement(combinedForce);
SuperLeadRopeEvent.hasFocus hasFocus = new SuperLeadRopeEvent.hasFocus(this.entity, targetEntity, combinedForce, vaildLeashHolders, vaildLeashKnots);
if (MinecraftForge.EVENT_BUS.post(hasFocus)) return;
combinedForce = hasFocus.getCombinedForce();
@ -850,11 +870,11 @@ public class LeashDataImpl implements ILeashData {
// 是真实玩家则交给客户端自行处理拴绳逻辑
// DO NOTHING
if(targetEntity != entity){
NetworkHandler.sendToPlayer(new UpdatePlayerMovementPacket(UpdatePlayerMovementPacket.Operation.ADD, combinedForce), player);
NetworkHandler.sendToPlayer(new UpdatePlayerMovementPacket(UpdatePlayerMovementPacket.Operation.ADD, limitMovement(combinedForce)), player);
}
return;
} else {
applyForceToNonPlayerEntity(targetEntity, combinedForce, validLeashes, combinedDirection);
applyForceToNonPlayerEntity(targetEntity, limitMovement(combinedForce), validLeashes, combinedDirection);
}
}

View File

@ -0,0 +1,40 @@
package top.r3944realms.superleadrope.content.effect;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.LivingEntity;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.api.type.capabilty.ILeashData;
import top.r3944realms.superleadrope.core.register.SLPEffects;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI;
import java.util.Optional;
public class NoSuperLeashEffect extends MobEffect {
public NoSuperLeashEffect(MobEffectCategory category, int color) {
super(category, color);
}
@Override
public void applyEffectTick(@NotNull LivingEntity pLivingEntity, int pAmplifier) {
MobEffectInstance effect = pLivingEntity.getEffect(SLPEffects.NO_SUPER_LEASH_EFFECT.get());
if(effect != null && effect.getDuration() != 0) {
Optional<ILeashData> leashData = LeashDataInnerAPI.getLeashData(pLivingEntity);
if (leashData.isPresent() && leashData.get().hasLeash()) {
LeashDataInnerAPI.LeashOperations.detachAll(pLivingEntity);
pLivingEntity.level().playSound(null,
pLivingEntity.getOnPos(),
SLPSoundEvents.LEAD_UNTIED.get(),
SoundSource.AMBIENT
);
}
}
}
@Override
public boolean isDurationEffectTick(int duration, int amplifier) {
return duration >= 1;
}
}

View File

@ -0,0 +1,23 @@
package top.r3944realms.superleadrope.content.item;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.superleadrope.core.register.SLPBlocks;
import java.util.List;
public class DollItem extends BlockItem {
public DollItem(Properties properties) {
super(SLPBlocks.DOLL.get(), properties);
}
@Override
public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, List<Component> tooltip, @NotNull TooltipFlag flag) {
tooltip.add(Component.translatable("tooltip.superleadrope.author"));
}
}

View File

@ -20,6 +20,7 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@ -34,6 +35,7 @@ import top.r3944realms.superleadrope.api.SuperLeadRopeApi;
import top.r3944realms.superleadrope.api.type.capabilty.ILeashData;
import top.r3944realms.superleadrope.content.SLPToolTier;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.core.register.SLPEffects;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI;
@ -195,6 +197,10 @@ public class SuperLeadRopeItem extends TieredItem implements IForgeItem {
// 情况一拴自己到新 knot
if (shouldBindSelf && list.isEmpty()) {
MobEffectInstance effect = player.getEffect(SLPEffects.NO_SUPER_LEASH_EFFECT.get());
if (effect != null && effect.getDuration() >= 1) {
return false;
}
if (leashStack.isEmpty() || !canUse(leashStack)) {
return false;
}

View File

@ -20,7 +20,9 @@ import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@ -31,6 +33,7 @@ import top.r3944realms.superleadrope.api.SuperLeadRopeApi;
import top.r3944realms.superleadrope.api.type.capabilty.ILeashData;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.register.SLPEffects;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.register.SLPSoundEvents;
import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI;
@ -53,7 +56,6 @@ public class LeashInteractHandler {
//只有玩家可以互动触发其它的暂不支持考虑到0 Mixin)
public static void onEntityRightInteract(Level level, InteractionHand hand, Entity target , Player player, PlayerInteractEvent.EntityInteract event) {
//WARNING: 主手和副手都会触发一次该事件
// ===== 卫语句 =====
if (level.isClientSide) {
if (hand == InteractionHand.MAIN_HAND &&
@ -63,6 +65,12 @@ public class LeashInteractHandler {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
}
if (target instanceof LivingEntity livingEntity) { //禁止拴绳效果存在则取消下面的逻辑
MobEffectInstance effect = livingEntity.getEffect(SLPEffects.NO_SUPER_LEASH_EFFECT.get());
if (effect != null && effect.getDuration() >= 1) {
return;
}
}
if (SuperLeadRopeApi.isLeashHolder(target, player)) {
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
@ -97,6 +105,12 @@ public class LeashInteractHandler {
event.setCancellationResult(InteractionResult.SUCCESS);
}
} else {
if (target instanceof LivingEntity livingEntity) { //禁止拴绳效果存在则取消下面的逻辑
MobEffectInstance effect = livingEntity.getEffect(SLPEffects.NO_SUPER_LEASH_EFFECT.get());
if (effect != null && effect.getDuration() >= 1) {
return;
}
}
if (SuperLeadRopeApi.isLeashHolder(target, player)) {
LeashCap.ifPresent(
iLeashDataCapability -> iLeashDataCapability.removeLeash(player.getUUID())

View File

@ -0,0 +1,22 @@
package top.r3944realms.superleadrope.core.register;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.eventbus.api.IEventBus;
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.block.DollBlock;
import java.util.Collection;
public class SLPBlocks {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, SuperLeadRope.MOD_ID);
public static final RegistryObject<Block> DOLL = BLOCKS.register("doll", DollBlock::new);
public static Collection<RegistryObject<Block>> getEntries() {
return BLOCKS.getEntries();
}
public static void register(IEventBus eventBus) {
BLOCKS.register(eventBus);
}
}

View File

@ -0,0 +1,33 @@
package top.r3944realms.superleadrope.core.register;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.content.effect.NoSuperLeashEffect;
import java.util.function.Supplier;
public class SLPEffects {
public static DeferredRegister<MobEffect> MOB_EFFECT = DeferredRegister.create(Registries.MOB_EFFECT, SuperLeadRope.MOD_ID);
public static RegistryObject<MobEffect> NO_SUPER_LEASH_EFFECT = register(
"no_super_leash",
() -> new NoSuperLeashEffect(MobEffectCategory.NEUTRAL, 12063764)
);
public static <T extends MobEffect> RegistryObject<MobEffect> register(String name, Supplier<T> effect) {
return MOB_EFFECT.register(name, effect);
}
public static String getEffectKey(MobEffect effect) {
return effect.getDescriptionId();
}
public static String getModEffectKey(RegistryObject<MobEffect> effect) {
return getEffectKey(effect.get());
}
public static void register(IEventBus eventBus) {
MOB_EFFECT.register(eventBus);
}
}

View File

@ -16,11 +16,13 @@
package top.r3944realms.superleadrope.core.register;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Rarity;
import net.minecraftforge.eventbus.api.IEventBus;
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.DollItem;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
@ -49,6 +51,15 @@ public class SLPItems {
.stacksTo(1) // 只能有一颗
.fireResistant() // 防火
));
public static final RegistryObject<Item> DOLL =
ITEMS.register("doll",
() -> new DollItem(
new Item.Properties()
.stacksTo(1)
.fireResistant()
.rarity(Rarity.EPIC)
)
);
/**
* Register.

View File

@ -0,0 +1,59 @@
package top.r3944realms.superleadrope.core.register;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.Potion;
import net.minecraft.world.item.alchemy.PotionUtils;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraftforge.common.brewing.BrewingRecipe;
import net.minecraftforge.common.brewing.BrewingRecipeRegistry;
import net.minecraftforge.registries.RegistryObject;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
public class SLPPotionRecipeRegistry {
public static class ProperBrewingRecipe extends BrewingRecipe {
private final Ingredient input;
public ProperBrewingRecipe(Ingredient input, Ingredient ingredient, ItemStack output) {
super(input, ingredient, output);
this.input = input;
}
@Override
public boolean isInput(@NotNull ItemStack stack) {
ItemStack[] matchingStacks = input.getItems();
if (matchingStacks.length == 0) {
return stack.isEmpty();
} else {
for (ItemStack itemstack : matchingStacks) {
if (ItemStack.isSameItem(stack, itemstack) && ItemStack.isSameItemSameTags(itemstack, stack)) {
return true;
}
}
return false;
}
}
}
@Contract("_ -> new")
public static @NotNull ItemStack createPotion(@NotNull RegistryObject<Potion> potion){
return PotionUtils.setPotion(new ItemStack(Items.POTION), potion.get());
}
@Contract("_ -> new")
public static @NotNull ItemStack createPotion(Potion potion){
return PotionUtils.setPotion(new ItemStack(Items.POTION), potion);
}
public static void init() {
BrewingRecipeRegistry.addRecipe(
new ProperBrewingRecipe(Ingredient.of(createPotion(Potions.INVISIBILITY)), Ingredient.of(Items.SLIME_BALL), createPotion(SLPPotions.NO_SUPER_LEASH)));
BrewingRecipeRegistry.addRecipe(
new ProperBrewingRecipe(Ingredient.of(createPotion(Potions.LONG_INVISIBILITY)), Ingredient.of(Items.SLIME_BALL), createPotion(SLPPotions.LONG_NO_SUPER_LEASH)));
BrewingRecipeRegistry.addRecipe(
new ProperBrewingRecipe(Ingredient.of(createPotion(SLPPotions.NO_SUPER_LEASH)), Ingredient.of(Items.REDSTONE), createPotion(SLPPotions.LONG_NO_SUPER_LEASH)));
}
}

View File

@ -0,0 +1,44 @@
package top.r3944realms.superleadrope.core.register;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.item.alchemy.Potion;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import top.r3944realms.superleadrope.SuperLeadRope;
import java.util.function.Supplier;
public class SLPPotions {
public static DeferredRegister<Potion> POTIONS = DeferredRegister.create(Registries.POTION, SuperLeadRope.MOD_ID);
public static final RegistryObject<Potion> NO_SUPER_LEASH = register("no_super_leash",
() -> new Potion("no_super_leash", new MobEffectInstance(SLPEffects.NO_SUPER_LEASH_EFFECT.get(), 1200, 0))
);
public static final RegistryObject<Potion> LONG_NO_SUPER_LEASH = register("long_no_super_leash",
() -> new Potion("no_super_leash", new MobEffectInstance(SLPEffects.NO_SUPER_LEASH_EFFECT.get(), 3600, 0))
);
public static <T extends Potion>RegistryObject<Potion> register(String Name, Supplier<T> supplier) {
return POTIONS.register(Name, supplier);
}
/**
*
* @param name the Name of Potion
* @param type (char)<br/> [ <br/> 0 & 3 ~ 255 : potion <br/>1 : lingering_potion <br/>2 : splash_potion<br/>]
* @return Language Key
*/
public static String getPotionNameKey(String name, char type) {
return "item.minecraft." +
(type == 1 ? "lingering_potion" :
(type == 2 ? "splash_potion" : "potion")
)
+ ".effect." + name;
}
public static String getTippedArrowNameKey(String Name) {
return "item.minecraft.tipped_arrow.effect." + Name;
}
public static void register(IEventBus eventBus) {
POTIONS.register(eventBus);
}
}

View File

@ -54,7 +54,9 @@ public class SLPDataGenEvent {
LanguageGenerator(event, LanguageEnum.LiteraryChinese);
RecipeGenerator(event);
ModelDataGenerate(event);
BlockStateGenerate(event);
TagsProvider(event, lookupProvider);
LootTableGenerate(event);
SoundProvider(event);
}
private static void LanguageGenerator(GatherDataEvent event, LanguageEnum language) {
@ -97,4 +99,16 @@ public class SLPDataGenEvent {
(DataProvider.Factory<SLPItemModelProvider>) pOutput -> new SLPItemModelProvider(pOutput, event.getExistingFileHelper())
);
}
private static void LootTableGenerate(GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<SLPLootTableProvider>) SLPLootTableProvider::new
);
}
private static void BlockStateGenerate(GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<SLPBlockStateGenerator>) pOutput -> new SLPBlockStateGenerator(pOutput, event.getExistingFileHelper())
);
}
}

View File

@ -25,9 +25,7 @@ import top.r3944realms.superleadrope.content.command.MotionCommand;
import top.r3944realms.superleadrope.content.gamerule.server.CreateSuperLeashKnotEntityIfAbsent;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
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.core.register.*;
import top.r3944realms.superleadrope.util.lang.LanguageEnum;
import top.r3944realms.superleadrope.util.lang.ModPartEnum;
@ -917,6 +915,47 @@ public enum SLPLangKeyValue {
"成功觸發%s的力",
"%s之力今已發"
),
NO_LEASH_EFFECT(
SLPEffects.getEffectKey(SLPEffects.NO_SUPER_LEASH_EFFECT.get()), ModPartEnum.NAME,
"No Super Leash",
"禁拴",
"禁拴",
"禁系之效"
),
NO_LEASH_POTION(
SLPPotions.getPotionNameKey("no_super_leash", (char) 0), ModPartEnum.NAME,
"No Super Leash Potion",
"禁拴药水",
"禁拴藥水",
"禁系汤剂"
),
NO_LEASH_POTION_SPLASH(
SLPPotions.getPotionNameKey("no_super_leash", (char) 2), ModPartEnum.NAME,
"Splash No Super Leash Potion",
"喷溅型禁拴药水",
"噴濺型禁拴藥水",
"飞溅禁系汤"
),
NO_LEASH_POTION_LINGERING(
SLPPotions.getPotionNameKey("no_super_leash", (char) 1), ModPartEnum.NAME,
"Splash No Super Leash Potion",
"滞留型禁拴药水",
"滯留型禁拴藥水",
"缠绵禁系汤"
),
DOLL_BLOCK(
SLPBlocks.DOLL, ModPartEnum.BLOCK,
"Doll", "玩偶", "玩偶", "戲像"
),
DOLL_ITEM(
SLPItems.DOLL, ModPartEnum.ITEM,
"Doll", "玩偶", "玩偶", "戲像"
),
AUTHOR_TOOLTIP(
"tooltip.superleadrope.author", ModPartEnum.DESCRIPTION,
"Author : R3944Realms", "作者 : R3944Realms",
"作者 : R3944Realms", "作者 : R3944Realms"
)
;
private final Supplier<?> supplier;

View File

@ -0,0 +1,38 @@
/*
* *
* * Copyright (c) 2025 R3944Realms. All rights reserved.
* *
* * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
* * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
* *
* * 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可
*
*/
package top.r3944realms.superleadrope.datagen.provider;
import net.minecraft.data.loot.BlockLootSubProvider;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.registries.RegistryObject;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.core.register.SLPBlocks;
import java.util.Set;
public class SLPBlockLootTables extends BlockLootSubProvider {
public SLPBlockLootTables() {
super(Set.of(), FeatureFlags.REGISTRY.allFlags());
}
@Override
protected void generate() {
dropSelf(SLPBlocks.DOLL.get());
}
@Override
protected @NotNull Iterable<Block> getKnownBlocks() {
return SLPBlocks.getEntries().stream().map(RegistryObject::get)::iterator;
}
}

View File

@ -0,0 +1,18 @@
package top.r3944realms.superleadrope.datagen.provider;
import net.minecraft.data.PackOutput;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.common.data.ExistingFileHelper;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.core.register.SLPBlocks;
public class SLPBlockStateGenerator extends BlockStateProvider {
public SLPBlockStateGenerator(PackOutput output, ExistingFileHelper exFileHelper) {
super(output, SuperLeadRope.MOD_ID, exFileHelper);
}
@Override
protected void registerStatesAndModels() {
horizontalBlock(SLPBlocks.DOLL.get(), models().getExistingFile(modLoc("block/doll")));
}
}

View File

@ -24,6 +24,7 @@ import net.minecraftforge.client.model.generators.ModelFile;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.registries.ForgeRegistries;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
import java.util.ArrayList;
@ -35,6 +36,7 @@ import java.util.Objects;
*/
public class SLPItemModelProvider extends ItemModelProvider {
private static List<Item> objectList;
public static final ResourceLocation DOLL = new ResourceLocation(SuperLeadRope.MOD_ID, "block/doll");
/**
* The constant GENERATED.
*/
@ -60,6 +62,7 @@ public class SLPItemModelProvider extends ItemModelProvider {
protected void registerModels() {
DefaultModItemModelRegister();
superLeadRopeModel();
generateDollItemModel();
}
private void init() {
for(SLPLangKeyValue obj : SLPLangKeyValue.values()) {
@ -93,6 +96,10 @@ public class SLPItemModelProvider extends ItemModelProvider {
public void itemHandHeldModel(Item item, ResourceLocation location){
withExistingParent(itemName(item), HANDHELD).texture("layer0", location);
}
private void generateDollItemModel() {
withExistingParent(itemName(SLPItems.DOLL.get()), DOLL);
}
/**
* Super lead rope model.

View File

@ -0,0 +1,37 @@
package top.r3944realms.superleadrope.datagen.provider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.ValidationContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The type Simple loot table provider.
*/
public class SLPLootTableProvider extends LootTableProvider {
/**
* Instantiates a new Simple loot table provider.
*
* @param output the output
* @param subProvidersWrapper the sub providers wrapper
*/
public SLPLootTableProvider(PackOutput output) {
super(output, Set.of(), List.of(new LootTableProvider.SubProviderEntry(
SLPBlockLootTables::new,
LootContextParamSets.BLOCK
)));
}
@Override
protected void validate(@NotNull Map<ResourceLocation, LootTable> map, @NotNull ValidationContext validationcontext) {
map.forEach((id, table) -> table.validate(validationcontext));
}
}

View File

@ -0,0 +1,128 @@
package top.r3944realms.superleadrope.util.entity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.*;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import net.minecraft.server.level.TicketType;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
public class TeleportUtil {
/**
* 通用实体传送支持玩家普通生物跨维度生物停飞导航停止等
*/
public static @NotNull Entity teleportEntity(Entity oldEntity, ServerLevel targetWorld, @NotNull Vec3 targetPos, @NotNull Direction direction) {
float yaw = direction.toYRot();
double x = targetPos.x;
double y = targetPos.y;
double z = targetPos.z;
if (oldEntity instanceof ServerPlayer player) {
teleportPlayer(player, targetWorld, x, y, z, yaw);
} else {
oldEntity = teleportNonPlayer(oldEntity, targetWorld, x, y, z, yaw);
}
// 停止滑翔否则 Elytra 无限飞
if (!(oldEntity instanceof LivingEntity living) || !living.isFallFlying()) {
oldEntity.setDeltaMovement(oldEntity.getDeltaMovement().multiply(1.0, 0.0, 1.0));
oldEntity.setOnGround(true);
}
// 停止 AI 导航避免卡行为树
if (oldEntity instanceof PathfinderMob mob) {
mob.getNavigation().stop();
}
sendHackySyncPacketsAfterTeleport(oldEntity);
return oldEntity;
}
// --- 分离后的子方法 ------------------------------------------------
private static void teleportPlayer(@NotNull ServerPlayer player, @NotNull ServerLevel targetWorld,
double x, double y, double z, float yaw) {
ChunkPos chunkPos = new ChunkPos(BlockPos.containing(x, y, z));
targetWorld.getChunkSource().addRegionTicket(
TicketType.POST_TELEPORT,
chunkPos,
1,
player.getId()
);
// 玩家睡觉状态处理
if (player.isSleeping()) {
player.stopSleepInBed(true, true);
}
player.stopRiding();
// 同维度直接 connection.teleport
if (targetWorld == player.level()) {
player.connection.teleport(x, y, z, yaw, player.getXRot(), Collections.emptySet());
} else {
player.teleportTo(targetWorld, x, y, z, yaw, player.getXRot());
}
player.setYHeadRot(yaw);
}
private static @NotNull Entity teleportNonPlayer(@NotNull Entity entity, ServerLevel targetWorld,
double x, double y, double z, float yaw) {
float pitch = Mth.clamp(entity.getXRot(), -90, 90);
// 同维度移动
if (targetWorld == entity.level()) {
entity.moveTo(x, y, z, yaw, pitch);
entity.setYHeadRot(yaw);
return entity;
}
// 跨维度传送
entity.unRide();
Entity newEntity = entity.getType().create(targetWorld);
if (newEntity == null) {
return entity;
}
newEntity.restoreFrom(entity);
newEntity.moveTo(x, y, z, yaw, pitch);
newEntity.setYHeadRot(yaw);
entity.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
targetWorld.addDuringTeleport(newEntity);
return newEntity;
}
/**
* 修复玩家经验条显示等问题的同步包
*/
private static void sendHackySyncPacketsAfterTeleport(Entity entity) {
if (entity instanceof ServerPlayer player) {
player.connection.send(
new ClientboundSetExperiencePacket(
player.experienceProgress,
player.totalExperience,
player.experienceLevel
)
);
}
}
}

View File

@ -15,6 +15,8 @@
package top.r3944realms.superleadrope.util.model;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
@ -138,6 +140,121 @@ public class RidingRelationship {
}
return false;
}
/**
* 查找并替换所有匹配的UUID
*
* @param oldUuid 要查找的旧UUID
* @param newUuid 要替换的新UUID
* @return 替换的数量
*/
public int findAndReplaceAll(UUID oldUuid, UUID newUuid) {
int replacedCount = 0;
// 替换当前节点的entityId
if (Objects.equals(this.entityId, oldUuid)) {
this.entityId = newUuid;
replacedCount++;
}
// 替换当前节点的vehicleId
if (Objects.equals(this.vehicleId, oldUuid)) {
this.vehicleId = newUuid;
replacedCount++;
}
// 递归替换所有乘客
for (RidingRelationship passenger : passengers) {
replacedCount += passenger.findAndReplaceAll(oldUuid, newUuid);
}
return replacedCount;
}
/**
* 查找所有出现的UUID位置
*
* @param targetUuid 要查找的UUID
* @return 包含位置的列表格式为"角色(实体/载具)-索引"
*/
public List<String> findAllOccurrences(UUID targetUuid) {
List<String> occurrences = new ArrayList<>();
findAllOccurrencesRecursive(targetUuid, this, occurrences, "");
return occurrences;
}
private void findAllOccurrencesRecursive(UUID targetUuid, @NotNull RidingRelationship node,
List<String> occurrences, String path) {
// 检查当前节点的entityId
if (Objects.equals(node.entityId, targetUuid)) {
String fullPath = path.isEmpty() ? "根实体" : path + "->乘客";
occurrences.add(fullPath + "(实体ID)");
}
// 检查当前节点的vehicleId
if (Objects.equals(node.vehicleId, targetUuid)) {
String fullPath = path.isEmpty() ? "根实体" : path;
occurrences.add(fullPath + "(载具ID)");
}
// 递归检查乘客
for (int i = 0; i < node.passengers.size(); i++) {
String newPath = path.isEmpty() ? "乘客[" + i + "]" : path + "->乘客[" + i + "]";
findAllOccurrencesRecursive(targetUuid, node.passengers.get(i), occurrences, newPath);
}
}
/**
* 查找并替换所有UUID支持批量替换
*
* @param replacements 替换映射表key为旧UUIDvalue为新UUID
* @return 总替换数量
*/
public int batchFindAndReplace(Map<UUID, UUID> replacements) {
int totalReplaced = 0;
// 替换当前节点的entityId
if (this.entityId != null && replacements.containsKey(this.entityId)) {
this.entityId = replacements.get(this.entityId);
totalReplaced++;
}
// 替换当前节点的vehicleId
if (this.vehicleId != null && replacements.containsKey(this.vehicleId)) {
this.vehicleId = replacements.get(this.vehicleId);
totalReplaced++;
}
// 递归批量替换所有乘客
for (RidingRelationship passenger : passengers) {
totalReplaced += passenger.batchFindAndReplace(replacements);
}
return totalReplaced;
}
/**
* 获取树中所有唯一的UUID集合
*
* @return 包含所有UUID的集合
*/
public Set<UUID> getAllUniqueUUIDs() {
Set<UUID> uuids = new HashSet<>();
getAllUniqueUUIDsRecursive(this, uuids);
return uuids;
}
private void getAllUniqueUUIDsRecursive(RidingRelationship node, Set<UUID> uuids) {
if (node.entityId != null) {
uuids.add(node.entityId);
}
if (node.vehicleId != null) {
uuids.add(node.vehicleId);
}
for (RidingRelationship passenger : node.passengers) {
getAllUniqueUUIDsRecursive(passenger, uuids);
}
}
@Override
public String toString() {

View File

@ -17,7 +17,10 @@ package top.r3944realms.superleadrope.util.riding;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.core.exception.RidingCycleException;
import top.r3944realms.superleadrope.core.util.ImmutablePair;
import top.r3944realms.superleadrope.util.model.RidingRelationship;
@ -36,7 +39,8 @@ public class RidingSaver {
* @param entity the entity
* @return the riding relationship
*/
public static RidingRelationship save(@Nullable Entity entity) {
@Contract("null -> new")
public static @NotNull RidingRelationship save(@Nullable Entity entity) {
return save(entity, true);
}
@ -47,7 +51,8 @@ public class RidingSaver {
* @param findRoot the find root
* @return the riding relationship
*/
public static RidingRelationship save(@Nullable Entity entity, boolean findRoot) {
@Contract("null, _ -> new")
public static @NotNull RidingRelationship save(@Nullable Entity entity, boolean findRoot) {
if (entity == null) {
return new RidingRelationship(Collections.emptyList(), null, null);
}
@ -118,33 +123,37 @@ public class RidingSaver {
*/
public static RidingRelationship filterByWhitelistRoot(RidingRelationship relationship) {
if (relationship == null) return null;
// 如果当前根节点在白名单则直接处理子节点
if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(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 (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(Objects.requireNonNull(getEntityType(child.getEntityId())))) {
// 设置父节点为当前节点的父倒二叉逻辑
RidingRelationship newRoot = new RidingRelationship();
newRoot.setEntityId(child.getEntityId());
newRoot.setVehicleId(relationship.getVehicleId());
newRoot.setPassengers(filterPassengers(child.getPassengers()));
return newRoot;
try {
// 如果当前根节点在白名单则直接处理子节点
if (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(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 (CommonEventHandler.leashConfigManager.isEntityTeleportAllowed(Objects.requireNonNull(getEntityType(child.getEntityId())))) {
// 设置父节点为当前节点的父倒二叉逻辑
RidingRelationship newRoot = new RidingRelationship();
newRoot.setEntityId(child.getEntityId());
newRoot.setVehicleId(relationship.getVehicleId());
newRoot.setPassengers(filterPassengers(child.getPassengers()));
return newRoot;
}
}
}
} catch (NullPointerException e) {
SuperLeadRope.logger.error("Catch null", e);
}
// 如果整个子树都不在白名单返回空关系
return new RidingRelationship(new ArrayList<>(), null, null);
}
private static List<RidingRelationship> filterPassengers(List<RidingRelationship> passengers) {
@Contract("null -> new")
private static @NotNull List<RidingRelationship> filterPassengers(List<RidingRelationship> passengers) {
if (passengers == null || passengers.isEmpty()) return new ArrayList<>();
List<RidingRelationship> filtered = new ArrayList<>();
for (RidingRelationship passenger : passengers) {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,291 @@
{
"format_version": "1.21.6",
"credit": "3D Model © 2025 LeisureTimeDock",
"render_type": "cutout",
"textures": {
"0": "superleadrope:block/custom/author",
"1": "block/pink_tulip",
"particle": "block/white_wool"
},
"elements": [
{
"name": "Toggle_Helmet",
"from": [3.5, 8.8, 7.5],
"to": [12.5, 17.8, 16.5],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
"faces": {
"north": {"uv": [10, 2, 12, 4], "texture": "#0"},
"east": {"uv": [8, 2, 10, 4], "texture": "#0"},
"south": {"uv": [14, 2, 16, 4], "texture": "#0"},
"west": {"uv": [12, 2, 14, 4], "texture": "#0"},
"up": {"uv": [10, 2, 12, 0], "texture": "#0"},
"down": {"uv": [12, 0, 14, 2], "texture": "#0"}
}
},
{
"name": "Head",
"from": [4, 9.3, 8],
"to": [12, 17.3, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
"faces": {
"north": {"uv": [2, 2, 4, 4], "texture": "#0"},
"east": {"uv": [0, 2, 2, 4], "texture": "#0"},
"south": {"uv": [6, 2, 8, 4], "texture": "#0"},
"west": {"uv": [4, 2, 6, 4], "texture": "#0"},
"up": {"uv": [4, 2, 2, 0], "texture": "#0"},
"down": {"uv": [6, 0, 4, 2], "texture": "#0"}
}
},
{
"name": "Toggle_Chest_Armor",
"from": [4.75, 2.05, 9.75],
"to": [11.25, 9.55, 13.25],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
"faces": {
"north": {"uv": [5, 9, 7, 12], "texture": "#0"},
"east": {"uv": [4, 9, 5, 12], "texture": "#0"},
"south": {"uv": [8, 9, 10, 12], "texture": "#0"},
"west": {"uv": [7, 9, 8, 12], "texture": "#0"},
"up": {"uv": [5, 8, 7, 9], "texture": "#0"},
"down": {"uv": [7, 8, 9, 9], "texture": "#0"}
}
},
{
"name": "Body",
"from": [5, 2.3, 10],
"to": [11, 9.3, 13],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
"faces": {
"north": {"uv": [5, 5, 7, 8], "texture": "#0"},
"east": {"uv": [4, 5, 5, 8], "texture": "#0"},
"south": {"uv": [8, 5, 10, 8], "texture": "#0"},
"west": {"uv": [7, 5, 8, 8], "texture": "#0"},
"up": {"uv": [7, 5, 5, 4], "texture": "#0"},
"down": {"uv": [9, 4, 7, 5], "texture": "#0"}
}
},
{
"name": "Toggle_Left_Arm_Armor",
"from": [2.75, 6.25, 3.8],
"to": [5.25, 9.75, 13.3],
"rotation": {"angle": -22.5, "axis": "y", "origin": [3, 8, 12.5]},
"faces": {
"north": {"uv": [13.75, 12, 14.5, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [12, 13, 13, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [13, 12, 13.75, 13], "texture": "#0"},
"west": {"uv": [13.75, 13, 14.75, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [13, 13, 13.75, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"}
}
},
{
"name": "Left_arm",
"from": [3, 6.5, 3.8],
"to": [5, 9.5, 12.8],
"rotation": {"angle": -22.5, "axis": "y", "origin": [4, 8, 12.5]},
"faces": {
"north": {"uv": [10.5, 12, 9.75, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [8, 13, 9, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [9.75, 13, 9, 12], "texture": "#0"},
"west": {"uv": [9.75, 13, 10.5, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [9, 13, 9.75, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"}
}
},
{
"name": "Toggle_Right_Arm_Armor",
"from": [10.75, 6.25, 3.8],
"to": [13.25, 9.75, 13.3],
"rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]},
"faces": {
"north": {"uv": [11.75, 8, 12.5, 9], "rotation": 180, "texture": "#0"},
"east": {"uv": [10, 9, 11, 12], "rotation": 270, "texture": "#0"},
"south": {"uv": [11, 8, 11.75, 9], "texture": "#0"},
"west": {"uv": [11.75, 9, 12.75, 12], "rotation": 90, "texture": "#0"},
"up": {"uv": [11, 9, 11.75, 12], "rotation": 180, "texture": "#0"},
"down": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"}
}
},
{
"name": "Right_arm",
"from": [11, 6.5, 3.8],
"to": [13.5, 9.5, 12.8],
"rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]},
"faces": {
"north": {"uv": [12.5, 4, 11.75, 5], "rotation": 180, "texture": "#0"},
"east": {"uv": [10, 5, 11, 8], "rotation": 270, "texture": "#0"},
"south": {"uv": [11.75, 5, 11, 4], "texture": "#0"},
"west": {"uv": [11.75, 5, 12.5, 8], "rotation": 90, "texture": "#0"},
"up": {"uv": [11, 5, 11.75, 8], "rotation": 180, "texture": "#0"},
"down": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"}
}
},
{
"name": "Toggle_Left_Leg_Armor",
"from": [5.2, -0.25, 3.05],
"to": [8.7, 3.25, 12.55],
"rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]},
"faces": {
"north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [1, 12, 2, 13], "texture": "#0"},
"west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 13, 4, 16], "texture": "#0"}
}
},
{
"name": "Left_leg",
"from": [5.5, 0, 3.3],
"to": [8.5, 3, 12.3],
"rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]},
"faces": {
"north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [5.95, 13, 5, 11.925], "texture": "#0"},
"west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [7, 13, 8, 16], "texture": "#0"}
}
},
{
"name": "Toggle_Right_Leg_Armor",
"from": [7.2, -0.25, 3.05],
"to": [10.7, 3.25, 12.55],
"rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]},
"faces": {
"north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"},
"south": {"uv": [1, 8, 2, 9], "texture": "#0"},
"west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 9, 4, 12], "texture": "#0"}
}
},
{
"name": "Right_leg",
"from": [7.5, 0, 3.3],
"to": [10.5, 3, 12.3],
"rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]},
"faces": {
"north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"},
"south": {"uv": [2, 5, 1, 4], "texture": "#0"},
"west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 5, 4, 8], "texture": "#0"}
}
},
{
"from": [7, 6, 1],
"to": [7, 14, 9],
"rotation": {"angle": 45, "axis": "y", "origin": [7, 10, 3.5]},
"faces": {
"east": {"uv": [0, 0, 16, 16], "texture": "#1"},
"west": {"uv": [0, 0, 16, 16], "texture": "#1"}
}
},
{
"from": [9, 6, 1],
"to": [9, 14, 9],
"rotation": {"angle": -45, "axis": "y", "origin": [9, 10, 3.5]},
"faces": {
"east": {"uv": [0, 0, 16, 16], "texture": "#1"},
"west": {"uv": [0, 0, 16, 16], "texture": "#1"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 124, 0],
"translation": [2, 3, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 120, 0],
"translation": [1.5, 2.75, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 2, 0],
"scale": [0.5, 0.5, 0.5]
},
"gui": {
"rotation": [30, -135, 0],
"translation": [0.75, -1, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"translation": [0, 14, -0.75]
},
"fixed": {
"translation": [0, 0, -2.75],
"scale": [0.5, 0.5, 0.5]
},
"on_shelf": {
"rotation": [0, -180, 0],
"translation": [0, 0, 5.25]
}
},
"groups": [
{
"name": "Player",
"origin": [3, -6.7, 6],
"color": 0,
"children": [
{
"name": "Head",
"origin": [8, 16, 8],
"color": 0,
"children": [0, 1]
},
{
"name": "Body",
"origin": [8, 11, 8],
"color": 0,
"children": [2, 3]
},
{
"name": "Left_Arm",
"origin": [5, 15, 6],
"color": 0,
"children": [4, 5]
},
{
"name": "Right_Arm",
"origin": [11, 15, 6],
"color": 0,
"children": [6, 7]
},
{
"name": "Left_Leg",
"origin": [7, 13, 7],
"color": 0,
"children": [8, 9]
},
{
"name": "Right_Leg",
"origin": [10, 13, 7],
"color": 0,
"children": [10, 11]
}
]
},
{
"name": "item",
"origin": [0, 4, 2.5],
"color": 0,
"children": [12, 13]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B