lib/control-flow.js

/**
 * @module control-flow
 */
/**
* Creates a spawn trigger and returns it
* @param {group} group group to be spawned
* @param {number} time delay to spawn group
* @returns {object}
*/
let spawn_trigger = (group, time = 0) => {
    return trigger({
        OBJ_ID: 1268,
        SPAWN_DURATION: time,
        TARGET: group,
    });
};

let frame_loop_setup = () => {
    let empty_tfn = trigger_function(_ => {});
    let trig_fn = trigger_function(() => {
        let move_g = unknown_g();
        let frame_loop1 = unknown_b();
        let frame_loop2 = unknown_b();
        // col 1
        $.add(object({
            OBJ_ID: obj_ids.special.COLLISION_BLOCK,
            BLOCK_A: frame_loop1,
            X: -150,
            Y: 15,
            DYNAMIC_BLOCK: true
        }));
        // col 2
        $.add(object({
            OBJ_ID: obj_ids.special.COLLISION_BLOCK,
            BLOCK_A: frame_loop2,
            X: -150,
            Y: 50,
            GROUPS: move_g,
            DYNAMIC_BLOCK: true
        }));
        on(collision(frame_loop1, frame_loop2), trigger_function(() => {
            move_g.toggle_off();
            empty_tfn.call();
        }));
        on(collision_exit(frame_loop1, frame_loop2), trigger_function(() => {
            move_g.toggle_on();
            empty_tfn.call();
        }));
        move_g.move(0, -10); // start loop
    });
    trig_fn.call();
    return { trig_fn, empty_tfn };
};


let fl_setup;
/**
 * Creates a loop that repeats every tick
 * @param {group} trigger_function The group to call every tick
 * @returns {group} Group that can be used to stop the loop
 */
let frame_loop = (tfn) => {
    if (!fl_setup) fl_setup = frame_loop_setup();
    $.extend_trigger_func(fl_setup.empty_tfn, () => {
        tfn.call();
    });
    return fl_setup.trig_fn;
};

/**
 * Waits a specific amount of ticks
 * @param {number} frames How many ticks to wait for
 */
let frames = (frames) => {
    let id = crypto.randomUUID();
    let oldContext = Context.current;
    let newContext = new Context(id);
  
    let frame_c = counter();
    let loop = frame_loop(trigger_function(() => {
        frame_c.add(1);
    }));
    on(count(frame_c.item, frames), trigger_function(() => {
        loop.stop();
        newContext.group.call();
    }));
    Context.set(id);
    Context.link(oldContext);
  }

let rfl_setup = () => {
    let enter_group = trigger_function(() => {});
    let blockA = unknown_b();
    let blockB = unknown_b();

    let center = unknown_g();
    let target = unknown_g();
    // collision blocks
    blockA.collision_block(-135, 135).with(obj_props.SCALING, 0.25).with(obj_props.GROUPS, target).add();
    blockB.collision_block(-135, 105).with(obj_props.SCALING, 0.25).with(obj_props.GROUPS, center).with(obj_props.DYNAMIC_BLOCK, true).add();
    // center
    object({
        OBJ_ID: 3807,
        X: -135,
        Y: 105,
        GROUPS: center,
        GROUP_PARENTS: center
    }).add();

    let area = trigger({
        OBJ_ID: 3006,
        155: 1,
        36: 1,
        222: 30000,
        218: -30000,
        243: 2,
        249: 2,
        287: 1,
        TARGET: target,
        CENTER: center,
        263: 1,
        264: 1,
        276: 1,
        10: 0.5
    });
    area.add();
    let move_loop = trigger_function(() => {
        move_trigger(center, -2400, 0).with(obj_props.SILENT, true).add();
        ignore_context_change(() => center.move(2400, 0, 1));
        $.trigger_fn_context().call(1);
    });

    let timer_cycle = trigger_function(() => {
        timer(0, 1, $.trigger_fn_context(), false, false, true, 240).start();
        blockA.if_colliding(blockB, enter_group);
    });
    move_loop.call();
    timer_cycle.call();
    return { enter_group, timer_cycle };
}

let g_setup;

/**
 * Creates a loop that repeats every render frame (different from ticks, which are a constant of 1/240 seconds, while render frames are variable and can be changed in settings)
 * @param {group} trigger_function The group to call every frame
 * @returns {group} Group that can be used to stop the loop
 */
let render_frame_loop = (fn) => {
    if (!g_setup) g_setup = rfl_setup();
    $.extend_trigger_func(g_setup.enter_group, () => {
        fn.call();
    });
    return g_setup.timer_cycle;
}
/**
 * Waits a specific amount of render frames
 * @param {number} frames How many frames to wait for
 */
let render_frames = (frames) => {
    let id = crypto.randomUUID();
    let oldContext = Context.current;
    let newContext = new Context(id);

    let frame_c = counter();
    frame_c.display(135, 75);
    let loop = render_frame_loop(trigger_function(() => {
        frame_c.add(1);
    }));
    on(count(frame_c.item, frames), trigger_function(() => {
        loop.stop();
        newContext.group.call();
    }));
    Context.set(id);
    Context.link(oldContext);
}
  
/**
 * Returns a greater than condition
 * @param {counter} counter Counter to compare to number
 * @param {number} other Number to be compared to counter
 * @returns {condition}
 */
let greater_than = (count, other) => ({
    count,
    comparison: GREATER,
    other,
});
/**
 * Returns a equal to condition
 * @param {counter} counter Counter to compare to number
 * @param {number} other Number to be compared to counter
 * @returns {condition}
 */
let equal_to = (count, other) => ({ count, comparison: EQ, other });
/**
 * Returns a less than condition
 * @param {counter} counter Counter to compare to number
 * @param {number} other Number to be compared to counter
 * @returns {condition}
 */
let less_than = (count, other) => ({ count, comparison: LESS, other });

/**
 * Calls a group with a delay
 * @param {number} delay How much to delay by
 * @param {group} group Group to call
 */
let call_with_delay = (time, func) => {
    $.add(trigger({
        OBJ_ID: 1268,
        SPAWN_DURATION: time,
        TARGET: func,
    }));
};

/**
* Implementation of sequence trigger
* @param {array} sequence Sequence of groups to be called (e.g. [[group(1), 1], [group(2), 1]] is a valid input)
* @param {number} [mode=0] Mode of sequence trigger (0 = stop, 1 = loop, 2 = last)
* @param {number} [min_int=0] MinInt of sequence trigger
* @param {number} [reset=0] Reset of sequence trigger (0 = full, 1 = step)
* @returns {function} Function that steps through the sequence once
*/
let sequence = (sequence, mode = 0, min_int = 0, reset = 0) => {
    let seq_gr = trigger_function(() => {
        $.add(trigger({
            OBJ_ID: 3607,
            SEQUENCE: sequence.map(x => x[0].value + '.' + x[1]).join('.'),
            MIN_INT: min_int,
            RESET: reset,
            MODE: mode
        }));
    });
    return () => seq_gr.call()
};

/**
 * Creates trigger function-like systems, but can be called normally with item IDs as arguments (e.g. a remappable can be called like `my_remappable(counter1.item)`)
 * @param {function} fn Function that remappable uses
 * @returns {function} Function to call
 */
let remappable = (fn) => {
    let args_arr = Array(fn.length).fill(0).map((_, i) => i);
    let r = trigger_function(() => fn(...args_arr));
    return (...args) => {
        // remap fn_args to args
        let rmps = [];
        args.forEach((x, i) => rmps.push([args_arr[i], args[i]]));
        r.remap(...rmps).call();
    };
}

/**
 * Loops a function a specific amount of times (defined by range)
 * @param {array} range Range of numbers defining how many times to loop fn by
 * @param {function} fn Function to loop
 * @param {number} [delay=0.05] How much to delay between cycle
 */
let for_loop = (rang, fn, delay = 0.05) => {
    let c = counter(rang[0]);
    while_loop(less_than(c, rang[rang.length - 1] + 1), () => {
        fn();
        c.add(1);
    }, delay);
};

module.exports = { remappable, sequence, call_with_delay, equal_to, less_than, greater_than, for_loop, spawn_trigger, frame_loop, frames, render_frame_loop, render_frames }