Compare commits
14 Commits
be8dad2513
...
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3f3a23c42 | ||
|
|
6b558f34b4 | ||
|
|
0a0333faed | ||
|
|
a88ac00cde | ||
|
|
8d4903e1f5 | ||
|
|
c2e58fbf0e | ||
|
|
f8e58cc2f1 | ||
|
|
08cba8216a | ||
|
|
b4c09a194e | ||
|
|
92b9438253 | ||
|
|
9f85d3c610 | ||
|
|
ceb59bb625 | ||
|
|
33145ade44 | ||
|
|
abc303b996 |
2
.github/workflows/build.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
3
.gitignore
vendored
@@ -32,6 +32,9 @@ bin/
|
|||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
### Git Credentials ###
|
||||||
|
.git-credentials
|
||||||
|
|
||||||
### Minecraft Modding ###
|
### Minecraft Modding ###
|
||||||
run/
|
run/
|
||||||
!**/src/**/run/
|
!**/src/**/run/
|
||||||
|
|||||||
99
README.md
@@ -1,25 +1,86 @@
|
|||||||
|
该项目是一个minecraft mod项目。版本1.21.1,加载器为neoforge。
|
||||||
|
mod包名为com.le.teleportmirror 。
|
||||||
|
目前规划功能如下:
|
||||||
|
# 一、核心功能分类
|
||||||
|
魔镜分为两种类型:
|
||||||
|
|
||||||
Installation information
|
回城魔镜:使用后快速传送回出生点(优先床/重生锚设置点,否则世界出生点)。
|
||||||
=======
|
|
||||||
|
|
||||||
This template repository can be directly cloned to get you started with a new
|
传送魔镜:使用后传送使用者至其他使用者指定的玩家身边。
|
||||||
mod. Simply create a new repository cloned from this one, by following the
|
|
||||||
instructions provided by [GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template).
|
|
||||||
|
|
||||||
Once you have your clone, simply open the repository in the IDE of your choice. The usual recommendation for an IDE is either IntelliJ IDEA or Eclipse.
|
# 二、等级与属性
|
||||||
|
每种魔镜分多个等级(初级 / 中级 / 高级 / 永久),属性差异包括:
|
||||||
|
|
||||||
If at any point you are missing libraries in your IDE, or you've run into problems you can
|
属性 说明
|
||||||
run `gradlew --refresh-dependencies` to refresh the local cache. `gradlew clean` to reset everything
|
耐久度 每次使用消耗1点,耗尽后物品消失。初级默认10,中级50,高级100,永久无限(可配置)。
|
||||||
{this does not affect your code} and then start the process again.
|
冷却时间 两次使用之间的间隔,默认1分钟(可配置)。
|
||||||
|
副作用 使用后附加负面效果,等级越低副作用越强。
|
||||||
|
|
||||||
Mapping Names:
|
# 三、副作用设计(低等级/一次性镜子)
|
||||||
============
|
初级:反胃10s;凋零5s;饱食度减半
|
||||||
By default, the MDK is configured to use the official mapping names from Mojang for methods and fields
|
中级:反胃5s;凋零3s;饱食度减半
|
||||||
in the Minecraft codebase. These names are covered by a specific license. All modders should be aware of this
|
高级:反胃3s;饱食度减半
|
||||||
license. For the latest license text, refer to the mapping file itself, or the reference copy here:
|
永久:无
|
||||||
https://github.com/NeoForged/NeoForm/blob/main/Mojang.md
|
|
||||||
|
|
||||||
Additional Resources:
|
四、合成配方(以为例)
|
||||||
==========
|
|
||||||
Community Documentation: https://docs.neoforged.net/
|
## 回城魔镜
|
||||||
NeoForged Discord: https://discord.neoforged.net/
|
### 初级回城魔镜
|
||||||
|
金锭 指南针 金锭
|
||||||
|
铁锭 蓝色染色玻璃板 铁锭
|
||||||
|
铜锭 铁锭 铜锭
|
||||||
|
|
||||||
|
### 中级回城魔镜
|
||||||
|
钻石 末影珍珠 钻石
|
||||||
|
青金石 初级回城魔镜 青金石
|
||||||
|
红石 紫水晶碎片 红石
|
||||||
|
|
||||||
|
### 高级回城魔镜
|
||||||
|
钻石 末影之眼 钻石
|
||||||
|
绿宝石 中级回城魔镜 绿宝石
|
||||||
|
钻石 烈焰棒 钻石
|
||||||
|
|
||||||
|
### 永久回城魔镜
|
||||||
|
潜影壳 紫颂果 潜影壳
|
||||||
|
末影珍珠 高级回城魔镜 末影珍珠
|
||||||
|
潜影壳 回响碎片 潜影壳
|
||||||
|
|
||||||
|
## 传送魔镜
|
||||||
|
### 初级传送魔镜
|
||||||
|
金锭 拴绳 金锭
|
||||||
|
铁锭 橙色染色玻璃板 铁锭
|
||||||
|
铜锭 铁锭 铜锭
|
||||||
|
|
||||||
|
### 中级传送魔镜
|
||||||
|
钻石 末影珍珠 钻石
|
||||||
|
青金石 初级传送魔镜 青金石
|
||||||
|
红石 紫水晶碎片 红石
|
||||||
|
|
||||||
|
### 高级传送魔镜
|
||||||
|
钻石 末影之眼 钻石
|
||||||
|
绿宝石 中级传送魔镜 绿宝石
|
||||||
|
钻石 烈焰棒 钻石
|
||||||
|
|
||||||
|
### 永久传送魔镜
|
||||||
|
潜影壳 紫颂果 潜影壳
|
||||||
|
末影珍珠 高级传送魔镜 末影珍珠
|
||||||
|
潜影壳 回响碎片 潜影壳
|
||||||
|
|
||||||
|
# 五、特殊机制
|
||||||
|
待定(保留接口,暂时不实装):一次性魔镜:低成本、低副作用(例如红石+青金石合成),用后即毁。
|
||||||
|
|
||||||
|
使用方式:长按右键蓄力使用。传送魔镜需选择目标玩家,给出两种可选的 UI 方案:
|
||||||
|
|
||||||
|
长按右键弹出轮盘选择同队伍(原版的team)的玩家头像。(默认方案)
|
||||||
|
|
||||||
|
右键后输入玩家名字(简单直接)。
|
||||||
|
|
||||||
|
# 六、可配置项
|
||||||
|
各级魔镜耐久度与合成配方。
|
||||||
|
|
||||||
|
冷却时间。
|
||||||
|
|
||||||
|
副作用类型与强度。
|
||||||
|
|
||||||
|
# 七、贴图
|
||||||
|
创建每个魔镜对应的占位贴图。
|
||||||
@@ -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.2
|
||||||
# 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
src/main/java/com/le/teleportmirror/Config.java
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||||
|
|
||||||
|
public class Config {
|
||||||
|
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();
|
||||||
|
|
||||||
|
// ==================== General ====================
|
||||||
|
|
||||||
|
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("Ticks required to charge the mirror (20 ticks = 1 second)")
|
||||||
|
.defineInRange("chargeTicks", 40, 1, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
// ==================== Cross-Dimension ====================
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ALLOW_CROSS_DIMENSION_BASIC = BUILDER
|
||||||
|
.comment("Allow Basic mirrors to teleport across dimensions")
|
||||||
|
.define("allowCrossDimension.basic", false);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ALLOW_CROSS_DIMENSION_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Allow Intermediate mirrors to teleport across dimensions")
|
||||||
|
.define("allowCrossDimension.intermediate", false);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ALLOW_CROSS_DIMENSION_ADVANCED = BUILDER
|
||||||
|
.comment("Allow Advanced mirrors to teleport across dimensions")
|
||||||
|
.define("allowCrossDimension.advanced", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ALLOW_CROSS_DIMENSION_PERMANENT = BUILDER
|
||||||
|
.comment("Allow Permanent mirrors to teleport across dimensions")
|
||||||
|
.define("allowCrossDimension.permanent", true);
|
||||||
|
|
||||||
|
// ==================== Teleport Restrictions ====================
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue TELEPORT_TEAM_ONLY = BUILDER
|
||||||
|
.comment("Teleport Mirror can only target players with the same team name (tag)")
|
||||||
|
.define("teleportTeamOnly", false);
|
||||||
|
|
||||||
|
// ==================== Durability ====================
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue BASIC_DURABILITY = BUILDER
|
||||||
|
.comment("Durability of Basic tier mirrors")
|
||||||
|
.defineInRange("durability.basic", 10, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue INTERMEDIATE_DURABILITY = BUILDER
|
||||||
|
.comment("Durability of Intermediate tier mirrors")
|
||||||
|
.defineInRange("durability.intermediate", 50, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue ADVANCED_DURABILITY = BUILDER
|
||||||
|
.comment("Durability of Advanced tier mirrors")
|
||||||
|
.defineInRange("durability.advanced", 100, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
// Permanent tier has no configurable durability (unlimited)
|
||||||
|
|
||||||
|
// ==================== Food Cost ====================
|
||||||
|
|
||||||
|
// -- Basic --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> FOOD_LEVEL_BASIC = BUILDER
|
||||||
|
.comment("Food level cost for Basic mirrors. \"50%\"=percentage, \"10\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.basic.foodLevel", "50%");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> SATURATION_COST_BASIC = BUILDER
|
||||||
|
.comment("Saturation cost for Basic mirrors. \"50%\"=percentage, \"4.0\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.basic.saturation", "50%");
|
||||||
|
|
||||||
|
// -- Intermediate --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> FOOD_LEVEL_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Food level cost for Intermediate mirrors. \"50%\"=percentage, \"10\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.intermediate.foodLevel", "50%");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> SATURATION_COST_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Saturation cost for Intermediate mirrors. \"50%\"=percentage, \"4.0\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.intermediate.saturation", "50%");
|
||||||
|
|
||||||
|
// -- Advanced --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> FOOD_LEVEL_ADVANCED = BUILDER
|
||||||
|
.comment("Food level cost for Advanced mirrors. \"50%\"=percentage, \"10\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.advanced.foodLevel", "50%");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> SATURATION_COST_ADVANCED = BUILDER
|
||||||
|
.comment("Saturation cost for Advanced mirrors. \"50%\"=percentage, \"4.0\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.advanced.saturation", "50%");
|
||||||
|
|
||||||
|
// -- Permanent --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> FOOD_LEVEL_PERMANENT = BUILDER
|
||||||
|
.comment("Food level cost for Permanent mirrors. \"50%\"=percentage, \"10\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.permanent.foodLevel", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> SATURATION_COST_PERMANENT = BUILDER
|
||||||
|
.comment("Saturation cost for Permanent mirrors. \"50%\"=percentage, \"4.0\"=fixed, \"\"=none")
|
||||||
|
.define("foodCost.permanent.saturation", "");
|
||||||
|
|
||||||
|
// ==================== Side Effects ====================
|
||||||
|
// Format per entry: "effect_id,dur_seconds,amplifier;..."
|
||||||
|
|
||||||
|
// -- Return Mirrors --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_RETURN_BASIC = BUILDER
|
||||||
|
.comment("Side effects for Basic Return Mirror")
|
||||||
|
.define("effects.return.basic", "minecraft:nausea,10,0;minecraft:wither,5,0");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_RETURN_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Side effects for Intermediate Return Mirror")
|
||||||
|
.define("effects.return.intermediate", "minecraft:nausea,5,0;minecraft:wither,3,0");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_RETURN_ADVANCED = BUILDER
|
||||||
|
.comment("Side effects for Advanced Return Mirror")
|
||||||
|
.define("effects.return.advanced", "minecraft:nausea,3,0");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_RETURN_PERMANENT = BUILDER
|
||||||
|
.comment("Side effects for Permanent Return Mirror")
|
||||||
|
.define("effects.return.permanent", "");
|
||||||
|
|
||||||
|
// -- Teleport Mirrors --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_TELEPORT_BASIC = BUILDER
|
||||||
|
.comment("Side effects for Basic Teleport Mirror")
|
||||||
|
.define("effects.teleport.basic", "minecraft:nausea,10,0;minecraft:wither,5,0");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_TELEPORT_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Side effects for Intermediate Teleport Mirror")
|
||||||
|
.define("effects.teleport.intermediate", "minecraft:nausea,5,0;minecraft:wither,3,0");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_TELEPORT_ADVANCED = BUILDER
|
||||||
|
.comment("Side effects for Advanced Teleport Mirror")
|
||||||
|
.define("effects.teleport.advanced", "minecraft:nausea,3,0");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> EFFECTS_TELEPORT_PERMANENT = BUILDER
|
||||||
|
.comment("Side effects for Permanent Teleport Mirror")
|
||||||
|
.define("effects.teleport.permanent", "");
|
||||||
|
|
||||||
|
// ==================== Recipe Toggles ====================
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_BASIC_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Basic tier mirrors")
|
||||||
|
.define("enableRecipe.basic", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_INTERMEDIATE_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Intermediate tier mirrors")
|
||||||
|
.define("enableRecipe.intermediate", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_ADVANCED_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Advanced tier mirrors")
|
||||||
|
.define("enableRecipe.advanced", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue ENABLE_PERMANENT_RECIPE = BUILDER
|
||||||
|
.comment("Enable crafting recipe for Permanent tier mirrors")
|
||||||
|
.define("enableRecipe.permanent", true);
|
||||||
|
|
||||||
|
// ==================== Recipe Overrides ====================
|
||||||
|
// Format: "row1;row2;row3|key=item_id;..."
|
||||||
|
// Example: " C ;CGC; C |C=minecraft:copper_ingot;G=minecraft:glass_pane"
|
||||||
|
// Empty "" means use the default JSON recipe.
|
||||||
|
|
||||||
|
// -- Return Mirrors --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_RETURN_BASIC = BUILDER
|
||||||
|
.comment("Override recipe for Basic Return Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.return.basic", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_RETURN_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Override recipe for Intermediate Return Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.return.intermediate", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_RETURN_ADVANCED = BUILDER
|
||||||
|
.comment("Override recipe for Advanced Return Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.return.advanced", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_RETURN_PERMANENT = BUILDER
|
||||||
|
.comment("Override recipe for Permanent Return Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.return.permanent", "");
|
||||||
|
|
||||||
|
// -- Teleport Mirrors --
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_TELEPORT_BASIC = BUILDER
|
||||||
|
.comment("Override recipe for Basic Teleport Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.teleport.basic", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_TELEPORT_INTERMEDIATE = BUILDER
|
||||||
|
.comment("Override recipe for Intermediate Teleport Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.teleport.intermediate", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_TELEPORT_ADVANCED = BUILDER
|
||||||
|
.comment("Override recipe for Advanced Teleport Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.teleport.advanced", "");
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> RECIPE_OVERRIDE_TELEPORT_PERMANENT = BUILDER
|
||||||
|
.comment("Override recipe for Permanent Teleport Mirror. Empty = use default.")
|
||||||
|
.define("recipeOverride.teleport.permanent", "");
|
||||||
|
|
||||||
|
static final ModConfigSpec SPEC = BUILDER.build();
|
||||||
|
}
|
||||||
284
src/main/java/com/le/teleportmirror/MirrorItem.java
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
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.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.food.FoodData;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.UseAnim;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
|
||||||
|
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 onUseTick(Level level, LivingEntity livingEntity, ItemStack stack, int remainingUseDuration) {
|
||||||
|
if (level.isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chargeTicks = Config.CHARGE_TICKS.get();
|
||||||
|
int usedTicks = getUseDuration(stack, livingEntity) - remainingUseDuration;
|
||||||
|
|
||||||
|
if (usedTicks >= chargeTicks) {
|
||||||
|
if (!(livingEntity instanceof ServerPlayer serverPlayer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
livingEntity.stopUsingItem();
|
||||||
|
|
||||||
|
if (type == MirrorType.RETURN) {
|
||||||
|
performReturnTeleport(serverPlayer, stack);
|
||||||
|
} else {
|
||||||
|
MirrorNetwork.sendOpenSelectionToClient(serverPlayer, tier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeCharged) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canCrossDimension() {
|
||||||
|
return switch (tier) {
|
||||||
|
case BASIC -> Config.ALLOW_CROSS_DIMENSION_BASIC.get();
|
||||||
|
case INTERMEDIATE -> Config.ALLOW_CROSS_DIMENSION_INTERMEDIATE.get();
|
||||||
|
case ADVANCED -> Config.ALLOW_CROSS_DIMENSION_ADVANCED.get();
|
||||||
|
case PERMANENT -> Config.ALLOW_CROSS_DIMENSION_PERMANENT.get();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canCrossDimension() && targetLevel.dimension() != serverLevel.dimension()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!canCrossDimension() && targetLevel.dimension() != player.serverLevel().dimension()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String getEffectsConfig() {
|
||||||
|
return switch (type) {
|
||||||
|
case RETURN -> switch (tier) {
|
||||||
|
case BASIC -> Config.EFFECTS_RETURN_BASIC.get();
|
||||||
|
case INTERMEDIATE -> Config.EFFECTS_RETURN_INTERMEDIATE.get();
|
||||||
|
case ADVANCED -> Config.EFFECTS_RETURN_ADVANCED.get();
|
||||||
|
case PERMANENT -> Config.EFFECTS_RETURN_PERMANENT.get();
|
||||||
|
};
|
||||||
|
case TELEPORT -> switch (tier) {
|
||||||
|
case BASIC -> Config.EFFECTS_TELEPORT_BASIC.get();
|
||||||
|
case INTERMEDIATE -> Config.EFFECTS_TELEPORT_INTERMEDIATE.get();
|
||||||
|
case ADVANCED -> Config.EFFECTS_TELEPORT_ADVANCED.get();
|
||||||
|
case PERMANENT -> Config.EFFECTS_TELEPORT_PERMANENT.get();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFoodLevelConfig() {
|
||||||
|
return switch (tier) {
|
||||||
|
case BASIC -> Config.FOOD_LEVEL_BASIC.get();
|
||||||
|
case INTERMEDIATE -> Config.FOOD_LEVEL_INTERMEDIATE.get();
|
||||||
|
case ADVANCED -> Config.FOOD_LEVEL_ADVANCED.get();
|
||||||
|
case PERMANENT -> Config.FOOD_LEVEL_PERMANENT.get();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSaturationConfig() {
|
||||||
|
return switch (tier) {
|
||||||
|
case BASIC -> Config.SATURATION_COST_BASIC.get();
|
||||||
|
case INTERMEDIATE -> Config.SATURATION_COST_INTERMEDIATE.get();
|
||||||
|
case ADVANCED -> Config.SATURATION_COST_ADVANCED.get();
|
||||||
|
case PERMANENT -> Config.SATURATION_COST_PERMANENT.get();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySideEffects(Player player) {
|
||||||
|
String effectsConfig = getEffectsConfig();
|
||||||
|
if (effectsConfig == null || effectsConfig.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] effectEntries = effectsConfig.split(";");
|
||||||
|
for (String entry : effectEntries) {
|
||||||
|
entry = entry.trim();
|
||||||
|
if (entry.isEmpty()) continue;
|
||||||
|
|
||||||
|
String[] parts = entry.split(",");
|
||||||
|
if (parts.length < 3) continue;
|
||||||
|
|
||||||
|
String effectId = parts[0].trim();
|
||||||
|
int durationSeconds;
|
||||||
|
int amplifier;
|
||||||
|
try {
|
||||||
|
durationSeconds = Integer.parseInt(parts[1].trim());
|
||||||
|
amplifier = Integer.parseInt(parts[2].trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (durationSeconds <= 0) continue;
|
||||||
|
|
||||||
|
var effectKey = ResourceKey.create(Registries.MOB_EFFECT,
|
||||||
|
ResourceLocation.parse(effectId));
|
||||||
|
var effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectKey);
|
||||||
|
effectHolder.ifPresent(holder ->
|
||||||
|
player.addEffect(new MobEffectInstance(holder, durationSeconds * 20, amplifier)));
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFoodCost(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFoodCost(Player player) {
|
||||||
|
FoodData foodData = player.getFoodData();
|
||||||
|
applyCostValue(getFoodLevelConfig(), (float) foodData.getFoodLevel(), newVal -> foodData.setFoodLevel(newVal.intValue()));
|
||||||
|
applyCostValue(getSaturationConfig(), foodData.getSaturationLevel(), newVal -> foodData.setSaturation(newVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCostValue(String config, float currentValue, java.util.function.Consumer<Float> setter) {
|
||||||
|
if (config == null || config.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String trimmed = config.trim();
|
||||||
|
if (trimmed.endsWith("%")) {
|
||||||
|
String pctStr = trimmed.substring(0, trimmed.length() - 1);
|
||||||
|
try {
|
||||||
|
float percentage = Float.parseFloat(pctStr) / 100f;
|
||||||
|
setter.accept(Math.max(0, currentValue * (1f - percentage)));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
float cost = Float.parseFloat(trimmed);
|
||||||
|
setter.accept(Math.max(0, currentValue - cost));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnchantable(ItemStack stack) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFoil(ItemStack stack) {
|
||||||
|
return tier == MirrorTier.PERMANENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
167
src/main/java/com/le/teleportmirror/MirrorNetwork.java
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
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<>();
|
||||||
|
boolean teamOnly = Config.TELEPORT_TEAM_ONLY.get();
|
||||||
|
|
||||||
|
for (ServerPlayer onlinePlayer : player.server.getPlayerList().getPlayers()) {
|
||||||
|
if (onlinePlayer.getUUID().equals(player.getUUID())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (teamOnly) {
|
||||||
|
var playerTeam = player.getTeam();
|
||||||
|
var targetTeam = onlinePlayer.getTeam();
|
||||||
|
if (playerTeam == null || targetTeam == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!playerTeam.getName().equals(targetTeam.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(() -> {
|
||||||
|
try {
|
||||||
|
Class<?> clientHandlerClass = Class.forName("com.le.teleportmirror.MirrorNetworkClient");
|
||||||
|
clientHandlerClass.getMethod("openPlayerSelection", OpenPlayerSelectionPayload.class)
|
||||||
|
.invoke(null, payload);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException("Failed to open player selection screen", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/com/le/teleportmirror/MirrorNetworkClient.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import com.le.teleportmirror.screen.PlayerSelectionScreen;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.neoforged.api.distmarker.Dist;
|
||||||
|
import net.neoforged.api.distmarker.OnlyIn;
|
||||||
|
|
||||||
|
@OnlyIn(Dist.CLIENT)
|
||||||
|
public class MirrorNetworkClient {
|
||||||
|
|
||||||
|
public static void openPlayerSelection(MirrorNetwork.OpenPlayerSelectionPayload payload) {
|
||||||
|
Minecraft client = Minecraft.getInstance();
|
||||||
|
MirrorTier tier = MirrorTier.valueOf(payload.tierName().toUpperCase());
|
||||||
|
client.setScreen(new PlayerSelectionScreen(payload.playerNames(), payload.playerUuids(), tier));
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
186
src/main/java/com/le/teleportmirror/TeleportMirrorMod.java
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package com.le.teleportmirror;
|
||||||
|
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||||
|
import net.minecraft.world.item.crafting.RecipeType;
|
||||||
|
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||||
|
import net.minecraft.world.item.crafting.ShapedRecipePattern;
|
||||||
|
import net.minecraft.world.item.crafting.Ingredient;
|
||||||
|
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||||
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
|
import net.neoforged.fml.common.Mod;
|
||||||
|
import net.neoforged.fml.config.ModConfig;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
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;
|
||||||
|
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
NeoForge.EVENT_BUS.addListener(this::onServerStarted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRegisterPayloads(final RegisterPayloadHandlersEvent event) {
|
||||||
|
MirrorNetwork.registerPayloads(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServerStarted(final ServerStartedEvent event) {
|
||||||
|
registerConfigRecipes(event.getServer().getRecipeManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerConfigRecipes(net.minecraft.world.item.crafting.RecipeManager manager) {
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_BASIC_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_RETURN_BASIC.get(), BASIC_RETURN_MIRROR.get(), "basic_return_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_INTERMEDIATE_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_RETURN_INTERMEDIATE.get(), INTERMEDIATE_RETURN_MIRROR.get(), "intermediate_return_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_ADVANCED_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_RETURN_ADVANCED.get(), ADVANCED_RETURN_MIRROR.get(), "advanced_return_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_PERMANENT_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_RETURN_PERMANENT.get(), PERMANENT_RETURN_MIRROR.get(), "permanent_return_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_BASIC_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_TELEPORT_BASIC.get(), BASIC_TELEPORT_MIRROR.get(), "basic_teleport_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_INTERMEDIATE_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_TELEPORT_INTERMEDIATE.get(), INTERMEDIATE_TELEPORT_MIRROR.get(), "intermediate_teleport_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_ADVANCED_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_TELEPORT_ADVANCED.get(), ADVANCED_TELEPORT_MIRROR.get(), "advanced_teleport_mirror");
|
||||||
|
tryRegisterRecipe(manager, Config.ENABLE_PERMANENT_RECIPE.get(),
|
||||||
|
Config.RECIPE_OVERRIDE_TELEPORT_PERMANENT.get(), PERMANENT_TELEPORT_MIRROR.get(), "permanent_teleport_mirror");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void tryRegisterRecipe(net.minecraft.world.item.crafting.RecipeManager manager,
|
||||||
|
boolean enabled, String override, Item result, String name) {
|
||||||
|
if (!enabled) return;
|
||||||
|
if (override.isBlank()) return;
|
||||||
|
|
||||||
|
RecipeHolder<?> recipe = parseRecipeOverride(override, result, name);
|
||||||
|
if (recipe == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Field recipesField = manager.getClass().getDeclaredField("recipes");
|
||||||
|
recipesField.setAccessible(true);
|
||||||
|
Object recipeMap = recipesField.get(manager);
|
||||||
|
|
||||||
|
Field byTypeField = recipeMap.getClass().getDeclaredField("byType");
|
||||||
|
byTypeField.setAccessible(true);
|
||||||
|
Map<RecipeType<?>, Map<ResourceLocation, RecipeHolder<?>>> byType =
|
||||||
|
(Map<RecipeType<?>, Map<ResourceLocation, RecipeHolder<?>>>) byTypeField.get(recipeMap);
|
||||||
|
|
||||||
|
Map<ResourceLocation, RecipeHolder<?>> crafting = byType
|
||||||
|
.computeIfAbsent(RecipeType.CRAFTING, k -> new HashMap<>());
|
||||||
|
crafting.put(recipe.id(), recipe);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Silently ignore - default JSON recipe will be used
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecipeHolder<?> parseRecipeOverride(String override, Item result, String name) {
|
||||||
|
String[] parts = override.split("\\|");
|
||||||
|
if (parts.length < 2) return null;
|
||||||
|
|
||||||
|
String[] rows = parts[0].split(";");
|
||||||
|
if (rows.length != 3) return null;
|
||||||
|
|
||||||
|
Map<Character, Ingredient> key = new HashMap<>();
|
||||||
|
String[] keyEntries = parts[1].split(";");
|
||||||
|
for (String entry : keyEntries) {
|
||||||
|
String[] kv = entry.split("=");
|
||||||
|
if (kv.length != 2) continue;
|
||||||
|
char k = kv[0].trim().charAt(0);
|
||||||
|
String itemId = kv[1].trim();
|
||||||
|
var item = BuiltInRegistries.ITEM.get(ResourceLocation.parse(itemId));
|
||||||
|
key.put(k, Ingredient.of(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> pattern = List.of(rows[0].trim(), rows[1].trim(), rows[2].trim());
|
||||||
|
|
||||||
|
ShapedRecipePattern shapedPattern;
|
||||||
|
try {
|
||||||
|
shapedPattern = ShapedRecipePattern.of(key, pattern);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapedRecipe recipe = new ShapedRecipe("", CraftingBookCategory.MISC, shapedPattern, new ItemStack(result));
|
||||||
|
|
||||||
|
return new RecipeHolder<>(
|
||||||
|
ResourceLocation.fromNamespaceAndPath(MODID, name + "_config"),
|
||||||
|
recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
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) {
|
||||||
|
guiGraphics.fill(0, 0, this.width, this.height, 0xC0101010);
|
||||||
|
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 12, 0xFFFFFF);
|
||||||
|
|
||||||
|
for (var renderable : this.renderables) {
|
||||||
|
renderable.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float 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: 401 B |
|
After Width: | Height: | Size: 397 B |
|
After Width: | Height: | Size: 360 B |
|
After Width: | Height: | Size: 276 B |
|
After Width: | Height: | Size: 403 B |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 439 B |
|
After Width: | Height: | Size: 442 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:echo_shard" }
|
||||||
|
},
|
||||||
|
"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:echo_shard" }
|
||||||
|
},
|
||||||
|
"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,)"
|
|
||||||
|
|||||||