Compare commits
11 Commits
ceb59bb625
...
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3f3a23c42 | ||
|
|
6b558f34b4 | ||
|
|
0a0333faed | ||
|
|
a88ac00cde | ||
|
|
8d4903e1f5 | ||
|
|
c2e58fbf0e | ||
|
|
f8e58cc2f1 | ||
|
|
08cba8216a | ||
|
|
b4c09a194e | ||
|
|
92b9438253 | ||
|
|
9f85d3c610 |
2
.github/workflows/build.yml
vendored
@@ -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
|
||||
@@ -43,7 +43,7 @@ mod包名为com.le.teleportmirror 。
|
||||
### 永久回城魔镜
|
||||
潜影壳 紫颂果 潜影壳
|
||||
末影珍珠 高级回城魔镜 末影珍珠
|
||||
潜影壳 烈焰棒 潜影壳
|
||||
潜影壳 回响碎片 潜影壳
|
||||
|
||||
## 传送魔镜
|
||||
### 初级传送魔镜
|
||||
@@ -64,7 +64,7 @@ mod包名为com.le.teleportmirror 。
|
||||
### 永久传送魔镜
|
||||
潜影壳 紫颂果 潜影壳
|
||||
末影珍珠 高级传送魔镜 末影珍珠
|
||||
潜影壳 烈焰棒 潜影壳
|
||||
潜影壳 回响碎片 潜影壳
|
||||
|
||||
# 五、特殊机制
|
||||
待定(保留接口,暂时不实装):一次性魔镜:低成本、低副作用(例如红石+青金石合成),用后即毁。
|
||||
|
||||
@@ -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.2
|
||||
# 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
|
||||
|
||||
@@ -5,61 +5,187 @@ 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 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 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.IntValue BASIC_NAUSEA_SECONDS = BUILDER
|
||||
.comment("Nausea effect duration in seconds for Basic tier")
|
||||
.defineInRange("basicNauseaSeconds", 10, 0, Integer.MAX_VALUE);
|
||||
// Permanent tier has no configurable durability (unlimited)
|
||||
|
||||
public static final ModConfigSpec.IntValue BASIC_WITHER_SECONDS = BUILDER
|
||||
.comment("Wither effect duration in seconds for Basic tier")
|
||||
.defineInRange("basicWitherSeconds", 5, 0, Integer.MAX_VALUE);
|
||||
// ==================== Food Cost ====================
|
||||
|
||||
public static final ModConfigSpec.IntValue INTERMEDIATE_NAUSEA_SECONDS = BUILDER
|
||||
.comment("Nausea effect duration in seconds for Intermediate tier")
|
||||
.defineInRange("intermediateNauseaSeconds", 5, 0, Integer.MAX_VALUE);
|
||||
// -- 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.IntValue INTERMEDIATE_WITHER_SECONDS = BUILDER
|
||||
.comment("Wither effect duration in seconds for Intermediate tier")
|
||||
.defineInRange("intermediateWitherSeconds", 3, 0, Integer.MAX_VALUE);
|
||||
public static final ModConfigSpec.ConfigValue<String> SATURATION_COST_BASIC = BUILDER
|
||||
.comment("Saturation cost for Basic mirrors. \"50%\"=percentage, \"4.0\"=fixed, \"\"=none")
|
||||
.define("foodCost.basic.saturation", "50%");
|
||||
|
||||
public static final ModConfigSpec.IntValue ADVANCED_NAUSEA_SECONDS = BUILDER
|
||||
.comment("Nausea effect duration in seconds for Advanced tier")
|
||||
.defineInRange("advancedNauseaSeconds", 3, 0, Integer.MAX_VALUE);
|
||||
// -- 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("enableBasicRecipe", true);
|
||||
.define("enableRecipe.basic", true);
|
||||
|
||||
public static final ModConfigSpec.BooleanValue ENABLE_INTERMEDIATE_RECIPE = BUILDER
|
||||
.comment("Enable crafting recipe for Intermediate tier mirrors")
|
||||
.define("enableIntermediateRecipe", true);
|
||||
.define("enableRecipe.intermediate", true);
|
||||
|
||||
public static final ModConfigSpec.BooleanValue ENABLE_ADVANCED_RECIPE = BUILDER
|
||||
.comment("Enable crafting recipe for Advanced tier mirrors")
|
||||
.define("enableAdvancedRecipe", true);
|
||||
.define("enableRecipe.advanced", true);
|
||||
|
||||
public static final ModConfigSpec.BooleanValue ENABLE_PERMANENT_RECIPE = BUILDER
|
||||
.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();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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;
|
||||
@@ -7,16 +12,13 @@ import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.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;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.food.FoodData;
|
||||
|
||||
public class MirrorItem extends Item {
|
||||
private final MirrorTier tier;
|
||||
@@ -57,29 +59,42 @@ public class MirrorItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeCharged) {
|
||||
if (!(entity instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int chargeTicks = Config.CHARGE_TICKS.get();
|
||||
if (timeCharged < chargeTicks) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void onUseTick(Level level, LivingEntity livingEntity, ItemStack stack, int remainingUseDuration) {
|
||||
if (level.isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer serverPlayer = (ServerPlayer) player;
|
||||
int chargeTicks = Config.CHARGE_TICKS.get();
|
||||
int usedTicks = getUseDuration(stack, livingEntity) - remainingUseDuration;
|
||||
|
||||
if (type == MirrorType.RETURN) {
|
||||
performReturnTeleport(serverPlayer, stack);
|
||||
} else {
|
||||
MirrorNetwork.sendOpenSelectionToClient(serverPlayer, tier);
|
||||
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;
|
||||
@@ -101,6 +116,10 @@ public class MirrorItem extends Item {
|
||||
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,
|
||||
@@ -113,6 +132,10 @@ public class MirrorItem extends Item {
|
||||
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,
|
||||
@@ -151,49 +174,102 @@ public class MirrorItem extends Item {
|
||||
player.getCooldowns().addCooldown(this, cooldownTicks);
|
||||
}
|
||||
|
||||
private void applySideEffects(Player player) {
|
||||
int nauseaDuration;
|
||||
int witherDuration;
|
||||
|
||||
switch (tier) {
|
||||
case BASIC:
|
||||
nauseaDuration = Config.BASIC_NAUSEA_SECONDS.get() * 20;
|
||||
witherDuration = Config.BASIC_WITHER_SECONDS.get() * 20;
|
||||
if (nauseaDuration > 0) {
|
||||
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, nauseaDuration, 0));
|
||||
}
|
||||
if (witherDuration > 0) {
|
||||
player.addEffect(new MobEffectInstance(MobEffects.WITHER, witherDuration, 0));
|
||||
}
|
||||
halveFood(player);
|
||||
break;
|
||||
case INTERMEDIATE:
|
||||
nauseaDuration = Config.INTERMEDIATE_NAUSEA_SECONDS.get() * 20;
|
||||
witherDuration = Config.INTERMEDIATE_WITHER_SECONDS.get() * 20;
|
||||
if (nauseaDuration > 0) {
|
||||
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, nauseaDuration, 0));
|
||||
}
|
||||
if (witherDuration > 0) {
|
||||
player.addEffect(new MobEffectInstance(MobEffects.WITHER, witherDuration, 0));
|
||||
}
|
||||
halveFood(player);
|
||||
break;
|
||||
case ADVANCED:
|
||||
nauseaDuration = Config.ADVANCED_NAUSEA_SECONDS.get() * 20;
|
||||
if (nauseaDuration > 0) {
|
||||
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, nauseaDuration, 0));
|
||||
}
|
||||
halveFood(player);
|
||||
break;
|
||||
case PERMANENT:
|
||||
break;
|
||||
}
|
||||
private 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 void halveFood(Player player) {
|
||||
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();
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
@@ -38,12 +36,24 @@ public class MirrorNetwork {
|
||||
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())) {
|
||||
names.add(onlinePlayer.getName().getString());
|
||||
uuids.add(onlinePlayer.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());
|
||||
uuids.add(onlinePlayer.getUUID());
|
||||
}
|
||||
|
||||
PacketDistributor.sendToPlayer(player, new OpenPlayerSelectionPayload(names, uuids, tier.getName()));
|
||||
@@ -51,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,34 @@
|
||||
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.bus.api.SubscribeEvent;
|
||||
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 {
|
||||
@@ -77,9 +92,95 @@ public class TeleportMirrorMod {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +88,16 @@ public class PlayerSelectionScreen extends Screen {
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
|
||||
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 15, 0xFFFFFF);
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||
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
|
||||
|
||||
|
Before Width: | Height: | Size: 143 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 143 B After Width: | Height: | Size: 397 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 143 B After Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 442 B |
@@ -10,7 +10,7 @@
|
||||
"C": { "item": "minecraft:chorus_fruit" },
|
||||
"E": { "item": "minecraft:ender_pearl" },
|
||||
"R": { "item": "teleportmirror:advanced_return_mirror" },
|
||||
"B": { "item": "minecraft:blaze_rod" }
|
||||
"B": { "item": "minecraft:echo_shard" }
|
||||
},
|
||||
"result": {
|
||||
"id": "teleportmirror:permanent_return_mirror",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"C": { "item": "minecraft:chorus_fruit" },
|
||||
"E": { "item": "minecraft:ender_pearl" },
|
||||
"R": { "item": "teleportmirror:advanced_teleport_mirror" },
|
||||
"B": { "item": "minecraft:blaze_rod" }
|
||||
"B": { "item": "minecraft:echo_shard" }
|
||||
},
|
||||
"result": {
|
||||
"id": "teleportmirror:permanent_teleport_mirror",
|
||||
|
||||