- 回城魔镜:传送至出生点(优先床/重生锚,否则世界出生点) - 传送魔镜:打开玩家选择UI后传送到目标玩家身边 - 4个等级(初级/中级/高级/永久),支持耐久、冷却、副作用配置 - 8个合成配方 - 支持中英文语言 - 修复配置加载顺序问题
@@ -12,28 +12,28 @@ parchment_mappings_version=2024.11.10
|
|||||||
# Environment Properties
|
# Environment Properties
|
||||||
# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge
|
# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge
|
||||||
# The Minecraft version must agree with the Neo version to get a valid artifact
|
# The Minecraft version must agree with the Neo version to get a valid artifact
|
||||||
minecraft_version=1.21
|
minecraft_version=1.21.1
|
||||||
# The Minecraft version range can use any release version of Minecraft as bounds.
|
# The Minecraft version range can use any release version of Minecraft as bounds.
|
||||||
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
|
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
|
||||||
# as they do not follow standard versioning conventions.
|
# as they do not follow standard versioning conventions.
|
||||||
minecraft_version_range=[1.21]
|
minecraft_version_range=[1.21.1]
|
||||||
# The Neo version must agree with the Minecraft version to get a valid artifact
|
# The Neo version must agree with the Minecraft version to get a valid artifact
|
||||||
neo_version=21.0.167
|
neo_version=21.1.227
|
||||||
# The loader version range can only use the major version of FML as bounds
|
# The loader version range can only use the major version of FML as bounds
|
||||||
loader_version_range=[1,)
|
loader_version_range=[2,)
|
||||||
|
|
||||||
## Mod Properties
|
## Mod Properties
|
||||||
|
|
||||||
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
|
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
|
||||||
# Must match the String constant located in the main mod class annotated with @Mod.
|
# Must match the String constant located in the main mod class annotated with @Mod.
|
||||||
mod_id=examplemod
|
mod_id=teleportmirror
|
||||||
# The human-readable display name for the mod.
|
# The human-readable display name for the mod.
|
||||||
mod_name=Example Mod
|
mod_name=Teleport Mirror
|
||||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||||
mod_license=All Rights Reserved
|
mod_license=MIT
|
||||||
# The mod version. See https://semver.org/
|
# The mod version. See https://semver.org/
|
||||||
mod_version=1.0.0
|
mod_version=1.0.0
|
||||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
# 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.
|
# This should match the base package used for the mod sources.
|
||||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||||
mod_group_id=com.example.examplemod
|
mod_group_id=com.le.teleportmirror
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package com.example.examplemod;
|
|
||||||
|
|
||||||
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
|
|
||||||
public class Config {
|
|
||||||
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();
|
|
||||||
|
|
||||||
public static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
|
|
||||||
.comment("Whether to log the dirt block on common setup")
|
|
||||||
.define("logDirtBlock", true);
|
|
||||||
|
|
||||||
public 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
|
|
||||||
public 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();
|
|
||||||
|
|
||||||
private static boolean validateItemName(final Object obj) {
|
|
||||||
return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(ResourceLocation.parse(itemName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package com.example.examplemod;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import com.mojang.logging.LogUtils;
|
|
||||||
|
|
||||||
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.common.Mod;
|
|
||||||
import net.neoforged.fml.config.ModConfig;
|
|
||||||
import net.neoforged.fml.ModContainer;
|
|
||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
|
||||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
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
|
|
||||||
public 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(FMLCommonSetupEvent event) {
|
|
||||||
// Some common setup code
|
|
||||||
LOGGER.info("HELLO FROM COMMON SETUP");
|
|
||||||
|
|
||||||
if (Config.LOG_DIRT_BLOCK.getAsBoolean()) {
|
|
||||||
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("{}{}", Config.MAGIC_NUMBER_INTRODUCTION.get(), Config.MAGIC_NUMBER.getAsInt());
|
|
||||||
|
|
||||||
Config.ITEM_STRINGS.get().forEach((item) -> LOGGER.info("ITEM >> {}", item));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = ExampleMod.MODID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
|
||||||
static class ClientModEvents {
|
|
||||||
@SubscribeEvent
|
|
||||||
static void onClientSetup(FMLClientSetupEvent event) {
|
|
||||||
// Some client setup code
|
|
||||||
LOGGER.info("HELLO FROM CLIENT SETUP");
|
|
||||||
LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
65
src/main/java/com/le/teleportmirror/Config.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||||
|
|
||||||
|
public class Config {
|
||||||
|
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue COOLDOWN_SECONDS = BUILDER
|
||||||
|
.comment("Cooldown in seconds between mirror uses")
|
||||||
|
.defineInRange("cooldownSeconds", 60, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue CHARGE_TICKS = BUILDER
|
||||||
|
.comment("Number of ticks required to charge the mirror (20 ticks = 1 second)")
|
||||||
|
.defineInRange("chargeTicks", 40, 1, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue BASIC_DURABILITY = BUILDER
|
||||||
|
.comment("Durability for Basic tier mirrors")
|
||||||
|
.defineInRange("basicDurability", 10, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue INTERMEDIATE_DURABILITY = BUILDER
|
||||||
|
.comment("Durability for Intermediate tier mirrors")
|
||||||
|
.defineInRange("intermediateDurability", 50, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue ADVANCED_DURABILITY = BUILDER
|
||||||
|
.comment("Durability for Advanced tier mirrors")
|
||||||
|
.defineInRange("advancedDurability", 100, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue BASIC_NAUSEA_SECONDS = BUILDER
|
||||||
|
.comment("Nausea effect duration in seconds for Basic tier")
|
||||||
|
.defineInRange("basicNauseaSeconds", 10, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue BASIC_WITHER_SECONDS = BUILDER
|
||||||
|
.comment("Wither effect duration in seconds for Basic tier")
|
||||||
|
.defineInRange("basicWitherSeconds", 5, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue INTERMEDIATE_NAUSEA_SECONDS = BUILDER
|
||||||
|
.comment("Nausea effect duration in seconds for Intermediate tier")
|
||||||
|
.defineInRange("intermediateNauseaSeconds", 5, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue INTERMEDIATE_WITHER_SECONDS = BUILDER
|
||||||
|
.comment("Wither effect duration in seconds for Intermediate tier")
|
||||||
|
.defineInRange("intermediateWitherSeconds", 3, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue ADVANCED_NAUSEA_SECONDS = BUILDER
|
||||||
|
.comment("Nausea effect duration in seconds for Advanced tier")
|
||||||
|
.defineInRange("advancedNauseaSeconds", 3, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_BASIC_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Basic tier mirrors")
|
||||||
|
.define("enableBasicRecipe", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_INTERMEDIATE_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Intermediate tier mirrors")
|
||||||
|
.define("enableIntermediateRecipe", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_ADVANCED_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Advanced tier mirrors")
|
||||||
|
.define("enableAdvancedRecipe", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_PERMANENT_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Permanent tier mirrors")
|
||||||
|
.define("enablePermanentRecipe", true);
|
||||||
|
|
||||||
|
static final ModConfigSpec SPEC = BUILDER.build();
|
||||||
|
}
|
||||||
208
src/main/java/com/le/teleportmirror/MirrorItem.java
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.sounds.SoundEvents;
|
||||||
|
import net.minecraft.sounds.SoundSource;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.InteractionResultHolder;
|
||||||
|
import net.minecraft.world.effect.MobEffectInstance;
|
||||||
|
import net.minecraft.world.effect.MobEffects;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.UseAnim;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.world.food.FoodData;
|
||||||
|
|
||||||
|
public class MirrorItem extends Item {
|
||||||
|
private final MirrorTier tier;
|
||||||
|
private final MirrorType type;
|
||||||
|
|
||||||
|
public MirrorItem(MirrorTier tier, MirrorType type, Properties properties) {
|
||||||
|
super(properties);
|
||||||
|
this.tier = tier;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MirrorTier getTier() {
|
||||||
|
return tier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MirrorType getMirrorType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UseAnim getUseAnimation(ItemStack stack) {
|
||||||
|
return UseAnim.BOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUseDuration(ItemStack stack, LivingEntity entity) {
|
||||||
|
return 72000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||||
|
ItemStack stack = player.getItemInHand(hand);
|
||||||
|
if (player.getCooldowns().isOnCooldown(stack.getItem())) {
|
||||||
|
return InteractionResultHolder.fail(stack);
|
||||||
|
}
|
||||||
|
player.startUsingItem(hand);
|
||||||
|
return InteractionResultHolder.consume(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeCharged) {
|
||||||
|
if (!(entity instanceof Player player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chargeTicks = Config.CHARGE_TICKS.get();
|
||||||
|
if (timeCharged < chargeTicks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level.isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerPlayer serverPlayer = (ServerPlayer) player;
|
||||||
|
|
||||||
|
if (type == MirrorType.RETURN) {
|
||||||
|
performReturnTeleport(serverPlayer, stack);
|
||||||
|
} else {
|
||||||
|
MirrorNetwork.sendOpenSelectionToClient(serverPlayer, tier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performReturnTeleport(ServerPlayer player, ItemStack stack) {
|
||||||
|
ServerLevel serverLevel = player.serverLevel();
|
||||||
|
BlockPos targetPos;
|
||||||
|
ServerLevel targetLevel;
|
||||||
|
ResourceKey<Level> respawnDimension = player.getRespawnDimension();
|
||||||
|
BlockPos respawnPos = player.getRespawnPosition();
|
||||||
|
|
||||||
|
if (respawnPos != null) {
|
||||||
|
targetLevel = player.server.getLevel(respawnDimension);
|
||||||
|
if (targetLevel == null) {
|
||||||
|
targetLevel = serverLevel;
|
||||||
|
}
|
||||||
|
targetPos = respawnPos;
|
||||||
|
} else {
|
||||||
|
targetLevel = player.server.getLevel(Level.OVERWORLD);
|
||||||
|
if (targetLevel == null) {
|
||||||
|
targetLevel = serverLevel;
|
||||||
|
}
|
||||||
|
targetPos = targetLevel.getSharedSpawnPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeMirrorUse(player, stack);
|
||||||
|
|
||||||
|
player.teleportTo(targetLevel, targetPos.getCenter().x, targetPos.getCenter().y, targetPos.getCenter().z,
|
||||||
|
player.getYRot(), player.getXRot());
|
||||||
|
|
||||||
|
targetLevel.playSound(null, targetPos, SoundEvents.ENDERMAN_TELEPORT, SoundSource.PLAYERS, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void performTeleportToPlayer(ServerPlayer player, ServerPlayer target, ItemStack stack) {
|
||||||
|
ServerLevel targetLevel = target.serverLevel();
|
||||||
|
BlockPos targetPos = target.blockPosition();
|
||||||
|
|
||||||
|
consumeMirrorUse(player, stack);
|
||||||
|
|
||||||
|
player.teleportTo(targetLevel, targetPos.getCenter().x, targetPos.getCenter().y, targetPos.getCenter().z,
|
||||||
|
player.getYRot(), player.getXRot());
|
||||||
|
|
||||||
|
targetLevel.playSound(null, targetPos, SoundEvents.ENDERMAN_TELEPORT, SoundSource.PLAYERS, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getConfigMaxDamage() {
|
||||||
|
return switch (tier) {
|
||||||
|
case BASIC -> Config.BASIC_DURABILITY.get();
|
||||||
|
case INTERMEDIATE -> Config.INTERMEDIATE_DURABILITY.get();
|
||||||
|
case ADVANCED -> Config.ADVANCED_DURABILITY.get();
|
||||||
|
case PERMANENT -> Integer.MAX_VALUE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMirrorUse(ServerPlayer player, ItemStack stack) {
|
||||||
|
applySideEffects(player);
|
||||||
|
applyCooldown(player);
|
||||||
|
|
||||||
|
if (!tier.isPermanent()) {
|
||||||
|
int maxDamage = getConfigMaxDamage();
|
||||||
|
if (stack.getDamageValue() + 1 >= maxDamage) {
|
||||||
|
stack.shrink(1);
|
||||||
|
player.level().playSound(null, player.blockPosition(),
|
||||||
|
SoundEvents.ITEM_BREAK, SoundSource.PLAYERS, 1.0f, 1.0f);
|
||||||
|
} else {
|
||||||
|
stack.setDamageValue(stack.getDamageValue() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCooldown(Player player) {
|
||||||
|
int cooldownTicks = Config.COOLDOWN_SECONDS.get() * 20;
|
||||||
|
player.getCooldowns().addCooldown(this, cooldownTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySideEffects(Player player) {
|
||||||
|
int nauseaDuration;
|
||||||
|
int witherDuration;
|
||||||
|
|
||||||
|
switch (tier) {
|
||||||
|
case BASIC:
|
||||||
|
nauseaDuration = Config.BASIC_NAUSEA_SECONDS.get() * 20;
|
||||||
|
witherDuration = Config.BASIC_WITHER_SECONDS.get() * 20;
|
||||||
|
if (nauseaDuration > 0) {
|
||||||
|
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, nauseaDuration, 0));
|
||||||
|
}
|
||||||
|
if (witherDuration > 0) {
|
||||||
|
player.addEffect(new MobEffectInstance(MobEffects.WITHER, witherDuration, 0));
|
||||||
|
}
|
||||||
|
halveFood(player);
|
||||||
|
break;
|
||||||
|
case INTERMEDIATE:
|
||||||
|
nauseaDuration = Config.INTERMEDIATE_NAUSEA_SECONDS.get() * 20;
|
||||||
|
witherDuration = Config.INTERMEDIATE_WITHER_SECONDS.get() * 20;
|
||||||
|
if (nauseaDuration > 0) {
|
||||||
|
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, nauseaDuration, 0));
|
||||||
|
}
|
||||||
|
if (witherDuration > 0) {
|
||||||
|
player.addEffect(new MobEffectInstance(MobEffects.WITHER, witherDuration, 0));
|
||||||
|
}
|
||||||
|
halveFood(player);
|
||||||
|
break;
|
||||||
|
case ADVANCED:
|
||||||
|
nauseaDuration = Config.ADVANCED_NAUSEA_SECONDS.get() * 20;
|
||||||
|
if (nauseaDuration > 0) {
|
||||||
|
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, nauseaDuration, 0));
|
||||||
|
}
|
||||||
|
halveFood(player);
|
||||||
|
break;
|
||||||
|
case PERMANENT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void halveFood(Player player) {
|
||||||
|
FoodData foodData = player.getFoodData();
|
||||||
|
foodData.setFoodLevel(Math.max(1, foodData.getFoodLevel() / 2));
|
||||||
|
foodData.setSaturation(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnchantable(ItemStack stack) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFoil(ItemStack stack) {
|
||||||
|
return tier == MirrorTier.PERMANENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/main/java/com/le/teleportmirror/MirrorNetwork.java
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import com.le.teleportmirror.screen.PlayerSelectionScreen;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
|
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||||
|
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class MirrorNetwork {
|
||||||
|
|
||||||
|
public static void registerPayloads(final RegisterPayloadHandlersEvent event) {
|
||||||
|
final PayloadRegistrar registrar = event.registrar(TeleportMirrorMod.MODID).versioned("1.0.0");
|
||||||
|
|
||||||
|
registrar.playToClient(
|
||||||
|
OpenPlayerSelectionPayload.TYPE,
|
||||||
|
OpenPlayerSelectionPayload.STREAM_CODEC,
|
||||||
|
MirrorNetwork::handleOpenSelection
|
||||||
|
);
|
||||||
|
|
||||||
|
registrar.playToServer(
|
||||||
|
RequestTeleportPayload.TYPE,
|
||||||
|
RequestTeleportPayload.STREAM_CODEC,
|
||||||
|
MirrorNetwork::handleRequestTeleport
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendOpenSelectionToClient(ServerPlayer player, MirrorTier tier) {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
List<UUID> uuids = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ServerPlayer onlinePlayer : player.server.getPlayerList().getPlayers()) {
|
||||||
|
if (!onlinePlayer.getUUID().equals(player.getUUID())) {
|
||||||
|
names.add(onlinePlayer.getName().getString());
|
||||||
|
uuids.add(onlinePlayer.getUUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketDistributor.sendToPlayer(player, new OpenPlayerSelectionPayload(names, uuids, tier.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleOpenSelection(OpenPlayerSelectionPayload payload, IPayloadContext context) {
|
||||||
|
context.enqueueWork(() -> {
|
||||||
|
Minecraft client = Minecraft.getInstance();
|
||||||
|
MirrorTier tier = MirrorTier.valueOf(payload.tierName.toUpperCase());
|
||||||
|
client.setScreen(new PlayerSelectionScreen(payload.playerNames, payload.playerUuids, tier));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleRequestTeleport(RequestTeleportPayload payload, IPayloadContext context) {
|
||||||
|
context.enqueueWork(() -> {
|
||||||
|
ServerPlayer sender = (ServerPlayer) context.player();
|
||||||
|
ServerPlayer target = sender.server.getPlayerList().getPlayer(payload.targetUuid);
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MirrorTier tier = MirrorTier.valueOf(payload.tierName.toUpperCase());
|
||||||
|
ItemStack mainHand = sender.getMainHandItem();
|
||||||
|
ItemStack offHand = sender.getOffhandItem();
|
||||||
|
|
||||||
|
ItemStack mirrorStack = null;
|
||||||
|
if (mainHand.getItem() instanceof MirrorItem mirrorItem && mirrorItem.getMirrorType() == MirrorType.TELEPORT) {
|
||||||
|
mirrorStack = mainHand;
|
||||||
|
} else if (offHand.getItem() instanceof MirrorItem mirrorItem && mirrorItem.getMirrorType() == MirrorType.TELEPORT) {
|
||||||
|
mirrorStack = offHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mirrorStack == null || mirrorStack.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MirrorItem mirrorItem = (MirrorItem) mirrorStack.getItem();
|
||||||
|
if (mirrorItem.getTier() != tier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mirrorItem.performTeleportToPlayer(sender, target, mirrorStack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public record OpenPlayerSelectionPayload(List<String> playerNames, List<UUID> playerUuids,
|
||||||
|
String tierName) implements CustomPacketPayload {
|
||||||
|
public static final Type<OpenPlayerSelectionPayload> TYPE = new Type<>(
|
||||||
|
ResourceLocation.fromNamespaceAndPath(TeleportMirrorMod.MODID, "open_player_selection"));
|
||||||
|
|
||||||
|
public static final StreamCodec<FriendlyByteBuf, OpenPlayerSelectionPayload> STREAM_CODEC = new StreamCodec<>() {
|
||||||
|
@Override
|
||||||
|
public OpenPlayerSelectionPayload decode(FriendlyByteBuf buf) {
|
||||||
|
int size = buf.readVarInt();
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
List<UUID> uuids = new ArrayList<>();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
names.add(buf.readUtf());
|
||||||
|
uuids.add(buf.readUUID());
|
||||||
|
}
|
||||||
|
String tierName = buf.readUtf();
|
||||||
|
return new OpenPlayerSelectionPayload(names, uuids, tierName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(FriendlyByteBuf buf, OpenPlayerSelectionPayload payload) {
|
||||||
|
buf.writeVarInt(payload.playerNames.size());
|
||||||
|
for (int i = 0; i < payload.playerNames.size(); i++) {
|
||||||
|
buf.writeUtf(payload.playerNames.get(i));
|
||||||
|
buf.writeUUID(payload.playerUuids.get(i));
|
||||||
|
}
|
||||||
|
buf.writeUtf(payload.tierName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record RequestTeleportPayload(UUID targetUuid, String tierName) implements CustomPacketPayload {
|
||||||
|
public static final Type<RequestTeleportPayload> TYPE = new Type<>(
|
||||||
|
ResourceLocation.fromNamespaceAndPath(TeleportMirrorMod.MODID, "request_teleport"));
|
||||||
|
|
||||||
|
public static final StreamCodec<FriendlyByteBuf, RequestTeleportPayload> STREAM_CODEC = new StreamCodec<>() {
|
||||||
|
@Override
|
||||||
|
public RequestTeleportPayload decode(FriendlyByteBuf buf) {
|
||||||
|
UUID targetUuid = buf.readUUID();
|
||||||
|
String tierName = buf.readUtf();
|
||||||
|
return new RequestTeleportPayload(targetUuid, tierName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(FriendlyByteBuf buf, RequestTeleportPayload payload) {
|
||||||
|
buf.writeUUID(payload.targetUuid);
|
||||||
|
buf.writeUtf(payload.tierName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/com/le/teleportmirror/MirrorTier.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
public enum MirrorTier {
|
||||||
|
BASIC(10, "basic"),
|
||||||
|
INTERMEDIATE(50, "intermediate"),
|
||||||
|
ADVANCED(100, "advanced"),
|
||||||
|
PERMANENT(-1, "permanent");
|
||||||
|
|
||||||
|
private final int defaultDurability;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
MirrorTier(int defaultDurability, String name) {
|
||||||
|
this.defaultDurability = defaultDurability;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultDurability() {
|
||||||
|
return defaultDurability;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPermanent() {
|
||||||
|
return this == PERMANENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/com/le/teleportmirror/MirrorType.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
public enum MirrorType {
|
||||||
|
RETURN("return"),
|
||||||
|
TELEPORT("teleport");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
MirrorType(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/com/le/teleportmirror/TeleportMirrorMod.java
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
|
import net.neoforged.fml.common.Mod;
|
||||||
|
import net.neoforged.fml.config.ModConfig;
|
||||||
|
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredItem;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
@Mod(TeleportMirrorMod.MODID)
|
||||||
|
public class TeleportMirrorMod {
|
||||||
|
public static final String MODID = "teleportmirror";
|
||||||
|
|
||||||
|
public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MODID);
|
||||||
|
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS =
|
||||||
|
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> BASIC_RETURN_MIRROR = ITEMS.register("basic_return_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.BASIC, MirrorType.RETURN,
|
||||||
|
new Item.Properties().stacksTo(1).durability(MirrorTier.BASIC.getDefaultDurability())));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> INTERMEDIATE_RETURN_MIRROR = ITEMS.register("intermediate_return_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.INTERMEDIATE, MirrorType.RETURN,
|
||||||
|
new Item.Properties().stacksTo(1).durability(MirrorTier.INTERMEDIATE.getDefaultDurability())));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> ADVANCED_RETURN_MIRROR = ITEMS.register("advanced_return_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.ADVANCED, MirrorType.RETURN,
|
||||||
|
new Item.Properties().stacksTo(1).durability(MirrorTier.ADVANCED.getDefaultDurability())));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> PERMANENT_RETURN_MIRROR = ITEMS.register("permanent_return_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.PERMANENT, MirrorType.RETURN,
|
||||||
|
new Item.Properties().stacksTo(1)));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> BASIC_TELEPORT_MIRROR = ITEMS.register("basic_teleport_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.BASIC, MirrorType.TELEPORT,
|
||||||
|
new Item.Properties().stacksTo(1).durability(MirrorTier.BASIC.getDefaultDurability())));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> INTERMEDIATE_TELEPORT_MIRROR = ITEMS.register("intermediate_teleport_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.INTERMEDIATE, MirrorType.TELEPORT,
|
||||||
|
new Item.Properties().stacksTo(1).durability(MirrorTier.INTERMEDIATE.getDefaultDurability())));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> ADVANCED_TELEPORT_MIRROR = ITEMS.register("advanced_teleport_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.ADVANCED, MirrorType.TELEPORT,
|
||||||
|
new Item.Properties().stacksTo(1).durability(MirrorTier.ADVANCED.getDefaultDurability())));
|
||||||
|
|
||||||
|
public static final DeferredItem<MirrorItem> PERMANENT_TELEPORT_MIRROR = ITEMS.register("permanent_teleport_mirror",
|
||||||
|
() -> new MirrorItem(MirrorTier.PERMANENT, MirrorType.TELEPORT,
|
||||||
|
new Item.Properties().stacksTo(1)));
|
||||||
|
|
||||||
|
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> MIRROR_TAB =
|
||||||
|
CREATIVE_MODE_TABS.register("mirror_tab", () -> CreativeModeTab.builder()
|
||||||
|
.title(Component.translatable("itemGroup.teleportmirror"))
|
||||||
|
.withTabsBefore(CreativeModeTabs.COMBAT)
|
||||||
|
.icon(() -> BASIC_RETURN_MIRROR.get().getDefaultInstance())
|
||||||
|
.displayItems((parameters, output) -> {
|
||||||
|
output.accept(BASIC_RETURN_MIRROR.get());
|
||||||
|
output.accept(INTERMEDIATE_RETURN_MIRROR.get());
|
||||||
|
output.accept(ADVANCED_RETURN_MIRROR.get());
|
||||||
|
output.accept(PERMANENT_RETURN_MIRROR.get());
|
||||||
|
output.accept(BASIC_TELEPORT_MIRROR.get());
|
||||||
|
output.accept(INTERMEDIATE_TELEPORT_MIRROR.get());
|
||||||
|
output.accept(ADVANCED_TELEPORT_MIRROR.get());
|
||||||
|
output.accept(PERMANENT_TELEPORT_MIRROR.get());
|
||||||
|
}).build());
|
||||||
|
|
||||||
|
public TeleportMirrorMod(IEventBus modEventBus, ModContainer modContainer) {
|
||||||
|
ITEMS.register(modEventBus);
|
||||||
|
CREATIVE_MODE_TABS.register(modEventBus);
|
||||||
|
|
||||||
|
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
|
||||||
|
modEventBus.addListener(this::onRegisterPayloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRegisterPayloads(final RegisterPayloadHandlersEvent event) {
|
||||||
|
MirrorNetwork.registerPayloads(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.le.teleportmirror.screen;
|
||||||
|
|
||||||
|
import com.le.teleportmirror.MirrorNetwork;
|
||||||
|
import com.le.teleportmirror.MirrorTier;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PlayerSelectionScreen extends Screen {
|
||||||
|
private final List<String> playerNames;
|
||||||
|
private final List<UUID> playerUuids;
|
||||||
|
private final MirrorTier tier;
|
||||||
|
private int scrollOffset = 0;
|
||||||
|
private static final int BUTTONS_PER_PAGE = 8;
|
||||||
|
|
||||||
|
public PlayerSelectionScreen(List<String> playerNames, List<UUID> playerUuids, MirrorTier tier) {
|
||||||
|
super(Component.translatable("screen.teleportmirror.select_player"));
|
||||||
|
this.playerNames = playerNames;
|
||||||
|
this.playerUuids = playerUuids;
|
||||||
|
this.tier = tier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
super.init();
|
||||||
|
rebuildButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildButtons() {
|
||||||
|
this.clearWidgets();
|
||||||
|
|
||||||
|
int centerX = this.width / 2;
|
||||||
|
int startY = 40;
|
||||||
|
int buttonWidth = 200;
|
||||||
|
int buttonHeight = 20;
|
||||||
|
int spacing = 4;
|
||||||
|
|
||||||
|
int maxOffset = Math.max(0, playerNames.size() - BUTTONS_PER_PAGE);
|
||||||
|
if (scrollOffset > maxOffset) scrollOffset = maxOffset;
|
||||||
|
|
||||||
|
int endIndex = Math.min(scrollOffset + BUTTONS_PER_PAGE, playerNames.size());
|
||||||
|
for (int i = scrollOffset; i < endIndex; i++) {
|
||||||
|
final int idx = i;
|
||||||
|
int y = startY + (i - scrollOffset) * (buttonHeight + spacing);
|
||||||
|
this.addRenderableWidget(Button.builder(
|
||||||
|
Component.literal(playerNames.get(i)),
|
||||||
|
btn -> selectPlayer(idx))
|
||||||
|
.pos(centerX - buttonWidth / 2, y)
|
||||||
|
.size(buttonWidth, buttonHeight)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerNames.size() > BUTTONS_PER_PAGE) {
|
||||||
|
this.addRenderableWidget(Button.builder(
|
||||||
|
Component.literal("\u25B2"),
|
||||||
|
btn -> { scrollOffset = Math.max(0, scrollOffset - 1); rebuildButtons(); })
|
||||||
|
.pos(centerX + buttonWidth / 2 + 5, startY)
|
||||||
|
.size(20, 20)
|
||||||
|
.build());
|
||||||
|
this.addRenderableWidget(Button.builder(
|
||||||
|
Component.literal("\u25BC"),
|
||||||
|
btn -> { scrollOffset = Math.min(maxOffset, scrollOffset + 1); rebuildButtons(); })
|
||||||
|
.pos(centerX + buttonWidth / 2 + 5, startY + BUTTONS_PER_PAGE * (buttonHeight + spacing) - spacing)
|
||||||
|
.size(20, 20)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addRenderableWidget(Button.builder(
|
||||||
|
Component.translatable("gui.cancel"),
|
||||||
|
btn -> this.onClose())
|
||||||
|
.pos(centerX - 50, this.height - 30)
|
||||||
|
.size(100, 20)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectPlayer(int index) {
|
||||||
|
if (index >= 0 && index < playerUuids.size()) {
|
||||||
|
UUID targetUuid = playerUuids.get(index);
|
||||||
|
PacketDistributor.sendToServer(new MirrorNetwork.RequestTeleportPayload(targetUuid, tier.getName()));
|
||||||
|
}
|
||||||
|
this.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||||
|
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
|
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 15, 0xFFFFFF);
|
||||||
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPauseScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"itemGroup.examplemod": "Example Mod Tab",
|
|
||||||
"block.examplemod.example_block": "Example Block",
|
|
||||||
"item.examplemod.example_item": "Example Item"
|
|
||||||
}
|
|
||||||
13
src/main/resources/assets/teleportmirror/lang/en_us.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"itemGroup.teleportmirror": "Teleport Mirror",
|
||||||
|
"item.teleportmirror.basic_return_mirror": "Basic Return Mirror",
|
||||||
|
"item.teleportmirror.intermediate_return_mirror": "Intermediate Return Mirror",
|
||||||
|
"item.teleportmirror.advanced_return_mirror": "Advanced Return Mirror",
|
||||||
|
"item.teleportmirror.permanent_return_mirror": "Permanent Return Mirror",
|
||||||
|
"item.teleportmirror.basic_teleport_mirror": "Basic Teleport Mirror",
|
||||||
|
"item.teleportmirror.intermediate_teleport_mirror": "Intermediate Teleport Mirror",
|
||||||
|
"item.teleportmirror.advanced_teleport_mirror": "Advanced Teleport Mirror",
|
||||||
|
"item.teleportmirror.permanent_teleport_mirror": "Permanent Teleport Mirror",
|
||||||
|
"screen.teleportmirror.select_player": "Select Target Player",
|
||||||
|
"teleportmirror.cooldown": "Mirror is on cooldown"
|
||||||
|
}
|
||||||
13
src/main/resources/assets/teleportmirror/lang/zh_cn.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"itemGroup.teleportmirror": "传送魔镜",
|
||||||
|
"item.teleportmirror.basic_return_mirror": "初级回城魔镜",
|
||||||
|
"item.teleportmirror.intermediate_return_mirror": "中级回城魔镜",
|
||||||
|
"item.teleportmirror.advanced_return_mirror": "高级回城魔镜",
|
||||||
|
"item.teleportmirror.permanent_return_mirror": "永久回城魔镜",
|
||||||
|
"item.teleportmirror.basic_teleport_mirror": "初级传送魔镜",
|
||||||
|
"item.teleportmirror.intermediate_teleport_mirror": "中级传送魔镜",
|
||||||
|
"item.teleportmirror.advanced_teleport_mirror": "高级传送魔镜",
|
||||||
|
"item.teleportmirror.permanent_teleport_mirror": "永久传送魔镜",
|
||||||
|
"screen.teleportmirror.select_player": "选择目标玩家",
|
||||||
|
"teleportmirror.cooldown": "魔镜冷却中"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/advanced_return_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/advanced_teleport_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/basic_return_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/basic_teleport_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/intermediate_return_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/intermediate_teleport_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/permanent_return_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "teleportmirror:item/permanent_teleport_mirror"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 143 B |
|
After Width: | Height: | Size: 143 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 143 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 144 B |
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"DED",
|
||||||
|
"MRM",
|
||||||
|
"DBD"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"D": { "item": "minecraft:diamond" },
|
||||||
|
"E": { "item": "minecraft:ender_eye" },
|
||||||
|
"M": { "item": "minecraft:emerald" },
|
||||||
|
"R": { "item": "teleportmirror:intermediate_return_mirror" },
|
||||||
|
"B": { "item": "minecraft:blaze_rod" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:advanced_return_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"DED",
|
||||||
|
"MRM",
|
||||||
|
"DBD"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"D": { "item": "minecraft:diamond" },
|
||||||
|
"E": { "item": "minecraft:ender_eye" },
|
||||||
|
"M": { "item": "minecraft:emerald" },
|
||||||
|
"R": { "item": "teleportmirror:intermediate_teleport_mirror" },
|
||||||
|
"B": { "item": "minecraft:blaze_rod" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:advanced_teleport_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"GCG",
|
||||||
|
"IBI",
|
||||||
|
"PIP"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"G": { "item": "minecraft:gold_ingot" },
|
||||||
|
"C": { "item": "minecraft:compass" },
|
||||||
|
"B": { "item": "minecraft:blue_stained_glass_pane" },
|
||||||
|
"I": { "item": "minecraft:iron_ingot" },
|
||||||
|
"P": { "item": "minecraft:copper_ingot" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:basic_return_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"GLG",
|
||||||
|
"IOI",
|
||||||
|
"PIP"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"G": { "item": "minecraft:gold_ingot" },
|
||||||
|
"L": { "item": "minecraft:lead" },
|
||||||
|
"O": { "item": "minecraft:orange_stained_glass_pane" },
|
||||||
|
"I": { "item": "minecraft:iron_ingot" },
|
||||||
|
"P": { "item": "minecraft:copper_ingot" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:basic_teleport_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"DED",
|
||||||
|
"LRL",
|
||||||
|
"TAT"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"D": { "item": "minecraft:diamond" },
|
||||||
|
"E": { "item": "minecraft:ender_pearl" },
|
||||||
|
"L": { "item": "minecraft:lapis_lazuli" },
|
||||||
|
"R": { "item": "teleportmirror:basic_return_mirror" },
|
||||||
|
"T": { "item": "minecraft:redstone" },
|
||||||
|
"A": { "item": "minecraft:amethyst_shard" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:intermediate_return_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"DED",
|
||||||
|
"LRL",
|
||||||
|
"TAT"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"D": { "item": "minecraft:diamond" },
|
||||||
|
"E": { "item": "minecraft:ender_pearl" },
|
||||||
|
"L": { "item": "minecraft:lapis_lazuli" },
|
||||||
|
"R": { "item": "teleportmirror:basic_teleport_mirror" },
|
||||||
|
"T": { "item": "minecraft:redstone" },
|
||||||
|
"A": { "item": "minecraft:amethyst_shard" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:intermediate_teleport_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"SCS",
|
||||||
|
"ERE",
|
||||||
|
"SBS"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"S": { "item": "minecraft:shulker_shell" },
|
||||||
|
"C": { "item": "minecraft:chorus_fruit" },
|
||||||
|
"E": { "item": "minecraft:ender_pearl" },
|
||||||
|
"R": { "item": "teleportmirror:advanced_return_mirror" },
|
||||||
|
"B": { "item": "minecraft:blaze_rod" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:permanent_return_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"pattern": [
|
||||||
|
"SCS",
|
||||||
|
"ERE",
|
||||||
|
"SBS"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"S": { "item": "minecraft:shulker_shell" },
|
||||||
|
"C": { "item": "minecraft:chorus_fruit" },
|
||||||
|
"E": { "item": "minecraft:ender_pearl" },
|
||||||
|
"R": { "item": "teleportmirror:advanced_teleport_mirror" },
|
||||||
|
"B": { "item": "minecraft:blaze_rod" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"id": "teleportmirror:permanent_teleport_mirror",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,95 +1,33 @@
|
|||||||
# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
|
modLoader="javafml"
|
||||||
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
|
|
||||||
# The overall format is standard TOML format, v0.5.0.
|
|
||||||
# Note that there are a couple of TOML lists in this file.
|
|
||||||
# Find more information on toml format here: https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
|
loaderVersion="${loader_version_range}"
|
||||||
modLoader="javafml" #mandatory
|
|
||||||
|
|
||||||
# A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2.
|
|
||||||
loaderVersion="${loader_version_range}" #mandatory
|
|
||||||
|
|
||||||
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
|
|
||||||
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
|
|
||||||
license="${mod_license}"
|
license="${mod_license}"
|
||||||
|
|
||||||
# A URL to refer people to when problems occur with this mod
|
[[mods]]
|
||||||
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
|
|
||||||
|
|
||||||
# A list of mods - how many allowed here is determined by the individual mod loader
|
modId="${mod_id}"
|
||||||
[[mods]] #mandatory
|
|
||||||
|
|
||||||
# The modid of the mod
|
version="${mod_version}"
|
||||||
modId="${mod_id}" #mandatory
|
|
||||||
|
|
||||||
# The version number of the mod
|
displayName="${mod_name}"
|
||||||
version="${mod_version}" #mandatory
|
|
||||||
|
|
||||||
# A display name for the mod
|
|
||||||
displayName="${mod_name}" #mandatory
|
|
||||||
|
|
||||||
# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/
|
|
||||||
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
|
|
||||||
|
|
||||||
# A URL for the "homepage" for this mod, displayed in the mod UI
|
|
||||||
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
|
||||||
|
|
||||||
# A file name (in the root of the mod JAR) containing a logo for display
|
|
||||||
#logoFile="examplemod.png" #optional
|
|
||||||
|
|
||||||
# A text field displayed in the mod UI
|
|
||||||
#credits="" #optional
|
|
||||||
|
|
||||||
# The authors of the mod, displayed in the mod UI (optional)
|
|
||||||
#authors=""
|
|
||||||
|
|
||||||
# The description text for the mod (multi line!) (#mandatory)
|
|
||||||
description='''
|
description='''
|
||||||
Example mod description.
|
Teleport Mirror mod adds magical mirrors that allow players to teleport.
|
||||||
|
Two types: Return Mirror (teleports to spawn) and Teleport Mirror (teleports to another player).
|
||||||
|
Four tiers: Basic, Intermediate, Advanced, and Permanent.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
|
[[dependencies.${mod_id}]]
|
||||||
#[[mixins]]
|
modId="neoforge"
|
||||||
#config="${mod_id}.mixins.json"
|
type="required"
|
||||||
|
versionRange="[${neo_version},)"
|
||||||
# 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"
|
|
||||||
|
|
||||||
# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json
|
|
||||||
|
|
||||||
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
|
|
||||||
[[dependencies.${mod_id}]] #optional
|
|
||||||
# the modid of the dependency
|
|
||||||
modId="neoforge" #mandatory
|
|
||||||
# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive).
|
|
||||||
# 'required' requires the mod to exist, 'optional' does not
|
|
||||||
# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning
|
|
||||||
type="required" #mandatory
|
|
||||||
# Optional field describing why the dependency is required or why it is incompatible
|
|
||||||
# reason="..."
|
|
||||||
# The version range of the dependency
|
|
||||||
versionRange="[${neo_version},)" #mandatory
|
|
||||||
# An ordering relationship for the dependency.
|
|
||||||
# BEFORE - This mod is loaded BEFORE the dependency
|
|
||||||
# AFTER - This mod is loaded AFTER the dependency
|
|
||||||
ordering="NONE"
|
ordering="NONE"
|
||||||
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
|
|
||||||
side="BOTH"
|
side="BOTH"
|
||||||
|
|
||||||
# Here's another dependency
|
|
||||||
[[dependencies.${mod_id}]]
|
[[dependencies.${mod_id}]]
|
||||||
modId="minecraft"
|
modId="minecraft"
|
||||||
type="required"
|
type="required"
|
||||||
# This version range declares a minimum of the current minecraft version up to but not including the next major version
|
|
||||||
versionRange="${minecraft_version_range}"
|
versionRange="${minecraft_version_range}"
|
||||||
ordering="NONE"
|
ordering="NONE"
|
||||||
side="BOTH"
|
side="BOTH"
|
||||||
|
|
||||||
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
|
|
||||||
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
|
|
||||||
# stop your mod loading on the server for example.
|
|
||||||
#[features.${mod_id}]
|
|
||||||
#openGLVersion="[3.2,)"
|
|
||||||
|
|||||||