Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ([#987](https://github.com/FallingColors/HexMod/pull/987)) @Robotgiggle
- Added config options to scale pattern costs per action and globally ([#1041](https://github.com/FallingColors/HexMod/pull/1041)) @Robotgiggle
- Added a config toggle for the pattern wobble animation on active slates to improve performance ([#1017](https://github.com/FallingColors/HexMod/pull/1017)) @aloaloolaola
- Added keybinds to flip forward and backwards in a spellbook ([#1028](https://github.com/FallingColors/HexMod/pull/1028)) @pythonmcpi
- Added a config toggle to disable scrolling through spellbooks outside of the staff grid ([#1028](https://github.com/FallingColors/HexMod/pull/1028)) @pythonmcpi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import at.petrak.hexcasting.api.casting.mishaps.MishapDisallowedSpell;
import at.petrak.hexcasting.api.casting.mishaps.MishapEntityTooFarAway;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.mod.HexTags;
import at.petrak.hexcasting.api.pigment.FrozenPigment;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.lib.HexAttributes;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
Expand All @@ -38,6 +40,7 @@

import static at.petrak.hexcasting.api.HexAPI.modLoc;
import static at.petrak.hexcasting.api.casting.eval.CastingEnvironmentComponent.*;
import static at.petrak.hexcasting.api.utils.HexUtils.isOfTag;

/**
* Environment within which hexes are cast.
Expand Down Expand Up @@ -180,9 +183,10 @@ public <T extends CastingEnvironmentComponent> T getExtension(@NotNull CastingEn
}

/**
* If something about this ARE itself is invalid, mishap.
* <p>
* This is used for stuff like requiring enlightenment and pattern denylists
* If something about this Action itself is invalid, mishap.
* This is used for stuff like requiring enlightenment and pattern denylists.<br>
* If the Action <i>is</i> valid, this sets the CastingEnvironment's {@code costModifier} based on the
* appropriate modifier for that action as returned by {@code getCostModifier()}.
*/
public void precheckAction(PatternShapeMatch match) throws Mishap {
// TODO: this doesn't let you select special handlers.
Expand All @@ -193,14 +197,19 @@ 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 values
* 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) {
if (isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) {
// blacklisted actions can still be manually scaled in the config, but the global scaling doesn't apply
return HexConfig.server().getActionCostScaling(loc);
}
return HexConfig.server().getActionCostScaling(loc) * HexConfig.server().globalCostScaling();
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ public interface ServerConfigAccess {

boolean isActionAllowedInCircles(ResourceLocation actionID);

double getActionCostScaling(ResourceLocation actionID);

double globalCostScaling();

boolean doesGreaterTeleportSplatItems();

boolean doVillagersTakeOffenseAtMindMurder();
Expand All @@ -94,6 +98,7 @@ public interface ServerConfigAccess {
int DEFAULT_MAX_SPELL_CIRCLE_LENGTH = 1024;
int DEFAULT_OP_BREAK_HARVEST_LEVEL = 3;

double DEFAULT_GLOBAL_COST_SCALING = 1.0;
double DEFAULT_TRADER_SCROLL_CHANCE = 0.2;
boolean DEFAULT_GREATER_TELEPORT_SPLATS_ITEMS = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public static final class Actions {
public static final TagKey<ActionRegistryEntry> CAN_START_ENLIGHTEN = create("can_start_enlighten");

/**
* Actions that should not be affected by the media_consumption attribute
* Actions that should not be affected by the media_consumption attribute or the globalCostScaling config
*/
public static final TagKey<ActionRegistryEntry> CANNOT_MODIFY_COST = create("cannot_modify_cost");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,19 @@
},
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.",
},
globalCostScaling: {
"": "Global Cost Scaling Factor",
"@Tooltip": "All media costs, except for actions in the #hexcasting:cannot_modify_cost tag, will be multiplied by this value. If you want to modify the cost of an action in that tag, use the per-action cost scaling list.",
},
costRescaleListRaw: {
"": "Per-Action Cost Scaling Factors",
"@Tooltip": "Maps resource locations to the scaling factor for that specific action's media cost. For example, hexcasting:add_motion 3 will make Impulse cost 3x as much.",
},
greaterTeleportSplatsItems: {
"": "Greater Teleport Splats Items",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -209,6 +211,11 @@ public static final class Server implements HexConfig.ServerConfigAccess, Config
@ConfigEntry.Gui.Tooltip
private List<String> circleActionDenyList = List.of();
@ConfigEntry.Gui.Tooltip
private List<String> costRescaleListRaw = List.of();
private transient Object2DoubleMap<ResourceLocation> costRescaleMap;
@ConfigEntry.Gui.Tooltip
private double globalCostScaling = DEFAULT_GLOBAL_COST_SCALING;
@ConfigEntry.Gui.Tooltip
private boolean greaterTeleportSplatsItems = DEFAULT_GREATER_TELEPORT_SPLATS_ITEMS;
@ConfigEntry.Gui.Tooltip
private boolean villagersOffendedByMindMurder = DEFAULT_VILLAGERS_DISLIKE_MIND_MURDER;
Expand Down Expand Up @@ -253,10 +260,24 @@ public static final class Server implements HexConfig.ServerConfigAccess, Config

@Override
public void validatePostLoad() throws ValidationException {
this.opBreakHarvestLevel = Mth.clamp(this.opBreakHarvestLevel, 0, 4);
this.maxOpCount = Math.max(this.maxOpCount, 0);
this.maxSpellCircleLength = Math.max(this.maxSpellCircleLength, 4);
this.globalCostScaling = Math.max(this.globalCostScaling, 0);
this.traderScrollChance = Mth.clamp(this.traderScrollChance, 0.0, 1.0);

this.costRescaleMap = 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.costRescaleMap.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) {
Expand All @@ -265,7 +286,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);
}
Expand Down Expand Up @@ -320,6 +340,14 @@ public boolean isActionAllowedInCircles(ResourceLocation actionID) {
return noneMatch(circleActionDenyList, actionID);
}

@Override
public double getActionCostScaling(ResourceLocation actionID) {
return this.costRescaleMap.getOrDefault(actionID, 1.0);
}

@Override
public double globalCostScaling() { return globalCostScaling; }

@Override
public boolean doesGreaterTeleportSplatItems() { return greaterTeleportSplatsItems; }

Expand Down
79 changes: 69 additions & 10 deletions Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import net.minecraft.world.level.Level;
import net.minecraftforge.common.ForgeConfigSpec;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static at.petrak.hexcasting.api.mod.HexConfig.noneMatch;

Expand Down Expand Up @@ -173,6 +176,9 @@ public static class Server implements HexConfig.ServerConfigAccess {
private static ForgeConfigSpec.IntValue maxSpellCircleLength;
private static ForgeConfigSpec.ConfigValue<List<? extends String>> actionDenyList;
private static ForgeConfigSpec.ConfigValue<List<? extends String>> circleActionDenyList;
private static ForgeConfigSpec.ConfigValue<List<? extends String>> costRescaleList;
private static Map<ResourceLocation, Double> costRescaleMap = new HashMap<>();
private static ForgeConfigSpec.DoubleValue globalCostScaling;

private static ForgeConfigSpec.BooleanValue greaterTeleportSplatsItems;
private static ForgeConfigSpec.BooleanValue villagersOffendedByMindMurder;
Expand All @@ -185,10 +191,30 @@ 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 specific action's media cost. " +
"For example, hexcasting:add_motion 3 will make Impulse cost 3x as much.")
.defineList("costRescaleList", List.of(), Server::validateAndStoreMapping);

globalCostScaling = builder.comment(
"All media costs, except for actions in the #hexcasting:cannot_modify_cost tag, will be multiplied by this value." +
"If you want to modify the cost of an action in that tag, use the per-action list above.")
.defineInRange("globalCostScaling", DEFAULT_GLOBAL_COST_SCALING, 0.0, Double.MAX_VALUE);
Comment thread
Robotgiggle marked this conversation as resolved.
builder.pop();

builder.push("Spell Circles");
Expand All @@ -197,7 +223,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();

Expand All @@ -206,17 +232,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);
Expand Down Expand Up @@ -254,6 +271,17 @@ public boolean isActionAllowedInCircles(ResourceLocation actionID) {
return noneMatch(circleActionDenyList.get(), actionID);
}

@Override
public double getActionCostScaling(ResourceLocation actionID) {
Comment thread
Robotgiggle marked this conversation as resolved.
if (costRescaleMap.size() != costRescaleList.get().size()) {
removeStaleCostScaleMappings();
}
return costRescaleMap.getOrDefault(actionID, 1.0);
}

@Override
public double globalCostScaling() { return globalCostScaling.get(); }

@Override
public boolean doesGreaterTeleportSplatItems() { return greaterTeleportSplatsItems.get(); }

Expand All @@ -280,5 +308,36 @@ public double traderScrollChance() {
private static boolean isValidReslocArg(Object o) {
return o instanceof String s && ResourceLocation.isValidResourceLocation(s);
}

private static boolean validateAndStoreMapping(Object o) {
if (o instanceof String s) {
try {
String[] split = s.split(" ");
ResourceLocation loc = new ResourceLocation(split[0]);
double scale = Double.parseDouble(split[1]);
costRescaleMap.put(loc, scale);
return true;
} catch (Exception e) {
costRescaleMap.clear();
return false;
}
}
costRescaleMap.clear();
return false;
}

/**
* When an element is removed from the config-backed {@code costRescaleList}, there's no way for the {@code costRescaleMap}
* to know that it's gone since Forge's list validation is only done on a per-element basis. To handle this, we
* check if {@code costRescaleList} and {@code costRescaleMap} are the same size, and if not we call this method
* to remove the erroneous key/value pairs from the map.
*/
private static void removeStaleCostScaleMappings() {
var actualKeys = costRescaleList.get().stream().map(entry -> {
String[] split = entry.split(" ");
return new ResourceLocation(split[0]);
}).collect(Collectors.toSet());
costRescaleMap.keySet().retainAll(actualKeys);
}
}
}
Loading