Compare commits

7 Commits

Author SHA1 Message Date
LiuEnder
8d4903e1f5 refactor: reorganize Config.java with grouped sections and consistent formatting
Some checks failed
Build / build (push) Failing after 6s
Grouped into 8 sections: General/CrossDimension/Teleport/Durability/FoodCost/Effects/RecipeToggles/RecipeOverrides

Unified TOML keys to hierarchical dotted naming (durability.basic, foodCost.basic.foodLevel, etc.)

Standardized comment style and added format example comments
2026-04-26 12:13:13 +08:00
LiuEnder
c2e58fbf0e refactor: remove old team-object filter, split food cost into foodLevel and saturation
Some checks failed
Build / build (push) Has been cancelled
teleportTeamOnly now uses team tag (name) comparison only

foodCost split into foodCost.xxx.foodLevel and foodCost.xxx.saturation, independently configurable

supports percentage and fixed value independently for each
2026-04-26 11:46:51 +08:00
LiuEnder
f8e58cc2f1 feat: configurable food cost and same team tag restriction
Some checks failed
Build / build (push) Has been cancelled
foodCost.xxx replaces halveFood.xxx, supports percentage or fixed value

teleportSameTeamOnly: only target players with matching team name tag
2026-04-26 11:36:40 +08:00
LiuEnder
08cba8216a fix: 修复传送选择界面标题被blur着色器二次模糊
Some checks failed
Build / build (push) Has been cancelled
- super.render()会调用renderBackground施加模糊shader到整个framebuffer

- 改为手动遍历renderables渲染控件,避免触发blur

- 覆盖renderBackground为空操作,防止任何意外调用导致模糊
2026-04-26 11:24:47 +08:00
LiuEnder
b4c09a194e asset: 更新8个魔镜贴图
Some checks failed
Build / build (push) Has been cancelled
2026-04-25 22:09:12 +08:00
LiuEnder
92b9438253 feat: 跨维度/同队伍限制、动态debuff与配方配置、模糊界面修复
Some checks failed
Build / build (push) Has been cancelled
- 选择界面:弃用renderBackground模糊着色器,改为纯色覆盖层,文字清晰

- 跨维度传送:初级/中级默认禁止,高级/永久允许(config可配)

- 传送魔镜:默认仅限同team玩家(teleportTeamOnly可配)

- debuff完全可配置:格式 effect_id,seconds,amplifier;... 每镜独立

- 饱食度减半独立开关(halveFood.xxx)

- 合成配方可通过config覆盖(格式 row1;row2;row3|key=item;...)

- 蓄力满自动触发(onUseTick托管)
2026-04-25 21:22:33 +08:00
LiuEnder
9f85d3c610 修改蓄力使用逻辑;修正选择界面文字 2026-04-25 20:02:39 +08:00
13 changed files with 414 additions and 92 deletions

View File

@@ -5,61 +5,187 @@ import net.neoforged.neoforge.common.ModConfigSpec;
public class Config { public class Config {
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();
// ==================== General ====================
public static final ModConfigSpec.IntValue COOLDOWN_SECONDS = BUILDER public static final ModConfigSpec.IntValue COOLDOWN_SECONDS = BUILDER
.comment("Cooldown in seconds between mirror uses") .comment("Cooldown in seconds between mirror uses")
.defineInRange("cooldownSeconds", 60, 0, Integer.MAX_VALUE); .defineInRange("cooldownSeconds", 60, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.IntValue CHARGE_TICKS = BUILDER public static final ModConfigSpec.IntValue CHARGE_TICKS = BUILDER
.comment("Number of ticks required to charge the mirror (20 ticks = 1 second)") .comment("Ticks required to charge the mirror (20 ticks = 1 second)")
.defineInRange("chargeTicks", 40, 1, Integer.MAX_VALUE); .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 public static final ModConfigSpec.IntValue BASIC_DURABILITY = BUILDER
.comment("Durability for Basic tier mirrors") .comment("Durability of Basic tier mirrors")
.defineInRange("basicDurability", 10, 0, Integer.MAX_VALUE); .defineInRange("durability.basic", 10, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.IntValue INTERMEDIATE_DURABILITY = BUILDER public static final ModConfigSpec.IntValue INTERMEDIATE_DURABILITY = BUILDER
.comment("Durability for Intermediate tier mirrors") .comment("Durability of Intermediate tier mirrors")
.defineInRange("intermediateDurability", 50, 0, Integer.MAX_VALUE); .defineInRange("durability.intermediate", 50, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.IntValue ADVANCED_DURABILITY = BUILDER public static final ModConfigSpec.IntValue ADVANCED_DURABILITY = BUILDER
.comment("Durability for Advanced tier mirrors") .comment("Durability of Advanced tier mirrors")
.defineInRange("advancedDurability", 100, 0, Integer.MAX_VALUE); .defineInRange("durability.advanced", 100, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.IntValue BASIC_NAUSEA_SECONDS = BUILDER // Permanent tier has no configurable durability (unlimited)
.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 // ==================== Food Cost ====================
.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 // -- Basic --
.comment("Nausea effect duration in seconds for Intermediate tier") public static final ModConfigSpec.ConfigValue<String> FOOD_LEVEL_BASIC = BUILDER
.defineInRange("intermediateNauseaSeconds", 5, 0, Integer.MAX_VALUE); .comment("Food level cost for Basic mirrors. \"50%\"=percentage, \"10\"=fixed, \"\"=none")
.define("foodCost.basic.foodLevel", "50%");
public static final ModConfigSpec.IntValue INTERMEDIATE_WITHER_SECONDS = BUILDER public static final ModConfigSpec.ConfigValue<String> SATURATION_COST_BASIC = BUILDER
.comment("Wither effect duration in seconds for Intermediate tier") .comment("Saturation cost for Basic mirrors. \"50%\"=percentage, \"4.0\"=fixed, \"\"=none")
.defineInRange("intermediateWitherSeconds", 3, 0, Integer.MAX_VALUE); .define("foodCost.basic.saturation", "50%");
public static final ModConfigSpec.IntValue ADVANCED_NAUSEA_SECONDS = BUILDER // -- Intermediate --
.comment("Nausea effect duration in seconds for Advanced tier") public static final ModConfigSpec.ConfigValue<String> FOOD_LEVEL_INTERMEDIATE = BUILDER
.defineInRange("advancedNauseaSeconds", 3, 0, Integer.MAX_VALUE); .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 public static final ModConfigSpec.BooleanValue ENABLE_BASIC_RECIPE = BUILDER
.comment("Enable crafting recipe for Basic tier mirrors") .comment("Enable crafting recipe for Basic tier mirrors")
.define("enableBasicRecipe", true); .define("enableRecipe.basic", true);
public static final ModConfigSpec.BooleanValue ENABLE_INTERMEDIATE_RECIPE = BUILDER public static final ModConfigSpec.BooleanValue ENABLE_INTERMEDIATE_RECIPE = BUILDER
.comment("Enable crafting recipe for Intermediate tier mirrors") .comment("Enable crafting recipe for Intermediate tier mirrors")
.define("enableIntermediateRecipe", true); .define("enableRecipe.intermediate", true);
public static final ModConfigSpec.BooleanValue ENABLE_ADVANCED_RECIPE = BUILDER public static final ModConfigSpec.BooleanValue ENABLE_ADVANCED_RECIPE = BUILDER
.comment("Enable crafting recipe for Advanced tier mirrors") .comment("Enable crafting recipe for Advanced tier mirrors")
.define("enableAdvancedRecipe", true); .define("enableRecipe.advanced", true);
public static final ModConfigSpec.BooleanValue ENABLE_PERMANENT_RECIPE = BUILDER public static final ModConfigSpec.BooleanValue ENABLE_PERMANENT_RECIPE = BUILDER
.comment("Enable crafting recipe for Permanent tier mirrors") .comment("Enable crafting recipe for Permanent tier mirrors")
.define("enablePermanentRecipe", true); .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(); static final ModConfigSpec SPEC = BUILDER.build();
} }

View File

@@ -1,5 +1,10 @@
package com.le.teleportmirror; 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.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
@@ -7,16 +12,13 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.UseAnim; import net.minecraft.world.item.UseAnim;
import net.minecraft.world.level.Level; 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 { public class MirrorItem extends Item {
private final MirrorTier tier; private final MirrorTier tier;
@@ -57,21 +59,20 @@ public class MirrorItem extends Item {
} }
@Override @Override
public void releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeCharged) { public void onUseTick(Level level, LivingEntity livingEntity, ItemStack stack, int remainingUseDuration) {
if (!(entity instanceof Player player)) {
return;
}
int chargeTicks = Config.CHARGE_TICKS.get();
if (timeCharged < chargeTicks) {
return;
}
if (level.isClientSide) { if (level.isClientSide) {
return; return;
} }
ServerPlayer serverPlayer = (ServerPlayer) player; 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) { if (type == MirrorType.RETURN) {
performReturnTeleport(serverPlayer, stack); performReturnTeleport(serverPlayer, stack);
@@ -79,6 +80,20 @@ public class MirrorItem extends Item {
MirrorNetwork.sendOpenSelectionToClient(serverPlayer, tier); 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) { private void performReturnTeleport(ServerPlayer player, ItemStack stack) {
ServerLevel serverLevel = player.serverLevel(); ServerLevel serverLevel = player.serverLevel();
@@ -101,6 +116,10 @@ public class MirrorItem extends Item {
targetPos = targetLevel.getSharedSpawnPos(); targetPos = targetLevel.getSharedSpawnPos();
} }
if (!canCrossDimension() && targetLevel.dimension() != serverLevel.dimension()) {
return;
}
consumeMirrorUse(player, stack); consumeMirrorUse(player, stack);
player.teleportTo(targetLevel, targetPos.getCenter().x, targetPos.getCenter().y, targetPos.getCenter().z, player.teleportTo(targetLevel, targetPos.getCenter().x, targetPos.getCenter().y, targetPos.getCenter().z,
@@ -113,6 +132,10 @@ public class MirrorItem extends Item {
ServerLevel targetLevel = target.serverLevel(); ServerLevel targetLevel = target.serverLevel();
BlockPos targetPos = target.blockPosition(); BlockPos targetPos = target.blockPosition();
if (!canCrossDimension() && targetLevel.dimension() != player.serverLevel().dimension()) {
return;
}
consumeMirrorUse(player, stack); consumeMirrorUse(player, stack);
player.teleportTo(targetLevel, targetPos.getCenter().x, targetPos.getCenter().y, targetPos.getCenter().z, player.teleportTo(targetLevel, targetPos.getCenter().x, targetPos.getCenter().y, targetPos.getCenter().z,
@@ -151,49 +174,102 @@ public class MirrorItem extends Item {
player.getCooldowns().addCooldown(this, cooldownTicks); 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) { private void applySideEffects(Player player) {
int nauseaDuration; String effectsConfig = getEffectsConfig();
int witherDuration; if (effectsConfig == null || effectsConfig.isBlank()) {
return;
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) { 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(); FoodData foodData = player.getFoodData();
foodData.setFoodLevel(Math.max(1, foodData.getFoodLevel() / 2)); applyCostValue(getFoodLevelConfig(), (float) foodData.getFoodLevel(), newVal -> foodData.setFoodLevel(newVal.intValue()));
foodData.setSaturation(0); 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 @Override

View File

@@ -38,13 +38,25 @@ public class MirrorNetwork {
public static void sendOpenSelectionToClient(ServerPlayer player, MirrorTier tier) { public static void sendOpenSelectionToClient(ServerPlayer player, MirrorTier tier) {
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
List<UUID> uuids = new ArrayList<>(); List<UUID> uuids = new ArrayList<>();
boolean teamOnly = Config.TELEPORT_TEAM_ONLY.get();
for (ServerPlayer onlinePlayer : player.server.getPlayerList().getPlayers()) { for (ServerPlayer onlinePlayer : player.server.getPlayerList().getPlayers()) {
if (!onlinePlayer.getUUID().equals(player.getUUID())) { 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()); names.add(onlinePlayer.getName().getString());
uuids.add(onlinePlayer.getUUID()); uuids.add(onlinePlayer.getUUID());
} }
}
PacketDistributor.sendToPlayer(player, new OpenPlayerSelectionPayload(names, uuids, tier.getName())); PacketDistributor.sendToPlayer(player, new OpenPlayerSelectionPayload(names, uuids, tier.getName()));
} }

View File

@@ -1,19 +1,34 @@
package com.le.teleportmirror; package com.le.teleportmirror;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.Item; 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.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod; import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.config.ModConfig;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredItem; import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister; 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) @Mod(TeleportMirrorMod.MODID)
public class TeleportMirrorMod { public class TeleportMirrorMod {
@@ -77,9 +92,95 @@ public class TeleportMirrorMod {
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC); modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
modEventBus.addListener(this::onRegisterPayloads); modEventBus.addListener(this::onRegisterPayloads);
NeoForge.EVENT_BUS.addListener(this::onServerStarted);
} }
private void onRegisterPayloads(final RegisterPayloadHandlersEvent event) { private void onRegisterPayloads(final RegisterPayloadHandlersEvent event) {
MirrorNetwork.registerPayloads(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);
}
} }

View File

@@ -88,9 +88,16 @@ public class PlayerSelectionScreen extends Screen {
@Override @Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick); guiGraphics.fill(0, 0, this.width, this.height, 0xC0101010);
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 15, 0xFFFFFF); guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 12, 0xFFFFFF);
super.render(guiGraphics, mouseX, mouseY, partialTick);
for (var renderable : this.renderables) {
renderable.render(guiGraphics, mouseX, mouseY, partialTick);
}
}
@Override
public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
} }
@Override @Override

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 442 B