diff --git a/CHANGELOG.md b/CHANGELOG.md index f4498b275..b7708b250 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 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 cd58baca7..912282649 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,14 +193,15 @@ public void precheckAction(PatternShapeMatch match) throws Mishap { throw new MishapDisallowedSpell("disallowed", loc); } - costModifier = this.getCostModifier(match); + costModifier = (loc != null) ? this.getCostModifier(loc) : 1.0; } /** - * 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; + protected double getCostModifier(@NotNull ResourceLocation loc) { + 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 f78794ad1..c4fdebb4b 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,12 +72,12 @@ public ServerPlayer getCaster() { } @Override - protected double getCostModifier(PatternShapeMatch match) { - ResourceLocation loc = actionKey(match); - if (loc != null && isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) { - return 1.0; + 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 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 986d0f500..e1d537ff4 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/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 298e68093..065f07f5d 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/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java index 4f0de5bd4..38e4f9a2f 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 89762811b..37a35d635 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,25 @@ 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. " + + "For example, \"hexcasting:get_caster\" will prevent Mind's Reflection.") + .defineList("actionDenyList", List.of(), Server::isValidReslocArg); + + costRescaleList = builder.comment( + "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"); @@ -197,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(); @@ -206,17 +222,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 +261,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 +298,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; + } } }