2024-09-02

初版完成
This commit is contained in:
叁玖领域 2024-09-03 18:18:28 +08:00
parent 0c3dd146cf
commit 6b912c6498
40 changed files with 1498 additions and 207 deletions

View File

@ -163,7 +163,9 @@ publishing {
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
minecraft {
accessTransformers.file("src/main/resources/META-INF/accesstransformer.cfg")
}
// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
idea {
module {

View File

@ -32,12 +32,12 @@ mod_name=Leashed Player
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved
# The mod version. See https://semver.org/
mod_version=1.0.0
mod_version=0.0.1
# 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
mod_group_id=com.r3944realms.leashedplayer
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=YourNameHere, OtherNameHere
mod_authors=r3944Realms
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description=Example mod description.\nNewline characters can be used and will be replaced properly.
mod_description= Player but can be leash

View File

@ -0,0 +1,2 @@
// 1.21 2024-09-03T17:46:39.9837977 Languages: en_us for mod: leashedplayer
d770cde6d74b269e8a0447edeb0a65668dda07d3 assets/leashedplayer/lang/en_us.json

View File

@ -0,0 +1,2 @@
// 1.21 2024-09-03T17:46:39.9787945 Languages: lzh for mod: leashedplayer
bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f assets/leashedplayer/lang/lzh.json

View File

@ -0,0 +1,2 @@
// 1.21 2024-09-03T17:46:39.9827933 Languages: zh_cn for mod: leashedplayer
eb4dc9fdfece3e001d0f32abcad5de6b926b0a4d assets/leashedplayer/lang/zh_cn.json

View File

@ -0,0 +1,2 @@
// 1.21 2024-09-03T17:46:39.9817953 Languages: zh_tw for mod: leashedplayer
0f4c1499f9241f8d8c33033dbcad397c4f3df5fa assets/leashedplayer/lang/zh_tw.json

View File

@ -0,0 +1,7 @@
{
"gamerule.RWN.TeleportWithLeashedPlayers": "Teleport with leashed player",
"gamerule.RWN.TeleportWithLeashedPlayers.description": "You will teleport with your leashed players ",
"leashedplayer.command.leash.message.leash.length.fail": "Failed (Internal Error)",
"leashedplayer.command.leash.message.leash.length.set": "The Leash length of %s is set to %f blocks",
"leashedplayer.command.leash.message.leash.length.show": "The Leash Length of %s is %f blocks"
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,7 @@
{
"gamerule.RWN.TeleportWithLeashedPlayers": "传送被栓玩家",
"gamerule.RWN.TeleportWithLeashedPlayers.description": "传送时将被栓玩家与自己一起传送",
"leashedplayer.command.leash.message.leash.length.fail": "失败(内部错误)",
"leashedplayer.command.leash.message.leash.length.set": "%s的拴绳长度被设置为%f格",
"leashedplayer.command.leash.message.leash.length.show": "%s的拴绳长度为%f格"
}

View File

@ -0,0 +1,7 @@
{
"gamerule.RWN.TeleportWithLeashedPlayers": "傳送被栓玩家",
"gamerule.RWN.TeleportWithLeashedPlayers.description": "傳送時將被栓玩家與隨自己一起傳送",
"leashedplayer.command.leash.message.leash.length.fail": "失敗(内部錯誤)",
"leashedplayer.command.leash.message.leash.length.set": "%s的栓繩長度被設置為%f格",
"leashedplayer.command.leash.message.leash.length.show": "%s的栓繩長度為%f格"
}

View File

@ -0,0 +1,17 @@
package com.r3944realms.leashedplayer;
import com.mojang.brigadier.CommandDispatcher;
import com.r3944realms.leashedplayer.content.commands.LeashCommand;
import net.minecraft.commands.CommandSourceStack;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
@EventBusSubscriber(modid = LeashedPlayer.MOD_ID)
public class CommonEventHandler {
@SubscribeEvent
public static void onRegisterCommander(RegisterCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
LeashCommand.register(dispatcher);
}
}

View File

@ -1,63 +0,0 @@
package com.r3944realms.leashedplayer;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.config.ModConfigEvent;
import net.neoforged.neoforge.common.ModConfigSpec;
// An example config class. This is not required, but it's a good idea to have one to keep your config organized.
// Demonstrates how to use Neo's config APIs
@EventBusSubscriber(modid = ExampleMod.MODID, bus = EventBusSubscriber.Bus.MOD)
public class Config
{
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();
private static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
.comment("Whether to log the dirt block on common setup")
.define("logDirtBlock", true);
private static final ModConfigSpec.IntValue MAGIC_NUMBER = BUILDER
.comment("A magic number")
.defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.ConfigValue<String> MAGIC_NUMBER_INTRODUCTION = BUILDER
.comment("What you want the introduction message to be for the magic number")
.define("magicNumberIntroduction", "The magic number is... ");
// a list of strings that are treated as resource locations for items
private static final ModConfigSpec.ConfigValue<List<? extends String>> ITEM_STRINGS = BUILDER
.comment("A list of items to log on common setup.")
.defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName);
static final ModConfigSpec SPEC = BUILDER.build();
public static boolean logDirtBlock;
public static int magicNumber;
public static String magicNumberIntroduction;
public static Set<Item> items;
private static boolean validateItemName(final Object obj)
{
return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(ResourceLocation.parse(itemName));
}
@SubscribeEvent
static void onLoad(final ModConfigEvent event)
{
logDirtBlock = LOG_DIRT_BLOCK.get();
magicNumber = MAGIC_NUMBER.get();
magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get();
// convert the list of strings into a set of items
items = ITEM_STRINGS.get().stream()
.map(itemName -> BuiltInRegistries.ITEM.get(ResourceLocation.parse(itemName)))
.collect(Collectors.toSet());
}
}

View File

@ -1,136 +0,0 @@
package com.r3944realms.leashedplayer;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import net.neoforged.neoforge.registries.DeferredBlock;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister;
// The value here should match an entry in the META-INF/neoforge.mods.toml file
@Mod(ExampleMod.MODID)
public class ExampleMod
{
// Define mod id in a common place for everything to reference
public static final String MODID = "examplemod";
// Directly reference a slf4j logger
private static final Logger LOGGER = LogUtils.getLogger();
// Create a Deferred Register to hold Blocks which will all be registered under the "examplemod" namespace
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MODID);
// Create a Deferred Register to hold Items which will all be registered under the "examplemod" namespace
public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MODID);
// Create a Deferred Register to hold CreativeModeTabs which will all be registered under the "examplemod" namespace
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
// Creates a new Block with the id "examplemod:example_block", combining the namespace and path
public static final DeferredBlock<Block> EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock("example_block", BlockBehaviour.Properties.of().mapColor(MapColor.STONE));
// Creates a new BlockItem with the id "examplemod:example_block", combining the namespace and path
public static final DeferredItem<BlockItem> EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", EXAMPLE_BLOCK);
// Creates a new food item with the id "examplemod:example_id", nutrition 1 and saturation 2
public static final DeferredItem<Item> EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item", new Item.Properties().food(new FoodProperties.Builder()
.alwaysEdible().nutrition(1).saturationModifier(2f).build()));
// Creates a creative tab with the id "examplemod:example_tab" for the example item, that is placed after the combat tab
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder()
.title(Component.translatable("itemGroup.examplemod")) //The language key for the title of your CreativeModeTab
.withTabsBefore(CreativeModeTabs.COMBAT)
.icon(() -> EXAMPLE_ITEM.get().getDefaultInstance())
.displayItems((parameters, output) -> {
output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event
}).build());
// The constructor for the mod class is the first code that is run when your mod is loaded.
// FML will recognize some parameter types like IEventBus or ModContainer and pass them in automatically.
public ExampleMod(IEventBus modEventBus, ModContainer modContainer)
{
// Register the commonSetup method for modloading
modEventBus.addListener(this::commonSetup);
// Register the Deferred Register to the mod event bus so blocks get registered
BLOCKS.register(modEventBus);
// Register the Deferred Register to the mod event bus so items get registered
ITEMS.register(modEventBus);
// Register the Deferred Register to the mod event bus so tabs get registered
CREATIVE_MODE_TABS.register(modEventBus);
// Register ourselves for server and other game events we are interested in.
// Note that this is necessary if and only if we want *this* class (ExampleMod) to respond directly to events.
// Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below.
NeoForge.EVENT_BUS.register(this);
// Register the item to a creative tab
modEventBus.addListener(this::addCreative);
// Register our mod's ModConfigSpec so that FML can create and load the config file for us
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
}
private void commonSetup(final FMLCommonSetupEvent event)
{
// Some common setup code
LOGGER.info("HELLO FROM COMMON SETUP");
if (Config.logDirtBlock)
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber);
Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString()));
}
// Add the example block item to the building blocks tab
private void addCreative(BuildCreativeModeTabContentsEvent event)
{
if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS)
event.accept(EXAMPLE_BLOCK_ITEM);
}
// You can use SubscribeEvent and let the Event Bus discover methods to call
@SubscribeEvent
public void onServerStarting(ServerStartingEvent event)
{
// Do something when the server starts
LOGGER.info("HELLO from server starting");
}
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
@EventBusSubscriber(modid = MODID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public static class ClientModEvents
{
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event)
{
// Some client setup code
LOGGER.info("HELLO FROM CLIENT SETUP");
LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
}
}
}

View File

@ -0,0 +1,9 @@
package com.r3944realms.leashedplayer;
import net.neoforged.fml.common.Mod;
//TODO: 13:40
@Mod(LeashedPlayer.MOD_ID)
public class LeashedPlayer {
public static final String MOD_ID = "leashedplayer";
}

View File

@ -0,0 +1,5 @@
package com.r3944realms.leashedplayer.content.commands;
public class Command {
public static final String PREFIX = "lp";
}

View File

@ -0,0 +1,101 @@
package com.r3944realms.leashedplayer.content.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.r3944realms.leashedplayer.modInterface.ILivingEntityExtension;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
public class LeashCommand {
private final static String LEASHEDPLAYER_LEASH_MESSAGE_ = "leashedplayer.command.leash.message.";
public final static String LEASH_LENGTH_SHOW = LEASHEDPLAYER_LEASH_MESSAGE_ + "leash.length.show",
LEASH_LENGTH_FAIL = LEASHEDPLAYER_LEASH_MESSAGE_ + "leash.length.fail",
LEASH_LENGTH_SET = LEASHEDPLAYER_LEASH_MESSAGE_ + "leash.length.set";
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> literalArgumentBuilder = Commands.literal(com.r3944realms.leashedplayer.content.commands.Command.PREFIX);
Command<CommandSourceStack> getSelfLeashLength = context -> {
CommandSourceStack source = context.getSource();
try {
ServerPlayer player = source.getPlayerOrException();
float leashLength = ((ILivingEntityExtension)player).getLeashLength();
source.sendSuccess(() -> Component.translatable(LEASH_LENGTH_SHOW, player.getName(), leashLength), true);
} catch (Exception e) {
source.sendFailure(Component.translatable(LEASH_LENGTH_FAIL));
return -1;
}
return 0;
};
Command<CommandSourceStack> getRefPlayerLeashLength = context -> {
CommandSourceStack source = context.getSource();
try {
ServerPlayer player = EntityArgument.getPlayer(context, "player");
float leashLength = ((ILivingEntityExtension)player).getLeashLength();
source.sendSuccess(() -> Component.translatable(LEASH_LENGTH_SHOW, player.getName(), leashLength), true);
} catch (Exception e) {
source.sendFailure(Component.translatable(LEASH_LENGTH_FAIL));
return -1;
}
return 0;
};
Command<CommandSourceStack> setSelfLengthLeashLength = context -> {
CommandSourceStack source = context.getSource();
try {
ServerPlayer player = source.getPlayerOrException();
float leashLength = context.getArgument("leashLength", Float.class);
((ILivingEntityExtension)player).setLeashLength(leashLength);
source.sendSuccess(() -> Component.translatable(LEASH_LENGTH_SET, player.getName(), leashLength), true);
} catch (Exception e) {
source.sendFailure(Component.translatable(LEASH_LENGTH_FAIL));
return -1;
}
return 0;
};
Command<CommandSourceStack> setLengthLeashLength = context -> {
CommandSourceStack source = context.getSource();
try {
// Player player = context.getArgument("player", Player.class);
ServerPlayer player = EntityArgument.getPlayer(context, "player");
float leashLength = context.getArgument("leashLength", Float.class);
((ILivingEntityExtension)player).setLeashLength(leashLength);
source.sendSuccess(() -> Component.translatable(LEASH_LENGTH_SET, player.getName(), leashLength), true);
} catch (Exception e) {
source.sendFailure(Component.translatable(LEASH_LENGTH_FAIL));
return -1;
}
return 0;
};
LiteralArgumentBuilder<CommandSourceStack> $$leashRoot = Commands.literal("leash").requires(cs -> cs.hasPermission(2));
literalArgumentBuilder.then(
$$leashRoot.then(Commands.literal("length").executes(getSelfLeashLength)
.then(Commands.literal("getLength").executes(getSelfLeashLength))
.then(Commands.literal("setLength")
.then(Commands.argument("leashLength", FloatArgumentType.floatArg(5, 1024)).executes(setSelfLengthLeashLength)
)
)
)
);
literalArgumentBuilder.then(
$$leashRoot.then(
Commands.literal("length")
.then(Commands.argument("player", EntityArgument.player()).executes(getRefPlayerLeashLength)
.then(Commands.literal("getLength").executes(getRefPlayerLeashLength))
.then(Commands.literal("setLength")
.then(
Commands.argument("leashLength", FloatArgumentType.floatArg(5, 1024)).executes(setLengthLeashLength)
)
)
)
)
);
dispatcher.register(literalArgumentBuilder);
}
}

View File

@ -0,0 +1,70 @@
package com.r3944realms.leashedplayer.content.gamerules;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public enum GameruleRegistry {
INSTANCE;
public static final Map<String, GameRules.Key<?>> gamerules = new HashMap<>();;
public static final Map<String, RuleDataType> gameruleDataTypes = new HashMap<>();
public enum RuleDataType {
BOOLEAN,
INTEGER,
}
@SuppressWarnings("unchecked")
public static boolean getGameruleBoolValue(Level level, String gameruleName) {
if (level.isClientSide && Gamerules.gamerulesBooleanValuesClient.containsKey(gameruleName)) {
return Gamerules.gamerulesBooleanValuesClient.get(gameruleName);
}
if (gameruleDataTypes.get(gameruleName) != RuleDataType.BOOLEAN) {
return false;
}
return level.getGameRules().getBoolean((GameRules.Key<GameRules.BooleanValue>) gamerules.get(gameruleName));
}
@SuppressWarnings("unchecked")
public static Integer getGameruleIntValue(Level level, String gameruleName) {
if (level.isClientSide && Gamerules.gameruleIntegerValuesClient.containsKey(gameruleName)) {
return Gamerules.gameruleIntegerValuesClient.get(gameruleName);
}
if (gameruleDataTypes.get(gameruleName) != RuleDataType.INTEGER) {
return 0;
}
return level.getGameRules().getInt((GameRules.Key<GameRules.IntegerValue>)gamerules.get(gameruleName));
}
public void registerGamerule(String gameruleName, GameRules.Category category, boolean pDefault) {
registerGamerule(gameruleName, category, pDefault, (s,i)->{});//最后一个仅占位无用
}
public void registerGamerule(String gameruleName, GameRules.Category category, boolean pDefault, BiConsumer<MinecraftServer, GameRules.BooleanValue> pChangeListener) {
gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.BooleanValue.create(pDefault, pChangeListener)));
gameruleDataTypes.put(gameruleName, RuleDataType.BOOLEAN);
}
public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault) {
registerGamerule(gameruleName, category, pDefault, (BiConsumer<MinecraftServer, GameRules.IntegerValue>) (s, i)->{});//最后一个仅占位无用
}
public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault, BiConsumer<MinecraftServer, GameRules.IntegerValue> pChangeListener) {
gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.IntegerValue.create(pDefault, pChangeListener)));
gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER);
}
public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault, int pMin, int pMax, BiConsumer<MinecraftServer, GameRules.IntegerValue> pChangeListener) {
gamerules.put(gameruleName, GameRules.register(gameruleName, category, GameRules.IntegerValue.create(pDefault, pMin, pMax, pChangeListener)));
gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER);
}
public void registerGamerule(String gameruleName, GameRules.Category category,float value) {
registerGamerule(gameruleName, category, value, (s,i)->{});
}
public void registerGamerule(String gameruleName, GameRules.Category category, float pDefault, BiConsumer<MinecraftServer, Gamerules.FloatValue> pChangeListener) {
gamerules.put(gameruleName, GameRules.register(gameruleName, category, Gamerules.FloatValue.create(pDefault, pChangeListener)));
gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER);
}
public void registerGamerule(String gameruleName, GameRules.Category category, float pDefault, float pMin, float pMax,BiConsumer<MinecraftServer, Gamerules.FloatValue> pChangeListener) {
gamerules.put(gameruleName, GameRules.register(gameruleName, category, Gamerules.FloatValue.create(pDefault, pMin, pMax,pChangeListener)));
gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER);
}
}

View File

@ -0,0 +1,102 @@
package com.r3944realms.leashedplayer.content.gamerules;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.GameRules;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.function.BiConsumer;
public class Gamerules {
public static final String GAMERULE_PREFIX = "RWN.";
public static final GameruleRegistry GAMERULE_REGISTRY = GameruleRegistry.INSTANCE;
public static final HashMap<String, Boolean> gamerulesBooleanValuesClient = new HashMap<>();
public static final HashMap<String, Integer> gameruleIntegerValuesClient = new HashMap<>();
public static final HashMap<String, Float> gameruleFloatValuesClient = new HashMap<>();
public static final String RULE_KEY_PERFix_ = "gamerule." + GAMERULE_PREFIX;
public static String getDescriptionKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName() + ".description";
}
public static String getDescriptionKey(String gameRuleName) {
return RULE_KEY_PERFix_ + gameRuleName + ".description";
}
public static String getNameKey(Class<?> gameRuleClass) {
return RULE_KEY_PERFix_ + gameRuleClass.getSimpleName();
}
//此次定义了一个浮点数类型的游戏规则
public static class FloatValue extends GameRules.Value<FloatValue> {
private float value;
public static GameRules.Type<FloatValue> create(
float pDefaultValue, BiConsumer<MinecraftServer, FloatValue> pChangeListener
) {
return new GameRules.Type<>
(FloatArgumentType::floatArg,
pType -> new FloatValue(pType, pDefaultValue),
pChangeListener,
GameRules.GameRuleTypeVisitor::visit
);
}
public static GameRules.Type<FloatValue> create(
float pDefaultValue, float pMin, float pMax , BiConsumer<MinecraftServer, FloatValue> pChangeListener
) {
return new GameRules.Type<>(
() -> FloatArgumentType.floatArg(pMin, pMax),
pType -> new FloatValue(pType, pDefaultValue),
pChangeListener,
GameRules.GameRuleTypeVisitor::visit
);
}
public FloatValue(GameRules.Type<FloatValue> pType, float value) {
super(pType);
this.value = value;
}
@Override
protected void updateFromArgument(@NotNull CommandContext<CommandSourceStack> pContext, @NotNull String pParamName) {
this.value = FloatArgumentType.getFloat(pContext, pParamName);
}
public float get() {
return this.value;
}
public void set(float pValue, @Nullable MinecraftServer pServer) {
this.value = pValue;
this.onChanged(pServer);
}
@Override
protected void deserialize(@NotNull String pValue) {
this.value = Float.parseFloat(pValue);
}
@Override
public @NotNull String serialize() {
return Float.toString(this.value);
}
@Override
public int getCommandResult() {
return 1;
}
@Override
protected @NotNull FloatValue getSelf() {
return this;
}
@Override
protected @NotNull FloatValue copy() {
return new FloatValue(this.type, this.value);
}
@Override
public void setFrom(FloatValue pValue, @Nullable MinecraftServer pServer) {
this.value = pValue.value;
this.onChanged(pServer);
}
}
}

View File

@ -0,0 +1,26 @@
package com.r3944realms.leashedplayer.content.gamerules.Server;
import com.r3944realms.leashedplayer.LeashedPlayer;
import com.r3944realms.leashedplayer.content.gamerules.Gamerules;
import com.r3944realms.leashedplayer.utils.Util;
import net.minecraft.world.level.GameRules;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import static com.r3944realms.leashedplayer.content.gamerules.Gamerules.GAMERULE_REGISTRY;
//It had so Long Name at first.(
@EventBusSubscriber(modid = LeashedPlayer.MOD_ID, bus = EventBusSubscriber.Bus.MOD)
public class TeleportWithLeashedPlayers {
public static final boolean DEFAULT_VALUE = true;
public static final String ID = Util.getGameruleName(TeleportWithLeashedPlayers.class);
public static final String DESCRIPTION_KEY = Gamerules.getDescriptionKey(TeleportWithLeashedPlayers.class);
public static final String NAME_KEY = Gamerules.getNameKey(TeleportWithLeashedPlayers.class);
public static final GameRules.Category CATEGORY = GameRules.Category.PLAYER;
@SubscribeEvent
public static void onCommonSetup(final FMLCommonSetupEvent event) {
GAMERULE_REGISTRY.registerGamerule(ID, CATEGORY, DEFAULT_VALUE);
}
}

View File

@ -0,0 +1,117 @@
package com.r3944realms.leashedplayer.datagen.LanguageAndOtherData;
import com.r3944realms.leashedplayer.content.commands.LeashCommand;
import com.r3944realms.leashedplayer.content.gamerules.Server.TeleportWithLeashedPlayers;
import com.r3944realms.leashedplayer.utils.Enum.LanguageEnum;
import com.r3944realms.leashedplayer.utils.Enum.ModPartEnum;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.function.Supplier;
public enum ModLangKeyValue {
//COMMAND_MESSAGE
MESSAGE_LEASH_LENGTH_FAIL(LeashCommand.LEASH_LENGTH_FAIL, ModPartEnum.COMMAND, "Failed (Internal Error)", "失败(内部错误)", "失敗(内部錯誤)", false),
MESSAGE_LEASH_LENGTH_SHOW(LeashCommand.LEASH_LENGTH_SHOW, ModPartEnum.COMMAND, "The Leash Length of %s is %f blocks", "%s的拴绳长度为%f格", "%s的栓繩長度為%f格" , false),
MESSAGE_LEASH_LENGTH_SET(LeashCommand.LEASH_LENGTH_SET, ModPartEnum.COMMAND, "The Leash length of %s is set to %f blocks", "%s的拴绳长度被设置为%f格", "%s的栓繩長度被設置為%f格" , false),
//GAME_RULE_NAME
TELEPORT_WITH_LEASHED_PLAYERS(TeleportWithLeashedPlayers.NAME_KEY, ModPartEnum.NAME, "Teleport with leashed player", "传送被栓玩家", "傳送被栓玩家" ,false),
//GAME_RULE_DESCRIPTION
TELEPORT_WITH_LEASHED_DESCRIPTION(TeleportWithLeashedPlayers.DESCRIPTION_KEY, ModPartEnum.DESCRIPTION, "You will teleport with your leashed players ", "传送时将被栓玩家与自己一起传送", "傳送時將被栓玩家與隨自己一起傳送" ,false),
;
private final Supplier<?> supplier;
private String key;
private final String US_EN;
private final String SIM_CN;
private final String TRA_CN;
private final String LZH;
private final Boolean Default;
private final ModPartEnum MPE;
ModLangKeyValue(Supplier<?> Supplier, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, String LZH, Boolean isDefault) {
this.supplier = Supplier;
this.MPE = MPE;
this.US_EN = US_EN;
this.SIM_CN = SIM_CN;
this.TRA_CN = TRA_CN;
this.LZH = LZH;
this.Default = isDefault;
}
ModLangKeyValue(@NotNull String ResourceKey, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, String LZH, Boolean isDefault) {
this.supplier = null;
this.key = ResourceKey;
this.MPE = MPE;
this.US_EN = US_EN;
this.SIM_CN = SIM_CN;
this.TRA_CN = TRA_CN;
this.LZH = LZH;
this.Default = isDefault;
}
ModLangKeyValue(Supplier<?> Supplier, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, Boolean isDefault) {
this(Supplier, MPE, US_EN, SIM_CN, TRA_CN, null, isDefault);
}
ModLangKeyValue(@NotNull String ResourceKey, ModPartEnum MPE, String US_EN, String SIM_CN, String TRA_CN, Boolean isDefault) {
this(ResourceKey, MPE, US_EN, SIM_CN, TRA_CN, null, isDefault);
}
public static String getLan(LanguageEnum lan, ModLangKeyValue key) {
if (lan == null || lan == LanguageEnum.English) return getEnglish(key);
else {
switch (lan) {
case SimpleChinese -> {
return getSimpleChinese(key);
}
case TraditionalChinese -> {
return getTraditionalChinese(key);
}
case LiteraryChinese -> {
return getLiteraryChinese(key);
}
default -> {
return getEnglish(key);
}
}
}
}
private static String getEnglish(ModLangKeyValue key) {
return key.US_EN;
}
private static String getSimpleChinese(ModLangKeyValue key) {
return key.SIM_CN;
}
private static String getTraditionalChinese(ModLangKeyValue key) {
return key.TRA_CN;
}
@Nullable
public static String getLiteraryChinese(ModLangKeyValue key) {
return key.LZH;
}
public String getKey() {
if(key == null){
switch (MPE) {//Don't need to use "break;"[Java feature];
case CREATIVE_TAB, MESSAGE, INFO, DEFAULT, COMMAND, CONFIG -> throw new UnsupportedOperationException("The Key value is NULL! Please use the correct constructor and write the parameters correctly");
case ITEM -> key = (getItem()).getDescriptionId();
case BLOCK -> key =(getBlock()).getDescriptionId();
}
//需要完善
}
return key;
}
@SuppressWarnings("null")
public Item getItem() {
assert supplier != null;
return (Item)supplier.get();
}
@SuppressWarnings("null")
public Block getBlock() {
assert supplier != null;
return (Block)supplier.get();
}
public boolean isDefaultItem(){
return MPE == ModPartEnum.ITEM && Default;
}
public boolean isDefaultBlock() {
return MPE == ModPartEnum.BLOCK && Default;
}
}

View File

@ -0,0 +1,34 @@
package com.r3944realms.leashedplayer.datagen;
import com.r3944realms.leashedplayer.LeashedPlayer;
import com.r3944realms.leashedplayer.datagen.provider.ModLanguageProvider;
import com.r3944realms.leashedplayer.utils.Enum.LanguageEnum;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.DataProvider;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.common.data.ExistingFileHelper;
import net.neoforged.neoforge.data.event.GatherDataEvent;
import java.util.concurrent.CompletableFuture;
@EventBusSubscriber(modid = LeashedPlayer.MOD_ID, bus = EventBusSubscriber.Bus.MOD)
public class ModDataGeneratorHandler {
@SubscribeEvent
public static void genData(GatherDataEvent event) {
CompletableFuture<HolderLookup.Provider> HolderFolder = event.getLookupProvider();
ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
/*Language Provider ENGLISH CHINESE(SIM/TRA)*/
addLanguage(event, LanguageEnum.English, "en_us");
addLanguage(event, LanguageEnum.SimpleChinese, "zh_cn");
addLanguage(event, LanguageEnum.TraditionalChinese, "zh_tw");
addLanguage(event, LanguageEnum.LiteraryChinese, "lzh");
}
private static void addLanguage(GatherDataEvent event, LanguageEnum language, String lan_regex){
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<ModLanguageProvider>) pOutput -> new ModLanguageProvider(pOutput, LeashedPlayer.MOD_ID, language)
);
}
}

View File

@ -0,0 +1,40 @@
package com.r3944realms.leashedplayer.datagen.provider;
import com.r3944realms.leashedplayer.datagen.LanguageAndOtherData.ModLangKeyValue;
import com.r3944realms.leashedplayer.utils.Enum.LanguageEnum;
import net.minecraft.data.PackOutput;
import net.neoforged.neoforge.common.data.LanguageProvider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.r3944realms.leashedplayer.datagen.LanguageAndOtherData.ModLangKeyValue.getLan;
public class ModLanguageProvider extends LanguageProvider {
private final LanguageEnum Language;
private final Map<String, String> LanKeyMap;
private static final List<String> objects = new ArrayList<>();
public ModLanguageProvider(PackOutput output, String modId, LanguageEnum Lan) {
super(output, modId, Lan.local);
this.Language = Lan;
LanKeyMap = new HashMap<>();
init();
}
private void init() {
for (ModLangKeyValue key : ModLangKeyValue.values()) {
addLang(key.getKey(), getLan(Language, key));
}
}
private void addLang(String Key, String value) {
if(!objects.contains(Key)) objects.add(Key);
LanKeyMap.put(Key, value);
}
@Override
protected void addTranslations() {
objects.forEach(key -> add(key,LanKeyMap.get(key)));
}
}

View File

@ -0,0 +1,28 @@
package com.r3944realms.leashedplayer.mixin.both;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Leashable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(Entity.class)
public class MixinEntity {
/**
* 这里重定向当实体类实现了{@link PlayerLeashable}接口时<br/>
* 阻止原版的{@link Leashable} 的tickLeash方法调用将其<br/>
* 我们需自己实现相关的逻辑
* @param entity 实体
* @param <E> 实体类型
*/
@Redirect(
method = "baseTick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Leashable;tickLeash(Lnet/minecraft/world/entity/Entity;)V")
)
<E extends Entity & Leashable> void checkAndCancelIfTure(E entity) {
if(!(entity instanceof PlayerLeashable)) {
Leashable.tickLeash(entity);
}
}
}

View File

@ -0,0 +1,53 @@
package com.r3944realms.leashedplayer.mixin.both;
import com.r3944realms.leashedplayer.modInterface.ILivingEntityExtension;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class MixinLivingEntity extends Entity implements ILivingEntityExtension {
public MixinLivingEntity(EntityType<?> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
}
@Unique
@SuppressWarnings("WrongEntityDataParameterClass")
private static final EntityDataAccessor<Float> DATA_ENTITY_LEASH_LENGTH = SynchedEntityData.defineId(LivingEntity.class, EntityDataSerializers.FLOAT);
@SuppressWarnings("AddedMixinMembersNamePattern")
@Override
public float getLeashLength() {
return this.entityData.get(DATA_ENTITY_LEASH_LENGTH);
}
@SuppressWarnings("AddedMixinMembersNamePattern")
@Override
public void setLeashLength(float length) {
this.entityData.set(DATA_ENTITY_LEASH_LENGTH, length);
}
@Inject(method = {"defineSynchedData"}, at = {@At("TAIL")})
//定义Client/Server实体同步数据
private void defineSyncData(SynchedEntityData.Builder pBuilder, CallbackInfo ci) {
pBuilder.define(DATA_ENTITY_LEASH_LENGTH, 5.0F);
}
@Inject(method = {"readAdditionalSaveData"}, at = {@At("RETURN")})
private void readSaveData(CompoundTag compoundTag, CallbackInfo callbackInfo) {
if(compoundTag.contains("LeashLength")) {
this.setLeashLength(compoundTag.getFloat("LeashLength"));
}
}
@Inject(method = {"addAdditionalSaveData"}, at = {@At("RETURN")})
private void addSaveData(CompoundTag compoundTag, CallbackInfo callbackInfo) {
compoundTag.putFloat("LeashLength", getLeashLength());
}
}

View File

@ -0,0 +1,253 @@
package com.r3944realms.leashedplayer.mixin.both;
import com.r3944realms.leashedplayer.modInterface.ILivingEntityExtension;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.annotation.Nullable;
import java.util.function.Consumer;
@Mixin(Player.class)
public abstract class MixinPlayer extends LivingEntity implements PlayerLeashable {
@Unique
@Nullable
private LeashData Pl$LeashData;//Data
@SuppressWarnings("WrongEntityDataParameterClass")
@Unique//客户端与服务器端的实体同步数据
private static final EntityDataAccessor<CompoundTag> Pl$LEASH_DATA = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG);
protected MixinPlayer(EntityType<? extends LivingEntity> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
}
@Inject(method = {"tick"}, at = {@At("HEAD")})
private void tickForLeash(CallbackInfo ci) {
if(!this.level().isClientSide) {
Pl$tickLeash();//服务器端每tick任务
}
PlayerLeashable playerLeashable = this;
Entity leashHolder = playerLeashable.getLeashHolder();
if(leashHolder != null ) {
//存在则更新
Pl$UpdateLeash(leashHolder, (Entity) playerLeashable);
}
}
@Unique
private static void Pl$UpdateLeash(Entity holderEntity, Entity restrainedEntity) {
if(holderEntity == null || holderEntity.level() != restrainedEntity.level())
return;
float leashLength = 6.0f;
if(restrainedEntity instanceof ILivingEntityExtension iEntity) {
//获取长度
float leashLengthFormValue = iEntity.getLeashLength();
leashLength = leashLengthFormValue > 6 ? leashLengthFormValue : 6;
}
//两者距离
float distance = holderEntity.distanceTo(restrainedEntity);
//大于长度情况
if(distance > leashLength) {
//作用对象实体所坐载体还是实体根据isPassenger来判断
Entity applyMovementEntity = restrainedEntity.isPassenger() ? restrainedEntity.getVehicle() : restrainedEntity;
if(applyMovementEntity != null){
double dX = (holderEntity.getX() - applyMovementEntity.getX()) / (double) distance;
double dY = (holderEntity.getY() - applyMovementEntity.getY()) / (double) distance;
double dZ = (holderEntity.getZ() - applyMovementEntity.getZ()) / (double) distance;
//给予作用实体其向holderEntity的一个速度动量
applyMovementEntity.setDeltaMovement(
applyMovementEntity.getDeltaMovement().add(
Math.copySign(dX * dX * 0.4d, dX),
Math.copySign(dY * dY * 0.4d, dY),
Math.copySign(dZ * dZ * 0.4d, dZ)
)
);
//刹车避免偏激移动
Whimsy$Brake(applyMovementEntity, 1, 1, 1);
}
}
//降低坠落伤害
restrainedEntity.checkSlowFallDistance();
}
/**
* 刹车
* @param pEntity 刹车的实体
* @param pMaxX X方向的最大动量
* @param pMaxY Y方向的最大动量
* @param pMaxZ Z方向的最大动量
*/
@Unique
private static void Whimsy$Brake(Entity pEntity, double pMaxX, double pMaxY, double pMaxZ) {
Vec3 deltaMovement = pEntity.getDeltaMovement();
double dX = deltaMovement.x > pMaxX ? 0 : deltaMovement.x;
double dY = deltaMovement.y > pMaxY ? 0 : deltaMovement.y;
double dZ = deltaMovement.z > pMaxZ ? 0 : deltaMovement.z;
pEntity.setDeltaMovement(dX, dY,dZ);
pEntity.hurtMarked = true;
}
/**
* 刹车
* @param pEntity 刹车的实体
* @param pOpt 自定义规则
*/
@Unique
private static void Whimsy$Brake(Entity pEntity, @Nullable Consumer<Entity> pOpt) {
Consumer<Entity> consumer = pOpt;
if(pOpt == null) {
consumer = entity -> {
Vec3 deltaMovement = entity.getDeltaMovement();
double dX = deltaMovement.x > 1 ? 0 : deltaMovement.x;
double dY = deltaMovement.y > 1 ? 0 : deltaMovement.y;
double dZ = deltaMovement.z > 1 ? 0 : deltaMovement.z;
entity.setDeltaMovement(dX, dY,dZ);
entity.hurtMarked = true;
};
}
consumer.accept(pEntity);
}
@Unique
protected void Pl$tickLeash() {
if(this.Pl$LeashData == null) return;//没有Data直接退出
//info -> Holder整理
Pl$RestoreLeashFormSave();
//默认值设为6.0f距离
float leashLength = 6.0f;
Entity entity = this.Pl$LeashData.leashHolder;
//保存数据
saveLeashData(Pl$LeashData);
if(this instanceof ILivingEntityExtension iEntityExtension) {
//获取设定值
float leashLengthSelf = iEntityExtension.getLeashLength();
leashLength = leashLengthSelf > 6 ? leashLengthSelf : 6;
}
if (entity != null) {
if(!isAlive() || !entity.isAlive() || distanceTo(entity) > Math.max(leashLength * 2.0f, 10.0f)){
//玩家死亡 持有者不存在 距离大于设定值的2倍长度2倍若低于10格则选10格
// 则取消拴绳关系并掉落拴绳
dropLeash(true, true);
} else if(distanceTo(entity) > leashLength * 1.3f) {
//大于1.3倍绳长则会让其跳跃<1.25格阻拦情况下跳跃阻拦
jumpFromGround();
}
}
}
@Override
public Entity getLeashHolder() {
if (Pl$LeashData == null) return null;
if (Pl$LeashData.leashHolder == null && Pl$LeashData.delayedLeashHolderId != 0 ) {
Pl$LeashData.leashHolder = this.level().getEntity(Pl$LeashData.delayedLeashHolderId);
}
return Pl$LeashData.leashHolder;
}
/**
* 数据整理 -> 如果Pl$LeashData非null最终Pl$LeashData的leashHolder将不为null
*/
@Unique
private void Pl$RestoreLeashFormSave() {
assert this.Pl$LeashData != null;
if(!(this.level() instanceof ServerLevel)) {
//非服务器端退出
return;
}
if(this.Pl$LeashData.delayedLeashInfo == null) {
//delayedLeashInfo无数据
if(Pl$LeashData.leashHolder != null) {//且LeashHolder不为null则直接用它
setLeashedTo(Pl$LeashData.leashHolder, true);
return;
} return;
}
if(this.Pl$LeashData.delayedLeashInfo.left().isPresent()) {
//如果有实体的UUID一般是LivingEntity则在服务器其通过UUID来查找实体
Entity entity = ((ServerLevel) this.level()).getEntity(this.Pl$LeashData.delayedLeashInfo.left().get());
if(entity != null) {
setLeashedTo(entity, true);
}
} else if(this.Pl$LeashData.delayedLeashInfo.right().isPresent()) {
//如果有实体的坐标一般就是拴绳结在服务器端获取拴绳结实体通过给定坐标和维度获取
setLeashedTo(LeashFenceKnotEntity.getOrCreateKnot(this.level(), this.Pl$LeashData.delayedLeashInfo.right().get()), true);
}
}
@org.jetbrains.annotations.Nullable
@Override
public LeashData getLeashData() {
return Pl$LeashData;
}
@SuppressWarnings("AddedMixinMembersNamePattern")
@Override
public LeashData getLeashDataFromEntityData() {
CompoundTag compoundTag = this.entityData.get(Pl$LEASH_DATA);
return readLeashData(compoundTag);
}
@Override
public void setLeashData(@org.jetbrains.annotations.Nullable Leashable.LeashData pLeashData) {
this.Pl$LeashData = pLeashData;
saveLeashData(pLeashData);
}
@SuppressWarnings("AddedMixinMembersNamePattern")
@Unique
private void saveLeashData(@org.jetbrains.annotations.Nullable LeashData pLeashData) {
CompoundTag compoundTag = new CompoundTag();
this.writeLeashData(compoundTag, pLeashData);
this.entityData.set(Pl$LEASH_DATA, compoundTag);
}
@SuppressWarnings("AddedMixinMembersNamePattern")
@Override
public boolean canBeLeashedInstantly(Player player) {
return !isLeashed();
}
@Inject(
method = {"defineSynchedData"}, at = {@At("TAIL")}
)
//定义Client/Server player 同步数据
private void defineSyncData (SynchedEntityData.Builder pBuilder, CallbackInfo ci) {
CompoundTag leashCompoundTag = new CompoundTag();
this.writeLeashData(leashCompoundTag, null);
pBuilder.define(Pl$LEASH_DATA, leashCompoundTag);
}
@Inject(
method = {"addAdditionalSaveData"}, at = {@At("RETURN")}
)//数据保存
private void addSaveData(CompoundTag pCompound, CallbackInfo ci) {
CompoundTag pLeashTag = new CompoundTag();
writeLeashData(pLeashTag, Pl$LeashData);
pCompound.put("Pl$LeashData", pLeashTag);
this.entityData.set(Pl$LEASH_DATA, pLeashTag);
}
@Inject(
method = {"readAdditionalSaveData"}, at = {@At("RETURN")}
)//数据读取
private void readSaveData(CompoundTag pCompound, CallbackInfo ci) {
if(pCompound.contains("Pl$LeashData")) {
CompoundTag pl$LeashData = pCompound.getCompound("Pl$LeashData");
this.entityData.set(Pl$LEASH_DATA, pl$LeashData);
Pl$LeashData = readLeashData(pl$LeashData);
}
}
}

View File

@ -0,0 +1,29 @@
package com.r3944realms.leashedplayer.mixin.client;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(EntityRenderer.class)
public abstract class MixinEntityRenderer {
@Redirect(
method = {"renderLeash"},
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/Entity;getLeashOffset(F)Lnet/minecraft/world/phys/Vec3;"
)
)
private @NotNull Vec3 ret(Entity instance, float pPartialTick) {
if(instance instanceof AbstractClientPlayer) {
//为了使拴绳在在第三视角下位于玩家脖子处
return instance.getLeashOffset(pPartialTick).add(0, -0.2, -0.2);
}
return instance.getLeashOffset(pPartialTick);//非实现这个接口则不变
}
}

View File

@ -0,0 +1,97 @@
package com.r3944realms.leashedplayer.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.datafixers.util.Either;
import com.r3944realms.leashedplayer.modInterface.IPlayerRendererExtension;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.client.Camera;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.annotation.Nullable;
import java.util.UUID;
@Mixin(LevelRenderer.class)
public abstract class MixinLevelRenderer {
@Shadow
@Nullable
private ClientLevel level;
@Shadow protected abstract void renderEntity(Entity pEntity, double pCamX, double pCamY, double pCamZ, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBufferSource);
@Shadow @Final
private Minecraft minecraft;
@Shadow @Final private RenderBuffers renderBuffers;
@Inject(
method = {"renderLevel"},
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/renderer/RenderBuffers;bufferSource()Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;",
shift = At.Shift.AFTER
)
)
private void renderLevel(DeltaTracker pDeltaTracker, boolean pRenderBlockOutline, Camera pCamera, GameRenderer pGameRenderer, LightTexture pLightTexture, Matrix4f pFrustumMatrix, Matrix4f pProjectionMatrix, CallbackInfo ci) {
assert this.level != null;
PoseStack poseStack = new PoseStack();
MultiBufferSource.BufferSource multibuffersource$buffersource = this.renderBuffers.bufferSource();
for(Entity entity : this.level.entitiesForRendering()) {
//对于玩家实体拴绳渲染从第一人称视角
if (entity instanceof AbstractClientPlayer abstractClientPlayer) {
if(!(pCamera.getEntity() instanceof AbstractClientPlayer)) return;
Minecraft mc = Minecraft.getInstance();
PlayerRenderer playerRenderer = (PlayerRenderer) mc.getEntityRenderDispatcher().getRenderer(abstractClientPlayer);
IPlayerRendererExtension playerRendererExtension = (IPlayerRendererExtension) playerRenderer;
if (mc.options.getCameraType().isFirstPerson()) {
Leashable.LeashData leashDataFromEntityData = ((PlayerLeashable) abstractClientPlayer).getLeashDataFromEntityData();
if(leashDataFromEntityData == null) return;
Either<UUID, BlockPos> delayedLeashInfo = leashDataFromEntityData.delayedLeashInfo;
if(delayedLeashInfo != null) {
float partialTickTime = pCamera.getPartialTickTime();
Vec3 position = pCamera.getPosition();
double dX = Mth.lerp(partialTickTime, abstractClientPlayer.xOld, abstractClientPlayer.getX()) - position.x;
double dY = Mth.lerp(partialTickTime, abstractClientPlayer.yOld, abstractClientPlayer.getY()) - position.y;
double dZ = Mth.lerp(partialTickTime, abstractClientPlayer.zOld, abstractClientPlayer.getZ()) - position.z;
Vec3 vec3 = playerRenderer.getRenderOffset(abstractClientPlayer, partialTickTime);
double dX_ = dX + vec3.x();
double dY_ = dY + vec3.y();
double dZ_ = dZ + vec3.z();
poseStack.pushPose();
poseStack.translate(dX_, dY_, dZ_);
ClientLevel level = mc.level;
if (delayedLeashInfo.right().isPresent() && delayedLeashInfo.left().isEmpty()) {
assert level != null;
playerRendererExtension.renderLeashForCamera(pCamera, partialTickTime, poseStack, multibuffersource$buffersource, LeashFenceKnotEntity.getOrCreateKnot(level, delayedLeashInfo.right().get()));
} else if (delayedLeashInfo.right().isEmpty() && delayedLeashInfo.left().isPresent()) {
assert level != null;
Player playerByUUID = level.getPlayerByUUID(delayedLeashInfo.left().get());
if (playerByUUID != null) {
playerRendererExtension.renderLeashForCamera(pCamera, partialTickTime, poseStack, multibuffersource$buffersource, playerByUUID);
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,202 @@
package com.r3944realms.leashedplayer.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.datafixers.util.Either;
import com.r3944realms.leashedplayer.modInterface.IPlayerRendererExtension;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.UUID;
@Mixin(PlayerRenderer.class)
public abstract class MixinPlayerRenderer extends LivingEntityRenderer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> implements IPlayerRendererExtension {
public MixinPlayerRenderer(EntityRendererProvider.Context pContext, PlayerModel<AbstractClientPlayer> pModel, float pShadowRadius) {
super(pContext, pModel, pShadowRadius);
}
@Inject(
at = @At("HEAD"),
method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V"
)
private void renderMixin(AbstractClientPlayer pEntity, float pEntityYaw, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, CallbackInfo ci) {
Leashable.LeashData leashDataFromEntityData = ((PlayerLeashable) pEntity).getLeashDataFromEntityData();
if(leashDataFromEntityData == null) return;
Either<UUID, BlockPos> delayedLeashInfo = leashDataFromEntityData.delayedLeashInfo;
if(delayedLeashInfo != null) {
Minecraft mc = Minecraft.getInstance();
ClientLevel level = mc.level;
if (delayedLeashInfo.right().isPresent() && delayedLeashInfo.left().isEmpty()) {
assert level != null;
renderLeash(pEntity, pPartialTicks, pPoseStack, pBuffer, LeashFenceKnotEntity.getOrCreateKnot(level, delayedLeashInfo.right().get()));
} else if (delayedLeashInfo.right().isEmpty() && delayedLeashInfo.left().isPresent()) {
assert level != null;
Player playerByUUID = level.getPlayerByUUID(delayedLeashInfo.left().get());
if (playerByUUID != null) {
renderLeash(pEntity, pPartialTicks, pPoseStack, pBuffer, playerByUUID);
}
}
}
}
/**
* <h1>1. 角度与弧度转换</h1>
* {@snippet lang=java :
* double d0 = (double)(pEntity.getPreciseBodyRotation(pPartialTick) * (float)(Math.PI / 180.0)) + (Math.PI / 2);
* }
* <ul>
* <li><code>pEntity.getPreciseBodyRotation(pPartialTick)</code> 返回实体的旋转角度通常是以度为单位/li>
* <li> <code>(Math.PI / 180.0)</code> 是将度数转换为弧度的乘数因为大多数三角函数 <code>cos</code> <code>sin</code>都需要弧度值</li>
* <li><code>+ (Math.PI / 2)</code> 用于将结果平移90度四分之一圆可能是为了校正方向或设置起始方向 </li>
* </ul>
*
* <p>
* <h1> 2. 三角函数计算位移</h1>
* {@snippet lang=java :
* double d1 = Math.cos(d0) * vec31.z + Math.sin(d0) * vec31.x;
* double d2 = Math.sin(d0) * vec31.z - Math.cos(d0) * vec31.x;
* }
* <ul>
* <li><code>d1</code> <code>d2</code> 是利用三角函数 <code>cos</code> <code>sin</code> 计算出来的位移量用于确定实体相对于其旋转的实际位置</li>
* <li><code>Math.cos(d0) * vec31.z</code> <code>Math.sin(d0) * vec31.x</code> 分别计算沿 X Z 轴的位移分量这种计算通常用于旋转一个点或向量</li>
* <li>两个公式结合起来用于旋转平面内的一个点 <code>(vec31.x, vec31.z)</code>从而得到旋转后的新坐标</li>
* </ul>
* <p>
* <h1> 3. 线性插值 (Lerp) </h1>
* {@snippet lang=java :
* double d3 = Mth.lerp(pPartialTick, pEntity.xo, pEntity.getX()) + d1;
* double d4 = Mth.lerp(pPartialTick, pEntity.yo, pEntity.getY()) + vec31.y;
* double d5 = Mth.lerp(pPartialTick, pEntity.zo, pEntity.getZ()) + d2;
* }
* <ul>
* <li><code>Mth.lerp</code> 是线性插值函数通常用于在两个值之间平滑过渡</li>
* <li><code>pEntity.xo</code>, <code>pEntity.yo</code>, <code>pEntity.zo</code> 是实体在上一个刻度tick中的位置 <code>pEntity.getX()</code>, <code>pEntity.getY()</code>, <code>pEntity.getZ()</code> 是当前刻度的位置</li>
* <li><code>pPartialTick</code> 介于 <code>0</code> <code>1</code> 之间用来平滑过渡使得动画更加流畅</li>
* </ul>
* <p>
* <h1> 4. 向量差值 </h1>
* {@snippet lang=java :
* float f = (float)(vec3.x - d3);
* float f1 = (float)(vec3.y - d4);
* float f2 = (float)(vec3.z - d5);
* }
* <ul>
* <li>计算两个点<code>vec3</code> <code>(d3, d4, d5)</code>之间的差值得到的 <code>f</code><code>f1</code><code>f2</code> 是向量差用于后续的渲染计算</li>
* </ul>
* <p>
* <h1> 5. 逆平方根与比例因子 </h1>
* {@snippet lang=java :
* float f4 = Mth.invSqrt(f * f + f2 * f2) * 0.025F / 2.0F;
* }
* <ul>
* <li><code>Mth.invSqrt</code> 计算的是逆平方根通常用于归一化向量或调整比例</li>
* <li><code>f * f + f2 * f2</code> 是计算向量 <code>(f, f2)</code> 的平方和用于得到其长度的平方</li>
* <li>乘以 <code>0.025F / 2.0F</code> 用于缩放结果使得线条在渲染时具有合适的比例</li>
* </ul>
* <p>
* <h1> 6. 循环绘制 </h1>
* {@snippet lang=java :
* for (int i1 = 0; i1 <= 24; i1++) {
* addVertexPair(vertexconsumer, matrix4f, f, f1, f2, i, j, k, l, 0.025F, 0.025F, f5, f6, i1, false);
* }
* }
* <ul>
* <li>循环从 <code>0</code> <code>24</code>用于创建24个顶点对形成一个链状结构或绳索的外观</li>
* <li>每个循环迭代都会更新顶点的位置颜色光照等属性使得链状结构被绘制出来</li>
* </ul>
* <p>
* <h1> 总结 </h1>
* 这些数学运算主要用于计算实体在三维空间中的位置和方向以确保在渲染链状结构如拴住的绳索链条能够跟随实体的移动和旋转并正确显示在图形编程中这些计算非常常见尤其是在处理旋转插值和光照效果时
*/
@SuppressWarnings("AddedMixinMembersNamePattern")
@Unique
public <E extends net.minecraft.world.entity.Entity> void renderLeashForCamera(
Camera camera,
float partialTick,
com.mojang.blaze3d.vertex.PoseStack poseStack,
net.minecraft.client.renderer.MultiBufferSource bufferSource,
E leashHolder
) {
poseStack.pushPose();
// 获得绳索持有者的位置
Vec3 leashHolderPosition = leashHolder.getRopeHoldPosition(partialTick);
// 获取当前观察的实体
Entity cameraEntity = camera.getEntity();
// 计算实体的朝向角度弧度
double entityRotationAngleRadians = (double)(cameraEntity.getPreciseBodyRotation(partialTick) * (float) (Math.PI / 180.0)) + (Math.PI / 2);
// 计算实体的绳索偏移此处add偏移让渲染拴绳显示在玩家头部下大约在脖子处
Vec3 cameraEntityLeashOffset = cameraEntity.getLeashOffset(partialTick).add(0, -0.2, -0.5);
double leashOffsetX = Math.cos(entityRotationAngleRadians) * cameraEntityLeashOffset.z + Math.sin(entityRotationAngleRadians) * cameraEntityLeashOffset.x;
double leashOffsetZ = Math.sin(entityRotationAngleRadians) * cameraEntityLeashOffset.z - Math.cos(entityRotationAngleRadians) * cameraEntityLeashOffset.x;
// 计算实体当前的实际位置
double entityPosX = Mth.lerp(partialTick, cameraEntity.xo, cameraEntity.getX()) + leashOffsetX;
double entityPosY = Mth.lerp(partialTick, cameraEntity.yo, cameraEntity.getY()) + cameraEntityLeashOffset.y;
double entityPosZ = Mth.lerp(partialTick, cameraEntity.zo, cameraEntity.getZ()) + leashOffsetZ;
// 在当前变换矩阵上应用偏移
poseStack.translate(leashOffsetX, cameraEntityLeashOffset.y , leashOffsetZ);
// 计算绳索的相对位置差
float deltaX = (float)(leashHolderPosition.x - entityPosX);
float deltaY = (float)(leashHolderPosition.y - entityPosY);
float deltaZ = (float)(leashHolderPosition.z - entityPosZ);
// 获取顶点消费者用于绘制绳索
VertexConsumer vertexConsumer = bufferSource.getBuffer(RenderType.leash());
Matrix4f matrix = poseStack.last().pose();
// 计算比例因子用于调节绳索的粗细
float leashLengthRatio = Mth.invSqrt(deltaX * deltaX + deltaZ * deltaZ) * 0.025F / 2.0F;
float leashXZScaleX = deltaZ * leashLengthRatio;
float leashXZScaleZ = deltaX * leashLengthRatio;
// 获取光照信息
BlockPos cameraEntityBlockPos = BlockPos.containing(cameraEntity.getEyePosition(partialTick));
BlockPos leashHolderBlockPos = BlockPos.containing(leashHolder.getEyePosition(partialTick));
int cameraEntityBlockLightLevel = this.getBlockLightLevel((AbstractClientPlayer) cameraEntity, cameraEntityBlockPos);
int leashHolderBlockLightLevel = 0; //getBlockLightLevel(leashHolder, leashHolderBlockPos);
int cameraEntitySkyLightLevel = cameraEntity.level().getBrightness(LightLayer.SKY, cameraEntityBlockPos);
int leashHolderSkyLightLevel = cameraEntity.level().getBrightness(LightLayer.SKY, leashHolderBlockPos);
// 绘制绳索的上半部分
for (int segment = 0; segment <= 24; segment++) {
addVertexPair(vertexConsumer, matrix, deltaX, deltaY, deltaZ, cameraEntityBlockLightLevel, leashHolderBlockLightLevel, cameraEntitySkyLightLevel, leashHolderSkyLightLevel, 0.025F, 0.025F, leashXZScaleX, leashXZScaleZ, segment, false);
}
// 绘制绳索的下半部分
for (int segment = 24; segment >= 0; segment--) {
addVertexPair(vertexConsumer, matrix, deltaX, deltaY, deltaZ, cameraEntityBlockLightLevel, leashHolderBlockLightLevel, cameraEntitySkyLightLevel, leashHolderSkyLightLevel, 0.025F, 0.0F, leashXZScaleX, leashXZScaleZ, segment, true);
}
poseStack.popPose();
}
}

View File

@ -0,0 +1,50 @@
package com.r3944realms.leashedplayer.mixin.item;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.LeadItem;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(LeadItem.class)
public class MixinLeadItem {
/**
* 拴住自己的逻辑
*/
@Inject(
method = {"bindPlayerMobs"},
at = @At("HEAD"),
cancellable = true)
private static void selfLeash(Player pPlayer, Level pLevel, BlockPos pPos, CallbackInfoReturnable<InteractionResult> cir) {
List<Leashable> list = LeadItem.leashableInArea(pLevel, pPos, p_353025_ -> p_353025_.getLeashHolder() == pPlayer);
if (list.isEmpty()) {
ItemStack mainHandItem = pPlayer.getMainHandItem();
if (!(mainHandItem.getItem() instanceof LeadItem )) {
return;
}
//非创造模式减少防止刷物品
if(!pPlayer.isCreative()) mainHandItem.shrink(1);
//自己
PlayerLeashable self = (PlayerLeashable) pPlayer;
//获取拴绳结实体
LeashFenceKnotEntity leashfenceknotentity = LeashFenceKnotEntity.getOrCreateKnot(pLevel, pPos);
//播放绳结被放置的声音
leashfenceknotentity.playPlacementSound();
//将自己与拴绳结绑定LeashData
self.setLeashedTo(leashfenceknotentity, true);
pLevel.gameEvent(GameEvent.BLOCK_ATTACH, pPos, GameEvent.Context.of(pPlayer));
cir.setReturnValue(InteractionResult.SUCCESS);
}
}
}

View File

@ -0,0 +1,56 @@
package com.r3944realms.leashedplayer.mixin.server;
import com.r3944realms.leashedplayer.content.gamerules.GameruleRegistry;
import com.r3944realms.leashedplayer.content.gamerules.Server.TeleportWithLeashedPlayers;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.RelativeMovement;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.*;
import java.util.stream.Collectors;
import static com.r3944realms.leashedplayer.utils.Logger.logger;
@Mixin(ServerGamePacketListenerImpl.class)
public class MixinServerGamePacketListenerImpl {
@Shadow
public ServerPlayer player;
@Unique
private List<Entity> Whimsy$LeashPlayers = new ArrayList<>();
@Inject(method = {"teleport(DDDFFLjava/util/Set;)V"}, at = {@At("HEAD")})
private void teleportHead(double pX, double pY, double pZ, float pYaw, float pPitch, Set<RelativeMovement> pRelativeSet, CallbackInfo ci) {
try {
//獲取Holder
this.Whimsy$LeashPlayers = ((PlayerLeashable)this.player).getLeashHolder() != null ? Collections.emptyList() : Objects.requireNonNull(this.player.getServer()).getPlayerList().getPlayers().stream().filter(serverPlayer -> (serverPlayer instanceof PlayerLeashable) && ((PlayerLeashable)serverPlayer).getLeashHolder() == this.player && player != serverPlayer).collect(Collectors.toList());
} catch (Exception e) {
logger.error("Internal Error:",e);
}
}
@Inject(method = {"teleport(DDDFFLjava/util/Set;)V"}, at = {@At("TAIL")})
private void teleportTail(double pX, double pY, double pZ, float pYaw, float pPitch, Set<RelativeMovement> pRelativeSet, CallbackInfo ci) {
if(GameruleRegistry.getGameruleBoolValue(this.player.serverLevel(), TeleportWithLeashedPlayers.ID)) {
for (Entity whimsy$LeashPlayer : this.Whimsy$LeashPlayers) {
if(whimsy$LeashPlayer instanceof ServerPlayer) {
if(whimsy$LeashPlayer instanceof PlayerLeashable playerLeashable) {
playerLeashable.dropLeash(false,false);
if(((ServerPlayer) playerLeashable).serverLevel() == this.player.serverLevel()) {
((ServerPlayer) playerLeashable).connection.teleport(pX, pY, pZ, pYaw, pPitch, pRelativeSet);
} else {
((ServerPlayer) playerLeashable).teleportTo(this.player.serverLevel(), pX, pY, pZ, pYaw, pPitch);
((ServerPlayer) playerLeashable).stopRiding();
}
playerLeashable.setLeashedTo(this.player, true);
}
}
}
}
}
}

View File

@ -0,0 +1,15 @@
package com.r3944realms.leashedplayer.modInterface;
public interface ILivingEntityExtension {
/**
* 获取拴绳的长度
* @return length 拴绳的长度Float
*/
float getLeashLength();
/**
* 设置拴绳的长度
* @param length 拴绳的长度Float
*/
void setLeashLength(float length);
}

View File

@ -0,0 +1,13 @@
package com.r3944realms.leashedplayer.modInterface;
import net.minecraft.client.Camera;
public interface IPlayerRendererExtension {
<E extends net. minecraft. world. entity. Entity> void renderLeashForCamera(
Camera pCamera,
float pPartialTick,
com.mojang.blaze3d.vertex.PoseStack pPoseStack,
net.minecraft.client.renderer.MultiBufferSource pBufferSource,
E pLeashHolder
);
}

View File

@ -0,0 +1,54 @@
package com.r3944realms.leashedplayer.modInterface;
import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
public interface PlayerLeashable extends Leashable {
/**
* 获取拴绳的持有者实体 可能不存在
*/
@Nullable
Entity getLeashHolder();
/**
* 获取拴绳的持有者数据 从EntityData中获取
*/
Leashable.LeashData getLeashDataFromEntityData();
/**
* 是否立即可被拴住
*/
boolean canBeLeashedInstantly(Player player);
/**
* 设置栓绳数据
* @param pLeashHolder 拴绳持有者
* @param pBroadcastPacket 是否广播包
*/
default void setLeashedTo(@NotNull Entity pLeashHolder, boolean pBroadcastPacket) {
setLeashedTo((Entity & Leashable)this, pLeashHolder, pBroadcastPacket);
}
static <E extends Entity & Leashable> void setLeashedTo(E pEntity, Entity pLeashHolder, boolean pBroadcastPacket) {
Leashable.LeashData leashable$leashdata = pEntity.getLeashData();
if (leashable$leashdata == null) {
leashable$leashdata = new Leashable.LeashData(pLeashHolder);
pEntity.setLeashData(leashable$leashdata);
} else {
leashable$leashdata.setLeashHolder(pLeashHolder);
}
if (pBroadcastPacket && pEntity.level() instanceof ServerLevel serverlevel) {
serverlevel.getChunkSource().broadcast(pEntity, new ClientboundSetEntityLinkPacket(pEntity, pLeashHolder));
}
//这边覆写去掉了乘坐相关的逻辑即乘坐状态下也可以正常被栓住不影响其乘坐状态
}
}

View File

@ -0,0 +1,13 @@
package com.r3944realms.leashedplayer.utils.Enum;
public enum LanguageEnum {
English("en_us"),
SimpleChinese("zh_cn"),
TraditionalChinese("zh_tw"),
LiteraryChinese("lzh"),
;
public final String local;
LanguageEnum(String local) {
this.local = local;
}
}

View File

@ -0,0 +1,21 @@
package com.r3944realms.leashedplayer.utils.Enum;
public enum ModPartEnum {
DEFAULT,
ITEM,
BLOCK,
ENCHANTMENT,
ADVANCEMENT,
CREATIVE_TAB,
CONFIG,
ENTITY,
GUI,
AUTHOR,
TITLE,
NAME,
DESCRIPTION,
INFO,
MESSAGE,
COMMAND,
}

View File

@ -0,0 +1,7 @@
package com.r3944realms.leashedplayer.utils;
import org.slf4j.LoggerFactory;
public class Logger {
public static final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
}

View File

@ -0,0 +1,13 @@
package com.r3944realms.leashedplayer.utils;
import com.r3944realms.leashedplayer.content.gamerules.Gamerules;
public class Util {
public static String getGameruleName(Class<?> clazz) {
return Gamerules.GAMERULE_PREFIX + clazz.getSimpleName();
}
public static String getGameruleName(String gamerulesName) {
return Gamerules.GAMERULE_PREFIX + gamerulesName;
}
}

View File

@ -0,0 +1,14 @@
#package-private -> public
public net.minecraft.world.entity.Leashable$LeashData <init>(Lnet/minecraft/world/entity/Entity;)V # LeashData
#priavte ->protected
protected net.minecraft.client.renderer.entity.EntityRenderer renderLeash(Lnet/minecraft/world/entity/Entity;FLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;Lnet/minecraft/world/entity/Entity;)V # renderLeash
#priavte ->protected
protected net.minecraft.client.renderer.entity.EntityRenderer addVertexPair(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lorg/joml/Matrix4f;FFFIIIIFFFFIZ)V # addVertexPair
#这个方法原包为package-priavet 有时需要限制范围故修改
public net.minecraft.world.level.GameRules$IntegerValue create(IIILjava/util/function/BiConsumer;)Lnet/minecraft/world/level/GameRules$Type; # create
#因为'net.minecraft.world.level.GameRules.Type' 中<init>不为 public。无法从外部软件包访问
public net.minecraft.world.level.GameRules$Type <init>(Ljava/util/function/Supplier;Ljava/util/function/Function;Ljava/util/function/BiConsumer;Lnet/minecraft/world/level/GameRules$VisitorCaller;)V # Type
#因为'net.minecraft.world.level.GameRules.VisitorCaller' 在 'net.minecraft.world.level.GameRules' 中不为 public。无法从外部软件包访问
public net.minecraft.world.level.GameRules$VisitorCaller #Interface
#private -> public
public net.minecraft.world.entity.Leashable$LeashData delayedLeashHolderId # delayedLeashHolderId

View File

@ -47,13 +47,13 @@ authors="${mod_authors}" #optional
description='''${mod_description}'''
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
#[[mixins]]
#config="${mod_id}.mixins.json"
[[mixins]]
config="${mod_id}.mixins.json"
# The [[accessTransformers]] block allows you to declare where your AT file is.
# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg
#[[accessTransformers]]
#file="META-INF/accesstransformer.cfg"
[[accessTransformers]]
file="META-INF/accesstransformer.cfg"
# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json

View File

@ -0,0 +1,19 @@
{
"package": "com.r3944realms.leashedplayer.mixin",
"mixins": [
"both.MixinEntity",
"both.MixinLivingEntity",
"both.MixinPlayer",
"item.MixinLeadItem",
"server.MixinServerGamePacketListenerImpl"
],
"client": [
"client.MixinEntityRenderer",
"client.MixinLevelRenderer",
"client.MixinPlayerRenderer"
],
"refmap": "whimsicality.refmap.json",
"required": true,
"minVersion": "0.8",
"compatibilityLevel": "JAVA_17"
}