From 4de35b348b771f1c846b9d76211f7ca1e9b2d7ea Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sun, 10 May 2026 19:14:50 -0400 Subject: [PATCH 1/4] Add config list for pattern cost rescaling --- .../api/casting/eval/CastingEnvironment.java | 6 ++- .../casting/eval/env/PlayerBasedCastEnv.java | 5 +- .../petrak/hexcasting/api/mod/HexConfig.java | 2 + .../hexcasting/fabric/FabricHexConfig.java | 23 ++++++++- .../hexcasting/forge/ForgeHexConfig.java | 47 +++++++++++++++---- 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java index cd58baca7c..0eaadefdb2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java @@ -197,10 +197,12 @@ public void precheckAction(PatternShapeMatch match) throws Mishap { } /** - * Casting env subclasses can override this to modify the cost for a given action + * Gets the cost modifier for a given action. By default, this is based on the cost scaling list + * in the config. Casting env subclasses can override this to modify the cost in other ways. */ protected double getCostModifier(PatternShapeMatch match) { - return 1.0; + ResourceLocation loc = actionKey(match); + return HexConfig.server().getActionCostScaling(loc); } @Nullable diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java index f78794ad14..f56be39940 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java @@ -72,11 +72,12 @@ public ServerPlayer getCaster() { @Override protected double getCostModifier(PatternShapeMatch match) { + var base = super.getCostModifier(match); ResourceLocation loc = actionKey(match); if (loc != null && isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) { - return 1.0; + return base; } - return this.caster.getAttributeValue(HexAttributes.MEDIA_CONSUMPTION_MODIFIER); + return base * this.caster.getAttributeValue(HexAttributes.MEDIA_CONSUMPTION_MODIFIER); } @Override diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java index 986d0f5000..e1d537ff47 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java @@ -79,6 +79,8 @@ public interface ServerConfigAccess { boolean isActionAllowedInCircles(ResourceLocation actionID); + double getActionCostScaling(ResourceLocation actionID); + boolean doesGreaterTeleportSplatItems(); boolean doVillagersTakeOffenseAtMindMurder(); diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java index 4f0de5bd42..38e4f9a2fa 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -7,6 +7,8 @@ import com.google.gson.GsonBuilder; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.ConfigData; import me.shedaniel.autoconfig.annotation.Config; @@ -209,6 +211,9 @@ public static final class Server implements HexConfig.ServerConfigAccess, Config @ConfigEntry.Gui.Tooltip private List circleActionDenyList = List.of(); @ConfigEntry.Gui.Tooltip + private List costRescaleListRaw = List.of(); + private transient Object2DoubleMap costRescaleList; + @ConfigEntry.Gui.Tooltip private boolean greaterTeleportSplatsItems = DEFAULT_GREATER_TELEPORT_SPLATS_ITEMS; @ConfigEntry.Gui.Tooltip private boolean villagersOffendedByMindMurder = DEFAULT_VILLAGERS_DISLIKE_MIND_MURDER; @@ -257,6 +262,18 @@ public void validatePostLoad() throws ValidationException { this.maxSpellCircleLength = Math.max(this.maxSpellCircleLength, 4); this.traderScrollChance = Mth.clamp(this.traderScrollChance, 0.0, 1.0); + this.costRescaleList = new Object2DoubleOpenHashMap<>(); + try { + for (var auugh : this.costRescaleListRaw) { + String[] split = auugh.split(" "); + ResourceLocation loc = new ResourceLocation(split[0]); + double scale = Double.parseDouble(split[1]); + this.costRescaleList.put(loc, scale); + } + } catch (Exception e) { + throw new ValidationException("Bad parsing of action cost rescaling", e); + } + this.scrollInjections = new Object2IntOpenHashMap<>(); try { for (var auugh : this.scrollInjectionsRaw) { @@ -265,7 +282,6 @@ public void validatePostLoad() throws ValidationException { int count = Integer.parseInt(split[1]); this.scrollInjections.put(loc, count); } - } catch (Exception e) { throw new ValidationException("Bad parsing of scroll injects", e); } @@ -320,6 +336,11 @@ public boolean isActionAllowedInCircles(ResourceLocation actionID) { return noneMatch(circleActionDenyList, actionID); } + @Override + public double getActionCostScaling(ResourceLocation actionID) { + return this.costRescaleList.getOrDefault(actionID, 1.0); + } + @Override public boolean doesGreaterTeleportSplatItems() { return greaterTeleportSplatsItems; } diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index 89762811bf..2bd0770166 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -173,6 +173,7 @@ public static class Server implements HexConfig.ServerConfigAccess { private static ForgeConfigSpec.IntValue maxSpellCircleLength; private static ForgeConfigSpec.ConfigValue> actionDenyList; private static ForgeConfigSpec.ConfigValue> circleActionDenyList; + private static ForgeConfigSpec.ConfigValue> costRescaleList; private static ForgeConfigSpec.BooleanValue greaterTeleportSplatsItems; private static ForgeConfigSpec.BooleanValue villagersOffendedByMindMurder; @@ -185,10 +186,23 @@ public Server(ForgeConfigSpec.Builder builder) { maxOpCount = builder.comment("The maximum number of actions that can be executed in one tick, to avoid " + "hanging the server.") .defineInRange("maxOpCount", DEFAULT_MAX_OP_COUNT, 0, Integer.MAX_VALUE); + opBreakHarvestLevel = builder.comment( "The harvest level of the Break Block spell.", "0 = wood, 1 = stone, 2 = iron, 3 = diamond, 4 = netherite." ).defineInRange("opBreakHarvestLevel", DEFAULT_OP_BREAK_HARVEST_LEVEL, 0, 4); + + greaterTeleportSplatsItems = builder.comment( + "Should items fly out of the player's inventory when using Greater Teleport?" + ).define("greaterTeleportSplatsItems", DEFAULT_GREATER_TELEPORT_SPLATS_ITEMS); + + actionDenyList = builder.comment( + "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap." + ).defineList("actionDenyList", List.of(), Server::isValidReslocArg); + + costRescaleList = builder.comment( + "[COST RESCALE LIST]" + ).defineList("costRescaleList", List.of(), Server::isValidReslocDoublePair); builder.pop(); builder.push("Spell Circles"); @@ -206,17 +220,8 @@ public Server(ForgeConfigSpec.Builder builder) { .defineInRange("traderScrollChance", DEFAULT_TRADER_SCROLL_CHANCE, 0.0, 1.0); // builders for loot (eg. scroll/lore/cypher pools and chances) should go here - builder.pop(); - actionDenyList = builder.comment( - "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap.") - .defineList("actionDenyList", List.of(), Server::isValidReslocArg); - - greaterTeleportSplatsItems = builder.comment( - "Should items fly out of the player's inventory when using Greater Teleport?" - ).define("greaterTeleportSplatsItems", DEFAULT_GREATER_TELEPORT_SPLATS_ITEMS); - villagersOffendedByMindMurder = builder.comment( "Should villagers take offense when you flay the mind of their fellow villagers?") .define("villagersOffendedByMindMurder", true); @@ -254,6 +259,17 @@ public boolean isActionAllowedInCircles(ResourceLocation actionID) { return noneMatch(circleActionDenyList.get(), actionID); } + @Override + public double getActionCostScaling(ResourceLocation actionID) { + for (var entry : costRescaleList.get()) { + String[] split = entry.split(" "); + if (actionID.toString().equals(split[0])) { + return Double.parseDouble(split[1]); + } + } + return 1.0; + } + @Override public boolean doesGreaterTeleportSplatItems() { return greaterTeleportSplatsItems.get(); } @@ -280,5 +296,18 @@ public double traderScrollChance() { private static boolean isValidReslocArg(Object o) { return o instanceof String s && ResourceLocation.isValidResourceLocation(s); } + + private static boolean isValidReslocDoublePair(Object o) { + if (o instanceof String s) { + String[] split = s.split(" "); + try { + Double.parseDouble(split[1]); + } catch (NumberFormatException e) { + return false; + } + return ResourceLocation.isValidResourceLocation(split[0]); + } + return false; + } } } From 769c80d4bcfa1d0cb0c169195ac4b96f6d29e7fc Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sun, 10 May 2026 19:32:50 -0400 Subject: [PATCH 2/4] Streamline getCostModifier process, move null check out --- .../hexcasting/api/casting/eval/CastingEnvironment.java | 5 ++--- .../api/casting/eval/env/PlayerBasedCastEnv.java | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java index 0eaadefdb2..912282649f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java @@ -193,15 +193,14 @@ public void precheckAction(PatternShapeMatch match) throws Mishap { throw new MishapDisallowedSpell("disallowed", loc); } - costModifier = this.getCostModifier(match); + costModifier = (loc != null) ? this.getCostModifier(loc) : 1.0; } /** * Gets the cost modifier for a given action. By default, this is based on the cost scaling list * in the config. Casting env subclasses can override this to modify the cost in other ways. */ - protected double getCostModifier(PatternShapeMatch match) { - ResourceLocation loc = actionKey(match); + protected double getCostModifier(@NotNull ResourceLocation loc) { return HexConfig.server().getActionCostScaling(loc); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java index f56be39940..c4fdebb4bc 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java @@ -30,6 +30,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -71,10 +72,9 @@ public ServerPlayer getCaster() { } @Override - protected double getCostModifier(PatternShapeMatch match) { - var base = super.getCostModifier(match); - ResourceLocation loc = actionKey(match); - if (loc != null && isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) { + protected double getCostModifier(@NotNull ResourceLocation loc) { + var base = super.getCostModifier(loc); + if (isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) { return base; } return base * this.caster.getAttributeValue(HexAttributes.MEDIA_CONSUMPTION_MODIFIER); From 344d9956201315abaeb1c2a48060f4ed077fa86f Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sun, 10 May 2026 20:11:12 -0400 Subject: [PATCH 3/4] Add proper config descriptions --- .../assets/hexcasting/lang/en_us.flatten.json5 | 8 ++++++-- .../at/petrak/hexcasting/forge/ForgeHexConfig.java | 12 +++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 298e680936..065f07f5d0 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -393,11 +393,15 @@ }, actionDenyList: { "": "Action Deny List", - "@Tooltip": "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap. For example, hexcasting:get_caster will prevent Mind's Reflection", + "@Tooltip": "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap. For example, hexcasting:get_caster will prevent Mind's Reflection.", }, circleActionDenyList: { "": "Circle Action Deny List", - "@Tooltip": "Resource locations of disallowed actions within circles. Trying to cast one of these from a circle will result in a mishap.", + "@Tooltip": "Resource locations of disallowed actions within circles. Trying to cast one of these from a circle will result in a mishap. For example, hexcasting:get_caster will prevent Mind's Reflection.", + }, + costRescaleListRaw: { + "": "Cost Scaling Factors", + "@Tooltip": "Maps resource locations to the scaling factor for that action's media cost. For example, hexcasting:add_motion 3 will make Impulse cost 3x as much.", }, greaterTeleportSplatsItems: { "": "Greater Teleport Splats Items", diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index 2bd0770166..37a35d6353 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -197,12 +197,14 @@ public Server(ForgeConfigSpec.Builder builder) { ).define("greaterTeleportSplatsItems", DEFAULT_GREATER_TELEPORT_SPLATS_ITEMS); actionDenyList = builder.comment( - "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap." - ).defineList("actionDenyList", List.of(), Server::isValidReslocArg); + "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap. " + + "For example, \"hexcasting:get_caster\" will prevent Mind's Reflection.") + .defineList("actionDenyList", List.of(), Server::isValidReslocArg); costRescaleList = builder.comment( - "[COST RESCALE LIST]" - ).defineList("costRescaleList", List.of(), Server::isValidReslocDoublePair); + "Maps resource locations to the scaling factor for that action's media cost. " + + "For example, \"hexcasting:add_motion 3\" will make Impulse cost 3x as much.") + .defineList("costRescaleList", List.of(), Server::isValidReslocDoublePair); builder.pop(); builder.push("Spell Circles"); @@ -211,7 +213,7 @@ public Server(ForgeConfigSpec.Builder builder) { circleActionDenyList = builder.comment( "Resource locations of disallowed actions within circles. Trying to cast one of these in a circle" + - " will result in a mishap. For example: hexcasting:get_caster will prevent Mind's Reflection.") + " will result in a mishap. For example, \"hexcasting:get_caster\" will prevent Mind's Reflection.") .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); builder.pop(); From 0d441ab28117783e7aa73c88c3b407ddec473e4a Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Sun, 10 May 2026 20:32:29 -0400 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4498b2755..b7708b250a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - Added the `cannot_modify_cost` tag for patterns that should ignore the `media_consumption` attribute when calculating cost, by Robotgiggle in [987](https://github.com/FallingColors/HexMod/pull/987). +- Added a config option to rescale the cost of specific actions, by Robotgiggle in [#1041](https://github.com/FallingColors/HexMod/pull/1041). ### Changed