From cf432c0809fffb9464b57e200d1b6c5bb3a678dd Mon Sep 17 00:00:00 2001 From: Skye Date: Thu, 15 Aug 2024 21:09:05 +0900 Subject: [PATCH 1/7] Fixes Empty Impetus not accepting pulses from the back. Fixes #656 --- .../hexcasting/common/blocks/circles/BlockEmptyImpetus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEmptyImpetus.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEmptyImpetus.java index 07adca100d..2cac9c79f1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEmptyImpetus.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEmptyImpetus.java @@ -38,7 +38,7 @@ public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Di @Override public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) { - return enterDir != bs.getValue(FACING); + return enterDir != bs.getValue(FACING).getOpposite(); } @Override From 5f434ed672da998e19f240087493738a93e8d22e Mon Sep 17 00:00:00 2001 From: navarchus Date: Mon, 19 Aug 2024 23:00:54 -0400 Subject: [PATCH 2/7] Prevent crashes due to unbounded growth of a few items. Add configurable setting for max number of iotas on the stack, default is 2048 iotas. --- .../hexcasting/damage_type/stack_size.json | 5 ++ .../hexcasting/api/casting/ActionUtils.kt | 17 +++++ .../api/casting/eval/vm/CastingImage.kt | 18 ++++- .../api/casting/mishaps/MishapStackSize.kt | 23 ++++++ .../petrak/hexcasting/api/mod/HexConfig.java | 3 + .../hexcasting/common/lib/HexDamageTypes.java | 19 +++-- .../datagen/tag/HexDamageTypeTagProvider.java | 7 ++ .../hexcasting/lang/en_us.flatten.json5 | 1 + .../hexcasting/fabric/FabricHexConfig.java | 6 ++ .../hexcasting/forge/ForgeHexConfig.java | 75 +++++++++++-------- 10 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt diff --git a/Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json b/Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json new file mode 100644 index 0000000000..0cfbe8c15e --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json @@ -0,0 +1,5 @@ +{ + "exhaustion": 0.0, + "message_id": "hexcasting.stack_size", + "scaling": "when_caused_by_living_non_player" +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt index 26f39da2f3..c2fffdddb5 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt @@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.casting.iota.* import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs +import at.petrak.hexcasting.api.casting.mishaps.MishapShameOnYou import at.petrak.hexcasting.api.utils.asTranslatedComponent import com.mojang.datafixers.util.Either import net.minecraft.core.BlockPos @@ -22,6 +23,22 @@ import kotlin.math.abs import kotlin.math.roundToInt import kotlin.math.roundToLong +fun List.getIotaListSize(): Int{ + val work = mutableListOf() + work.addAll(this) + var size = 0 + while (work.isNotEmpty()) { + val iota = work.removeLast() + val subiotas = iota.subIotas() + if (subiotas != null) { + work.addAll(subiotas) + } + size += iota.size() + } + + return size +} + fun List.getDouble(idx: Int, argc: Int = 0): Double { val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } if (x is DoubleIota) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index 3ffd771bcd..5ffcaf0b8a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -12,6 +12,10 @@ import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.Entity +import at.petrak.hexcasting.api.casting.getIotaListSize +import at.petrak.hexcasting.api.casting.mishaps.MishapStackSize +import at.petrak.hexcasting.api.mod.HexConfig + /** * The state of a casting VM, containing the stack and all @@ -26,6 +30,13 @@ data class CastingImage private constructor( val userData: CompoundTag ) { + init { + val size = stack.getIotaListSize() + if (size > HexConfig.common().stackIotaLimit()) { + throw MishapStackSize() + } + } + constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) { @@ -113,7 +124,12 @@ data class CastingImage private constructor( val parenEscapedTag = parenTag.getByteArray(TAG_ESCAPED) for ((subtag, isEscapedByte) in parenIotasTag.zipWithDefault(parenEscapedTag) { _ -> 0 }) { - parenthesized.add(ParenthesizedIota(IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world), isEscapedByte != 0.toByte())) + parenthesized.add( + ParenthesizedIota( + IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world), + isEscapedByte != 0.toByte() + ) + ) } val parenCount = tag.getInt(TAG_PAREN_COUNT) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt new file mode 100644 index 0000000000..f97016ddec --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt @@ -0,0 +1,23 @@ +package at.petrak.hexcasting.api.casting.mishaps + +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.pigment.FrozenPigment +import at.petrak.hexcasting.common.lib.HexDamageTypes +import net.minecraft.world.item.DyeColor + +class MishapStackSize() : Mishap() { + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BROWN) + + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + val caster = env.castingEntity + if (caster != null) { + // FIXME: handle null caster case + trulyHurt(caster, caster.damageSources().source(HexDamageTypes.STACK_SIZE), 2f) + } + } + + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("stack_size") +} 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 105994f61d..1aff751988 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 @@ -36,6 +36,9 @@ public interface CommonConfigAccess { int DEFAULT_TRINKET_COOLDOWN = 5; int DEFAULT_ARTIFACT_COOLDOWN = 3; + int DEFAUlT_STACK_IOTA_LIMIT = 2048; + + int stackIotaLimit(); } public interface ClientConfigAccess { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java index d9f5ad2111..bde8a14ce3 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java @@ -11,18 +11,25 @@ public class HexDamageTypes { public static final ResourceKey OVERCAST = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("overcast")); public static final ResourceKey SHAME_ON_YOU = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("shame")); + public static final ResourceKey STACK_SIZE = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("stack_size")); public static void bootstrap(BootstapContext ctx) { ctx.register(OVERCAST, new DamageType( - "hexcasting.overcast", - DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, - 0f + "hexcasting.overcast", + DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, + 0f )); ctx.register(SHAME_ON_YOU, new DamageType( - "hexcasting.shame", - DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, - 0f + "hexcasting.shame", + DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, + 0f + )); + + ctx.register(STACK_SIZE, new DamageType( + "hexcasting.stack_size", + DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, + 0f )); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java index 82eb35e7e2..693f02f98c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java @@ -31,6 +31,13 @@ protected void addTags(@NotNull HolderLookup.Provider provider) { DamageTypeTags.BYPASSES_INVULNERABILITY, DamageTypeTags.BYPASSES_SHIELD ); + + add(HexDamageTypes.STACK_SIZE, + DamageTypeTags.BYPASSES_ARMOR, + DamageTypeTags.BYPASSES_EFFECTS, + DamageTypeTags.BYPASSES_INVULNERABILITY, + DamageTypeTags.BYPASSES_SHIELD + ); } @SafeVarargs 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 470c006c0d..0b4258249d 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 @@ -901,6 +901,7 @@ invalid_spell_datum_type: "Tried to use a value of invalid type as a SpellDatum: %s (class %s). This is a bug in the mod.", unknown: "threw an exception (%s). This is a bug in the mod.", shame: "Shame on you!", + stack_size: "My mind was too small to contain the breadth of this spell.", invalid_value: { "": "expected %s at index %s of the stack, but got %s", 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 7622e7cf8f..d28df2ef64 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -77,6 +77,9 @@ public static final class Common implements HexConfig.CommonConfigAccess, Config @ConfigEntry.Gui.Tooltip private int artifactCooldown = DEFAULT_ARTIFACT_COOLDOWN; + @ConfigEntry.Gui.Tooltip + private int stackIotaLimit = DEFAUlT_STACK_IOTA_LIMIT; + @Override public void validatePostLoad() throws ValidationException { @@ -120,6 +123,9 @@ public int trinketCooldown() { public int artifactCooldown() { return artifactCooldown; } + + @Override + public int stackIotaLimit() { return stackIotaLimit;} } @Config(name = "client") 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 507d1fece7..038afd9795 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -20,26 +20,34 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess { private static ForgeConfigSpec.IntValue trinketCooldown; private static ForgeConfigSpec.IntValue artifactCooldown; + private static ForgeConfigSpec.IntValue stackIotaLimit; + public ForgeHexConfig(ForgeConfigSpec.Builder builder) { builder.push("Media Amounts"); dustMediaAmount = builder.comment("How much media a single Amethyst Dust item is worth") - .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); shardMediaAmount = builder.comment("How much media a single Amethyst Shard item is worth") - .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); chargedCrystalMediaAmount = builder.comment("How much media a single Charged Amethyst Crystal item is worth") - .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); mediaToHealthRate = builder.comment("How many points of media a half-heart is worth when casting from HP") - .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); + .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); builder.pop(); builder.push("Cooldowns"); cypherCooldown = builder.comment("Cooldown in ticks of a cypher") - .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); trinketCooldown = builder.comment("Cooldown in ticks of a trinket") - .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); artifactCooldown = builder.comment("Cooldown in ticks of a artifact") - .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); + builder.pop(); + + builder.push("Stack Limitations"); + stackIotaLimit = builder.comment("Max limit of iotas allowed on the stack") + .defineInRange("stackIotaLimit", DEFAUlT_STACK_IOTA_LIMIT, 0, Integer.MAX_VALUE); builder.pop(); + } @Override @@ -77,6 +85,9 @@ public int artifactCooldown() { return artifactCooldown.get(); } + @Override + public int stackIotaLimit() { return stackIotaLimit.get(); } + public static class Client implements HexConfig.ClientConfigAccess { private static ForgeConfigSpec.BooleanValue ctrlTogglesOffStrokeOrder; private static ForgeConfigSpec.BooleanValue invertSpellbookScrollDirection; @@ -85,19 +96,19 @@ public static class Client implements HexConfig.ClientConfigAccess { public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( - "Whether the ctrl key will instead turn *off* the color gradient on patterns") - .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); + "Whether the ctrl key will instead turn *off* the color gradient on patterns") + .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); invertSpellbookScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + - "vice versa") - .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); + "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + + "vice versa") + .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); invertAbacusScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") - .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); + "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") + .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); gridSnapThreshold = builder.comment( - "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + - "means 50% of the way.") - .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); + "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + + "means 50% of the way.") + .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); } @Override @@ -144,38 +155,38 @@ public static class Server implements HexConfig.ServerConfigAccess { public Server(ForgeConfigSpec.Builder builder) { builder.push("Spells"); 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); + "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." + "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); builder.pop(); builder.push("Spell Circles"); maxSpellCircleLength = builder.comment("The maximum number of slates in a spell circle") - .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); + .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); 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.") - .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); + "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.") + .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); 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); + "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap.") + .defineList("actionDenyList", List.of(), Server::isValidReslocArg); villagersOffendedByMindMurder = builder.comment( - "Should villagers take offense when you flay the mind of their fellow villagers?") - .define("villagersOffendedByMindMurder", true); + "Should villagers take offense when you flay the mind of their fellow villagers?") + .define("villagersOffendedByMindMurder", true); tpDimDenyList = builder.comment("Resource locations of dimensions you can't Blink or Greater Teleport in.") - .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); + .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); doesTrueNameHaveAmbit = builder.comment( - "when false makes player reference iotas behave as normal entity reference iotas") - .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); + "when false makes player reference iotas behave as normal entity reference iotas") + .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); } @Override From 7ec296ce5fb59d74b2e86ce0c68e6c6a1078c357 Mon Sep 17 00:00:00 2001 From: Toby Vestal Date: Tue, 20 Aug 2024 16:07:28 -0400 Subject: [PATCH 3/7] Address comments in discord, scale back changes to mishaps and revert formatting --- .../hexcasting/damage_type/stack_size.json | 5 -- .../hexcasting/api/casting/ActionUtils.kt | 17 ----- .../api/casting/eval/vm/CastingImage.kt | 17 +---- .../api/casting/eval/vm/CastingVM.kt | 14 +++- .../api/casting/mishaps/MishapStackSize.kt | 13 ++-- .../petrak/hexcasting/api/mod/HexConfig.java | 3 - .../hexcasting/common/lib/HexDamageTypes.java | 7 -- .../datagen/tag/HexDamageTypeTagProvider.java | 6 -- .../hexcasting/fabric/FabricHexConfig.java | 4 -- .../hexcasting/forge/ForgeHexConfig.java | 72 +++++++++---------- 10 files changed, 51 insertions(+), 107 deletions(-) delete mode 100644 Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json diff --git a/Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json b/Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json deleted file mode 100644 index 0cfbe8c15e..0000000000 --- a/Common/src/generated/resources/data/hexcasting/damage_type/stack_size.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "exhaustion": 0.0, - "message_id": "hexcasting.stack_size", - "scaling": "when_caused_by_living_non_player" -} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt index c2fffdddb5..26f39da2f3 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt @@ -6,7 +6,6 @@ import at.petrak.hexcasting.api.casting.iota.* import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs -import at.petrak.hexcasting.api.casting.mishaps.MishapShameOnYou import at.petrak.hexcasting.api.utils.asTranslatedComponent import com.mojang.datafixers.util.Either import net.minecraft.core.BlockPos @@ -23,22 +22,6 @@ import kotlin.math.abs import kotlin.math.roundToInt import kotlin.math.roundToLong -fun List.getIotaListSize(): Int{ - val work = mutableListOf() - work.addAll(this) - var size = 0 - while (work.isNotEmpty()) { - val iota = work.removeLast() - val subiotas = iota.subIotas() - if (subiotas != null) { - work.addAll(subiotas) - } - size += iota.size() - } - - return size -} - fun List.getDouble(idx: Int, argc: Int = 0): Double { val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } if (x is DoubleIota) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index 5ffcaf0b8a..15667309d5 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -12,10 +12,6 @@ import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.Entity -import at.petrak.hexcasting.api.casting.getIotaListSize -import at.petrak.hexcasting.api.casting.mishaps.MishapStackSize -import at.petrak.hexcasting.api.mod.HexConfig - /** * The state of a casting VM, containing the stack and all @@ -30,12 +26,6 @@ data class CastingImage private constructor( val userData: CompoundTag ) { - init { - val size = stack.getIotaListSize() - if (size > HexConfig.common().stackIotaLimit()) { - throw MishapStackSize() - } - } constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) @@ -124,12 +114,7 @@ data class CastingImage private constructor( val parenEscapedTag = parenTag.getByteArray(TAG_ESCAPED) for ((subtag, isEscapedByte) in parenIotasTag.zipWithDefault(parenEscapedTag) { _ -> 0 }) { - parenthesized.add( - ParenthesizedIota( - IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world), - isEscapedByte != 0.toByte() - ) - ) + parenthesized.add(ParenthesizedIota(IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world), isEscapedByte != 0.toByte())) } val parenCount = tag.getInt(TAG_PAREN_COUNT) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 849a43ba20..7de56bd08f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -8,6 +8,7 @@ import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType +import at.petrak.hexcasting.api.casting.iota.IotaType.isTooLargeToSerialize import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.iota.PatternIota import at.petrak.hexcasting.api.casting.math.HexDir @@ -53,12 +54,19 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { val image2 = next.evaluate(continuation.next, world, this) // Then write all pertinent data back to the harness for the next iteration. if (image2.newData != null) { - this.image = image2.newData + if (isTooLargeToSerialize(image2.newData.stack)){ + // Ugly cast, probably need to rethink location + (image2.sideEffects as MutableList).add(OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null))) + lastResolutionType = ResolvedPatternType.ERRORED + }else { + continuation = image2.continuation + lastResolutionType = image2.resolutionType + this.image = image2.newData + } } + this.env.postExecution(image2) - continuation = image2.continuation - lastResolutionType = image2.resolutionType try { performSideEffects(info, image2.sideEffects) } catch (e: Exception) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt index f97016ddec..571e39bcd9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt @@ -1,6 +1,8 @@ package at.petrak.hexcasting.api.casting.mishaps import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.iota.GarbageIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.pigment.FrozenPigment import at.petrak.hexcasting.common.lib.HexDamageTypes @@ -8,14 +10,13 @@ import net.minecraft.world.item.DyeColor class MishapStackSize() : Mishap() { override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BROWN) + dyeColor(DyeColor.BLACK) + + override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.ERRORED override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - val caster = env.castingEntity - if (caster != null) { - // FIXME: handle null caster case - trulyHurt(caster, caster.damageSources().source(HexDamageTypes.STACK_SIZE), 2f) - } + stack.clear() + stack.add(GarbageIota()) } override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = 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 1aff751988..105994f61d 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 @@ -36,9 +36,6 @@ public interface CommonConfigAccess { int DEFAULT_TRINKET_COOLDOWN = 5; int DEFAULT_ARTIFACT_COOLDOWN = 3; - int DEFAUlT_STACK_IOTA_LIMIT = 2048; - - int stackIotaLimit(); } public interface ClientConfigAccess { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java index bde8a14ce3..80121e15a0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java @@ -11,7 +11,6 @@ public class HexDamageTypes { public static final ResourceKey OVERCAST = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("overcast")); public static final ResourceKey SHAME_ON_YOU = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("shame")); - public static final ResourceKey STACK_SIZE = ResourceKey.create(Registries.DAMAGE_TYPE, modLoc("stack_size")); public static void bootstrap(BootstapContext ctx) { ctx.register(OVERCAST, new DamageType( @@ -25,11 +24,5 @@ public static void bootstrap(BootstapContext ctx) { DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, 0f )); - - ctx.register(STACK_SIZE, new DamageType( - "hexcasting.stack_size", - DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, - 0f - )); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java index 693f02f98c..de891b315d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java @@ -32,12 +32,6 @@ protected void addTags(@NotNull HolderLookup.Provider provider) { DamageTypeTags.BYPASSES_SHIELD ); - add(HexDamageTypes.STACK_SIZE, - DamageTypeTags.BYPASSES_ARMOR, - DamageTypeTags.BYPASSES_EFFECTS, - DamageTypeTags.BYPASSES_INVULNERABILITY, - DamageTypeTags.BYPASSES_SHIELD - ); } @SafeVarargs 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 d28df2ef64..fecf1a5397 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -77,8 +77,6 @@ public static final class Common implements HexConfig.CommonConfigAccess, Config @ConfigEntry.Gui.Tooltip private int artifactCooldown = DEFAULT_ARTIFACT_COOLDOWN; - @ConfigEntry.Gui.Tooltip - private int stackIotaLimit = DEFAUlT_STACK_IOTA_LIMIT; @Override @@ -124,8 +122,6 @@ public int artifactCooldown() { return artifactCooldown; } - @Override - public int stackIotaLimit() { return stackIotaLimit;} } @Config(name = "client") 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 038afd9795..6ad3203365 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -25,27 +25,22 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess { public ForgeHexConfig(ForgeConfigSpec.Builder builder) { builder.push("Media Amounts"); dustMediaAmount = builder.comment("How much media a single Amethyst Dust item is worth") - .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); shardMediaAmount = builder.comment("How much media a single Amethyst Shard item is worth") - .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); chargedCrystalMediaAmount = builder.comment("How much media a single Charged Amethyst Crystal item is worth") - .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); mediaToHealthRate = builder.comment("How many points of media a half-heart is worth when casting from HP") - .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); + .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); builder.pop(); builder.push("Cooldowns"); cypherCooldown = builder.comment("Cooldown in ticks of a cypher") - .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); trinketCooldown = builder.comment("Cooldown in ticks of a trinket") - .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); artifactCooldown = builder.comment("Cooldown in ticks of a artifact") - .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); - builder.pop(); - - builder.push("Stack Limitations"); - stackIotaLimit = builder.comment("Max limit of iotas allowed on the stack") - .defineInRange("stackIotaLimit", DEFAUlT_STACK_IOTA_LIMIT, 0, Integer.MAX_VALUE); + .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); builder.pop(); } @@ -85,9 +80,6 @@ public int artifactCooldown() { return artifactCooldown.get(); } - @Override - public int stackIotaLimit() { return stackIotaLimit.get(); } - public static class Client implements HexConfig.ClientConfigAccess { private static ForgeConfigSpec.BooleanValue ctrlTogglesOffStrokeOrder; private static ForgeConfigSpec.BooleanValue invertSpellbookScrollDirection; @@ -96,19 +88,19 @@ public static class Client implements HexConfig.ClientConfigAccess { public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( - "Whether the ctrl key will instead turn *off* the color gradient on patterns") - .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); + "Whether the ctrl key will instead turn *off* the color gradient on patterns") + .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); invertSpellbookScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + - "vice versa") - .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); + "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + + "vice versa") + .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); invertAbacusScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") - .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); + "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") + .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); gridSnapThreshold = builder.comment( - "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + - "means 50% of the way.") - .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); + "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + + "means 50% of the way.") + .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); } @Override @@ -155,38 +147,38 @@ public static class Server implements HexConfig.ServerConfigAccess { public Server(ForgeConfigSpec.Builder builder) { builder.push("Spells"); 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); + "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." + "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); builder.pop(); builder.push("Spell Circles"); maxSpellCircleLength = builder.comment("The maximum number of slates in a spell circle") - .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); + .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); 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.") - .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); + "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.") + .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); 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); + "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap.") + .defineList("actionDenyList", List.of(), Server::isValidReslocArg); villagersOffendedByMindMurder = builder.comment( - "Should villagers take offense when you flay the mind of their fellow villagers?") - .define("villagersOffendedByMindMurder", true); + "Should villagers take offense when you flay the mind of their fellow villagers?") + .define("villagersOffendedByMindMurder", true); tpDimDenyList = builder.comment("Resource locations of dimensions you can't Blink or Greater Teleport in.") - .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); + .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); doesTrueNameHaveAmbit = builder.comment( - "when false makes player reference iotas behave as normal entity reference iotas") - .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); + "when false makes player reference iotas behave as normal entity reference iotas") + .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); } @Override From 765ed926e15b9bafda1fdcfc6945859d9ced708a Mon Sep 17 00:00:00 2001 From: Toby Vestal Date: Tue, 20 Aug 2024 16:14:07 -0400 Subject: [PATCH 4/7] formatting revert 2 --- .../api/casting/eval/vm/CastingImage.kt | 1 - .../hexcasting/common/lib/HexDamageTypes.java | 12 ++-- .../datagen/tag/HexDamageTypeTagProvider.java | 1 - .../hexcasting/fabric/FabricHexConfig.java | 2 - .../hexcasting/forge/ForgeHexConfig.java | 69 +++++++++---------- 5 files changed, 39 insertions(+), 46 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index 15667309d5..3ffd771bcd 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -26,7 +26,6 @@ data class CastingImage private constructor( val userData: CompoundTag ) { - constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java index 80121e15a0..d9f5ad2111 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexDamageTypes.java @@ -14,15 +14,15 @@ public class HexDamageTypes { public static void bootstrap(BootstapContext ctx) { ctx.register(OVERCAST, new DamageType( - "hexcasting.overcast", - DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, - 0f + "hexcasting.overcast", + DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, + 0f )); ctx.register(SHAME_ON_YOU, new DamageType( - "hexcasting.shame", - DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, - 0f + "hexcasting.shame", + DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER, + 0f )); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java index de891b315d..82eb35e7e2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexDamageTypeTagProvider.java @@ -31,7 +31,6 @@ protected void addTags(@NotNull HolderLookup.Provider provider) { DamageTypeTags.BYPASSES_INVULNERABILITY, DamageTypeTags.BYPASSES_SHIELD ); - } @SafeVarargs 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 fecf1a5397..7622e7cf8f 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -78,7 +78,6 @@ public static final class Common implements HexConfig.CommonConfigAccess, Config private int artifactCooldown = DEFAULT_ARTIFACT_COOLDOWN; - @Override public void validatePostLoad() throws ValidationException { this.dustMediaAmount = Math.max(this.dustMediaAmount, 0); @@ -121,7 +120,6 @@ public int trinketCooldown() { public int artifactCooldown() { return artifactCooldown; } - } @Config(name = "client") 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 6ad3203365..d3471fdd97 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -20,29 +20,26 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess { private static ForgeConfigSpec.IntValue trinketCooldown; private static ForgeConfigSpec.IntValue artifactCooldown; - private static ForgeConfigSpec.IntValue stackIotaLimit; - public ForgeHexConfig(ForgeConfigSpec.Builder builder) { builder.push("Media Amounts"); dustMediaAmount = builder.comment("How much media a single Amethyst Dust item is worth") - .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); shardMediaAmount = builder.comment("How much media a single Amethyst Shard item is worth") - .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); chargedCrystalMediaAmount = builder.comment("How much media a single Charged Amethyst Crystal item is worth") - .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); mediaToHealthRate = builder.comment("How many points of media a half-heart is worth when casting from HP") - .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); + .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); builder.pop(); builder.push("Cooldowns"); cypherCooldown = builder.comment("Cooldown in ticks of a cypher") - .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); trinketCooldown = builder.comment("Cooldown in ticks of a trinket") - .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); artifactCooldown = builder.comment("Cooldown in ticks of a artifact") - .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); builder.pop(); - } @Override @@ -88,19 +85,19 @@ public static class Client implements HexConfig.ClientConfigAccess { public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( - "Whether the ctrl key will instead turn *off* the color gradient on patterns") - .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); + "Whether the ctrl key will instead turn *off* the color gradient on patterns") + .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); invertSpellbookScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + - "vice versa") - .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); + "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + + "vice versa") + .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); invertAbacusScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") - .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); + "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") + .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); gridSnapThreshold = builder.comment( - "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + - "means 50% of the way.") - .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); + "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + + "means 50% of the way.") + .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); } @Override @@ -147,38 +144,38 @@ public static class Server implements HexConfig.ServerConfigAccess { public Server(ForgeConfigSpec.Builder builder) { builder.push("Spells"); 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); + "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." + "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); builder.pop(); builder.push("Spell Circles"); maxSpellCircleLength = builder.comment("The maximum number of slates in a spell circle") - .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); + .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); 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.") - .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); + "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.") + .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); 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); + "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap.") + .defineList("actionDenyList", List.of(), Server::isValidReslocArg); villagersOffendedByMindMurder = builder.comment( - "Should villagers take offense when you flay the mind of their fellow villagers?") - .define("villagersOffendedByMindMurder", true); + "Should villagers take offense when you flay the mind of their fellow villagers?") + .define("villagersOffendedByMindMurder", true); tpDimDenyList = builder.comment("Resource locations of dimensions you can't Blink or Greater Teleport in.") - .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); + .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); doesTrueNameHaveAmbit = builder.comment( - "when false makes player reference iotas behave as normal entity reference iotas") - .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); + "when false makes player reference iotas behave as normal entity reference iotas") + .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); } @Override @@ -225,4 +222,4 @@ private static boolean isValidReslocArg(Object o) { return o instanceof String s && ResourceLocation.isValidResourceLocation(s); } } -} +} \ No newline at end of file From 9600a8f0306cd8387bf8f7c13f1e827a5054d1fe Mon Sep 17 00:00:00 2001 From: Toby Vestal Date: Tue, 20 Aug 2024 16:55:10 -0400 Subject: [PATCH 5/7] copy result instead of casting to mutable list --- .../api/casting/eval/vm/CastingVM.kt | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 7de56bd08f..34ad0cddde 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -51,22 +51,30 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { // ...and execute it. // TODO there used to be error checking code here; I'm pretty sure any and all mishaps should already // get caught and folded into CastResult by evaluate. - val image2 = next.evaluate(continuation.next, world, this) + val image2 = next.evaluate(continuation.next, world, this).let { result -> + // if stack is unable to be serialized, have the result be an error + if (result.newData != null && isTooLargeToSerialize(result.newData.stack)) { + result.copy( + newData = null, + sideEffects = result.sideEffects + OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null)), + resolutionType = ResolvedPatternType.ERRORED, + sound = HexEvalSounds.MISHAP, + ) + } else { + result + } + } + // Then write all pertinent data back to the harness for the next iteration. if (image2.newData != null) { - if (isTooLargeToSerialize(image2.newData.stack)){ - // Ugly cast, probably need to rethink location - (image2.sideEffects as MutableList).add(OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null))) - lastResolutionType = ResolvedPatternType.ERRORED - }else { - continuation = image2.continuation - lastResolutionType = image2.resolutionType - this.image = image2.newData - } + this.image = image2.newData } this.env.postExecution(image2) + continuation = image2.continuation + lastResolutionType = image2.resolutionType + try { performSideEffects(info, image2.sideEffects) } catch (e: Exception) { From bafcbdd314c2e5393b0dbf8368789089ea652feb Mon Sep 17 00:00:00 2001 From: Toby Vestal Date: Tue, 20 Aug 2024 17:10:01 -0400 Subject: [PATCH 6/7] addressed comments --- .../api/casting/eval/vm/CastingVM.kt | 7 +- .../hexcasting/lang/en_us.flatten.json5 | 2 +- .../hexcasting/forge/ForgeHexConfig.java | 66 +++++++++---------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 34ad0cddde..8c605d9fb2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -8,7 +8,6 @@ import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType -import at.petrak.hexcasting.api.casting.iota.IotaType.isTooLargeToSerialize import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.iota.PatternIota import at.petrak.hexcasting.api.casting.math.HexDir @@ -53,10 +52,10 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { // get caught and folded into CastResult by evaluate. val image2 = next.evaluate(continuation.next, world, this).let { result -> // if stack is unable to be serialized, have the result be an error - if (result.newData != null && isTooLargeToSerialize(result.newData.stack)) { + if (result.newData != null && IotaType.isTooLargeToSerialize(result.newData.stack)) { result.copy( newData = null, - sideEffects = result.sideEffects + OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null)), + sideEffects = listOf(OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null))), resolutionType = ResolvedPatternType.ERRORED, sound = HexEvalSounds.MISHAP, ) @@ -69,12 +68,10 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { if (image2.newData != null) { this.image = image2.newData } - this.env.postExecution(image2) continuation = image2.continuation lastResolutionType = image2.resolutionType - try { performSideEffects(info, image2.sideEffects) } catch (e: Exception) { 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 0b4258249d..a9e89263b4 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 @@ -901,7 +901,7 @@ invalid_spell_datum_type: "Tried to use a value of invalid type as a SpellDatum: %s (class %s). This is a bug in the mod.", unknown: "threw an exception (%s). This is a bug in the mod.", shame: "Shame on you!", - stack_size: "My mind was too small to contain the breadth of this spell.", + stack_size: "Exceeded stack size limit", invalid_value: { "": "expected %s at index %s of the stack, but got %s", 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 d3471fdd97..507d1fece7 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -23,22 +23,22 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess { public ForgeHexConfig(ForgeConfigSpec.Builder builder) { builder.push("Media Amounts"); dustMediaAmount = builder.comment("How much media a single Amethyst Dust item is worth") - .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("dustMediaAmount", DEFAULT_DUST_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); shardMediaAmount = builder.comment("How much media a single Amethyst Shard item is worth") - .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("shardMediaAmount", DEFAULT_SHARD_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); chargedCrystalMediaAmount = builder.comment("How much media a single Charged Amethyst Crystal item is worth") - .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); + .defineInRange("chargedCrystalMediaAmount", DEFAULT_CHARGED_MEDIA_AMOUNT, 0, Integer.MAX_VALUE); mediaToHealthRate = builder.comment("How many points of media a half-heart is worth when casting from HP") - .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); + .defineInRange("mediaToHealthRate", DEFAULT_MEDIA_TO_HEALTH_RATE, 0.0, Double.POSITIVE_INFINITY); builder.pop(); builder.push("Cooldowns"); cypherCooldown = builder.comment("Cooldown in ticks of a cypher") - .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("cypherCooldown", DEFAULT_CYPHER_COOLDOWN, 0, Integer.MAX_VALUE); trinketCooldown = builder.comment("Cooldown in ticks of a trinket") - .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("trinketCooldown", DEFAULT_TRINKET_COOLDOWN, 0, Integer.MAX_VALUE); artifactCooldown = builder.comment("Cooldown in ticks of a artifact") - .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); + .defineInRange("artifactCooldown", DEFAULT_ARTIFACT_COOLDOWN, 0, Integer.MAX_VALUE); builder.pop(); } @@ -85,19 +85,19 @@ public static class Client implements HexConfig.ClientConfigAccess { public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( - "Whether the ctrl key will instead turn *off* the color gradient on patterns") - .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); + "Whether the ctrl key will instead turn *off* the color gradient on patterns") + .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); invertSpellbookScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + - "vice versa") - .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); + "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + + "vice versa") + .define("invertSpellbookScrollDirection", DEFAULT_INVERT_SPELLBOOK_SCROLL); invertAbacusScrollDirection = builder.comment( - "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") - .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); + "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa") + .define("invertAbacusScrollDirection", DEFAULT_INVERT_ABACUS_SCROLL); gridSnapThreshold = builder.comment( - "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + - "means 50% of the way.") - .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); + "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + + "means 50% of the way.") + .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); } @Override @@ -144,38 +144,38 @@ public static class Server implements HexConfig.ServerConfigAccess { public Server(ForgeConfigSpec.Builder builder) { builder.push("Spells"); 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); + "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." + "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); builder.pop(); builder.push("Spell Circles"); maxSpellCircleLength = builder.comment("The maximum number of slates in a spell circle") - .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); + .defineInRange("maxSpellCircleLength", DEFAULT_MAX_SPELL_CIRCLE_LENGTH, 4, Integer.MAX_VALUE); 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.") - .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); + "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.") + .defineList("circleActionDenyList", List.of(), Server::isValidReslocArg); 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); + "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap.") + .defineList("actionDenyList", List.of(), Server::isValidReslocArg); villagersOffendedByMindMurder = builder.comment( - "Should villagers take offense when you flay the mind of their fellow villagers?") - .define("villagersOffendedByMindMurder", true); + "Should villagers take offense when you flay the mind of their fellow villagers?") + .define("villagersOffendedByMindMurder", true); tpDimDenyList = builder.comment("Resource locations of dimensions you can't Blink or Greater Teleport in.") - .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); + .defineList("tpDimDenyList", DEFAULT_DIM_TP_DENYLIST, Server::isValidReslocArg); doesTrueNameHaveAmbit = builder.comment( - "when false makes player reference iotas behave as normal entity reference iotas") - .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); + "when false makes player reference iotas behave as normal entity reference iotas") + .define("doesTrueNameHaveAmbit", DEFAULT_TRUE_NAME_HAS_AMBIT); } @Override @@ -222,4 +222,4 @@ private static boolean isValidReslocArg(Object o) { return o instanceof String s && ResourceLocation.isValidResourceLocation(s); } } -} \ No newline at end of file +} From 4d837ccb1fc4b90dd77b7317e3ca2626810f153d Mon Sep 17 00:00:00 2001 From: Toby Vestal Date: Tue, 20 Aug 2024 17:15:18 -0400 Subject: [PATCH 7/7] Update Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 Co-authored-by: [object Object] --- .../main/resources/assets/hexcasting/lang/en_us.flatten.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a9e89263b4..9188928801 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 @@ -901,7 +901,7 @@ invalid_spell_datum_type: "Tried to use a value of invalid type as a SpellDatum: %s (class %s). This is a bug in the mod.", unknown: "threw an exception (%s). This is a bug in the mod.", shame: "Shame on you!", - stack_size: "Exceeded stack size limit", + stack_size: "Exceeded the size limit of the stack", invalid_value: { "": "expected %s at index %s of the stack, but got %s",