Compare commits

8 Commits

Author SHA1 Message Date
LiuEnder
6b558f34b4 发布 1.0.1
Some checks failed
Build / build (push) Has been cancelled
2026-04-26 21:11:38 +08:00
LiuEnder
0a0333faed 修复服务端加载客户端界面类崩溃
Some checks failed
Build / build (push) Has been cancelled
2026-04-26 20:24:59 +08:00
LiuEnder
a88ac00cde 修正工作流错误
Some checks failed
Build / build (push) Has been cancelled
2026-04-26 17:49:54 +08:00
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
15 changed files with 166 additions and 64 deletions

View File

@@ -19,7 +19,7 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v3
- name: Build with Gradle
run: ./gradlew build

View File

@@ -32,7 +32,7 @@ mod_name=Teleport Mirror
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT
# The mod version. See https://semver.org/
mod_version=1.0.0
mod_version=1.0.1
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html

View File

@@ -1,100 +1,137 @@
package com.le.teleportmirror;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
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("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);
// ==================== Cross-Dimension ====================
public static final ModConfigSpec.BooleanValue ALLOW_CROSS_DIMENSION_BASIC = BUILDER
.comment("Allow Basic tier mirrors to teleport across dimensions")
.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 tier mirrors to teleport across dimensions")
.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 tier mirrors to teleport across dimensions")
.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 tier mirrors to teleport across dimensions")
.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 on the same team")
.define("teleportTeamOnly", true);
.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 for Basic tier mirrors")
.defineInRange("basicDurability", 10, 0, Integer.MAX_VALUE);
.comment("Durability of Basic tier mirrors")
.defineInRange("durability.basic", 10, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.IntValue INTERMEDIATE_DURABILITY = BUILDER
.comment("Durability for Intermediate tier mirrors")
.defineInRange("intermediateDurability", 50, 0, Integer.MAX_VALUE);
.comment("Durability of Intermediate tier mirrors")
.defineInRange("durability.intermediate", 50, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.IntValue ADVANCED_DURABILITY = BUILDER
.comment("Durability for Advanced tier mirrors")
.defineInRange("advancedDurability", 100, 0, Integer.MAX_VALUE);
.comment("Durability of Advanced tier mirrors")
.defineInRange("durability.advanced", 100, 0, Integer.MAX_VALUE);
public static final ModConfigSpec.BooleanValue HALVE_FOOD_BASIC = BUILDER
.comment("Halve food when using Basic tier mirrors")
.define("halveFood.basic", true);
// Permanent tier has no configurable durability (unlimited)
public static final ModConfigSpec.BooleanValue HALVE_FOOD_INTERMEDIATE = BUILDER
.comment("Halve food when using Intermediate tier mirrors")
.define("halveFood.intermediate", true);
// ==================== Food Cost ====================
public static final ModConfigSpec.BooleanValue HALVE_FOOD_ADVANCED = BUILDER
.comment("Halve food when using Advanced tier mirrors")
.define("halveFood.advanced", true);
// -- 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.BooleanValue HALVE_FOOD_PERMANENT = BUILDER
.comment("Halve food when using Permanent tier mirrors")
.define("halveFood.permanent", false);
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("Effects for Basic Return Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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("Effects for Intermediate Return Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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("Effects for Advanced Return Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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("Effects for Permanent Return Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.comment("Side effects for Permanent Return Mirror")
.define("effects.return.permanent", "");
// -- Teleport Mirrors --
public static final ModConfigSpec.ConfigValue<String> EFFECTS_TELEPORT_BASIC = BUILDER
.comment("Effects for Basic Teleport Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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("Effects for Intermediate Teleport Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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("Effects for Advanced Teleport Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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("Effects for Permanent Teleport Mirror. Format: \"effect_id,dur_sec,amplifier;...\"")
.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);
@@ -111,36 +148,43 @@ public class Config {
.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. Format: \"row1;row2;row3|key=item_id;...\". Empty=use default.")
.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.")
.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.")
.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.")
.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.")
.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.")
.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.")
.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.")
.comment("Override recipe for Permanent Teleport Mirror. Empty = use default.")
.define("recipeOverride.teleport.permanent", "");
static final ModConfigSpec SPEC = BUILDER.build();

View File

@@ -191,12 +191,21 @@ public class MirrorItem extends Item {
};
}
private boolean shouldHalveFood() {
private String getFoodLevelConfig() {
return switch (tier) {
case BASIC -> Config.HALVE_FOOD_BASIC.get();
case INTERMEDIATE -> Config.HALVE_FOOD_INTERMEDIATE.get();
case ADVANCED -> Config.HALVE_FOOD_ADVANCED.get();
case PERMANENT -> Config.HALVE_FOOD_PERMANENT.get();
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();
};
}
@@ -233,15 +242,34 @@ public class MirrorItem extends Item {
player.addEffect(new MobEffectInstance(holder, durationSeconds * 20, amplifier)));
}
if (shouldHalveFood()) {
halveFood(player);
}
applyFoodCost(player);
}
private void halveFood(Player player) {
private void applyFoodCost(Player player) {
FoodData foodData = player.getFoodData();
foodData.setFoodLevel(Math.max(1, foodData.getFoodLevel() / 2));
foodData.setSaturation(0);
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

View File

@@ -1,7 +1,5 @@
package com.le.teleportmirror;
import com.le.teleportmirror.screen.PlayerSelectionScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
@@ -44,8 +42,13 @@ public class MirrorNetwork {
if (onlinePlayer.getUUID().equals(player.getUUID())) {
continue;
}
if (teamOnly && player.getTeam() != null) {
if (onlinePlayer.getTeam() != player.getTeam()) {
if (teamOnly) {
var playerTeam = player.getTeam();
var targetTeam = onlinePlayer.getTeam();
if (playerTeam == null || targetTeam == null) {
continue;
}
if (!playerTeam.getName().equals(targetTeam.getName())) {
continue;
}
}
@@ -58,9 +61,13 @@ public class MirrorNetwork {
private static void handleOpenSelection(OpenPlayerSelectionPayload payload, IPayloadContext context) {
context.enqueueWork(() -> {
Minecraft client = Minecraft.getInstance();
MirrorTier tier = MirrorTier.valueOf(payload.tierName.toUpperCase());
client.setScreen(new PlayerSelectionScreen(payload.playerNames, payload.playerUuids, tier));
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);
}
});
}

View 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));
}
}

View File

@@ -90,7 +90,14 @@ public class PlayerSelectionScreen extends Screen {
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);
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

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