Skip to content

ActionJS

YukkuriC edited this page Sep 4, 2026 · 2 revisions

basis for all executables

  • customizable operate & operateInParens, with high degree-of-freedom return value handling:
    • an OperationResult like vanilla: directly accepted
    • a Mishap: throws it no matter it's returned or thrown
    • a list of things among:
      • Iota: append to current stack
      • number, bool, entity, Vec3, null, undefined...: auto converted to iota
      • SideEffect: append to side-effect list
      • SpellContinuation and/or EvalSound: overrides default
  • additional helper methods for creating operate varieties:
    • setOperateMutableStack: converts current stack to a mutable iota list, and modifies it freely
    • setOperateArgsSplit: auto-extracts first X iotas on stack top as ArgsJS object like most of vanilla spells
  • assigning mediaCost to auto-attach media-cost side effect on operate, with preCheckMedia methods to help check & mishap before logics

Examples

let { ActionJS } = HexJS
let {
    /* vanilla Hex API omitted */
} = HexJS.APIFlat

let action = new ActionJS()

1. basic method setter pair

let { ActionJS } = HexJS
let { /* vanilla Hex API omitted */ } = HexJS.APIFlat

let action = new ActionJS()

// outside parens
action.setOperate((env, image, cont) => {
    // returns `OperationResult` to override everything
    if (you_are_determined) return thePreparedOperationResult

    // shortcut 1: a list of stuff
    if (list_of_kaleido_scope) return [
        // iotas append to stack top
        NullIota(), DoubleIota(114514)

        // certain raw values wrapped automatically
        [GarbageIota(), EntityIota(env.castingEntity), [/* yes it can be nested */]] /* ListIota */,
        env.castingEntity /* EntityIota */, env.castingEntity.position() /* Vec3Iota */,
        1919810 /* DoubleIota */, null, undefined, // NullIota
        cont /* ContinuationIota (Jump) */, // ...

        // side effects added to list
        OperatorSideEffect.Particles(ParticleSpray.burst(env.castingEntity.position(), 1, 1))

        // casting sound and continuation for overrides (once at most each)
        HexEvalSounds.THOTH.get(),
        SpellContinuation.Done.INSTANCE // no auto-singleton sadly :(

        // invalid values: mishap for unsupported
    ]

    // shortcut 2: Mishap = throws it anyway
    let mishap = MishapEvalTooMuch()
    if (want_it_return) return mishap
    else if (want it throw) throw mishap

    // invalid values: mishap for unsupported
    if (its_invalid) return HexJS

    // no return or return undefined: end with no further side effects or stack additions
})
// inside parens
action.setOperateInParens((env, image, cont, myIota) => {
    // almost similar as above, only list differences here

    // returns `OperationResult` to override almost everything
    if (you_are_determined) return thePreparedOperationResult
    // returns `ParenthesizedOperationResult` to truly override everything
    if (you_are_super_determined) return thePreparedParenthesizedOperationResult

    // differences inside list
    if (list_of_kaleido_scope) return [
        // returning iotas here adds them to parenthesized list instead, same as below
        NullIota(), DoubleIota(114514)

        // returning prepared ParenthesizedIota is fine too
        ParenthesizedIota(NullIota(), false),
    ]
})

2. operate setter variants

action.setOperateMutableStack((stack, env, image, cont) => {
    // `stack` is the mutable whole stack from the image; doing modifications on it pass to the new image later
})
action.setOperateArgsSplit(argsCount, (args, env, image, cont) => {
    // splits an ArgsJS object of given argument counts
})

3. misc functions

action.mediaCost = 114514 // set to > 0 results in an additional `OperatorSideEffect.ConsumeMedia` during operates outside parens
action.preCheckMedia(env) // mishaps if CastEnv media < action's media cost
action.preCheckMedia(env, num) // mishaps if CastEnv media < given num

// it's also possible to pass handlers in constructors
let action2 = new ActionJS(funOperate)
let action3 = new ActionJS(funOperate, funOperateInParens)

4. NO SpellAction support

because there's no difference between the effort preparing the full SpellAction logic, and that preparing the very SpellAction$Result.
As an exchange, here's an example of creating an alternative pattern action who does everything a SpellAction does.

ActionRegistryJS.of(
    HexPattern.fromAnglesUnchecked('assassass', 'WEST'),
    'hexjsneo:spell_action_alternative',
).setOperateMutableStack((stack, env) => {
    let takeANum = new Args(stack, 1).double(0)

    // precheck and throw first
    spellAlternative.preCheckMedia(env, takeANum)
    // spellAlternative.mediaCost = takeANum // changing media cost for auto-add ConsumeMedia side effect, or...
    let ret = [OperatorSideEffect.ConsumeMedia(takeANum)] // side effects inside return array will be accepted
    ret.push(
        OperatorSideEffect.AttemptSpell(
            // due to some deep dark causes, JS objects can be converted to KT interfaces seamlessly
            {
                cast: envArg => {
                    env.castingEntity.tell(`env outside = ${env}; env inside = ${envArg}; cost = ${takeANum}`)
                },
            },
            true,
            true,
        ),
    )

    // add particle spray(s)
    ret.push(OperatorSideEffect.Particles(ParticleSpray.burst(env.castingEntity.position(), 1, 1)))

    // optional: override casting sound (1 at max, more will mishap)
    ret.push(HexEvalSounds.THOTH.get())

    // return all weak-typed override array altogether
    return ret
})

yes, everything about a SpellAction is the procedure of:

  • take arguments
  • pre-check media cost (and mishap)
  • sequential side effects of:
    • consume media
    • do spell
    • do particles
  • might change casting sound

and everything is just covered already, with even more abilities and freedom

Clone this wiki locally