Skip to content
Open
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 @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Internal

- The mod now uses Fabric Loom 1.9, Gradle 8.11, and Kotlin 2.0.20, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043).
- Added a method in `Action` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns, by Robotgiggle in [#1047](https://github.com/FallingColors/HexMod/pull/1047).
- Updated Inline dependency from 1.0.1 to 1.2.2, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043).

## `0.11.3` - 2025-11-22
Expand Down
5 changes: 2 additions & 3 deletions Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java
Comment thread
Robotgiggle marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ default String getSpecialHandlerI18nKey(ResourceKey<SpecialHandler.Factory<?>> a
return "hexcasting.special.%s".formatted(action.location().toString());
}

/**
* Currently introspection/retrospection/consideration are hardcoded, but at least their names won't be
*/
@Deprecated(since = "0.11.4")
default String getRawHookI18nKey(ResourceLocation name) {
return "hexcasting.rawhook.%s".formatted(name);
}
Expand All @@ -79,6 +77,7 @@ default Component getSpecialHandlerI18n(ResourceKey<SpecialHandler.Factory<?>> k
.withStyle(ChatFormatting.LIGHT_PURPLE);
}

@Deprecated(since = "0.11.4")
default Component getRawHookI18n(ResourceLocation name) {
return Component.translatable(getRawHookI18nKey(name)).withStyle(ChatFormatting.LIGHT_PURPLE);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package at.petrak.hexcasting.api.casting.castables

import at.petrak.hexcasting.api.casting.eval.CastResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage.ParenthesizedIota
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.math.HexPattern
import at.petrak.hexcasting.api.casting.mishaps.Mishap
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import net.minecraft.world.phys.Vec3
import java.text.DecimalFormat

Expand Down Expand Up @@ -41,6 +46,25 @@ interface Action {
continuation: SpellContinuation
): OperationResult

/**
* The behavior of this action when inside parentheses. By default, this is just to add the pattern
* to the parenthesized list without updating the op count or performing any of its usual effects.
*/
@Throws(Mishap::class)
fun operateInParens(
env: CastingEnvironment,
image: CastingImage,
continuation: SpellContinuation,
thisIota: Iota,
): Pair<OperationResult, ResolvedPatternType> {
return OperationResult(
image.withNewParenthesized(thisIota, false),
listOf(),
continuation,
HexEvalSounds.NORMAL_EXECUTE
) to ResolvedPatternType.ESCAPED
}

companion object {
// I see why vazkii did this: you can't raycast out to infinity!
const val RAYCAST_DISTANCE: Double = 32.0
Expand Down
Comment thread
Robotgiggle marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import at.petrak.hexcasting.api.casting.math.HexDir;
import at.petrak.hexcasting.api.casting.math.HexPattern;

@Deprecated(since = "0.11.4")
public final class SpecialPatterns {
public static final HexPattern INTROSPECTION = HexPattern.fromAngles("qqq", HexDir.WEST);
public static final HexPattern RETROSPECTION = HexPattern.fromAngles("eee", HexDir.EAST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ data class CastingImage private constructor(
*/
fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false)

/**
* Returns a copy of this with the provided iota added to the parenthetized list.
* [escaped] determines whether the iota should be considered escaped or not.
*/
fun withNewParenthesized(iota: Iota, escaped: Boolean): CastingImage {
val newParens = this.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, escaped))
return this.copy(parenthesized = newParens)
}

fun serializeToNbt() = NBTBuilder {
TAG_STACK %= stack.serializeToNBT()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,31 +109,38 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) {
try {
// TODO we can have a special intro/retro sound
// ALSO TODO need to add reader macro-style things
try {
this.handleParentheses(iota)?.let { (data, resolutionType) ->
return@executeInner CastResult(iota, continuation, data, listOf(), resolutionType, HexEvalSounds.NORMAL_EXECUTE)

// Handle single-iota escaping (ie via Consideration)
// This is here rather than in Iota since this behavior should not be overriden.
if (this.image.escapeNext) {
val newImage: CastingImage
if (this.image.parenCount > 0) {
// if we're inside parentheses, add the iota to the list with escaped set to true
val newParens = this.image.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, true))
newImage = this.image.copy(
escapeNext = false,
parenthesized = newParens
)
} else {
// if we're not in parentheses, just push the iota to the stack
val newStack = this.image.stack.toMutableList()
newStack.add(iota)
newImage = this.image.copy(
stack = newStack,
escapeNext = false,
)
}
} catch (e: MishapTooManyCloseParens) {
// This is ridiculous and needs to be fixed
return CastResult(
iota,
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
e,
Mishap.Context(
(iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST),
HexAPI.instance().getRawHookI18n(HexAPI.modLoc("close_paren"))
)
)
),
ResolvedPatternType.ERRORED,
HexEvalSounds.MISHAP
)
return CastResult(iota, continuation, newImage, listOf(), ResolvedPatternType.ESCAPED, HexEvalSounds.NORMAL_EXECUTE)
}

return iota.execute(this, world, continuation)
if (this.image.parenCount > 0) {
// Handle parens escaping
return iota.executeInParens(this, world, continuation)
} else {
// Handle normal execution behavior
return iota.execute(this, world, continuation)
}
} catch (exception: Exception) {
// This means something very bad has happened
exception.printStackTrace()
Expand Down Expand Up @@ -173,133 +180,6 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) {
return Pair(stackDescs, ravenmind)
}

/**
* Return a non-null value if we handled this in some sort of parenthesey way,
* either escaping it onto the stack or changing the parenthese-handling state.
*/
@Throws(MishapTooManyCloseParens::class)
private fun handleParentheses(iota: Iota): Pair<CastingImage, ResolvedPatternType>? {
val sig = (iota as? PatternIota)?.pattern?.angles

var displayDepth = this.image.parenCount

val out = if (displayDepth > 0) {
if (this.image.escapeNext) {
val newParens = this.image.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, true))
this.image.copy(
escapeNext = false,
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
} else {

when (sig) {
SpecialPatterns.CONSIDERATION.angles -> {
this.image.copy(
escapeNext = true,
) to ResolvedPatternType.EVALUATED
}

SpecialPatterns.EVANITION.angles -> {
val newParens = this.image.parenthesized.toMutableList()
val last = newParens.removeLastOrNull()
val newParenCount = this.image.parenCount + if (last == null || last.escaped || last.iota !is PatternIota) 0 else when (last.iota.pattern) {
SpecialPatterns.INTROSPECTION -> -1
SpecialPatterns.RETROSPECTION -> 1
else -> 0
}
this.image.copy(parenthesized = newParens, parenCount = newParenCount) to if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE
}

SpecialPatterns.INTROSPECTION.angles -> {
// we have escaped the parens onto the stack; we just also record our count.
val newParens = this.image.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, false))
this.image.copy(
parenthesized = newParens,
parenCount = this.image.parenCount + 1
) to if (this.image.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED
}

SpecialPatterns.RETROSPECTION.angles -> {
val newParenCount = this.image.parenCount - 1
displayDepth--
if (newParenCount == 0) {
val newStack = this.image.stack.toMutableList()
newStack.add(ListIota(this.image.parenthesized.toList().map { it.iota }))
this.image.copy(
stack = newStack,
parenCount = newParenCount,
parenthesized = listOf()
) to ResolvedPatternType.EVALUATED
} else if (newParenCount < 0) {
throw MishapTooManyCloseParens()
} else {
// we have this situation: "(()"
// we need to add the close paren
val newParens = this.image.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, false))
this.image.copy(
parenCount = newParenCount,
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
}
}

else -> {
val newParens = this.image.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, false))
this.image.copy(
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
}
}
}
} else if (this.image.escapeNext) {
val newStack = this.image.stack.toMutableList()
newStack.add(iota)
this.image.copy(
stack = newStack,
escapeNext = false,
) to ResolvedPatternType.ESCAPED
} else {
when (sig) {
SpecialPatterns.CONSIDERATION.angles -> {
this.image.copy(
escapeNext = true
) to ResolvedPatternType.EVALUATED
}

SpecialPatterns.INTROSPECTION.angles -> {
this.image.copy(
parenCount = this.image.parenCount + 1
) to ResolvedPatternType.EVALUATED
}

SpecialPatterns.RETROSPECTION.angles -> {
throw MishapTooManyCloseParens()
}

else -> {
null
}
}
}

// TODO: replace this once we can read things from the client
/*
if (out != null) {
val display = if (iota is PatternIota) {
PatternNameHelper.representationForPattern(iota.pattern)
.copy()
.withStyle(if (out.second == ResolvedPatternType.ESCAPED) ChatFormatting.YELLOW else ChatFormatting.AQUA)
} else iota.display()
displayPatternDebug(this.escapeNext, displayDepth, display)
}
*/
return out
}

data class TempControllerInfo(
var earlyExit: Boolean,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ protected Iota(@NotNull IotaType<?> type, @NotNull Object payload) {
abstract public @NotNull Tag serialize();

/**
* This method is called when this iota is executed (i.e. Hermes is run on a list containing it, unescaped).
* By default it will return a {@link CastResult} indicating an error has occurred.
* This method is called when this iota is directly executed (i.e. Hermes is run on a list containing it, unescaped).
* By default, it will return a {@link CastResult} indicating an error has occurred.
*/
public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a breaking change - any addon that implements custom iota execution (and also ContinuationIota) will now execute their unescaped behaviour inside of parentheses until patched. Likely we should do the same thing as with Action (ie add an executeInParens method).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! This has now been fixed.

return new CastResult(
Expand All @@ -67,6 +67,21 @@ protected Iota(@NotNull IotaType<?> type, @NotNull Object payload) {
HexEvalSounds.MISHAP);
}

/**
* This method is called when this iota is executed inside parentheses (i.e. Hermes is run on a list containing Introspection, this, Retrospection).
* By default, the iota will be added to the in-progress parenthesized list instead of causing a mishap.
* This is specifically for parentheses-based escaping, Consideration escaping is handled in {@link CastingVM#executeInner}.
*/
public @NotNull CastResult executeInParens(CastingVM vm, ServerLevel world, SpellContinuation continuation) {
return new CastResult(
this,
continuation,
vm.getImage().withNewParenthesized(this, false),
List.of(),
ResolvedPatternType.ESCAPED,
HexEvalSounds.NORMAL_EXECUTE);
}

/**
* Returns whether this iota is possible to execute (i.e. whether {@link Iota#execute} has been overridden.
*/
Expand Down
Loading
Loading