From 40b760c508382b7fad5bf3f579336949b6818fd4 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 22 Jan 2025 20:56:54 -0800 Subject: [PATCH 01/14] Create Camera.js --- extensions/SharkPool/Camera.js | 761 +++++++++++++++++++++++++++++++++ 1 file changed, 761 insertions(+) create mode 100644 extensions/SharkPool/Camera.js diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js new file mode 100644 index 0000000000..66ae25a963 --- /dev/null +++ b/extensions/SharkPool/Camera.js @@ -0,0 +1,761 @@ +// Name: Camera +// ID: SPcamera +// Description: Move the visible part of the stage. +// By: SharkPool +// Licence: MIT + +// Version V.1.0.0 + +(function (Scratch) { + "use strict"; + if (!Scratch.extensions.unsandboxed) throw new Error("Camera must run unsandboxed!"); + + const menuIconURI = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MSIgaGVpZ2h0PSI0MSIgdmlld0JveD0iMCAwIDQxIDQxIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgMjAuNUMwIDkuMTc4IDkuMTc4IDAgMjAuNSAwUzQxIDkuMTc4IDQxIDIwLjUgMzEuODIyIDQxIDIwLjUgNDEgMCAzMS44MjIgMCAyMC41IiBmaWxsPSIjMjg1MWM5Ii8+PHBhdGggZD0iTTIuMzc4IDIwLjVjMC0xMC4wMDkgOC4xMTMtMTguMTIyIDE4LjEyMi0xOC4xMjJTMzguNjIyIDEwLjQ5MSAzOC42MjIgMjAuNSAzMC41MDkgMzguNjIyIDIwLjUgMzguNjIyIDIuMzc4IDMwLjUwOSAyLjM3OCAyMC41IiBmaWxsPSIjNTE3YWY1Ii8+PHBhdGggZD0iTTMxLjg3MSAxNS4wMDdjLjA3My4xNDkuMTI5LjI4LjEyOS4yNDN2MTAuM2MwIC4yODMtLjIzMy41LS41LjVhLjMuMyAwIDAgMS0uMTQ2LS4wNTRsLS4wOTctLjA3NUwyNSAyMi4xNjd2Mi4yODNjMCAxLjk0Ny0xLjU5OCAzLjYtMy41IDMuNmgtOC45Yy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNmg4LjljMS45MzcgMCAzLjUgMS41OSAzLjUgMy42djIuMzJsNi4xNzItNGMuMjctLjE2Mi41NTQtLjEwNS43LjEzN3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; + const cameraIcon = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTI2LjM3MSA5LjUwN2MuMDczLjE0OS4xMjkuMjguMTI5LjI0M3YxMC4zYzAgLjI4My0uMjMzLjUtLjUuNWEuMy4zIDAgMCAxLS4xNDYtLjA1NGwtLjA5Ny0uMDc1LTYuMjU3LTMuNzU0djIuMjgzYzAgMS45NDctMS41OTggMy42LTMuNSAzLjZINy4xYy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNkgxNmMxLjkzNyAwIDMuNSAxLjU5IDMuNSAzLjZ2Mi4zMmw2LjE3Mi00Yy4yNy0uMTYyLjU1NC0uMTA1LjcuMTM3eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0wIDMwVjBoMzB2MzB6IiBmaWxsPSJub25lIi8+PC9nPjwvc3ZnPg=="; + const rightArrow = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIyLjY4IDEyLjJhMS42IDEuNiAwIDAgMS0xLjI3LjYzaC03LjY5YTEuNTkgMS41OSAwIDAgMS0xLjE2LTIuNThsMS4xMi0xLjQxYTQuODIgNC44MiAwIDAgMC0zLjE0LS43NyA0LjMgNC4zIDAgMCAwLTIgLjhBNC4yNSA0LjI1IDAgMCAwIDcuMiAxMC42YTUuMDYgNS4wNiAwIDAgMCAuNTQgNC42MkE1LjU4IDUuNTggMCAwIDAgMTIgMTcuNzRhMi4yNiAyLjI2IDAgMCAxLS4xNiA0LjUyQTEwLjI1IDEwLjI1IDAgMCAxIDMuNzQgMThhMTAuMTQgMTAuMTQgMCAwIDEtMS40OS05LjIyIDkuNyA5LjcgMCAwIDEgMi44My00LjE0QTkuOSA5LjkgMCAwIDEgOS42NiAyLjVhMTAuNjYgMTAuNjYgMCAwIDEgNy43MiAxLjY4bDEuMDgtMS4zNWExLjU3IDEuNTcgMCAwIDEgMS4yNC0uNiAxLjYgMS42IDAgMCAxIDEuNTQgMS4yMWwxLjcgNy4zN2ExLjU3IDEuNTcgMCAwIDEtLjI2IDEuMzkiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0yMS4zOCAxMS44M2gtNy42MWEuNTkuNTkgMCAwIDEtLjQzLTFsMS43NS0yLjE5YTUuOSA1LjkgMCAwIDAtNC43LTEuNTggNS4wNyA1LjA3IDAgMCAwLTQuMTEgMy4xN0E2IDYgMCAwIDAgNyAxNS43N2E2LjUxIDYuNTEgMCAwIDAgNSAyLjkyIDEuMzEgMS4zMSAwIDAgMS0uMDggMi42MiA5LjMgOS4zIDAgMCAxLTcuMzUtMy44MiA5LjE2IDkuMTYgMCAwIDEtMS40LTguMzdBOC41IDguNSAwIDAgMSA1LjcxIDUuNGE4Ljc2IDguNzYgMCAwIDEgNC4xMS0xLjkyIDkuNyA5LjcgMCAwIDEgNy43NSAyLjA3bDEuNjctMi4xYS41OS41OSAwIDAgMSAxIC4yMUwyMiAxMS4wOGEuNTkuNTkgMCAwIDEtLjYyLjc1IiBzdHlsZT0iZmlsbDojZmZmIi8+PC9zdmc+"; + const leftArrow = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLjM0IDE4LjIxYTEwLjI0IDEwLjI0IDAgMCAxLTguMSA0LjIyIDIuMjYgMi4yNiAwIDAgMS0uMTYtNC41MiA1LjU4IDUuNTggMCAwIDAgNC4yNS0yLjUzIDUuMDYgNS4wNiAwIDAgMCAuNTQtNC42MkE0LjI1IDQuMjUgMCAwIDAgMTUuNTUgOWE0LjMgNC4zIDAgMCAwLTItLjggNC44MiA0LjgyIDAgMCAwLTMuMTUuOGwxLjEyIDEuNDFBMS41OSAxLjU5IDAgMCAxIDEwLjM2IDEzSDIuNjdhMS41NiAxLjU2IDAgMCAxLTEuMjYtLjYzQTEuNTQgMS41NCAwIDAgMSAxLjEzIDExbDEuNzItNy40M0ExLjU5IDEuNTkgMCAwIDEgNC4zOCAyLjRhMS41NyAxLjU3IDAgMCAxIDEuMjQuNkw2LjcgNC4zNWExMC42NiAxMC42NiAwIDAgMSA3LjcyLTEuNjhBOS45IDkuOSAwIDAgMSAxOSA0LjgxIDkuNiA5LjYgMCAwIDEgMjEuODMgOWExMC4wOCAxMC4wOCAwIDAgMS0xLjQ5IDkuMjEiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0xOS41NiAxNy42NWE5LjI5IDkuMjkgMCAwIDEtNy4zNSAzLjgzIDEuMzEgMS4zMSAwIDAgMS0uMDgtMi42MiA2LjUzIDYuNTMgMCAwIDAgNS0yLjkyIDYuMDUgNi4wNSAwIDAgMCAuNjctNS41MSA1LjMgNS4zIDAgMCAwLTEuNjQtMi4xNiA1LjIgNS4yIDAgMCAwLTIuNDgtMUE1Ljg2IDUuODYgMCAwIDAgOSA4Ljg0TDEwLjc0IDExYS41OS41OSAwIDAgMS0uNDMgMUgyLjdhLjYuNiAwIDAgMS0uNi0uNzVsMS43MS03LjQyYS41OS41OSAwIDAgMSAxLS4yMWwxLjY3IDIuMWE5LjcgOS43IDAgMCAxIDcuNzUtMi4wNyA4Ljg0IDguODQgMCAwIDEgNC4xMiAxLjkyIDguNyA4LjcgMCAwIDEgMi41NCAzLjcyIDkuMTQgOS4xNCAwIDAgMS0xLjMzIDguMzYiIHN0eWxlPSJmaWxsOiNmZmYiLz48L3N2Zz4="; + + const Cast = Scratch.Cast; + const vm = Scratch.vm; + const runtime = vm.runtime; + const render = vm.renderer; + const isEditor = typeof scaffolding === "undefined"; + const cameraSymbol = Symbol("SPcameraData"); + + let allCameras = { + default: { + xy: [0, 0], + zoom: 1, + dir: 0, + binds: undefined + } + }; + + // TODO add support for interpolation at some point + runtime.setInterpolation(false); + runtime.runtimeOptions.fencing = false; + render.offscreenTouching = true; + + // custom gui + function openModal(titleName, func) { + // in a Button Context, ScratchBlocks always exists + ScratchBlocks.Variables.createVariable(ScratchBlocks.mainWorkspace, null, "broadcast_msg"); + const modalHolder = document.querySelector(`div[class="ReactModalPortal"]`); + const modal = modalHolder.querySelector(`div[class="box_box_2jjDp"]`); + + modal.querySelector(`div[class^="modal_header-item_"]`).textContent = "Camera Manager"; + modal.querySelector(`div[class^="prompt_label_"]`).textContent = titleName; + + const button = modal.querySelector(`button[class^="prompt_ok-button_"]`); + const cloneOkay = button.cloneNode(true); + button.parentNode.appendChild(cloneOkay); + button.remove(); + cloneOkay.addEventListener("click", (e) => { + func(e, modal); + cloneOkay.previousElementSibling.click(); + runtime.requestBlocksUpdate(); + }); + + modalHolder.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + func(e, modal); + cloneOkay.previousElementSibling.click(); + runtime.requestBlocksUpdate(); + } + }); + } + + // camera utils + function setupState(drawable) { + drawable[cameraSymbol] = { + name: "default", + needsRefresh: false, + ogXY: [0,0], + ogSZ: 1, + ogDir: 0 + }; + } + + function translatePosition(xy, invert, camData) { + if (invert) { + const invRads = (camData.ogDir / 180) * Math.PI; + const invSin = Math.sin(invRads), invCos = Math.cos(invRads); + const scaledX = xy[0] / camData.ogSZ; + const scaledY = xy[1] / camData.ogSZ; + const invOffX = scaledX * invCos + scaledY * invSin; + const invOffY = -scaledX * invSin + scaledY * invCos; + return [ + invOffX - camData.ogXY[0], + invOffY - camData.ogXY[1] + ]; + } else { + const rads = (camData.dir / 180) * Math.PI; + const sin = Math.sin(rads), cos = Math.cos(rads); + const offX = xy[0] + camData.xy[0]; + const offY = xy[1] + camData.xy[1]; + return [ + camData.zoom * (offX * cos - offY * sin), + camData.zoom * (offX * sin + offY * cos) + ]; + } + } + + function bindDrawable(drawable, camera) { + const camSystem = drawable[cameraSymbol]; + if (camSystem.name === camera) return; + + // invert camera transformations + const fixedPos = translatePosition(drawable._position, true, camSystem); + const fixedDir = drawable._direction + camSystem.ogDir; + const fixedScale = [ + drawable._scale[0] / camSystem.ogSZ, + drawable._scale[1] / camSystem.ogSZ + ]; + + const camData = allCameras[camera]; + drawable[cameraSymbol] = { + name: camera, + needsRefresh: true, + ogXY: [0,0], + ogSZ: 1, + ogDir: 0 + }; + + const id = drawable._id; + render.updateDrawablePosition(id, fixedPos); + render.updateDrawableDirection(id, fixedDir); + render.updateDrawableScale(id, fixedScale); + } + + function unbindDrawable(drawable, camera) { + if (drawable[cameraSymbol].name === camera) return; + drawable[cameraSymbol] = { + name: camera, + needsRefresh: true, + ogXY: [0,0], + ogSZ: 1, + ogDir: 0 + }; + + const id = drawable._id; + render.updateDrawablePosition(id, drawable._position); + render.updateDrawableDirection(id, drawable._direction); + render.updateDrawableScale(id, drawable._scale); + } + + function updateCamera(camera) { + for (let i = 0; i < render._allDrawables.length; i++) { + const drawable = render._allDrawables[i]; + if (!drawable || !drawable.getVisible() || !drawable.skin) continue; + if (!drawable[cameraSymbol]) setupState(drawable); + + const camSystem = drawable[cameraSymbol]; + if (camSystem.name === camera) { + camSystem.needsRefresh = true; + drawable.updatePosition(drawable._position); + drawable.updateDirection(drawable._direction); + drawable.updateScale(drawable._scale); + camSystem.needsRefresh = false; + } + } + } + + // camera system patches + const ogUpdatePosition = render.exports.Drawable.prototype.updatePosition; + render.exports.Drawable.prototype.updatePosition = function(position) { + if (!this[cameraSymbol]) setupState(this); + const camSystem = this[cameraSymbol]; + const thisCam = allCameras[camSystem.name]; + if (camSystem.needsRefresh) { + // invert camera transformations + position = translatePosition(position, true, camSystem); + } + + camSystem.ogXY = [...thisCam.xy]; + position = translatePosition(position, false, thisCam); + ogUpdatePosition.call(this, position); + } + + const ogUpdateDirection = render.exports.Drawable.prototype.updateDirection; + render.exports.Drawable.prototype.updateDirection = function(direction) { + if (!this[cameraSymbol]) setupState(this); + const camSystem = this[cameraSymbol]; + const thisCam = allCameras[camSystem.name]; + if (camSystem.needsRefresh) { + // invert camera transformations + direction += camSystem.ogDir + } + + camSystem.ogDir = thisCam.dir; + direction -= thisCam.dir; + ogUpdateDirection.call(this, direction); + } + + const ogUpdateScale = render.exports.Drawable.prototype.updateScale; + render.exports.Drawable.prototype.updateScale = function(scale) { + if (!this[cameraSymbol]) setupState(this); + const camSystem = this[cameraSymbol]; + const thisCam = allCameras[camSystem.name]; + if (camSystem.needsRefresh) { + // invert camera transformations + scale[0] /= camSystem.ogSZ; + scale[1] /= camSystem.ogSZ; + } + + camSystem.ogSZ = thisCam.zoom; + scale[0] *= thisCam.zoom; + scale[1] *= thisCam.zoom; + ogUpdateScale.call(this, scale); + this.skin?.emitWasAltered(); + } + + // Turbowarp Extension Storage + runtime.on("PROJECT_LOADED", () => { + const stored = runtime.extensionStorage["SPcamera"]; + if (stored) stored.cams.forEach((cam) => { + allCameras[cam] = { + xy: [0, 0], + zoom: 1, + dir: 0, + binds: name === "default" ? undefined : [] + }; + }); + }); + + class SPcamera { + getInfo() { + return { + id: "SPcamera", + name: "Camera", + color1: "#517af5", + color2: "#3460e3", + color3: "#2851c9", + menuIconURI, + blocks: [ + { + func: "addCamera", + blockType: Scratch.BlockType.BUTTON, + text: "Add Camera", + }, + { + func: "removeCamera", + blockType: Scratch.BlockType.BUTTON, + text: "Remove Camera", + }, + "---", + { + opcode: "bindTarget", + blockType: Scratch.BlockType.COMMAND, + text: "bind [TARGET] to camera [CAMERA]", + blockIconURI: cameraIcon, + arguments: { + TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + { + opcode: "unbindTarget", + blockType: Scratch.BlockType.COMMAND, + text: "unbind [TARGET] from camera [CAMERA]", + blockIconURI: cameraIcon, + arguments: { + TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + { + opcode: "targetCamera", + blockType: Scratch.BlockType.REPORTER, + text: "camera of [TARGET]", + blockIconURI: cameraIcon, + arguments: { + TARGET: { type: Scratch.ArgumentType.STRING, menu: "EXACT_OBJECTS" } + }, + }, + "---", + { + opcode: "setSpaceColor", + blockType: Scratch.BlockType.COMMAND, + text: "set background color to [COLOR]", + blockIconURI: cameraIcon, + arguments: { + COLOR: { type: Scratch.ArgumentType.COLOR } + }, + }, + { + opcode: "spaceColor", + blockType: Scratch.BlockType.REPORTER, + text: "background color", + blockIconURI: cameraIcon, + }, + { blockType: Scratch.BlockType.LABEL, text: "Camera Controls" }, + { + opcode: "setXY", + blockType: Scratch.BlockType.COMMAND, + text: "set [CAMERA] camera to x: [X] y: [Y]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + X: { type: Scratch.ArgumentType.NUMBER }, + Y: { type: Scratch.ArgumentType.NUMBER } + } + }, + { + opcode: "goToObject", + blockType: Scratch.BlockType.COMMAND, + text: "move [CAMERA] camera to [TARGET]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" } + }, + }, + "---", + { + opcode: "moveSteps", + blockType: Scratch.BlockType.COMMAND, + text: "move [CAMERA] camera [NUM] steps", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + }, + }, + { + opcode: "setX", + blockType: Scratch.BlockType.COMMAND, + text: "set [CAMERA] camera x to [NUM]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER } + }, + }, + { + opcode: "changeX", + blockType: Scratch.BlockType.COMMAND, + text: "change [CAMERA] camera x by [NUM]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + }, + }, + { + opcode: "setY", + blockType: Scratch.BlockType.COMMAND, + text: "set [CAMERA] camera y to [NUM]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER } + }, + }, + { + opcode: "changeY", + blockType: Scratch.BlockType.COMMAND, + text: "change [CAMERA] camera y by [NUM]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + }, + }, + "---", + { + opcode: "getX", + blockType: Scratch.BlockType.REPORTER, + text: "[CAMERA] camera x", + disableMonitor: true, + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + { + opcode: "getY", + blockType: Scratch.BlockType.REPORTER, + text: "[CAMERA] camera y", + disableMonitor: true, + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + "---", + { + opcode: "setDirection", + blockType: Scratch.BlockType.COMMAND, + text: "set [CAMERA] camera direction to [NUM]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 } + }, + }, + { + opcode: "pointCamera", + blockType: Scratch.BlockType.COMMAND, + text: "point [CAMERA] camera towards [TARGET]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" } + }, + }, + "---", + { + opcode: "turnCamRight", + blockType: Scratch.BlockType.COMMAND, + text: "turn [CAMERA] camera [IMG] [NUM] degrees", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + IMG: { type: Scratch.ArgumentType.IMAGE, dataURI: rightArrow }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 } + }, + }, + { + opcode: "turnCamLeft", + blockType: Scratch.BlockType.COMMAND, + text: "turn [CAMERA] camera [IMG] [NUM] degrees", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + IMG: { type: Scratch.ArgumentType.IMAGE, dataURI: leftArrow }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 } + }, + }, + { + opcode: "getDirection", + blockType: Scratch.BlockType.REPORTER, + text: "[CAMERA] camera direction", + disableMonitor: true, + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + "---", + { + opcode: "setZoom", + blockType: Scratch.BlockType.COMMAND, + text: "set [CAMERA] camera zoom to [NUM]%", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + }, + }, + { + opcode: "changeZoom", + blockType: Scratch.BlockType.COMMAND, + text: "change [CAMERA] camera zoom by [NUM]", + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + }, + }, + { + opcode: "getZoom", + blockType: Scratch.BlockType.REPORTER, + text: "[CAMERA] camera zoom", + disableMonitor: true, + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + { blockType: Scratch.BlockType.LABEL, text: "Utility" }, + { + opcode: "fixedMouseX", + blockType: Scratch.BlockType.REPORTER, + text: "mouse x in camera [CAMERA]", + disableMonitor: true, + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + { + opcode: "fixedMouseY", + blockType: Scratch.BlockType.REPORTER, + text: "mouse y in camera [CAMERA]", + disableMonitor: true, + arguments: { + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + }, + }, + ], + menus: { + CAMERAS: { acceptReporters: false, items: "getCameras" }, + TARGETS: { acceptReporters: true, items: "getTargets" }, + OBJECTS: { acceptReporters: true, items: "getObjects" }, + EXACT_OBJECTS: { acceptReporters: true, items: this.getObjects(false) }, + BINDS: { + acceptReporters: true, + items: [ + { text: "bind", value: "bind" }, + { text: "unbind", value: "unbind" } + ] + } + }, + }; + } + + // Helper Funcs + getObjects(includeAll) { + const objectNames = [{ text: Scratch.translate("myself"), value: "_myself_" }]; + if (includeAll) objectNames.push({ text: Scratch.translate("all objects"), value: "_all_" }); + objectNames.push({ text: Scratch.translate("Stage"), value: "_stage_" }); + + if (runtime.ext_videoSensing) objectNames.push({ text: Scratch.translate("Video Layer"), value: "_video_" }); + if (runtime.ext_pen) objectNames.push({ text: Scratch.translate("Pen Layer"), value: "_pen_" }); + + // Custom Drawable Layer (CST's 3D or Simple3D Exts for Example) + for (var i = 0; i < render._allDrawables.length; i++) { + const drawable = render._allDrawables[i]; + if (drawable !== undefined && drawable.customDrawableName !== undefined) objectNames.push({ + text: drawable.customDrawableName, value: `${i}=SP-custLayer` + }); + } + + // Sprites + const targets = runtime.targets; + for (let i = 1; i < targets.length; i++) { + const target = targets[i]; + if (target.isOriginal) objectNames.push({ text: target.getName(), value: target.getName() }); + } + return objectNames.length > 0 ? objectNames : [""]; + } + + getTargets() { + const targetNames = [ + { text: Scratch.translate("myself"), value: "_myself_" }, + { text: Scratch.translate("Stage"), value: "_stage_" } + ]; + const targets = runtime.targets; + for (let i = 1; i < targets.length; i++) { + const target = targets[i]; + if (target.isOriginal) targetNames.push({ text: target.getName(), value: target.getName() }); + } + return targetNames.length > 0 ? targetNames : [""]; + } + + getCameras() { + return Object.keys(allCameras); + } + + refreshBlocks() { + if (isEditor) { + runtime.once("BEFORE_EXECUTE", () => { runtime.requestBlocksUpdate() }); + runtime.extensionStorage["SPcamera"] = { cams: Object.keys(allCameras) }; + } + } + + addCamera() { + openModal("New Camera name:", (e, modal) =>{ + const name = modal.querySelector(`input[class^="prompt_variable-name-text-input_"]`); + if (name.value) { + allCameras[name.value] = { + xy: [0, 0], + zoom: 1, + dir: 0, + binds: [] + }; + this.refreshBlocks(); + } + e.stopPropagation(); + }); + } + + removeCamera() { + openModal("Remove Camera named:", (e, modal) =>{ + const name = modal.querySelector(`input[class^="prompt_variable-name-text-input_"]`); + if (name.value) { + if (name.value === "default") return; // never delete the placeholder + delete allCameras[name.value]; + this.refreshBlocks(); + } + e.stopPropagation(); + }); + } + + getTarget(name, util) { + if (name === "_all_") return "_all_"; + else if (name === "_stage_") return runtime.getTargetForStage(); + else if (name === "_myself_") return util.target; + const penLayer = runtime.ext_pen?._penDrawableId; + const videoLayer = runtime.ioDevices.video._drawable; + + if (name === "_pen_") return penLayer ? { drawableID: penLayer } : undefined; + else if (name === "_video_") return videoLayer !== -1 ? { drawableID: videoLayer } : undefined; + else if (name.includes("=SP-custLayer")) { + const drawableID = parseInt(args.TARGET); + if (render._allDrawables[drawableID]?.customDrawableName !== undefined) return { + drawableID + }; + } + return runtime.getSpriteTargetByName(name); + } + + translateAngledMovement(xy, steps, direction) { + const radians = (direction) * (Math. PI / 180); + return [ + xy[0] + (steps * Math.cos(radians)), + xy[1] + (steps * Math.sin(radians)) + ]; + } + + // Block Funcs + bindTarget(args, util) { + const target = this.getTarget(args.TARGET, util); + if (!target) return; + if (target === "_all_") { + render._allDrawables.forEach((drawable) => { + bindDrawable(drawable, args.CAMERA); + }); + } else { + const drawable = render._allDrawables[target.drawableID]; + bindDrawable(drawable, args.CAMERA); + } + } + + unbindTarget(args, util) { + const target = this.getTarget(args.TARGET, util); + if (!target) return; + if (target === "_all_") { + render._allDrawables.forEach((drawable) => { + bindDrawable(drawable, "default"); + }); + } else { + const drawable = render._allDrawables[target.drawableID]; + bindDrawable(drawable, "default"); + } + } + + targetCamera(args, util) { + const target = this.getTarget(args.TARGET, util); + if (!target) return ""; + return render._allDrawables[target.drawableID][cameraSymbol].name; + } + + setSpaceColor(args) { + const rgb = Cast.toRgbColorList(args.COLOR); + render.setBackgroundColor( + rgb[0] / 255, rgb[1] / 255, rgb[2] / 255 + ); + } + + spaceColor() { + const rgb = render._backgroundColor3b; + const decimal = (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]; + if (decimal < 0) decimal += 0xFFFFFF + 1; + const hex = Number(decimal).toString(16); + return `#${"000000".substr(0, 6 - hex.length)}${hex}`; + } + + setXY(args) { + allCameras[args.CAMERA].xy = [ + Cast.toNumber(args.X) * -1, + Cast.toNumber(args.Y) * -1 + ]; + updateCamera(args.CAMERA); + } + + moveSteps(args) { + const cam = allCameras[args.CAMERA]; + const steps = Cast.toNumber(args.NUM) * -1; + cam.xy = this.translateAngledMovement(cam.xy, steps, cam.dir); + updateCamera(args.CAMERA); + } + + setX(args) { + allCameras[args.CAMERA].xy[0] = Cast.toNumber(args.NUM) * -1; + updateCamera(args.CAMERA); + } + + changeX(args) { + const cam = allCameras[args.CAMERA]; + const steps = Cast.toNumber(args.NUM) * -1; + cam.xy = this.translateAngledMovement(cam.xy, steps, 0); + updateCamera(args.CAMERA); + } + + setY(args) { + allCameras[args.CAMERA].xy[1] = Cast.toNumber(args.NUM) * -1; + updateCamera(args.CAMERA); + } + + changeY(args) { + const cam = allCameras[args.CAMERA]; + const steps = Cast.toNumber(args.NUM) * -1; + cam.xy = this.translateAngledMovement(cam.xy, steps, 90); + updateCamera(args.CAMERA); + } + + goToObject(args, util) { + const target = this.getTarget(args.TARGET, util); + if (target) { + allCameras[args.CAMERA].xy = [target.x, target.y]; + updateCamera(args.CAMERA); + } + } + + getX(args) { + return allCameras[args.CAMERA].xy[0] * -1; + } + + getY(args) { + return allCameras[args.CAMERA].xy[1] * -1; + } + + setDirection(args) { + allCameras[args.CAMERA].dir = Cast.toNumber(args.NUM) - 90; + updateCamera(args.CAMERA); + } + + turnCamRight(args) { + allCameras[args.CAMERA].dir -= Cast.toNumber(args.NUM); + updateCamera(args.CAMERA); + } + + turnCamLeft(args) { + allCameras[args.CAMERA].dir += Cast.toNumber(args.NUM); + updateCamera(args.CAMERA); + } + + pointCamera(args, util) { + const target = this.getTarget(args.TARGET, util); + if (target) { + allCameras[args.CAMERA].dir = target.direction - 90; + updateCamera(args.CAMERA); + } + } + + getDirection(args) { + return allCameras[args.CAMERA].dir + 90; + } + + setZoom(args) { + allCameras[args.CAMERA].zoom = Cast.toNumber(args.NUM) / 100; + updateCamera(args.CAMERA); + } + + changeZoom(args) { + allCameras[args.CAMERA].zoom += Cast.toNumber(args.NUM) / 100; + updateCamera(args.CAMERA); + } + + getZoom(args) { + return allCameras[args.CAMERA].zoom * 100; + } + + fixedMouseX(args, util) { + const camData = allCameras[args.CAMERA]; + return translatePosition( + [util.ioQuery("mouse", "getScratchX"), util.ioQuery("mouse", "getScratchY")], + false, + camData + )[0]; + } + + fixedMouseY(args, util) { + const camData = allCameras[args.CAMERA]; + return translatePosition( + [util.ioQuery("mouse", "getScratchX"), util.ioQuery("mouse", "getScratchY")], + false, + camData + )[1]; + } + } + + Scratch.extensions.register(new SPcamera()); +})(Scratch); From ec021ff412daaa0cd588f80d9d959bc257e58f0f Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:12:36 -0800 Subject: [PATCH 02/14] Camera.js -- fix lint --- extensions/SharkPool/Camera.js | 97 +++++++++++++++------------------- 1 file changed, 43 insertions(+), 54 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 66ae25a963..9bf7b54386 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -47,7 +47,7 @@ const modalHolder = document.querySelector(`div[class="ReactModalPortal"]`); const modal = modalHolder.querySelector(`div[class="box_box_2jjDp"]`); - modal.querySelector(`div[class^="modal_header-item_"]`).textContent = "Camera Manager"; + modal.querySelector(`div[class^="modal_header-item_"]`).textContent = Scratch.translate("Camera Manager"); modal.querySelector(`div[class^="prompt_label_"]`).textContent = titleName; const button = modal.querySelector(`button[class^="prompt_ok-button_"]`); @@ -115,8 +115,7 @@ drawable._scale[0] / camSystem.ogSZ, drawable._scale[1] / camSystem.ogSZ ]; - - const camData = allCameras[camera]; + drawable[cameraSymbol] = { name: camera, needsRefresh: true, @@ -131,22 +130,6 @@ render.updateDrawableScale(id, fixedScale); } - function unbindDrawable(drawable, camera) { - if (drawable[cameraSymbol].name === camera) return; - drawable[cameraSymbol] = { - name: camera, - needsRefresh: true, - ogXY: [0,0], - ogSZ: 1, - ogDir: 0 - }; - - const id = drawable._id; - render.updateDrawablePosition(id, drawable._position); - render.updateDrawableDirection(id, drawable._direction); - render.updateDrawableScale(id, drawable._scale); - } - function updateCamera(camera) { for (let i = 0; i < render._allDrawables.length; i++) { const drawable = render._allDrawables[i]; @@ -230,7 +213,7 @@ getInfo() { return { id: "SPcamera", - name: "Camera", + name: Scratch.translate("Camera"), color1: "#517af5", color2: "#3460e3", color3: "#2851c9", @@ -239,18 +222,18 @@ { func: "addCamera", blockType: Scratch.BlockType.BUTTON, - text: "Add Camera", + text: Scratch.translate("Add Camera"), }, { func: "removeCamera", blockType: Scratch.BlockType.BUTTON, - text: "Remove Camera", + text: Scratch.translate("Remove Camera"), }, "---", { opcode: "bindTarget", blockType: Scratch.BlockType.COMMAND, - text: "bind [TARGET] to camera [CAMERA]", + text: Scratch.translate("bind [TARGET] to camera [CAMERA]"), blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, @@ -260,7 +243,7 @@ { opcode: "unbindTarget", blockType: Scratch.BlockType.COMMAND, - text: "unbind [TARGET] from camera [CAMERA]", + text: Scratch.translate("unbind [TARGET] from camera [CAMERA]"), blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, @@ -270,7 +253,7 @@ { opcode: "targetCamera", blockType: Scratch.BlockType.REPORTER, - text: "camera of [TARGET]", + text: Scratch.translate("camera of [TARGET]"), blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "EXACT_OBJECTS" } @@ -280,7 +263,7 @@ { opcode: "setSpaceColor", blockType: Scratch.BlockType.COMMAND, - text: "set background color to [COLOR]", + text: Scratch.translate("set background color to [COLOR]"), blockIconURI: cameraIcon, arguments: { COLOR: { type: Scratch.ArgumentType.COLOR } @@ -289,14 +272,17 @@ { opcode: "spaceColor", blockType: Scratch.BlockType.REPORTER, - text: "background color", + text: Scratch.translate("background color"), blockIconURI: cameraIcon, }, - { blockType: Scratch.BlockType.LABEL, text: "Camera Controls" }, + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate("Camera Controls") + }, { opcode: "setXY", blockType: Scratch.BlockType.COMMAND, - text: "set [CAMERA] camera to x: [X] y: [Y]", + text: Scratch.translate("set [CAMERA] camera to x: [X] y: [Y]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, X: { type: Scratch.ArgumentType.NUMBER }, @@ -306,7 +292,7 @@ { opcode: "goToObject", blockType: Scratch.BlockType.COMMAND, - text: "move [CAMERA] camera to [TARGET]", + text: Scratch.translate("move [CAMERA] camera to [TARGET]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" } @@ -316,7 +302,7 @@ { opcode: "moveSteps", blockType: Scratch.BlockType.COMMAND, - text: "move [CAMERA] camera [NUM] steps", + text: Scratch.translate("move [CAMERA] camera [NUM] steps"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } @@ -325,7 +311,7 @@ { opcode: "setX", blockType: Scratch.BlockType.COMMAND, - text: "set [CAMERA] camera x to [NUM]", + text: Scratch.translate("set [CAMERA] camera x to [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER } @@ -334,7 +320,7 @@ { opcode: "changeX", blockType: Scratch.BlockType.COMMAND, - text: "change [CAMERA] camera x by [NUM]", + text: Scratch.translate("change [CAMERA] camera x by [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } @@ -343,7 +329,7 @@ { opcode: "setY", blockType: Scratch.BlockType.COMMAND, - text: "set [CAMERA] camera y to [NUM]", + text: Scratch.translate("set [CAMERA] camera y to [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER } @@ -352,7 +338,7 @@ { opcode: "changeY", blockType: Scratch.BlockType.COMMAND, - text: "change [CAMERA] camera y by [NUM]", + text: Scratch.translate("change [CAMERA] camera y by [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } @@ -362,7 +348,7 @@ { opcode: "getX", blockType: Scratch.BlockType.REPORTER, - text: "[CAMERA] camera x", + text: Scratch.translate("[CAMERA] camera x"), disableMonitor: true, arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } @@ -371,7 +357,7 @@ { opcode: "getY", blockType: Scratch.BlockType.REPORTER, - text: "[CAMERA] camera y", + text: Scratch.translate("[CAMERA] camera y"), disableMonitor: true, arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } @@ -381,7 +367,7 @@ { opcode: "setDirection", blockType: Scratch.BlockType.COMMAND, - text: "set [CAMERA] camera direction to [NUM]", + text: Scratch.translate("set [CAMERA] camera direction to [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 } @@ -390,7 +376,7 @@ { opcode: "pointCamera", blockType: Scratch.BlockType.COMMAND, - text: "point [CAMERA] camera towards [TARGET]", + text: Scratch.translate("point [CAMERA] camera towards [TARGET]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" } @@ -400,7 +386,7 @@ { opcode: "turnCamRight", blockType: Scratch.BlockType.COMMAND, - text: "turn [CAMERA] camera [IMG] [NUM] degrees", + text: Scratch.translate("turn [CAMERA] camera [IMG] [NUM] degrees"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, IMG: { type: Scratch.ArgumentType.IMAGE, dataURI: rightArrow }, @@ -410,7 +396,7 @@ { opcode: "turnCamLeft", blockType: Scratch.BlockType.COMMAND, - text: "turn [CAMERA] camera [IMG] [NUM] degrees", + text: Scratch.translate("turn [CAMERA] camera [IMG] [NUM] degrees"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, IMG: { type: Scratch.ArgumentType.IMAGE, dataURI: leftArrow }, @@ -420,7 +406,7 @@ { opcode: "getDirection", blockType: Scratch.BlockType.REPORTER, - text: "[CAMERA] camera direction", + text: Scratch.translate("[CAMERA] camera direction"), disableMonitor: true, arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } @@ -430,7 +416,7 @@ { opcode: "setZoom", blockType: Scratch.BlockType.COMMAND, - text: "set [CAMERA] camera zoom to [NUM]%", + text: Scratch.translate("set [CAMERA] camera zoom to [NUM]%"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } @@ -439,7 +425,7 @@ { opcode: "changeZoom", blockType: Scratch.BlockType.COMMAND, - text: "change [CAMERA] camera zoom by [NUM]", + text: Scratch.translate("change [CAMERA] camera zoom by [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } @@ -448,17 +434,20 @@ { opcode: "getZoom", blockType: Scratch.BlockType.REPORTER, - text: "[CAMERA] camera zoom", + text: Scratch.translate("[CAMERA] camera zoom"), disableMonitor: true, arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } }, }, - { blockType: Scratch.BlockType.LABEL, text: "Utility" }, + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate("Utility") + }, { opcode: "fixedMouseX", blockType: Scratch.BlockType.REPORTER, - text: "mouse x in camera [CAMERA]", + text: Scratch.translate("mouse x in camera [CAMERA]"), disableMonitor: true, arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } @@ -467,7 +456,7 @@ { opcode: "fixedMouseY", blockType: Scratch.BlockType.REPORTER, - text: "mouse y in camera [CAMERA]", + text: Scratch.translate("mouse y in camera [CAMERA]"), disableMonitor: true, arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } @@ -482,8 +471,8 @@ BINDS: { acceptReporters: true, items: [ - { text: "bind", value: "bind" }, - { text: "unbind", value: "unbind" } + { text: Scratch.translate("bind"), value: "bind" }, + { text: Scratch.translate("unbind"), value: "unbind" } ] } }, @@ -541,7 +530,7 @@ } addCamera() { - openModal("New Camera name:", (e, modal) =>{ + openModal(Scratch.translate("New Camera name:"), (e, modal) =>{ const name = modal.querySelector(`input[class^="prompt_variable-name-text-input_"]`); if (name.value) { allCameras[name.value] = { @@ -557,7 +546,7 @@ } removeCamera() { - openModal("Remove Camera named:", (e, modal) =>{ + openModal(Scratch.translate("Remove Camera named:"), (e, modal) =>{ const name = modal.querySelector(`input[class^="prompt_variable-name-text-input_"]`); if (name.value) { if (name.value === "default") return; // never delete the placeholder @@ -578,7 +567,7 @@ if (name === "_pen_") return penLayer ? { drawableID: penLayer } : undefined; else if (name === "_video_") return videoLayer !== -1 ? { drawableID: videoLayer } : undefined; else if (name.includes("=SP-custLayer")) { - const drawableID = parseInt(args.TARGET); + const drawableID = parseInt(name); if (render._allDrawables[drawableID]?.customDrawableName !== undefined) return { drawableID }; @@ -636,7 +625,7 @@ spaceColor() { const rgb = render._backgroundColor3b; - const decimal = (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]; + let decimal = (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]; if (decimal < 0) decimal += 0xFFFFFF + 1; const hex = Number(decimal).toString(16); return `#${"000000".substr(0, 6 - hex.length)}${hex}`; From 97cd7cf5c64e22bc3fb8d421265b0f3722c6130b Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Thu, 23 Jan 2025 05:13:50 +0000 Subject: [PATCH 03/14] [Automated] Format code --- extensions/SharkPool/Camera.js | 245 +++++++++++++++++++-------------- 1 file changed, 145 insertions(+), 100 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 9bf7b54386..a6de836542 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -8,31 +8,32 @@ (function (Scratch) { "use strict"; - if (!Scratch.extensions.unsandboxed) throw new Error("Camera must run unsandboxed!"); + if (!Scratch.extensions.unsandboxed) + throw new Error("Camera must run unsandboxed!"); const menuIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MSIgaGVpZ2h0PSI0MSIgdmlld0JveD0iMCAwIDQxIDQxIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgMjAuNUMwIDkuMTc4IDkuMTc4IDAgMjAuNSAwUzQxIDkuMTc4IDQxIDIwLjUgMzEuODIyIDQxIDIwLjUgNDEgMCAzMS44MjIgMCAyMC41IiBmaWxsPSIjMjg1MWM5Ii8+PHBhdGggZD0iTTIuMzc4IDIwLjVjMC0xMC4wMDkgOC4xMTMtMTguMTIyIDE4LjEyMi0xOC4xMjJTMzguNjIyIDEwLjQ5MSAzOC42MjIgMjAuNSAzMC41MDkgMzguNjIyIDIwLjUgMzguNjIyIDIuMzc4IDMwLjUwOSAyLjM3OCAyMC41IiBmaWxsPSIjNTE3YWY1Ii8+PHBhdGggZD0iTTMxLjg3MSAxNS4wMDdjLjA3My4xNDkuMTI5LjI4LjEyOS4yNDN2MTAuM2MwIC4yODMtLjIzMy41LS41LjVhLjMuMyAwIDAgMS0uMTQ2LS4wNTRsLS4wOTctLjA3NUwyNSAyMi4xNjd2Mi4yODNjMCAxLjk0Ny0xLjU5OCAzLjYtMy41IDMuNmgtOC45Yy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNmg4LjljMS45MzcgMCAzLjUgMS41OSAzLjUgMy42djIuMzJsNi4xNzItNGMuMjctLjE2Mi41NTQtLjEwNS43LjEzN3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MSIgaGVpZ2h0PSI0MSIgdmlld0JveD0iMCAwIDQxIDQxIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgMjAuNUMwIDkuMTc4IDkuMTc4IDAgMjAuNSAwUzQxIDkuMTc4IDQxIDIwLjUgMzEuODIyIDQxIDIwLjUgNDEgMCAzMS44MjIgMCAyMC41IiBmaWxsPSIjMjg1MWM5Ii8+PHBhdGggZD0iTTIuMzc4IDIwLjVjMC0xMC4wMDkgOC4xMTMtMTguMTIyIDE4LjEyMi0xOC4xMjJTMzguNjIyIDEwLjQ5MSAzOC42MjIgMjAuNSAzMC41MDkgMzguNjIyIDIwLjUgMzguNjIyIDIuMzc4IDMwLjUwOSAyLjM3OCAyMC41IiBmaWxsPSIjNTE3YWY1Ii8+PHBhdGggZD0iTTMxLjg3MSAxNS4wMDdjLjA3My4xNDkuMTI5LjI4LjEyOS4yNDN2MTAuM2MwIC4yODMtLjIzMy41LS41LjVhLjMuMyAwIDAgMS0uMTQ2LS4wNTRsLS4wOTctLjA3NUwyNSAyMi4xNjd2Mi4yODNjMCAxLjk0Ny0xLjU5OCAzLjYtMy41IDMuNmgtOC45Yy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNmg4LjljMS45MzcgMCAzLjUgMS41OSAzLjUgMy42djIuMzJsNi4xNzItNGMuMjctLjE2Mi41NTQtLjEwNS43LjEzN3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; const cameraIcon = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTI2LjM3MSA5LjUwN2MuMDczLjE0OS4xMjkuMjguMTI5LjI0M3YxMC4zYzAgLjI4My0uMjMzLjUtLjUuNWEuMy4zIDAgMCAxLS4xNDYtLjA1NGwtLjA5Ny0uMDc1LTYuMjU3LTMuNzU0djIuMjgzYzAgMS45NDctMS41OTggMy42LTMuNSAzLjZINy4xYy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNkgxNmMxLjkzNyAwIDMuNSAxLjU5IDMuNSAzLjZ2Mi4zMmw2LjE3Mi00Yy4yNy0uMTYyLjU1NC0uMTA1LjcuMTM3eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0wIDMwVjBoMzB2MzB6IiBmaWxsPSJub25lIi8+PC9nPjwvc3ZnPg=="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTI2LjM3MSA5LjUwN2MuMDczLjE0OS4xMjkuMjguMTI5LjI0M3YxMC4zYzAgLjI4My0uMjMzLjUtLjUuNWEuMy4zIDAgMCAxLS4xNDYtLjA1NGwtLjA5Ny0uMDc1LTYuMjU3LTMuNzU0djIuMjgzYzAgMS45NDctMS41OTggMy42LTMuNSAzLjZINy4xYy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNkgxNmMxLjkzNyAwIDMuNSAxLjU5IDMuNSAzLjZ2Mi4zMmw2LjE3Mi00Yy4yNy0uMTYyLjU1NC0uMTA1LjcuMTM3eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0wIDMwVjBoMzB2MzB6IiBmaWxsPSJub25lIi8+PC9nPjwvc3ZnPg=="; const rightArrow = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIyLjY4IDEyLjJhMS42IDEuNiAwIDAgMS0xLjI3LjYzaC03LjY5YTEuNTkgMS41OSAwIDAgMS0xLjE2LTIuNThsMS4xMi0xLjQxYTQuODIgNC44MiAwIDAgMC0zLjE0LS43NyA0LjMgNC4zIDAgMCAwLTIgLjhBNC4yNSA0LjI1IDAgMCAwIDcuMiAxMC42YTUuMDYgNS4wNiAwIDAgMCAuNTQgNC42MkE1LjU4IDUuNTggMCAwIDAgMTIgMTcuNzRhMi4yNiAyLjI2IDAgMCAxLS4xNiA0LjUyQTEwLjI1IDEwLjI1IDAgMCAxIDMuNzQgMThhMTAuMTQgMTAuMTQgMCAwIDEtMS40OS05LjIyIDkuNyA5LjcgMCAwIDEgMi44My00LjE0QTkuOSA5LjkgMCAwIDEgOS42NiAyLjVhMTAuNjYgMTAuNjYgMCAwIDEgNy43MiAxLjY4bDEuMDgtMS4zNWExLjU3IDEuNTcgMCAwIDEgMS4yNC0uNiAxLjYgMS42IDAgMCAxIDEuNTQgMS4yMWwxLjcgNy4zN2ExLjU3IDEuNTcgMCAwIDEtLjI2IDEuMzkiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0yMS4zOCAxMS44M2gtNy42MWEuNTkuNTkgMCAwIDEtLjQzLTFsMS43NS0yLjE5YTUuOSA1LjkgMCAwIDAtNC43LTEuNTggNS4wNyA1LjA3IDAgMCAwLTQuMTEgMy4xN0E2IDYgMCAwIDAgNyAxNS43N2E2LjUxIDYuNTEgMCAwIDAgNSAyLjkyIDEuMzEgMS4zMSAwIDAgMS0uMDggMi42MiA5LjMgOS4zIDAgMCAxLTcuMzUtMy44MiA5LjE2IDkuMTYgMCAwIDEtMS40LTguMzdBOC41IDguNSAwIDAgMSA1LjcxIDUuNGE4Ljc2IDguNzYgMCAwIDEgNC4xMS0xLjkyIDkuNyA5LjcgMCAwIDEgNy43NSAyLjA3bDEuNjctMi4xYS41OS41OSAwIDAgMSAxIC4yMUwyMiAxMS4wOGEuNTkuNTkgMCAwIDEtLjYyLjc1IiBzdHlsZT0iZmlsbDojZmZmIi8+PC9zdmc+"; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIyLjY4IDEyLjJhMS42IDEuNiAwIDAgMS0xLjI3LjYzaC03LjY5YTEuNTkgMS41OSAwIDAgMS0xLjE2LTIuNThsMS4xMi0xLjQxYTQuODIgNC44MiAwIDAgMC0zLjE0LS43NyA0LjMgNC4zIDAgMCAwLTIgLjhBNC4yNSA0LjI1IDAgMCAwIDcuMiAxMC42YTUuMDYgNS4wNiAwIDAgMCAuNTQgNC42MkE1LjU4IDUuNTggMCAwIDAgMTIgMTcuNzRhMi4yNiAyLjI2IDAgMCAxLS4xNiA0LjUyQTEwLjI1IDEwLjI1IDAgMCAxIDMuNzQgMThhMTAuMTQgMTAuMTQgMCAwIDEtMS40OS05LjIyIDkuNyA5LjcgMCAwIDEgMi44My00LjE0QTkuOSA5LjkgMCAwIDEgOS42NiAyLjVhMTAuNjYgMTAuNjYgMCAwIDEgNy43MiAxLjY4bDEuMDgtMS4zNWExLjU3IDEuNTcgMCAwIDEgMS4yNC0uNiAxLjYgMS42IDAgMCAxIDEuNTQgMS4yMWwxLjcgNy4zN2ExLjU3IDEuNTcgMCAwIDEtLjI2IDEuMzkiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0yMS4zOCAxMS44M2gtNy42MWEuNTkuNTkgMCAwIDEtLjQzLTFsMS43NS0yLjE5YTUuOSA1LjkgMCAwIDAtNC43LTEuNTggNS4wNyA1LjA3IDAgMCAwLTQuMTEgMy4xN0E2IDYgMCAwIDAgNyAxNS43N2E2LjUxIDYuNTEgMCAwIDAgNSAyLjkyIDEuMzEgMS4zMSAwIDAgMS0uMDggMi42MiA5LjMgOS4zIDAgMCAxLTcuMzUtMy44MiA5LjE2IDkuMTYgMCAwIDEtMS40LTguMzdBOC41IDguNSAwIDAgMSA1LjcxIDUuNGE4Ljc2IDguNzYgMCAwIDEgNC4xMS0xLjkyIDkuNyA5LjcgMCAwIDEgNy43NSAyLjA3bDEuNjctMi4xYS41OS41OSAwIDAgMSAxIC4yMUwyMiAxMS4wOGEuNTkuNTkgMCAwIDEtLjYyLjc1IiBzdHlsZT0iZmlsbDojZmZmIi8+PC9zdmc+"; const leftArrow = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLjM0IDE4LjIxYTEwLjI0IDEwLjI0IDAgMCAxLTguMSA0LjIyIDIuMjYgMi4yNiAwIDAgMS0uMTYtNC41MiA1LjU4IDUuNTggMCAwIDAgNC4yNS0yLjUzIDUuMDYgNS4wNiAwIDAgMCAuNTQtNC42MkE0LjI1IDQuMjUgMCAwIDAgMTUuNTUgOWE0LjMgNC4zIDAgMCAwLTItLjggNC44MiA0LjgyIDAgMCAwLTMuMTUuOGwxLjEyIDEuNDFBMS41OSAxLjU5IDAgMCAxIDEwLjM2IDEzSDIuNjdhMS41NiAxLjU2IDAgMCAxLTEuMjYtLjYzQTEuNTQgMS41NCAwIDAgMSAxLjEzIDExbDEuNzItNy40M0ExLjU5IDEuNTkgMCAwIDEgNC4zOCAyLjRhMS41NyAxLjU3IDAgMCAxIDEuMjQuNkw2LjcgNC4zNWExMC42NiAxMC42NiAwIDAgMSA3LjcyLTEuNjhBOS45IDkuOSAwIDAgMSAxOSA0LjgxIDkuNiA5LjYgMCAwIDEgMjEuODMgOWExMC4wOCAxMC4wOCAwIDAgMS0xLjQ5IDkuMjEiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0xOS41NiAxNy42NWE5LjI5IDkuMjkgMCAwIDEtNy4zNSAzLjgzIDEuMzEgMS4zMSAwIDAgMS0uMDgtMi42MiA2LjUzIDYuNTMgMCAwIDAgNS0yLjkyIDYuMDUgNi4wNSAwIDAgMCAuNjctNS41MSA1LjMgNS4zIDAgMCAwLTEuNjQtMi4xNiA1LjIgNS4yIDAgMCAwLTIuNDgtMUE1Ljg2IDUuODYgMCAwIDAgOSA4Ljg0TDEwLjc0IDExYS41OS41OSAwIDAgMS0uNDMgMUgyLjdhLjYuNiAwIDAgMS0uNi0uNzVsMS43MS03LjQyYS41OS41OSAwIDAgMSAxLS4yMWwxLjY3IDIuMWE5LjcgOS43IDAgMCAxIDcuNzUtMi4wNyA4Ljg0IDguODQgMCAwIDEgNC4xMiAxLjkyIDguNyA4LjcgMCAwIDEgMi41NCAzLjcyIDkuMTQgOS4xNCAwIDAgMS0xLjMzIDguMzYiIHN0eWxlPSJmaWxsOiNmZmYiLz48L3N2Zz4="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLjM0IDE4LjIxYTEwLjI0IDEwLjI0IDAgMCAxLTguMSA0LjIyIDIuMjYgMi4yNiAwIDAgMS0uMTYtNC41MiA1LjU4IDUuNTggMCAwIDAgNC4yNS0yLjUzIDUuMDYgNS4wNiAwIDAgMCAuNTQtNC42MkE0LjI1IDQuMjUgMCAwIDAgMTUuNTUgOWE0LjMgNC4zIDAgMCAwLTItLjggNC44MiA0LjgyIDAgMCAwLTMuMTUuOGwxLjEyIDEuNDFBMS41OSAxLjU5IDAgMCAxIDEwLjM2IDEzSDIuNjdhMS41NiAxLjU2IDAgMCAxLTEuMjYtLjYzQTEuNTQgMS41NCAwIDAgMSAxLjEzIDExbDEuNzItNy40M0ExLjU5IDEuNTkgMCAwIDEgNC4zOCAyLjRhMS41NyAxLjU3IDAgMCAxIDEuMjQuNkw2LjcgNC4zNWExMC42NiAxMC42NiAwIDAgMSA3LjcyLTEuNjhBOS45IDkuOSAwIDAgMSAxOSA0LjgxIDkuNiA5LjYgMCAwIDEgMjEuODMgOWExMC4wOCAxMC4wOCAwIDAgMS0xLjQ5IDkuMjEiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0xOS41NiAxNy42NWE5LjI5IDkuMjkgMCAwIDEtNy4zNSAzLjgzIDEuMzEgMS4zMSAwIDAgMS0uMDgtMi42MiA2LjUzIDYuNTMgMCAwIDAgNS0yLjkyIDYuMDUgNi4wNSAwIDAgMCAuNjctNS41MSA1LjMgNS4zIDAgMCAwLTEuNjQtMi4xNiA1LjIgNS4yIDAgMCAwLTIuNDgtMUE1Ljg2IDUuODYgMCAwIDAgOSA4Ljg0TDEwLjc0IDExYS41OS41OSAwIDAgMS0uNDMgMUgyLjdhLjYuNiAwIDAgMS0uNi0uNzVsMS43MS03LjQyYS41OS41OSAwIDAgMSAxLS4yMWwxLjY3IDIuMWE5LjcgOS43IDAgMCAxIDcuNzUtMi4wNyA4Ljg0IDguODQgMCAwIDEgNC4xMiAxLjkyIDguNyA4LjcgMCAwIDEgMi41NCAzLjcyIDkuMTQgOS4xNCAwIDAgMS0xLjMzIDguMzYiIHN0eWxlPSJmaWxsOiNmZmYiLz48L3N2Zz4="; const Cast = Scratch.Cast; const vm = Scratch.vm; const runtime = vm.runtime; const render = vm.renderer; const isEditor = typeof scaffolding === "undefined"; - const cameraSymbol = Symbol("SPcameraData"); + const cameraSymbol = Symbol("SPcameraData"); let allCameras = { default: { xy: [0, 0], zoom: 1, dir: 0, - binds: undefined - } + binds: undefined, + }, }; // TODO add support for interpolation at some point @@ -43,11 +44,16 @@ // custom gui function openModal(titleName, func) { // in a Button Context, ScratchBlocks always exists - ScratchBlocks.Variables.createVariable(ScratchBlocks.mainWorkspace, null, "broadcast_msg"); + ScratchBlocks.Variables.createVariable( + ScratchBlocks.mainWorkspace, + null, + "broadcast_msg" + ); const modalHolder = document.querySelector(`div[class="ReactModalPortal"]`); const modal = modalHolder.querySelector(`div[class="box_box_2jjDp"]`); - modal.querySelector(`div[class^="modal_header-item_"]`).textContent = Scratch.translate("Camera Manager"); + modal.querySelector(`div[class^="modal_header-item_"]`).textContent = + Scratch.translate("Camera Manager"); modal.querySelector(`div[class^="prompt_label_"]`).textContent = titleName; const button = modal.querySelector(`button[class^="prompt_ok-button_"]`); @@ -74,32 +80,31 @@ drawable[cameraSymbol] = { name: "default", needsRefresh: false, - ogXY: [0,0], + ogXY: [0, 0], ogSZ: 1, - ogDir: 0 + ogDir: 0, }; } function translatePosition(xy, invert, camData) { if (invert) { const invRads = (camData.ogDir / 180) * Math.PI; - const invSin = Math.sin(invRads), invCos = Math.cos(invRads); + const invSin = Math.sin(invRads), + invCos = Math.cos(invRads); const scaledX = xy[0] / camData.ogSZ; const scaledY = xy[1] / camData.ogSZ; const invOffX = scaledX * invCos + scaledY * invSin; const invOffY = -scaledX * invSin + scaledY * invCos; - return [ - invOffX - camData.ogXY[0], - invOffY - camData.ogXY[1] - ]; + return [invOffX - camData.ogXY[0], invOffY - camData.ogXY[1]]; } else { const rads = (camData.dir / 180) * Math.PI; - const sin = Math.sin(rads), cos = Math.cos(rads); + const sin = Math.sin(rads), + cos = Math.cos(rads); const offX = xy[0] + camData.xy[0]; const offY = xy[1] + camData.xy[1]; return [ camData.zoom * (offX * cos - offY * sin), - camData.zoom * (offX * sin + offY * cos) + camData.zoom * (offX * sin + offY * cos), ]; } } @@ -107,21 +112,21 @@ function bindDrawable(drawable, camera) { const camSystem = drawable[cameraSymbol]; if (camSystem.name === camera) return; - + // invert camera transformations const fixedPos = translatePosition(drawable._position, true, camSystem); const fixedDir = drawable._direction + camSystem.ogDir; const fixedScale = [ drawable._scale[0] / camSystem.ogSZ, - drawable._scale[1] / camSystem.ogSZ + drawable._scale[1] / camSystem.ogSZ, ]; drawable[cameraSymbol] = { name: camera, needsRefresh: true, - ogXY: [0,0], + ogXY: [0, 0], ogSZ: 1, - ogDir: 0 + ogDir: 0, }; const id = drawable._id; @@ -149,7 +154,7 @@ // camera system patches const ogUpdatePosition = render.exports.Drawable.prototype.updatePosition; - render.exports.Drawable.prototype.updatePosition = function(position) { + render.exports.Drawable.prototype.updatePosition = function (position) { if (!this[cameraSymbol]) setupState(this); const camSystem = this[cameraSymbol]; const thisCam = allCameras[camSystem.name]; @@ -161,25 +166,25 @@ camSystem.ogXY = [...thisCam.xy]; position = translatePosition(position, false, thisCam); ogUpdatePosition.call(this, position); - } + }; const ogUpdateDirection = render.exports.Drawable.prototype.updateDirection; - render.exports.Drawable.prototype.updateDirection = function(direction) { + render.exports.Drawable.prototype.updateDirection = function (direction) { if (!this[cameraSymbol]) setupState(this); const camSystem = this[cameraSymbol]; const thisCam = allCameras[camSystem.name]; if (camSystem.needsRefresh) { // invert camera transformations - direction += camSystem.ogDir + direction += camSystem.ogDir; } camSystem.ogDir = thisCam.dir; direction -= thisCam.dir; ogUpdateDirection.call(this, direction); - } + }; const ogUpdateScale = render.exports.Drawable.prototype.updateScale; - render.exports.Drawable.prototype.updateScale = function(scale) { + render.exports.Drawable.prototype.updateScale = function (scale) { if (!this[cameraSymbol]) setupState(this); const camSystem = this[cameraSymbol]; const thisCam = allCameras[camSystem.name]; @@ -194,19 +199,20 @@ scale[1] *= thisCam.zoom; ogUpdateScale.call(this, scale); this.skin?.emitWasAltered(); - } + }; // Turbowarp Extension Storage runtime.on("PROJECT_LOADED", () => { const stored = runtime.extensionStorage["SPcamera"]; - if (stored) stored.cams.forEach((cam) => { - allCameras[cam] = { - xy: [0, 0], - zoom: 1, - dir: 0, - binds: name === "default" ? undefined : [] - }; - }); + if (stored) + stored.cams.forEach((cam) => { + allCameras[cam] = { + xy: [0, 0], + zoom: 1, + dir: 0, + binds: name === "default" ? undefined : [], + }; + }); }); class SPcamera { @@ -237,7 +243,7 @@ blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, { @@ -247,7 +253,7 @@ blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, { @@ -256,7 +262,10 @@ text: Scratch.translate("camera of [TARGET]"), blockIconURI: cameraIcon, arguments: { - TARGET: { type: Scratch.ArgumentType.STRING, menu: "EXACT_OBJECTS" } + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "EXACT_OBJECTS", + }, }, }, "---", @@ -266,7 +275,7 @@ text: Scratch.translate("set background color to [COLOR]"), blockIconURI: cameraIcon, arguments: { - COLOR: { type: Scratch.ArgumentType.COLOR } + COLOR: { type: Scratch.ArgumentType.COLOR }, }, }, { @@ -277,7 +286,7 @@ }, { blockType: Scratch.BlockType.LABEL, - text: Scratch.translate("Camera Controls") + text: Scratch.translate("Camera Controls"), }, { opcode: "setXY", @@ -286,8 +295,8 @@ arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, X: { type: Scratch.ArgumentType.NUMBER }, - Y: { type: Scratch.ArgumentType.NUMBER } - } + Y: { type: Scratch.ArgumentType.NUMBER }, + }, }, { opcode: "goToObject", @@ -295,7 +304,7 @@ text: Scratch.translate("move [CAMERA] camera to [TARGET]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" } + TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" }, }, }, "---", @@ -305,7 +314,7 @@ text: Scratch.translate("move [CAMERA] camera [NUM] steps"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, }, }, { @@ -314,7 +323,7 @@ text: Scratch.translate("set [CAMERA] camera x to [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER } + NUM: { type: Scratch.ArgumentType.NUMBER }, }, }, { @@ -323,7 +332,7 @@ text: Scratch.translate("change [CAMERA] camera x by [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, }, }, { @@ -332,7 +341,7 @@ text: Scratch.translate("set [CAMERA] camera y to [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER } + NUM: { type: Scratch.ArgumentType.NUMBER }, }, }, { @@ -341,7 +350,7 @@ text: Scratch.translate("change [CAMERA] camera y by [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, }, }, "---", @@ -351,7 +360,7 @@ text: Scratch.translate("[CAMERA] camera x"), disableMonitor: true, arguments: { - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, { @@ -360,7 +369,7 @@ text: Scratch.translate("[CAMERA] camera y"), disableMonitor: true, arguments: { - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, "---", @@ -370,7 +379,7 @@ text: Scratch.translate("set [CAMERA] camera direction to [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 } + NUM: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 }, }, }, { @@ -379,7 +388,7 @@ text: Scratch.translate("point [CAMERA] camera towards [TARGET]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" } + TARGET: { type: Scratch.ArgumentType.STRING, menu: "TARGETS" }, }, }, "---", @@ -390,7 +399,7 @@ arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, IMG: { type: Scratch.ArgumentType.IMAGE, dataURI: rightArrow }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, }, }, { @@ -400,7 +409,7 @@ arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, IMG: { type: Scratch.ArgumentType.IMAGE, dataURI: leftArrow }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, }, }, { @@ -409,7 +418,7 @@ text: Scratch.translate("[CAMERA] camera direction"), disableMonitor: true, arguments: { - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, "---", @@ -419,7 +428,7 @@ text: Scratch.translate("set [CAMERA] camera zoom to [NUM]%"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, }, }, { @@ -428,7 +437,7 @@ text: Scratch.translate("change [CAMERA] camera zoom by [NUM]"), arguments: { CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, }, }, { @@ -437,12 +446,12 @@ text: Scratch.translate("[CAMERA] camera zoom"), disableMonitor: true, arguments: { - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, { blockType: Scratch.BlockType.LABEL, - text: Scratch.translate("Utility") + text: Scratch.translate("Utility"), }, { opcode: "fixedMouseX", @@ -450,7 +459,7 @@ text: Scratch.translate("mouse x in camera [CAMERA]"), disableMonitor: true, arguments: { - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, { @@ -459,7 +468,7 @@ text: Scratch.translate("mouse y in camera [CAMERA]"), disableMonitor: true, arguments: { - CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" } + CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, }, }, ], @@ -467,40 +476,60 @@ CAMERAS: { acceptReporters: false, items: "getCameras" }, TARGETS: { acceptReporters: true, items: "getTargets" }, OBJECTS: { acceptReporters: true, items: "getObjects" }, - EXACT_OBJECTS: { acceptReporters: true, items: this.getObjects(false) }, + EXACT_OBJECTS: { + acceptReporters: true, + items: this.getObjects(false), + }, BINDS: { acceptReporters: true, items: [ { text: Scratch.translate("bind"), value: "bind" }, - { text: Scratch.translate("unbind"), value: "unbind" } - ] - } + { text: Scratch.translate("unbind"), value: "unbind" }, + ], + }, }, }; } // Helper Funcs getObjects(includeAll) { - const objectNames = [{ text: Scratch.translate("myself"), value: "_myself_" }]; - if (includeAll) objectNames.push({ text: Scratch.translate("all objects"), value: "_all_" }); + const objectNames = [ + { text: Scratch.translate("myself"), value: "_myself_" }, + ]; + if (includeAll) + objectNames.push({ + text: Scratch.translate("all objects"), + value: "_all_", + }); objectNames.push({ text: Scratch.translate("Stage"), value: "_stage_" }); - if (runtime.ext_videoSensing) objectNames.push({ text: Scratch.translate("Video Layer"), value: "_video_" }); - if (runtime.ext_pen) objectNames.push({ text: Scratch.translate("Pen Layer"), value: "_pen_" }); + if (runtime.ext_videoSensing) + objectNames.push({ + text: Scratch.translate("Video Layer"), + value: "_video_", + }); + if (runtime.ext_pen) + objectNames.push({ + text: Scratch.translate("Pen Layer"), + value: "_pen_", + }); // Custom Drawable Layer (CST's 3D or Simple3D Exts for Example) for (var i = 0; i < render._allDrawables.length; i++) { const drawable = render._allDrawables[i]; - if (drawable !== undefined && drawable.customDrawableName !== undefined) objectNames.push({ - text: drawable.customDrawableName, value: `${i}=SP-custLayer` - }); + if (drawable !== undefined && drawable.customDrawableName !== undefined) + objectNames.push({ + text: drawable.customDrawableName, + value: `${i}=SP-custLayer`, + }); } // Sprites const targets = runtime.targets; for (let i = 1; i < targets.length; i++) { const target = targets[i]; - if (target.isOriginal) objectNames.push({ text: target.getName(), value: target.getName() }); + if (target.isOriginal) + objectNames.push({ text: target.getName(), value: target.getName() }); } return objectNames.length > 0 ? objectNames : [""]; } @@ -508,12 +537,13 @@ getTargets() { const targetNames = [ { text: Scratch.translate("myself"), value: "_myself_" }, - { text: Scratch.translate("Stage"), value: "_stage_" } + { text: Scratch.translate("Stage"), value: "_stage_" }, ]; const targets = runtime.targets; for (let i = 1; i < targets.length; i++) { const target = targets[i]; - if (target.isOriginal) targetNames.push({ text: target.getName(), value: target.getName() }); + if (target.isOriginal) + targetNames.push({ text: target.getName(), value: target.getName() }); } return targetNames.length > 0 ? targetNames : [""]; } @@ -524,20 +554,26 @@ refreshBlocks() { if (isEditor) { - runtime.once("BEFORE_EXECUTE", () => { runtime.requestBlocksUpdate() }); - runtime.extensionStorage["SPcamera"] = { cams: Object.keys(allCameras) }; + runtime.once("BEFORE_EXECUTE", () => { + runtime.requestBlocksUpdate(); + }); + runtime.extensionStorage["SPcamera"] = { + cams: Object.keys(allCameras), + }; } } addCamera() { - openModal(Scratch.translate("New Camera name:"), (e, modal) =>{ - const name = modal.querySelector(`input[class^="prompt_variable-name-text-input_"]`); + openModal(Scratch.translate("New Camera name:"), (e, modal) => { + const name = modal.querySelector( + `input[class^="prompt_variable-name-text-input_"]` + ); if (name.value) { allCameras[name.value] = { xy: [0, 0], zoom: 1, dir: 0, - binds: [] + binds: [], }; this.refreshBlocks(); } @@ -546,8 +582,10 @@ } removeCamera() { - openModal(Scratch.translate("Remove Camera named:"), (e, modal) =>{ - const name = modal.querySelector(`input[class^="prompt_variable-name-text-input_"]`); + openModal(Scratch.translate("Remove Camera named:"), (e, modal) => { + const name = modal.querySelector( + `input[class^="prompt_variable-name-text-input_"]` + ); if (name.value) { if (name.value === "default") return; // never delete the placeholder delete allCameras[name.value]; @@ -564,22 +602,25 @@ const penLayer = runtime.ext_pen?._penDrawableId; const videoLayer = runtime.ioDevices.video._drawable; - if (name === "_pen_") return penLayer ? { drawableID: penLayer } : undefined; - else if (name === "_video_") return videoLayer !== -1 ? { drawableID: videoLayer } : undefined; + if (name === "_pen_") + return penLayer ? { drawableID: penLayer } : undefined; + else if (name === "_video_") + return videoLayer !== -1 ? { drawableID: videoLayer } : undefined; else if (name.includes("=SP-custLayer")) { const drawableID = parseInt(name); - if (render._allDrawables[drawableID]?.customDrawableName !== undefined) return { - drawableID - }; + if (render._allDrawables[drawableID]?.customDrawableName !== undefined) + return { + drawableID, + }; } return runtime.getSpriteTargetByName(name); } translateAngledMovement(xy, steps, direction) { - const radians = (direction) * (Math. PI / 180); + const radians = direction * (Math.PI / 180); return [ - xy[0] + (steps * Math.cos(radians)), - xy[1] + (steps * Math.sin(radians)) + xy[0] + steps * Math.cos(radians), + xy[1] + steps * Math.sin(radians), ]; } @@ -618,15 +659,13 @@ setSpaceColor(args) { const rgb = Cast.toRgbColorList(args.COLOR); - render.setBackgroundColor( - rgb[0] / 255, rgb[1] / 255, rgb[2] / 255 - ); + render.setBackgroundColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); } spaceColor() { const rgb = render._backgroundColor3b; let decimal = (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]; - if (decimal < 0) decimal += 0xFFFFFF + 1; + if (decimal < 0) decimal += 0xffffff + 1; const hex = Number(decimal).toString(16); return `#${"000000".substr(0, 6 - hex.length)}${hex}`; } @@ -634,7 +673,7 @@ setXY(args) { allCameras[args.CAMERA].xy = [ Cast.toNumber(args.X) * -1, - Cast.toNumber(args.Y) * -1 + Cast.toNumber(args.Y) * -1, ]; updateCamera(args.CAMERA); } @@ -730,7 +769,10 @@ fixedMouseX(args, util) { const camData = allCameras[args.CAMERA]; return translatePosition( - [util.ioQuery("mouse", "getScratchX"), util.ioQuery("mouse", "getScratchY")], + [ + util.ioQuery("mouse", "getScratchX"), + util.ioQuery("mouse", "getScratchY"), + ], false, camData )[0]; @@ -739,7 +781,10 @@ fixedMouseY(args, util) { const camData = allCameras[args.CAMERA]; return translatePosition( - [util.ioQuery("mouse", "getScratchX"), util.ioQuery("mouse", "getScratchY")], + [ + util.ioQuery("mouse", "getScratchX"), + util.ioQuery("mouse", "getScratchY"), + ], false, camData )[1]; From cca770b888c376729f48e50414232e9713f0b037 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:15:24 -0800 Subject: [PATCH 04/14] Update extensions.json --- extensions/extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/extensions.json b/extensions/extensions.json index 9027c00ac9..97cd039e69 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -70,7 +70,7 @@ "mbw/xml", "numerical-encoding-2", "cs2627883/numericalencoding", - "DT/cameracontrols", + "SharkPool/Camera", "TheShovel/CanvasEffects", "Longboost/color_channels", "CST1229/zip", From caf9077bf0283dcadbbefe24b44918b0f433bc6d Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:17:00 -0800 Subject: [PATCH 05/14] look at me! I dont know how to spell license --- extensions/SharkPool/Camera.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index a6de836542..f72c9f7680 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -2,7 +2,7 @@ // ID: SPcamera // Description: Move the visible part of the stage. // By: SharkPool -// Licence: MIT +// License: MIT // Version V.1.0.0 From 0f825afe4a3e4c4c60d73a5e72cfd89e65b5d878 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 22 Jan 2025 22:17:10 -0800 Subject: [PATCH 06/14] Create Camera.svg --- images/SharkPool/Camera.svg | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 images/SharkPool/Camera.svg diff --git a/images/SharkPool/Camera.svg b/images/SharkPool/Camera.svg new file mode 100644 index 0000000000..10c8b34511 --- /dev/null +++ b/images/SharkPool/Camera.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b8e32d41d02b8faa5e7bf71fa1370b9332b7b542 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 25 Jan 2025 00:10:09 -0800 Subject: [PATCH 07/14] Camera.js -- improvements --- extensions/SharkPool/Camera.js | 79 ++++++++++++++++------------------ 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index f72c9f7680..8f4c8742f5 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT -// Version V.1.0.0 +// Version V.1.0.01 (function (Scratch) { "use strict"; @@ -37,6 +37,7 @@ }; // TODO add support for interpolation at some point + // we need a api to allow pushing interpolation data runtime.setInterpolation(false); runtime.runtimeOptions.fencing = false; render.offscreenTouching = true; @@ -44,35 +45,13 @@ // custom gui function openModal(titleName, func) { // in a Button Context, ScratchBlocks always exists - ScratchBlocks.Variables.createVariable( - ScratchBlocks.mainWorkspace, - null, + ScratchBlocks.prompt( + titleName, + "", + (value) => func(value), + Scratch.translate("Camera Manager"), "broadcast_msg" ); - const modalHolder = document.querySelector(`div[class="ReactModalPortal"]`); - const modal = modalHolder.querySelector(`div[class="box_box_2jjDp"]`); - - modal.querySelector(`div[class^="modal_header-item_"]`).textContent = - Scratch.translate("Camera Manager"); - modal.querySelector(`div[class^="prompt_label_"]`).textContent = titleName; - - const button = modal.querySelector(`button[class^="prompt_ok-button_"]`); - const cloneOkay = button.cloneNode(true); - button.parentNode.appendChild(cloneOkay); - button.remove(); - cloneOkay.addEventListener("click", (e) => { - func(e, modal); - cloneOkay.previousElementSibling.click(); - runtime.requestBlocksUpdate(); - }); - - modalHolder.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - func(e, modal); - cloneOkay.previousElementSibling.click(); - runtime.requestBlocksUpdate(); - } - }); } // camera utils @@ -110,6 +89,7 @@ } function bindDrawable(drawable, camera) { + if (!drawable[cameraSymbol]) setupState(drawable); const camSystem = drawable[cameraSymbol]; if (camSystem.name === camera) return; @@ -564,12 +544,9 @@ } addCamera() { - openModal(Scratch.translate("New Camera name:"), (e, modal) => { - const name = modal.querySelector( - `input[class^="prompt_variable-name-text-input_"]` - ); - if (name.value) { - allCameras[name.value] = { + openModal(Scratch.translate("New Camera name:"), (name) => { + if (name) { + allCameras[name] = { xy: [0, 0], zoom: 1, dir: 0, @@ -577,21 +554,16 @@ }; this.refreshBlocks(); } - e.stopPropagation(); }); } removeCamera() { - openModal(Scratch.translate("Remove Camera named:"), (e, modal) => { - const name = modal.querySelector( - `input[class^="prompt_variable-name-text-input_"]` - ); - if (name.value) { - if (name.value === "default") return; // never delete the placeholder - delete allCameras[name.value]; + openModal(Scratch.translate("Remove Camera named:"), (name) => { + if (name) { + if (name === "default") return; // never delete the placeholder + delete allCameras[name]; this.refreshBlocks(); } - e.stopPropagation(); }); } @@ -626,6 +598,7 @@ // Block Funcs bindTarget(args, util) { + if (!allCameras[args.CAMERA]) return; const target = this.getTarget(args.TARGET, util); if (!target) return; if (target === "_all_") { @@ -639,6 +612,7 @@ } unbindTarget(args, util) { + if (!allCameras[args.CAMERA]) return; const target = this.getTarget(args.TARGET, util); if (!target) return; if (target === "_all_") { @@ -671,6 +645,7 @@ } setXY(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].xy = [ Cast.toNumber(args.X) * -1, Cast.toNumber(args.Y) * -1, @@ -679,6 +654,7 @@ } moveSteps(args) { + if (!allCameras[args.CAMERA]) return; const cam = allCameras[args.CAMERA]; const steps = Cast.toNumber(args.NUM) * -1; cam.xy = this.translateAngledMovement(cam.xy, steps, cam.dir); @@ -686,11 +662,13 @@ } setX(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].xy[0] = Cast.toNumber(args.NUM) * -1; updateCamera(args.CAMERA); } changeX(args) { + if (!allCameras[args.CAMERA]) return; const cam = allCameras[args.CAMERA]; const steps = Cast.toNumber(args.NUM) * -1; cam.xy = this.translateAngledMovement(cam.xy, steps, 0); @@ -698,11 +676,13 @@ } setY(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].xy[1] = Cast.toNumber(args.NUM) * -1; updateCamera(args.CAMERA); } changeY(args) { + if (!allCameras[args.CAMERA]) return; const cam = allCameras[args.CAMERA]; const steps = Cast.toNumber(args.NUM) * -1; cam.xy = this.translateAngledMovement(cam.xy, steps, 90); @@ -710,6 +690,7 @@ } goToObject(args, util) { + if (!allCameras[args.CAMERA]) return; const target = this.getTarget(args.TARGET, util); if (target) { allCameras[args.CAMERA].xy = [target.x, target.y]; @@ -718,29 +699,35 @@ } getX(args) { + if (!allCameras[args.CAMERA]) return 0; return allCameras[args.CAMERA].xy[0] * -1; } getY(args) { + if (!allCameras[args.CAMERA]) return 0; return allCameras[args.CAMERA].xy[1] * -1; } setDirection(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].dir = Cast.toNumber(args.NUM) - 90; updateCamera(args.CAMERA); } turnCamRight(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].dir -= Cast.toNumber(args.NUM); updateCamera(args.CAMERA); } turnCamLeft(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].dir += Cast.toNumber(args.NUM); updateCamera(args.CAMERA); } pointCamera(args, util) { + if (!allCameras[args.CAMERA]) return; const target = this.getTarget(args.TARGET, util); if (target) { allCameras[args.CAMERA].dir = target.direction - 90; @@ -749,24 +736,29 @@ } getDirection(args) { + if (!allCameras[args.CAMERA]) return 0; return allCameras[args.CAMERA].dir + 90; } setZoom(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].zoom = Cast.toNumber(args.NUM) / 100; updateCamera(args.CAMERA); } changeZoom(args) { + if (!allCameras[args.CAMERA]) return; allCameras[args.CAMERA].zoom += Cast.toNumber(args.NUM) / 100; updateCamera(args.CAMERA); } getZoom(args) { + if (!allCameras[args.CAMERA]) return 0; return allCameras[args.CAMERA].zoom * 100; } fixedMouseX(args, util) { + if (!allCameras[args.CAMERA]) return 0; const camData = allCameras[args.CAMERA]; return translatePosition( [ @@ -779,6 +771,7 @@ } fixedMouseY(args, util) { + if (!allCameras[args.CAMERA]) return 0; const camData = allCameras[args.CAMERA]; return translatePosition( [ From eb33c0d648f761f99a7429cde16f7451d016ed95 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:46:39 -0800 Subject: [PATCH 08/14] Camera.js -- Requested Change --- extensions/SharkPool/Camera.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 8f4c8742f5..dbbfddc5aa 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT -// Version V.1.0.01 +// Version V.1.0.02 (function (Scratch) { "use strict"; @@ -13,8 +13,6 @@ const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MSIgaGVpZ2h0PSI0MSIgdmlld0JveD0iMCAwIDQxIDQxIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgMjAuNUMwIDkuMTc4IDkuMTc4IDAgMjAuNSAwUzQxIDkuMTc4IDQxIDIwLjUgMzEuODIyIDQxIDIwLjUgNDEgMCAzMS44MjIgMCAyMC41IiBmaWxsPSIjMjg1MWM5Ii8+PHBhdGggZD0iTTIuMzc4IDIwLjVjMC0xMC4wMDkgOC4xMTMtMTguMTIyIDE4LjEyMi0xOC4xMjJTMzguNjIyIDEwLjQ5MSAzOC42MjIgMjAuNSAzMC41MDkgMzguNjIyIDIwLjUgMzguNjIyIDIuMzc4IDMwLjUwOSAyLjM3OCAyMC41IiBmaWxsPSIjNTE3YWY1Ii8+PHBhdGggZD0iTTMxLjg3MSAxNS4wMDdjLjA3My4xNDkuMTI5LjI4LjEyOS4yNDN2MTAuM2MwIC4yODMtLjIzMy41LS41LjVhLjMuMyAwIDAgMS0uMTQ2LS4wNTRsLS4wOTctLjA3NUwyNSAyMi4xNjd2Mi4yODNjMCAxLjk0Ny0xLjU5OCAzLjYtMy41IDMuNmgtOC45Yy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNmg4LjljMS45MzcgMCAzLjUgMS41OSAzLjUgMy42djIuMzJsNi4xNzItNGMuMjctLjE2Mi41NTQtLjEwNS43LjEzN3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; - const cameraIcon = - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj48ZyBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTI2LjM3MSA5LjUwN2MuMDczLjE0OS4xMjkuMjguMTI5LjI0M3YxMC4zYzAgLjI4My0uMjMzLjUtLjUuNWEuMy4zIDAgMCAxLS4xNDYtLjA1NGwtLjA5Ny0uMDc1LTYuMjU3LTMuNzU0djIuMjgzYzAgMS45NDctMS41OTggMy42LTMuNSAzLjZINy4xYy0yLjAxNS0uMDg4LTMuNi0xLjY3My0zLjYtMy42di03LjljMC0yLjAyNCAxLjU3Ni0zLjYgMy42LTMuNkgxNmMxLjkzNyAwIDMuNSAxLjU5IDMuNSAzLjZ2Mi4zMmw2LjE3Mi00Yy4yNy0uMTYyLjU1NC0uMTA1LjcuMTM3eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0wIDMwVjBoMzB2MzB6IiBmaWxsPSJub25lIi8+PC9nPjwvc3ZnPg=="; const rightArrow = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIyLjY4IDEyLjJhMS42IDEuNiAwIDAgMS0xLjI3LjYzaC03LjY5YTEuNTkgMS41OSAwIDAgMS0xLjE2LTIuNThsMS4xMi0xLjQxYTQuODIgNC44MiAwIDAgMC0zLjE0LS43NyA0LjMgNC4zIDAgMCAwLTIgLjhBNC4yNSA0LjI1IDAgMCAwIDcuMiAxMC42YTUuMDYgNS4wNiAwIDAgMCAuNTQgNC42MkE1LjU4IDUuNTggMCAwIDAgMTIgMTcuNzRhMi4yNiAyLjI2IDAgMCAxLS4xNiA0LjUyQTEwLjI1IDEwLjI1IDAgMCAxIDMuNzQgMThhMTAuMTQgMTAuMTQgMCAwIDEtMS40OS05LjIyIDkuNyA5LjcgMCAwIDEgMi44My00LjE0QTkuOSA5LjkgMCAwIDEgOS42NiAyLjVhMTAuNjYgMTAuNjYgMCAwIDEgNy43MiAxLjY4bDEuMDgtMS4zNWExLjU3IDEuNTcgMCAwIDEgMS4yNC0uNiAxLjYgMS42IDAgMCAxIDEuNTQgMS4yMWwxLjcgNy4zN2ExLjU3IDEuNTcgMCAwIDEtLjI2IDEuMzkiIHN0eWxlPSJmaWxsOiMwMDA7b3BhY2l0eTouMiIvPjxwYXRoIGQ9Ik0yMS4zOCAxMS44M2gtNy42MWEuNTkuNTkgMCAwIDEtLjQzLTFsMS43NS0yLjE5YTUuOSA1LjkgMCAwIDAtNC43LTEuNTggNS4wNyA1LjA3IDAgMCAwLTQuMTEgMy4xN0E2IDYgMCAwIDAgNyAxNS43N2E2LjUxIDYuNTEgMCAwIDAgNSAyLjkyIDEuMzEgMS4zMSAwIDAgMS0uMDggMi42MiA5LjMgOS4zIDAgMCAxLTcuMzUtMy44MiA5LjE2IDkuMTYgMCAwIDEtMS40LTguMzdBOC41IDguNSAwIDAgMSA1LjcxIDUuNGE4Ljc2IDguNzYgMCAwIDEgNC4xMS0xLjkyIDkuNyA5LjcgMCAwIDEgNy43NSAyLjA3bDEuNjctMi4xYS41OS41OSAwIDAgMSAxIC4yMUwyMiAxMS4wOGEuNTkuNTkgMCAwIDEtLjYyLjc1IiBzdHlsZT0iZmlsbDojZmZmIi8+PC9zdmc+"; const leftArrow = @@ -220,7 +218,6 @@ opcode: "bindTarget", blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("bind [TARGET] to camera [CAMERA]"), - blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, @@ -230,7 +227,6 @@ opcode: "unbindTarget", blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("unbind [TARGET] from camera [CAMERA]"), - blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, menu: "OBJECTS" }, CAMERA: { type: Scratch.ArgumentType.STRING, menu: "CAMERAS" }, @@ -240,7 +236,6 @@ opcode: "targetCamera", blockType: Scratch.BlockType.REPORTER, text: Scratch.translate("camera of [TARGET]"), - blockIconURI: cameraIcon, arguments: { TARGET: { type: Scratch.ArgumentType.STRING, @@ -253,7 +248,6 @@ opcode: "setSpaceColor", blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("set background color to [COLOR]"), - blockIconURI: cameraIcon, arguments: { COLOR: { type: Scratch.ArgumentType.COLOR }, }, @@ -262,7 +256,6 @@ opcode: "spaceColor", blockType: Scratch.BlockType.REPORTER, text: Scratch.translate("background color"), - blockIconURI: cameraIcon, }, { blockType: Scratch.BlockType.LABEL, @@ -485,12 +478,12 @@ if (runtime.ext_videoSensing) objectNames.push({ - text: Scratch.translate("Video Layer"), + text: Scratch.translate("video layer"), value: "_video_", }); if (runtime.ext_pen) objectNames.push({ - text: Scratch.translate("Pen Layer"), + text: Scratch.translate("pen layer"), value: "_pen_", }); From 3f5a09bbd926447ae6749962867b64b2534ea00d Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:24:57 -0800 Subject: [PATCH 09/14] Camera.js -- fix dragging to be related to world --- extensions/SharkPool/Camera.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index dbbfddc5aa..972bd47b13 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -131,6 +131,18 @@ } // camera system patches + const ogPostSpriteInfo = vm.postSpriteInfo; + vm.postSpriteInfo = function (data) { + if (this._dragTarget) { + const drawable = render._allDrawables[this._dragTarget.drawableID]; + if (!drawable[cameraSymbol]) setupState(drawable); + const camSystem = drawable[cameraSymbol]; + camSystem.needsRefresh = true; + camSystem.ogXY = [0, 0]; + } + ogPostSpriteInfo.call(this, data); + } + const ogUpdatePosition = render.exports.Drawable.prototype.updatePosition; render.exports.Drawable.prototype.updatePosition = function (position) { if (!this[cameraSymbol]) setupState(this); @@ -686,7 +698,7 @@ if (!allCameras[args.CAMERA]) return; const target = this.getTarget(args.TARGET, util); if (target) { - allCameras[args.CAMERA].xy = [target.x, target.y]; + allCameras[args.CAMERA].xy = [target.x * -1, target.y * -1]; updateCamera(args.CAMERA); } } From c340ff1055c88309d319b16da7e19c63c44b6e4a Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Tue, 28 Jan 2025 03:26:42 +0000 Subject: [PATCH 10/14] [Automated] Format code --- extensions/SharkPool/Camera.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 972bd47b13..32c35ae687 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -141,7 +141,7 @@ camSystem.ogXY = [0, 0]; } ogPostSpriteInfo.call(this, data); - } + }; const ogUpdatePosition = render.exports.Drawable.prototype.updatePosition; render.exports.Drawable.prototype.updatePosition = function (position) { From e84a49a1c6934c806f650c6eff4e95519614858b Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:14:40 -0800 Subject: [PATCH 11/14] Camera.js -- Fix Negative Scaling --- extensions/SharkPool/Camera.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 32c35ae687..857986fcfe 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -133,7 +133,7 @@ // camera system patches const ogPostSpriteInfo = vm.postSpriteInfo; vm.postSpriteInfo = function (data) { - if (this._dragTarget) { + if (this._dragTarget && data.x !== undefined) { const drawable = render._allDrawables[this._dragTarget.drawableID]; if (!drawable[cameraSymbol]) setupState(drawable); const camSystem = drawable[cameraSymbol]; @@ -179,14 +179,20 @@ const camSystem = this[cameraSymbol]; const thisCam = allCameras[camSystem.name]; if (camSystem.needsRefresh) { - // invert camera transformations - scale[0] /= camSystem.ogSZ; - scale[1] /= camSystem.ogSZ; + // invert camera transformations + const safeOgSZ = camSystem.ogSZ !== 0 ? camSystem.ogSZ : 1e-10; + scale[0] /= safeOgSZ; + scale[1] /= safeOgSZ; } - camSystem.ogSZ = thisCam.zoom; - scale[0] *= thisCam.zoom; - scale[1] *= thisCam.zoom; + // avoid dividing 0 by 0 + camSystem.ogSZ = thisCam.zoom || 1e-10; + const safeZoom = thisCam.zoom || 1e-10; + scale[0] *= safeZoom; + scale[1] *= safeZoom; + if (scale[0] === 0) scale[0] = 1e-10 * Math.sign(safeZoom); + if (scale[1] === 0) scale[1] = 1e-10 * Math.sign(safeZoom); + ogUpdateScale.call(this, scale); this.skin?.emitWasAltered(); }; From 62d0380831db10e04915a541b27edd2bf96d6892 Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Tue, 28 Jan 2025 04:17:17 +0000 Subject: [PATCH 12/14] [Automated] Format code --- extensions/SharkPool/Camera.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 857986fcfe..125c5f95da 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -179,10 +179,10 @@ const camSystem = this[cameraSymbol]; const thisCam = allCameras[camSystem.name]; if (camSystem.needsRefresh) { - // invert camera transformations - const safeOgSZ = camSystem.ogSZ !== 0 ? camSystem.ogSZ : 1e-10; - scale[0] /= safeOgSZ; - scale[1] /= safeOgSZ; + // invert camera transformations + const safeOgSZ = camSystem.ogSZ !== 0 ? camSystem.ogSZ : 1e-10; + scale[0] /= safeOgSZ; + scale[1] /= safeOgSZ; } // avoid dividing 0 by 0 From eb39826cb1435656d6c811f66a9169c3b8eb3bb4 Mon Sep 17 00:00:00 2001 From: Cubester Date: Mon, 27 Jan 2025 23:30:13 -0500 Subject: [PATCH 13/14] \n --- extensions/SharkPool/Camera.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index 125c5f95da..ff270ef343 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -8,6 +8,7 @@ (function (Scratch) { "use strict"; + if (!Scratch.extensions.unsandboxed) throw new Error("Camera must run unsandboxed!"); From baad95969e6934764e36f0b47d839d236556e74a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:14:48 -0800 Subject: [PATCH 14/14] Camera.js -- small improvements --- extensions/SharkPool/Camera.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/extensions/SharkPool/Camera.js b/extensions/SharkPool/Camera.js index ff270ef343..f6172c5334 100644 --- a/extensions/SharkPool/Camera.js +++ b/extensions/SharkPool/Camera.js @@ -23,7 +23,6 @@ const vm = Scratch.vm; const runtime = vm.runtime; const render = vm.renderer; - const isEditor = typeof scaffolding === "undefined"; const cameraSymbol = Symbol("SPcameraData"); let allCameras = { @@ -545,14 +544,10 @@ } refreshBlocks() { - if (isEditor) { - runtime.once("BEFORE_EXECUTE", () => { - runtime.requestBlocksUpdate(); - }); - runtime.extensionStorage["SPcamera"] = { - cams: Object.keys(allCameras), - }; - } + runtime.requestBlocksUpdate(); + runtime.extensionStorage["SPcamera"] = { + cams: Object.keys(allCameras), + }; } addCamera() {