diff --git a/build.gradle b/build.gradle index 6588f7a..75ad141 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/gradle.properties b/gradle.properties index 2221799..be81186 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/generated/resources/.cache/211976637bfb5e111401ad2bfb58570ef2fb3dff b/src/generated/resources/.cache/211976637bfb5e111401ad2bfb58570ef2fb3dff new file mode 100644 index 0000000..a655a10 --- /dev/null +++ b/src/generated/resources/.cache/211976637bfb5e111401ad2bfb58570ef2fb3dff @@ -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 diff --git a/src/generated/resources/.cache/853329c6e706e45295e80307b8a95a5709025422 b/src/generated/resources/.cache/853329c6e706e45295e80307b8a95a5709025422 new file mode 100644 index 0000000..7c6cab0 --- /dev/null +++ b/src/generated/resources/.cache/853329c6e706e45295e80307b8a95a5709025422 @@ -0,0 +1,2 @@ +// 1.21 2024-09-03T17:46:39.9787945 Languages: lzh for mod: leashedplayer +bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f assets/leashedplayer/lang/lzh.json diff --git a/src/generated/resources/.cache/a1129211d3ad6d65c101bb152ae8c66c8256bccb b/src/generated/resources/.cache/a1129211d3ad6d65c101bb152ae8c66c8256bccb new file mode 100644 index 0000000..931b7fa --- /dev/null +++ b/src/generated/resources/.cache/a1129211d3ad6d65c101bb152ae8c66c8256bccb @@ -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 diff --git a/src/generated/resources/.cache/ed628fd843215c1bf29a07b9cbd1b26a6af0636d b/src/generated/resources/.cache/ed628fd843215c1bf29a07b9cbd1b26a6af0636d new file mode 100644 index 0000000..d90dea0 --- /dev/null +++ b/src/generated/resources/.cache/ed628fd843215c1bf29a07b9cbd1b26a6af0636d @@ -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 diff --git a/src/generated/resources/assets/leashedplayer/lang/en_us.json b/src/generated/resources/assets/leashedplayer/lang/en_us.json new file mode 100644 index 0000000..0d4184c --- /dev/null +++ b/src/generated/resources/assets/leashedplayer/lang/en_us.json @@ -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" +} \ No newline at end of file diff --git a/src/generated/resources/assets/leashedplayer/lang/lzh.json b/src/generated/resources/assets/leashedplayer/lang/lzh.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/generated/resources/assets/leashedplayer/lang/lzh.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/generated/resources/assets/leashedplayer/lang/zh_cn.json b/src/generated/resources/assets/leashedplayer/lang/zh_cn.json new file mode 100644 index 0000000..35f62a5 --- /dev/null +++ b/src/generated/resources/assets/leashedplayer/lang/zh_cn.json @@ -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格" +} \ No newline at end of file diff --git a/src/generated/resources/assets/leashedplayer/lang/zh_tw.json b/src/generated/resources/assets/leashedplayer/lang/zh_tw.json new file mode 100644 index 0000000..1cc33af --- /dev/null +++ b/src/generated/resources/assets/leashedplayer/lang/zh_tw.json @@ -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格" +} \ No newline at end of file diff --git a/src/main/java/com/r3944realms/leashedplayer/CommonEventHandler.java b/src/main/java/com/r3944realms/leashedplayer/CommonEventHandler.java new file mode 100644 index 0000000..d5c7a5a --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/CommonEventHandler.java @@ -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 dispatcher = event.getDispatcher(); + LeashCommand.register(dispatcher); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/Config.java b/src/main/java/com/r3944realms/leashedplayer/Config.java deleted file mode 100644 index dec7781..0000000 --- a/src/main/java/com/r3944realms/leashedplayer/Config.java +++ /dev/null @@ -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 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> 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 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()); - } -} diff --git a/src/main/java/com/r3944realms/leashedplayer/ExampleMod.java b/src/main/java/com/r3944realms/leashedplayer/ExampleMod.java deleted file mode 100644 index ef4be3e..0000000 --- a/src/main/java/com/r3944realms/leashedplayer/ExampleMod.java +++ /dev/null @@ -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 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 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 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 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 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()); - } - } -} diff --git a/src/main/java/com/r3944realms/leashedplayer/LeashedPlayer.java b/src/main/java/com/r3944realms/leashedplayer/LeashedPlayer.java new file mode 100644 index 0000000..17bea37 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/LeashedPlayer.java @@ -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"; + +} diff --git a/src/main/java/com/r3944realms/leashedplayer/content/commands/Command.java b/src/main/java/com/r3944realms/leashedplayer/content/commands/Command.java new file mode 100644 index 0000000..2ac3362 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/content/commands/Command.java @@ -0,0 +1,5 @@ +package com.r3944realms.leashedplayer.content.commands; + +public class Command { + public static final String PREFIX = "lp"; +} diff --git a/src/main/java/com/r3944realms/leashedplayer/content/commands/LeashCommand.java b/src/main/java/com/r3944realms/leashedplayer/content/commands/LeashCommand.java new file mode 100644 index 0000000..93477eb --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/content/commands/LeashCommand.java @@ -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 dispatcher) { + LiteralArgumentBuilder literalArgumentBuilder = Commands.literal(com.r3944realms.leashedplayer.content.commands.Command.PREFIX); + Command 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 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 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 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 $$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); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/content/gamerules/GameruleRegistry.java b/src/main/java/com/r3944realms/leashedplayer/content/gamerules/GameruleRegistry.java new file mode 100644 index 0000000..9e79968 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/content/gamerules/GameruleRegistry.java @@ -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> gamerules = new HashMap<>();; + public static final Map 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.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.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 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) (s, i)->{});//最后一个仅占位无用 + } + public void registerGamerule(String gameruleName, GameRules.Category category, int pDefault, BiConsumer 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 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 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 pChangeListener) { + gamerules.put(gameruleName, GameRules.register(gameruleName, category, Gamerules.FloatValue.create(pDefault, pMin, pMax,pChangeListener))); + gameruleDataTypes.put(gameruleName, RuleDataType.INTEGER); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/content/gamerules/Gamerules.java b/src/main/java/com/r3944realms/leashedplayer/content/gamerules/Gamerules.java new file mode 100644 index 0000000..4f6108e --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/content/gamerules/Gamerules.java @@ -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 gamerulesBooleanValuesClient = new HashMap<>(); + public static final HashMap gameruleIntegerValuesClient = new HashMap<>(); + public static final HashMap 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 { + private float value; + public static GameRules.Type create( + float pDefaultValue, BiConsumer pChangeListener + ) { + return new GameRules.Type<> + (FloatArgumentType::floatArg, + pType -> new FloatValue(pType, pDefaultValue), + pChangeListener, + GameRules.GameRuleTypeVisitor::visit + ); + } + public static GameRules.Type create( + float pDefaultValue, float pMin, float pMax , BiConsumer pChangeListener + ) { + return new GameRules.Type<>( + () -> FloatArgumentType.floatArg(pMin, pMax), + pType -> new FloatValue(pType, pDefaultValue), + pChangeListener, + GameRules.GameRuleTypeVisitor::visit + ); + } + public FloatValue(GameRules.Type pType, float value) { + super(pType); + this.value = value; + } + + @Override + protected void updateFromArgument(@NotNull CommandContext 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); + } + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/content/gamerules/Server/TeleportWithLeashedPlayers.java b/src/main/java/com/r3944realms/leashedplayer/content/gamerules/Server/TeleportWithLeashedPlayers.java new file mode 100644 index 0000000..ec023e3 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/content/gamerules/Server/TeleportWithLeashedPlayers.java @@ -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); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/datagen/LanguageAndOtherData/ModLangKeyValue.java b/src/main/java/com/r3944realms/leashedplayer/datagen/LanguageAndOtherData/ModLangKeyValue.java new file mode 100644 index 0000000..9e44c44 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/datagen/LanguageAndOtherData/ModLangKeyValue.java @@ -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; + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/datagen/ModDataGeneratorHandler.java b/src/main/java/com/r3944realms/leashedplayer/datagen/ModDataGeneratorHandler.java new file mode 100644 index 0000000..b22d04d --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/datagen/ModDataGeneratorHandler.java @@ -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 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) pOutput -> new ModLanguageProvider(pOutput, LeashedPlayer.MOD_ID, language) + ); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/datagen/provider/ModLanguageProvider.java b/src/main/java/com/r3944realms/leashedplayer/datagen/provider/ModLanguageProvider.java new file mode 100644 index 0000000..4254282 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/datagen/provider/ModLanguageProvider.java @@ -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 LanKeyMap; + private static final List 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))); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinEntity.java b/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinEntity.java new file mode 100644 index 0000000..2f9fdf2 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinEntity.java @@ -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}接口时,
+ * 阻止原版的{@link Leashable}中 的tickLeash方法调用,将其
+ * 我们需自己实现相关的逻辑 + * @param entity 实体 + * @param 实体类型 + */ + @Redirect( + method = "baseTick", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Leashable;tickLeash(Lnet/minecraft/world/entity/Entity;)V") + ) + void checkAndCancelIfTure(E entity) { + if(!(entity instanceof PlayerLeashable)) { + Leashable.tickLeash(entity); + } + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinLivingEntity.java b/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinLivingEntity.java new file mode 100644 index 0000000..aa5ad87 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinLivingEntity.java @@ -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 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()); + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinPlayer.java b/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinPlayer.java new file mode 100644 index 0000000..f514510 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/both/MixinPlayer.java @@ -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 Pl$LEASH_DATA = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG); + + protected MixinPlayer(EntityType 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 pOpt) { + Consumer 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); + } + } + + +} diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinEntityRenderer.java b/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinEntityRenderer.java new file mode 100644 index 0000000..e75bb3b --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinEntityRenderer.java @@ -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);//非实现这个接口则不变 + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinLevelRenderer.java b/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinLevelRenderer.java new file mode 100644 index 0000000..294b058 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinLevelRenderer.java @@ -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 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); + } + } + } + } + } + } + } +} + diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinPlayerRenderer.java b/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinPlayerRenderer.java new file mode 100644 index 0000000..2783caf --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/client/MixinPlayerRenderer.java @@ -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> implements IPlayerRendererExtension { + public MixinPlayerRenderer(EntityRendererProvider.Context pContext, PlayerModel 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 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); + } + } + } + } + /** + *

1. 角度与弧度转换

+ * {@snippet lang=java : + * double d0 = (double)(pEntity.getPreciseBodyRotation(pPartialTick) * (float)(Math.PI / 180.0)) + (Math.PI / 2); + * } + *
    + *
  • pEntity.getPreciseBodyRotation(pPartialTick) 返回实体的旋转角度(通常是以度为单位)。/li> + *
  • (Math.PI / 180.0) 是将度数转换为弧度的乘数,因为大多数三角函数(如 cossin)都需要弧度值。
  • + *
  • + (Math.PI / 2) 用于将结果平移90度(四分之一圆),可能是为了校正方向或设置起始方向。
  • + *
+ * + *

+ *

2. 三角函数计算位移

+ * {@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; + * } + *
    + *
  • d1d2 是利用三角函数 cossin 计算出来的位移量,用于确定实体相对于其旋转的实际位置。
  • + *
  • Math.cos(d0) * vec31.zMath.sin(d0) * vec31.x 分别计算沿 X 和 Z 轴的位移分量,这种计算通常用于旋转一个点或向量。
  • + *
  • 两个公式结合起来用于旋转平面内的一个点 (vec31.x, vec31.z),从而得到旋转后的新坐标。
  • + *
+ *

+ *

3. 线性插值 (Lerp)

+ * {@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; + * } + *
    + *
  • Mth.lerp 是线性插值函数,通常用于在两个值之间平滑过渡。
  • + *
  • pEntity.xo, pEntity.yo, pEntity.zo 是实体在上一个刻度(tick)中的位置,而 pEntity.getX(), pEntity.getY(), pEntity.getZ() 是当前刻度的位置。
  • + *
  • pPartialTick 介于 01 之间,用来平滑过渡,使得动画更加流畅。
  • + *
+ *

+ *

4. 向量差值

+ * {@snippet lang=java : + * float f = (float)(vec3.x - d3); + * float f1 = (float)(vec3.y - d4); + * float f2 = (float)(vec3.z - d5); + * } + *
    + *
  • 计算两个点(vec3(d3, d4, d5))之间的差值,得到的 ff1f2 是向量差,用于后续的渲染计算。
  • + *
+ *

+ *

5. 逆平方根与比例因子

+ * {@snippet lang=java : + * float f4 = Mth.invSqrt(f * f + f2 * f2) * 0.025F / 2.0F; + * } + *
    + *
  • Mth.invSqrt 计算的是逆平方根(通常用于归一化向量或调整比例)。
  • + *
  • f * f + f2 * f2 是计算向量 (f, f2) 的平方和,用于得到其长度的平方。
  • + *
  • 乘以 0.025F / 2.0F 用于缩放结果,使得线条在渲染时具有合适的比例。
  • + *
+ *

+ *

6. 循环绘制

+ * {@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); + * } + * } + *
    + *
  • 循环从 024,用于创建24个顶点对,形成一个链状结构(或绳索)的外观。
  • + *
  • 每个循环迭代都会更新顶点的位置、颜色、光照等属性,使得链状结构被绘制出来。
  • + *
+ *

+ *

总结

+ * 这些数学运算主要用于计算实体在三维空间中的位置和方向,以确保在渲染链状结构(如拴住的绳索)时,链条能够跟随实体的移动和旋转并正确显示。在图形编程中,这些计算非常常见,尤其是在处理旋转、插值和光照效果时。 + */ + @SuppressWarnings("AddedMixinMembersNamePattern") + @Unique + public 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(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/item/MixinLeadItem.java b/src/main/java/com/r3944realms/leashedplayer/mixin/item/MixinLeadItem.java new file mode 100644 index 0000000..5bd7c29 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/item/MixinLeadItem.java @@ -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 cir) { + List 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); + } + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/mixin/server/MixinServerGamePacketListenerImpl.java b/src/main/java/com/r3944realms/leashedplayer/mixin/server/MixinServerGamePacketListenerImpl.java new file mode 100644 index 0000000..5dac58c --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/mixin/server/MixinServerGamePacketListenerImpl.java @@ -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 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 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 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); + } + } + } + } + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/modInterface/ILivingEntityExtension.java b/src/main/java/com/r3944realms/leashedplayer/modInterface/ILivingEntityExtension.java new file mode 100644 index 0000000..8eb1377 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/modInterface/ILivingEntityExtension.java @@ -0,0 +1,15 @@ +package com.r3944realms.leashedplayer.modInterface; + +public interface ILivingEntityExtension { + /** + * 获取拴绳的长度 + * @return length 拴绳的长度(Float) + */ + float getLeashLength(); + + /** + * 设置拴绳的长度 + * @param length 拴绳的长度(Float) + */ + void setLeashLength(float length); +} diff --git a/src/main/java/com/r3944realms/leashedplayer/modInterface/IPlayerRendererExtension.java b/src/main/java/com/r3944realms/leashedplayer/modInterface/IPlayerRendererExtension.java new file mode 100644 index 0000000..90d5038 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/modInterface/IPlayerRendererExtension.java @@ -0,0 +1,13 @@ +package com.r3944realms.leashedplayer.modInterface; + +import net.minecraft.client.Camera; + +public interface IPlayerRendererExtension { + void renderLeashForCamera( + Camera pCamera, + float pPartialTick, + com.mojang.blaze3d.vertex.PoseStack pPoseStack, + net.minecraft.client.renderer.MultiBufferSource pBufferSource, + E pLeashHolder + ); +} diff --git a/src/main/java/com/r3944realms/leashedplayer/modInterface/PlayerLeashable.java b/src/main/java/com/r3944realms/leashedplayer/modInterface/PlayerLeashable.java new file mode 100644 index 0000000..15f0830 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/modInterface/PlayerLeashable.java @@ -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 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)); + } + //这边覆写去掉了乘坐相关的逻辑,即乘坐状态下也可以正常被栓住,不影响其乘坐状态 + + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/utils/Enum/LanguageEnum.java b/src/main/java/com/r3944realms/leashedplayer/utils/Enum/LanguageEnum.java new file mode 100644 index 0000000..5593d0d --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/utils/Enum/LanguageEnum.java @@ -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; + } +} diff --git a/src/main/java/com/r3944realms/leashedplayer/utils/Enum/ModPartEnum.java b/src/main/java/com/r3944realms/leashedplayer/utils/Enum/ModPartEnum.java new file mode 100644 index 0000000..362b078 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/utils/Enum/ModPartEnum.java @@ -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, + +} diff --git a/src/main/java/com/r3944realms/leashedplayer/utils/Logger.java b/src/main/java/com/r3944realms/leashedplayer/utils/Logger.java new file mode 100644 index 0000000..da59223 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/utils/Logger.java @@ -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); +} diff --git a/src/main/java/com/r3944realms/leashedplayer/utils/Util.java b/src/main/java/com/r3944realms/leashedplayer/utils/Util.java new file mode 100644 index 0000000..6d28729 --- /dev/null +++ b/src/main/java/com/r3944realms/leashedplayer/utils/Util.java @@ -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; + } + +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..e6a6f50 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,14 @@ +#package-private -> public +public net.minecraft.world.entity.Leashable$LeashData (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' 中不为 public。无法从外部软件包访问 +public net.minecraft.world.level.GameRules$Type (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 \ No newline at end of file diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 1b9f6e9..a7fedad 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -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 diff --git a/src/main/resources/leashedplayer.mixins.json b/src/main/resources/leashedplayer.mixins.json new file mode 100644 index 0000000..4021e04 --- /dev/null +++ b/src/main/resources/leashedplayer.mixins.json @@ -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" +} \ No newline at end of file