TurboWarp / extensions

User-contributed unsandboxed extension gallery for TurboWarp
https://extensions.turbowarp.org/
MIT License
114 stars 236 forks source link

Scratch.vm.runtime.on("BEFORE_EXECUTE") not running in the editor #1022

Closed SDFTDusername closed 1 year ago

SDFTDusername commented 1 year ago

i'm making an extension that runs code every frame by running a function when "BEFORE_EXECUTE" is emitted from Scratch.vm.runtime, but it only works when it's packaged (for example a HTML file) and not in the editor. but extensions like DeltaTime does work in the editor while also listening for "BEFORE_EXECUTE"

LilyMakesThings commented 1 year ago

Scratch.vm.on("BEFORE_EXECUTE") is what I used (no runtime). Have a look at animated text or Skins for examples on how to use it.

SDFTDusername commented 1 year ago

Scratch.vm.on("BEFORE_EXECUTE") is what I used (no runtime). Have a look at animated text or Skins for examples on how to use it.

i used that and I already looked at an extension to see how to use it

LilyMakesThings commented 1 year ago

Scratch.vm.on("BEFORE_EXECUTE") is what I used (no runtime). Have a look at animated text or Skins for examples on how to use it.

i used that and I already looked at an extension to see how to use it

Please show us some code then, since you haven't described anything different for it to not work

LilyMakesThings commented 1 year ago

Also, I mentioned Animated Text and Skins because I have worked on those extensions directly and have seen first-hand that they work.

SDFTDusername commented 1 year ago

Scratch.vm.on("BEFORE_EXECUTE") is what I used (no runtime). Have a look at animated text or Skins for examples on how to use it.

i used that and I already looked at an extension to see how to use it

Please show us some code then, since you haven't described anything different for it to not work

Scratch.vm.on("BEFORE_EXECUTE", () => {
        vm.runtime.targets.forEach((target) => {
            if (target[anchor_updateAnchorEveryFrame]) {
                _updateAnchor({ target: target });
            }
        });
    });

I have tried using vm.runtime and without runtime, _updateAnchor also successfully runs when run via blocks

GarboMuffin commented 1 year ago

BEFORE_EXECUTE works fine, as noted previously. It's your code that's the problem - try debugging it. Look for errors, add console.log, debugger, etc.

we try to be helpful here but we also are not your personal javascript assistants

GarboMuffin commented 1 year ago

BEFORE_EXECUTE is also only on vm.runtime, not on vm itself, so at least the code sample you give is expected to not work

GarboMuffin commented 1 year ago

I would guess the issue is that target[anchor_updateAnchorEveryFrame] throws a reference error because you probably meant target.anchor_updateAnchorEveryFrame and anchor_updateAnchorEveryFrame is not defined

SDFTDusername commented 1 year ago

I would guess the issue is that target[anchor_updateAnchorEveryFrame] throws a reference error because you probably meant target.anchor_updateAnchorEveryFrame and anchor_updateAnchorEveryFrame is not defined

there's no errors in the console, when I also put a console log at the begin of the BEFORE_EXECUTE function, it logs nothing in the console

GarboMuffin commented 1 year ago

Do you see anything when you open a new tab and just run vm.runtime.on("BEFORE_EXECUTE", () => { console.log("test"); }) in the editor using the console? no extensions or anything, just run that

SDFTDusername commented 1 year ago

vm.runtime.on("BEFORE_EXECUTE", () => { console.log("test"); })

after typing that in the console, it prints "test" every frame just fine. in the extension I have the function outside of the class but inside the (function(Scratch) { 'use strict';, turbowarp is also getting my extension from localhost 8000

SDFTDusername commented 1 year ago

vm.runtime.on("BEFORE_EXECUTE", () => { console.log("test"); })

after typing that in the console, it prints "test" every frame just fine. in the extension I have the function outside of the class but inside the (function(Scratch) { 'use strict';, turbowarp is also getting my extension from localhost 8000

oh, it just started working again. but now it's unable to run any blocks and it just stays stuck on a yellow glow image

GarboMuffin commented 1 year ago

post your entire extension or we can't help you

SDFTDusername commented 1 year ago

post your entire extension or we can't help you

also it doesn't stay stuck on the yellow glow and works only if _updateAnchor does return and the condition target[anchor_position] == 'none' is met

// Name: Anchor
// ID: anchor
// Descriptions: Anchors sprites to screen positions

(function(Scratch) {
    'use strict';

    if (!Scratch.extensions.unsandboxed) {
        throw new Error("Anchor must be run unsandboxed");
    }

    const anchor_position = Symbol('anchor.position');

    const anchor_offset_x = Symbol('anchor.offset.x');
    const anchor_offset_y = Symbol('anchor.offset.y');

    const anchor_updateAnchorEveryBlock = Symbol('anchor.updateAnchorEveryBlock');
    const anchor_updateAnchorEveryFrame = Symbol('anchor.updateAnchorEveryFrame');

    const anchor_resolution = Symbol('anchor.resolution');
    const anchor_retreat = Symbol('anchor.retreat');

    const vm = Scratch.vm;

    /**
    * @param {VM.RenderedTarget} target
    * @param {VM.RenderedTarget} [originalTarget] If target is a clone, the original to copy from.
    */
    const implementAnchorForTarget = (target, originalTarget) => {
        if (anchor_position in target) {
            return;
        }

        target[anchor_position] = originalTarget ? originalTarget[anchor_position] : 'none';

        target[anchor_offset_x] = originalTarget ? originalTarget[anchor_offset_x] : 0;
        target[anchor_offset_y] = originalTarget ? originalTarget[anchor_offset_y] : 0;

        target[anchor_updateAnchorEveryBlock] = originalTarget ? originalTarget[anchor_updateAnchorEveryBlock] : false;
        target[anchor_updateAnchorEveryFrame] = originalTarget ? originalTarget[anchor_updateAnchorEveryFrame] : true;

        target[anchor_resolution] = originalTarget ? originalTarget[anchor_resolution] : 1;
        target[anchor_retreat] = originalTarget ? originalTarget[anchor_retreat] : true;
    };

    vm.runtime.targets.forEach((target) => implementAnchorForTarget(target));

    vm.runtime.on("targetWasCreated", (target, originalTarget) =>
        implementAnchorForTarget(target, originalTarget)
    );

    vm.runtime.on("PROJECT_LOADED", () => {
        vm.runtime.targets.forEach((target) => implementAnchorForTarget(target));
    });

    vm.runtime.on("BEFORE_EXECUTE", () => {
        vm.runtime.targets.forEach((target) => {
            if (target[anchor_updateAnchorEveryFrame]) {
                _updateAnchor({ target: target });
            }
        });
    });

    const _touchingEdge = (target, axis) => {
        if (target.renderer) {
            const stageWidth = vm.runtime.stageWidth;
            const stageHeight = vm.runtime.stageHeight;

            const bounds = target.getBounds();

            if (axis.toLowerCase() == "x") {
                return bounds.left < -stageWidth / 2 || bounds.right > stageWidth / 2;
            }

            if (axis.toLowerCase() == "y") {
                return bounds.top > stageHeight / 2 || bounds.bottom < -stageHeight / 2;
            }
        }

        return false;
    };

    const _move = (target, info, x, y, resolution = 1, retreat = true) => {
        const maxAttempts = (info.size.x + info.size.y) * resolution;
        let attempts = maxAttempts;

        /*
        while (target.isTouchingObject('_edge_') && attempts > 0) {
            target.setXY(target.x + x / resolution, target.y + y / resolution);
            attempts -= 1;
        }
        */

        if (x != 0) {
            attempts = maxAttempts;

            while (_touchingEdge(target, "x") && attempts > 0) {
                target.setXY(target.x + x / resolution, target.y);
                attempts -= 1;
            }

            if (retreat) {
                target.setXY(target.x - x / resolution, target.y);
            }
        }

        if (y != 0) {
            attempts = maxAttempts;

            while (_touchingEdge(target, "y") && attempts > 0) {
                target.setXY(target.x, target.y + y / resolution);
                attempts -= 1;
            }

            if (retreat) {
                target.setXY(target.x, target.y - y / resolution);
            }
        }

        if (x != 0) {
            attempts = maxAttempts;

            while (!_touchingEdge(target, "x") && attempts > 0) {
                target.setXY(target.x - x / resolution, target.y);
                attempts -= 1;
            }

            if (retreat) {
                target.setXY(target.x + x / resolution, target.y);
            }
        }

        if (y != 0) {
            attempts = maxAttempts;

            while (!_touchingEdge(target, "y") && attempts > 0) {
                target.setXY(target.x, target.y - y / resolution);
                attempts -= 1;
            }

            if (retreat) {
                target.setXY(target.x, target.y + y / resolution);
            }
        }
    };

    const _updateAnchor = (utils) => {
        const target = utils.target;

        if (target[anchor_position] == 'none') {
            return;
        }

        const renderedInfo = target._getRenderedDirectionAndScale();

        const scale = renderedInfo.scale;
        const direction = renderedInfo.direction;

        const costumes = target.sprite.costumes_;
        const costume_number = target.currentCostume;

        var info = {
            position: {
                x: target.x,
                y: target.y
            },
            size: {
                x: costumes[costume_number].size[0] * (scale[0] / 100),
                y: costumes[costume_number].size[1] * (scale[1] / 100)
            }
        }

        switch (target[anchor_position]) {
            case 'top left':
                target.setXY(
                    vm.runtime.stageWidth / -2,
                    vm.runtime.stageHeight / 2
                );

                _move(target, info, 1, -1, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'top center':
                target.setXY(
                    0,
                    vm.runtime.stageHeight / 2
                );

                _move(target, info, 0, -1, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'top right':
                target.setXY(
                    vm.runtime.stageWidth / 2,
                    vm.runtime.stageHeight / 2
                );

                _move(target, info, -1, -1, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'middle left':
                target.setXY(
                    vm.runtime.stageWidth / -2,
                    0
                );

                _move(target, info, 1, 0, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'middle center':
                target.setXY(0, 0);
                break;
            case 'middle right':
                target.setXY(
                    vm.runtime.stageWidth / 2,
                    0
                );

                _move(target, info, -1, 0, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'bottom left':
                target.setXY(
                    vm.runtime.stageWidth / -2,
                    vm.runtime.stageHeight / -2
                );

                _move(target, info, 1, 1, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'bottom center':
                target.setXY(
                    0,
                    vm.runtime.stageHeight / -2
                );

                _move(target, info, 0, 1, target[anchor_resolution], target[anchor_retreat]);

                break;
            case 'bottom right':
                target.setXY(
                    vm.runtime.stageWidth / 2,
                    vm.runtime.stageHeight / -2
                );

                _move(target, info, -1, 1, target[anchor_resolution], target[anchor_retreat]);

                break;
            default:
                break;
        }

        target.setXY(
            target.x + target[anchor_offset_x],
            target.y + target[anchor_offset_y]
        );
    };

    class Anchor {
        getInfo () {
            return {
                id: 'anchor',
                name: 'Anchor',
                color1: '#7b99a0',
                color2: '#6c8990',
                color3: '#51676c',
                blocks: [
                    {
                        opcode: 'label1',
                        blockType: Scratch.BlockType.LABEL,
                        text: 'Every sprite has its'
                    },
                    {
                        opcode: 'label2',
                        blockType: Scratch.BlockType.LABEL,
                        text: 'own anchor configurations.'
                    },
                    '---',
                    {
                        opcode: 'setPosition',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set anchor position to [POSITION]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            POSITION: {
                                type: Scratch.ArgumentType.STRING,
                                menu: 'POSITION'
                            }
                        }
                    },
                    {
                        opcode: 'setOffset',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set ancher offset to x: [OFFSET_X] y: [OFFSET_Y]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            OFFSET_X: {
                                type: Scratch.ArgumentType.NUMBER
                            },
                            OFFSET_Y: {
                                type: Scratch.ArgumentType.NUMBER
                            }
                        }
                    },
                    {
                        opcode: 'setPositionAndOffset',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set anchor position to [POSITION] with offset x: [OFFSET_X] y: [OFFSET_Y]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            POSITION: {
                                type: Scratch.ArgumentType.STRING,
                                menu: 'POSITION'
                            },
                            OFFSET_X: {
                                type: Scratch.ArgumentType.NUMBER
                            },
                            OFFSET_Y: {
                                type: Scratch.ArgumentType.NUMBER
                            }
                        }
                    },
                    '---',
                    {
                        opcode: 'setOffsetX',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set ancher offset x to [OFFSET_X]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            OFFSET_X: {
                                type: Scratch.ArgumentType.NUMBER
                            }
                        }
                    },
                    {
                        opcode: 'setOffsetY',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set ancher offset y to [OFFSET_Y]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            OFFSET_Y: {
                                type: Scratch.ArgumentType.NUMBER
                            }
                        }
                    },
                    '---',
                    {
                        opcode: 'changeOffsetXby',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'change ancher offset x by [CHANGE_OFFSET_X_BY]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            CHANGE_OFFSET_X_BY: {
                                type: Scratch.ArgumentType.NUMBER,
                                defaultValue: 10
                            }
                        }
                    },
                    {
                        opcode: 'changeOffsetYby',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'change ancher offset y by [CHANGE_OFFSET_Y_BY]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            CHANGE_OFFSET_Y_BY: {
                                type: Scratch.ArgumentType.NUMBER,
                                defaultValue: 10
                            }
                        }
                    },
                    '---',
                    {
                        opcode: 'getPosition',
                        blockType: Scratch.BlockType.REPORTER,
                        text: 'anchor position',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    },
                    '---',
                    {
                        opcode: 'getOffsetX',
                        blockType: Scratch.BlockType.REPORTER,
                        text: 'anchor offset x',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    },
                    {
                        opcode: 'getOffsetY',
                        blockType: Scratch.BlockType.REPORTER,
                        text: 'anchor offset y',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    },
                    '---',
                    {
                        opcode: 'setUpdateAnchorEveryBlock',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set update anchor every block to [UPDATE_ANCHOR_EVERY_BLOCK]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            UPDATE_ANCHOR_EVERY_BLOCK: {
                                type: Scratch.ArgumentType.STRING,
                                defaultValue: "disabled",
                                menu: 'UPDATE_ANCHOR_EVERY'
                            }
                        }
                    },
                    {
                        opcode: 'getUpdateAnchorEveryBlock',
                        blockType: Scratch.BlockType.BOOLEAN,
                        text: 'update anchor every block?',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    },
                    '---',
                    {
                        opcode: 'setUpdateAnchorEveryFrame',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set update anchor every frame to [UPDATE_ANCHOR_EVERY_FRAME]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            UPDATE_ANCHOR_EVERY_FRAME: {
                                type: Scratch.ArgumentType.STRING,
                                menu: 'UPDATE_ANCHOR_EVERY'
                            }
                        }
                    },
                    {
                        opcode: 'getUpdateAnchorEveryFrame',
                        blockType: Scratch.BlockType.BOOLEAN,
                        text: 'update anchor every frame?',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    },
                    '---',
                    {
                        opcode: 'updateAnchor',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'update anchor',
                        filter: [Scratch.TargetType.SPRITE]
                    },
                    '---',
                    {
                        opcode: 'setResolution',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set anchor resolution to [ANCHOR_RESOLUTION]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            ANCHOR_RESOLUTION: {
                                type: Scratch.ArgumentType.NUMBER,
                                defaultValue: 1
                            }
                        }
                    },
                    {
                        opcode: 'getResolution',
                        blockType: Scratch.BlockType.REPORTER,
                        text: 'get anchor resolution',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    },
                    '---',
                    {
                        opcode: 'setRetreat',
                        blockType: Scratch.BlockType.COMMAND,
                        text: 'set anchor retreat to [ANCHOR_RETREAT]',
                        filter: [Scratch.TargetType.SPRITE],
                        arguments: {
                            ANCHOR_RETREAT: {
                                type: Scratch.ArgumentType.STRING,
                                menu: "TRUE_FALSE"
                            }
                        }
                    },
                    {
                        opcode: 'getRetreat',
                        blockType: Scratch.BlockType.BOOLEAN,
                        text: 'get anchor retreat',
                        filter: [Scratch.TargetType.SPRITE],
                        disableMonitor: true
                    }
                ],
                menus: {
                    POSITION: {
                        acceptReporters: true,
                        items: ['top left', 'top center', 'top right', 'middle left', 'middle center', 'middle right', 'bottom left', 'bottom center', 'bottom right', 'none']
                    },
                    UPDATE_ANCHOR_EVERY: {
                        acceptReporters: true,
                        items: ['enabled', 'disabled']
                    },
                    TRUE_FALSE: {
                        acceptReporters: true,
                        items: ['true', 'false']
                    }
                }
            };
        }

        setPosition(args, utils) {
            utils.target[anchor_position] = args.POSITION;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        setOffset(args, utils) {
            utils.target[anchor_offset_x] = args.OFFSET_X;
            utils.target[anchor_offset_y] = args.OFFSET_Y;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        setPositionAndOffset(args, utils) {
            utils.target[anchor_position] = args.POSITION;

            utils.target[anchor_offset_x] = OFFSET_X;
            utils.target[anchor_offset_y] = args.args.OFFSET_Y;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        setOffsetX(args, utils) {
            utils.target[anchor_offset_x] = args.OFFSET_X;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        setOffsetY(args, utils) {
            utils.target[anchor_offset_y] = args.OFFSET_Y;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }
        changeOffsetXby(args, utils) {
            utils.target[anchor_offset_x] += args.CHANGE_OFFSET_X_BY;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        changeOffsetYby(args, utils) {
            utils.target[anchor_offset_y] += args.CHANGE_OFFSET_Y_BY;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        getPosition(args, utils) {
            return utils.target[anchor_position];
        }

        getOffsetX(args, utils) {
            return utils.target[anchor_offset_x];
        }

        getOffsetY(args, utils) {
            return utils.target[anchor_offset_y];
        }

        setUpdateAnchorEveryBlock(args, utils) {
            args.UPDATE_ANCHOR_EVERY_BLOCK = Scratch.Cast.toString(args.UPDATE_ANCHOR_EVERY_BLOCK).toLowerCase();

            utils.target[anchor_updateAnchorEveryBlock] = args.UPDATE_ANCHOR_EVERY_BLOCK == 'enabled' ||
                args.UPDATE_ANCHOR_EVERY_BLOCK == 'true';

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        getUpdateAnchorEveryBlock(args, utils) {
            return utils.target[anchor_updateAnchorEveryBlock];
        }

        setUpdateAnchorEveryFrame(args, utils) {
            args.UPDATE_ANCHOR_EVERY_FRAME = Scratch.Cast.toString(args.UPDATE_ANCHOR_EVERY_FRAME).toLowerCase();

            utils.target[anchor_updateAnchorEveryFrame] = args.UPDATE_ANCHOR_EVERY_FRAME == 'enabled' ||
                args.UPDATE_ANCHOR_EVERY_FRAME == 'true';

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        getUpdateAnchorEveryFrame(args, utils) {
            return utils.target[anchor_updateAnchorEveryFrame];
        }

        updateAnchor(args, utils) {
            _updateAnchor(utils);
        }

        setResolution(args, utils) {
            args.ANCHOR_RESOLUTION = Scratch.Cast.toNumber(args.ANCHOR_RESOLUTION);
            utils.target[anchor_resolution] = args.ANCHOR_RESOLUTION;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        getResolution(args, utils) {
            return utils.target[anchor_resolution]
        }

        setRetreat(args, utils) {
            args.ANCHOR_RETREAT = Scratch.Cast.toBoolean(args.ANCHOR_RETREAT);
            utils.target[anchor_retreat] = args.ANCHOR_RETREAT;

            if (utils.target[anchor_updateAnchorEveryBlock]) {
                this.updateAnchor(args, utils);
            }
        }

        getRetreat(args, utils) {
            return utils.target[anchor_retreat];
        }
    }

    Scratch.extensions.register(new Anchor());
})(Scratch);
GarboMuffin commented 1 year ago

The problem is not BEFORE_EXECUTE, it's that your BEFORE_EXECUTE moves sprites with setXY, which makes it think a redraw has been requested, so it ends up not executing the scripts. A bit silly.

If you put this at the end of your BEFORE_EXECUTE:

Scratch.vm.runtime.redrawRequested = false;

should fix it