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
5 changes: 5 additions & 0 deletions CHANGELOG.md
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.

Should we also document that Hasty Retrospection was renamed to Absent Introspection?

Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Added a config toggle (default false) for the effect that makes iota-holding items display their entire NBT data when Advanced Tooltips is enabled ([#1021](https://github.com/FallingColors/HexMod/pull/1021)) @Robotgiggle
- Added a pattern display overlay for pattern-holding items (ie scrolls or slates) while holding shift in the inventory ([#879](https://github.com/FallingColors/HexMod/pull/879)) @SamsTheNerd
- Added connected textures for Akashic Ligatures when using Continuity or Optifine ([#885](https://github.com/FallingColors/HexMod/pull/885)) @kineticneticat
- Added the Overeager Evanition mishap when trying to use Evanition with nothing to undo ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle

### Changed

- Changed the `media_consumption` attribute to only apply to player-based casting ([#987](https://github.com/FallingColors/HexMod/pull/987)) @Robotgiggle
- Changed Wayfarer's Flight and Anchorite's Flight to both cost 2 dust per unit, and enforced a minimum cost for Anchorite's Flight ([#1040](https://github.com/FallingColors/HexMod/pull/1040)) @Robotgiggle
- Changed the pattern limit to also include execution of non-pattern iotas like jumps ([#1035](https://github.com/FallingColors/HexMod/pull/1035)) @pythonmcpi
- Changed the Hasty Retrospection mishap to Absent Introspection as it's now used for anything that only works while parenthesized ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle
- Updated the Flay Mind recipe display in EMI and JEI to cycle through all valid entities if the input is an entity tag ([#1023](https://github.com/FallingColors/HexMod/pull/1023)) @YukkuriC
- Re-implemented the ability to extract stored media from items in trinket/curio slots ([#996](https://github.com/FallingColors/HexMod/pull/996)) @YukkuriC
- Patterns involving entity look direction now compensate for the vanilla bug that causes projectiles and phantoms to report the wrong direction ([#1025](https://github.com/FallingColors/HexMod/pull/1025)) @Robotgiggle
Expand All @@ -43,13 +45,16 @@ 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).
- Updated Inline dependency from 1.0.1 to 1.2.2, by Robotgiggle in [#1043](https://github.com/FallingColors/HexMod/pull/1043).
- The mod now uses Fabric Loom 1.9, Gradle 8.11, and Kotlin 2.0.20 ([#1043](https://github.com/FallingColors/HexMod/pull/1043)) @Robotgiggle
- Changed the internal implementation of Thoth's Gambit to use a `TreeList` for more efficiency ([#1031](https://github.com/FallingColors/HexMod/pull/1031)) @s5bug
- Deprecated the version of `matchPattern` that takes a boolean argument since it always raises an exception ([#1002](https://github.com/FallingColors/HexMod/pull/1002)) @beholderface
- `Iota` subclasses can now specify whether the iota should be displayed with or without commas when in a list ([#988](https://github.com/FallingColors/HexMod/pull/988)) @TheRobbie73
- Made the `getUsableStacks` and `getPrimaryStacks` methods in `CastingEnvironment` and its subclasses public ([#907](https://github.com/FallingColors/HexMod/pull/907)) @miyucomics
- Improved handling for duplicate pattern signatures in hexdoc ([#1007](https://github.com/FallingColors/HexMod/pull/1007)) @object-Object
- `CircleExecutionState` now stores the shape of the spell circle using two corners rather than an entire list of positions ([#908](https://github.com/FallingColors/HexMod/pull/908)) @Stick404
- Added methods in `Action` and `Iota` to define behavior when inside parentheses, and de-hardcoded the iota-escaping patterns ([#1047](https://github.com/FallingColors/HexMod/pull/1047)) @Robotgiggle

## `0.11.3` - 2025-11-22

Expand Down
7 changes: 6 additions & 1 deletion 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 @@ -63,8 +63,9 @@ default String getSpecialHandlerI18nKey(ResourceKey<SpecialHandler.Factory<?>> a
}

/**
* Currently introspection/retrospection/consideration are hardcoded, but at least their names won't be
* @deprecated Used to translate the names of patterns outside the standard Action system, none of which exist anymore.
*/
@Deprecated(since = "0.11.4")
Comment thread
Robotgiggle marked this conversation as resolved.
default String getRawHookI18nKey(ResourceLocation name) {
return "hexcasting.rawhook.%s".formatted(name);
}
Expand All @@ -79,6 +80,10 @@ default Component getSpecialHandlerI18n(ResourceKey<SpecialHandler.Factory<?>> k
.withStyle(ChatFormatting.LIGHT_PURPLE);
}

/**
* @deprecated Used to translate the names of patterns outside the standard Action system, none of which exist anymore.
*/
@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,17 @@
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.ParenthesizedOperationResult
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 +47,30 @@ interface Action {
continuation: SpellContinuation
): OperationResult

/**
* The behavior of this action when inside parentheses (meaning `image.parenCount` will always be greater than 0).
* By default, this just adds the pattern to the parenthesized list without updating the op count or performing any of its effects.
*
* Note that behavior defined here can throw mishaps as usual, such as when [OpUndo][at.petrak.hexcasting.common.casting.actions.escaping.OpUndo]
* tries to remove a pattern but there aren't any left. Mishapping here will not affect the paren count, so the caster will still
* be in list-building mode after the mishap resolves.
*/
Comment thread
Robotgiggle marked this conversation as resolved.
@Throws(Mishap::class)
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.

Hmm, is it really a good idea to allow addons to throw mishaps while parenthesized? Do we handle this correctly? I don't think any base hex pattern currently does this.

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.

Throwing mishaps while parenthesized will be necessary to address the Evanition issue you mentioned above, along with a plan I have for a read-from-offhand-into-parens pattern. Also, I see no reason why allowing addons to do that would be problematic - imo it's better to natively allow interesting functionality like that rather than forcing people to make their own workarounds.

As for handling, any thrown mishap will be caught and handled by the try/catch in Iota.lookupAndOperate, exactly like a mishap thrown from the normal Action.operate method.

fun operateInParens(
env: CastingEnvironment,
image: CastingImage,
continuation: SpellContinuation,
thisIota: Iota,
): ParenthesizedOperationResult {
return ParenthesizedOperationResult(
image.withNewParenthesized(thisIota),
listOf(),
continuation,
HexEvalSounds.NORMAL_EXECUTE,
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,30 @@ import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation

interface IOperationResult {
val newImage: CastingImage
val sideEffects: List<OperatorSideEffect>
val newContinuation: SpellContinuation
val sound: EvalSound
}

/**
* What happens when an operator is through?
*/
data class OperationResult(
val newImage: CastingImage,
val sideEffects: List<OperatorSideEffect>,
val newContinuation: SpellContinuation,
val sound: EvalSound,
)
override val newImage: CastingImage,
override val sideEffects: List<OperatorSideEffect>,
override val newContinuation: SpellContinuation,
override val sound: EvalSound,
) : IOperationResult

/**
* What happens when an operator is through while parenthesized?
*/
data class ParenthesizedOperationResult(
override val newImage: CastingImage,
override val sideEffects: List<OperatorSideEffect>,
override val newContinuation: SpellContinuation,
override val sound: EvalSound,
val resolutionType: ResolvedPatternType,
) : IOperationResult
Comment thread
Robotgiggle marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import at.petrak.hexcasting.api.casting.math.HexDir;
import at.petrak.hexcasting.api.casting.math.HexPattern;

/**
* @deprecated Stores angle signatures for non-Action patterns, all of which have since been made into Actions.
*/
@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 @@ -28,6 +28,10 @@ data class CastingImage private constructor(
) {
constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag())

/**
* `escaped` is used by [OpUndo][at.petrak.hexcasting.common.casting.actions.escaping.OpUndo] to determine whether the paren count
* needs to be adjusted when undoing an open or close paren pattern (if the pattern was escaped, no need to change anything).
*/
data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) {
companion object {
const val TAG_IOTAS = "iotas"
Expand Down Expand Up @@ -72,6 +76,15 @@ 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 parenthesized list.
*/
fun withNewParenthesized(iota: Iota): CastingImage {
val newParens = this.parenthesized.toMutableList()
newParens.add(ParenthesizedIota(iota, false))
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
Loading
Loading