From 3497a94d389ca449473b75f8135b551d845b8307 Mon Sep 17 00:00:00 2001 From: SharkPool-SP <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 15 Oct 2023 14:16:30 -0700 Subject: [PATCH 01/17] Create Animations.js --- extensions/SharkPool/Animations.js | 824 +++++++++++++++++++++++++++++ 1 file changed, 824 insertions(+) create mode 100644 extensions/SharkPool/Animations.js diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js new file mode 100644 index 0000000000..116ba6e7f1 --- /dev/null +++ b/extensions/SharkPool/Animations.js @@ -0,0 +1,824 @@ +// Name: Animations +// ID: SPanimations +// Description: Play Animations for your Sprites +// By: SharkPool + +// Version V.1.0.0 + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("Animations must run unsandboxed"); + } + + const vm = Scratch.vm; + const runtime = vm.runtime; + let allAnimations = []; + let keyFramesPlaying = []; + + const menuIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjcuNTA5OTIiIGhlaWdodD0iMTI3LjUwOTkyIiB2aWV3Qm94PSIwLDAsMTI3LjUwOTkyLDEyNy41MDk5MiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE3Ni4yNDUwNCwtMTE2LjI0NTA0KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTc2LjI0NTA0LDE4MGMwLC0zNS4yMTA4OSAyOC41NDQwNywtNjMuNzU0OTYgNjMuNzU0OTYsLTYzLjc1NDk2YzM1LjIxMDg5LDAgNjMuNzU0OTYsMjguNTQ0MDcgNjMuNzU0OTYsNjMuNzU0OTZjMCwzNS4yMTA4OSAtMjguNTQ0MDcsNjMuNzU0OTYgLTYzLjc1NDk2LDYzLjc1NDk2Yy0zNS4yMTA4OSwwIC02My43NTQ5NiwtMjguNTQ0MDcgLTYzLjc1NDk2LC02My43NTQ5NnoiIGZpbGw9IiM0NDQ0NDQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIi8+PHBhdGggZD0iTTE4Mi4xMTk0OSwxODBjMCwtMzEuOTY2NTMgMjUuOTEzOTksLTU3Ljg4MDUxIDU3Ljg4MDUxLC01Ny44ODA1MWMzMS45NjY1MywwIDU3Ljg4MDUxLDI1LjkxMzk5IDU3Ljg4MDUxLDU3Ljg4MDUxYzAsMzEuOTY2NTMgLTI1LjkxMzk5LDU3Ljg4MDUxIC01Ny44ODA1MSw1Ny44ODA1MWMtMzEuOTY2NTMsMCAtNTcuODgwNTEsLTI1LjkxMzk5IC01Ny44ODA1MSwtNTcuODgwNTF6IiBmaWxsPSIjN2E3YTdhIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0xOTkuOTQwMTEsMjA2Ljc3NDU5Yy01LjYxMjgsLTguMTI0OCAtMy41NzY0MywtMTkuMjYxMzMgNC41NDgzNywtMjQuODc0MTRjOC4xMjQ4LC01LjYxMjggMTkuMjYxMzMsLTMuNTc2NDMgMjQuODc0MTMsNC41NDgzN2M1LjYxMjgsOC4xMjQ4IDMuNTc2NDMsMTkuMjYxMzMgLTQuNTQ4MzYsMjQuODc0MTRjLTguMTI0OCw1LjYxMjggLTE5LjI2MTMzLDMuNTc2NDMgLTI0Ljg3NDE0LC00LjU0ODM3eiIgZmlsbD0iIzg5ODk4OSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik0yMDQuMzU1MDMsMTk2LjU4ODg2Yy02LjMzMDY1LC05LjE2MzkyIC00LjAzMzg0LC0yMS43MjQ3NSA1LjEzMDA4LC0yOC4wNTU0YzkuMTYzOTIsLTYuMzMwNjUgMjEuNzI0NzUsLTQuMDMzODQgMjguMDU1NCw1LjEzMDA4YzYuMzMwNjUsOS4xNjM5MiA0LjAzMzg0LDIxLjcyNDc1IC01LjEzMDA4LDI4LjA1NTRjLTkuMTYzOTIsNi4zMzA2NSAtMjEuNzI0NzUsNC4wMzM4NCAtMjguMDU1NDEsLTUuMTMwMDh6IiBmaWxsPSIjOWQ5ZDlkIiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTIxMi4zOTAwMiwxODkuNTcyNTVjLTcuNDE2MTIsLTEwLjczNTE4IC00LjcyNTQ4LC0yNS40NDk3MSA2LjAwOTcsLTMyLjg2NTgyYzEwLjczNTE4LC03LjQxNjEyIDI1LjQ0OTcxLC00LjcyNTQ5IDMyLjg2NTgyLDYuMDA5NjljNy40MTYxMiwxMC43MzUxOCA0LjcyNTQ4LDI1LjQ0OTcxIC02LjAwOTY5LDMyLjg2NTgzYy0xMC43MzUxOCw3LjQxNjEyIC0yNS40NDk3MSw0LjcyNTQ4IC0zMi44NjU4MywtNi4wMDk2OXoiIGZpbGw9IiNhZWFlYWUiIHN0cm9rZT0iIzdhN2E3YSIgc3Ryb2tlLXdpZHRoPSIyLjUiLz48cGF0aCBkPSJNMjIzLjU1NTkxLDE4NS43NzU4OWMtOC4wMzQxMiwtMTEuNjI5NzcgLTUuMTE5MjcsLTI3LjU3MDUyIDYuNTEwNSwtMzUuNjA0NjRjMTEuNjI5NzcsLTguMDM0MTIgMjcuNTcwNTIsLTUuMTE5MjcgMzUuNjA0NjQsNi41MTA1YzguMDM0MTIsMTEuNjI5NzcgNS4xMTkyNywyNy41NzA1MiAtNi41MTA1LDM1LjYwNDY0Yy0xMS42Mjk3Nyw4LjAzNDEyIC0yNy41NzA1Miw1LjExOTI3IC0zNS42MDQ2NCwtNi41MTA1eiIgZmlsbD0iI2JhYmFiYSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik0yMzEuOTYyODUsMTg5LjYxOTY5Yy04LjgyODcxLC0xMi43Nzk5NyAtNS42MjU1OCwtMzAuMjk3MjcgNy4xNTQzOSwtMzkuMTI1OTdjMTIuNzc5OTcsLTguODI4NzEgMzAuMjk3MjcsLTUuNjI1NTggMzkuMTI1OTcsNy4xNTQzOWM4LjgyODcxLDEyLjc3OTk3IDUuNjI1NTgsMzAuMjk3MjcgLTcuMTU0MzksMzkuMTI1OTdjLTEyLjc3OTk3LDguODI4NzEgLTMwLjI5NzI3LDUuNjI1NTggLTM5LjEyNTk3LC03LjE1NDM5eiIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiLz48L2c+PC9nPjwvc3ZnPg=="; + + const blockIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4NS4zMzE1MyIgaGVpZ2h0PSI4NS4zMzE1MyIgdmlld0JveD0iMCwwLDg1LjMzMTUzLDg1LjMzMTUzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk3LjMzNDIzLC0xMzcuMzM0MjQpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjgyLjY2NTc2LDEzNy4zMzQyNHY4NS4zMzE1M2gtODUuMzMxNTN2LTg1LjMzMTUzeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiM3YTdhN2EiLz48cGF0aCBkPSJNMjUyLjMwNDA3LDE2Mi42OTU5NWMyLjc2MTQzLDAgNSwyLjIzODU3IDUsNXY0MC40NTkyYzAsMi43NjE0MyAtMi4yMzg1OCw1IC01LDVoLTQwLjQ1OTJjLTIuNzYxNDMsMCAtNSwtMi4yMzg1NyAtNSwtNXYtNDAuNDU5MmMwLC0yLjc2MTQzIDIuMjM4NTcsLTUgNSwtNXoiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjOTI5MjkyIi8+PHBhdGggZD0iTTI2MC4yMjk2LDE1NC43NzA0MmMyLjc2MTQyLDAgNSwyLjIzODU3IDUsNXY0MC40NTkyYzAsMi43NjE0MyAtMi4yMzg1OCw1IC01LDVoLTQwLjQ1OTJjLTIuNzYxNDMsMCAtNSwtMi4yMzg1NyAtNSwtNXYtNDAuNDU5MmMwLC0yLjc2MTQzIDIuMjM4NTcsLTUgNSwtNXoiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjYzBjMGMwIi8+PHBhdGggZD0iTTI2OC4xNTUxMywxNDYuODQ0ODljMi43NjE0MiwwIDUsMi4yMzg1OCA1LDV2NDAuNDU5MmMwLDIuNzYxNDIgLTIuMjM4NTgsNSAtNSw1aC00MC40NTkyYy0yLjc2MTQyLDAgLTUsLTIuMjM4NTggLTUsLTV2LTQwLjQ1OTJjMCwtMi43NjE0MiAyLjIzODU4LC01IDUsLTV6IiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpbmRleCZxdW90OzpudWxsfSIgZmlsbD0iI2ZmZmZmZiIvPjwvZz48L2c+PC9zdmc+"; + + const playIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4NS4zMzE1MyIgaGVpZ2h0PSI4NS4zMzE1MyIgdmlld0JveD0iMCwwLDg1LjMzMTUzLDg1LjMzMTUzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk3LjMzNDIzLC0xMzcuMzM0MjUpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTk3LjMzNDI0LDIyMi42NjU3OHYtODUuMzMxNTNoODUuMzMxNTN2ODUuMzMxNTN6IiBmaWxsPSIjN2E3YTdhIi8+PHBhdGggZD0iTTIyNC43NjQ3MSwxNTMuNTYxNDdjNS40MjY0MSwzLjEzMjk0IDI2Ljc0NzY2LDE1LjQ0Mjc3IDM4LjMwODU4LDIyLjExNzQ3YzQuNTIyMTIsMi42MTA4NSA0LjU4MjEyLDYuMDczNTIgMC4xNDI5Myw4LjYzNjQ5Yy0xMS4zMDM3NCw2LjUyNjIyIC0zMi4xNDE1OSwxOC41NTY5NiAtMzcuNzc1NDcsMjEuODA5NjhjLTIuNzYzODEsMS41OTU2OSAtNS42MDYsMC43MzAwNiAtNS42MDYsLTMuMTk2ODhjMCwtMTIuMjEwNjkgMCwtMzguMTU0NjIgMCwtNDUuMTc1NTJjMCwtMy40MjY0MiAyLjQ1NDg1LC01LjYyMDI2IDQuOTI5OTYsLTQuMTkxMjV6IiBmaWxsPSIjZmZmZmZmIi8+PC9nPjwvZz48L3N2Zz4="; + + const keyIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4NS4zMzE1MyIgaGVpZ2h0PSI4NS4zMzE1MyIgdmlld0JveD0iMCwwLDg1LjMzMTUzLDg1LjMzMTUzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk3LjMzNDIzLC0xMzcuMzM0MjMpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4Mi42NjU3NywxMzcuMzM0MjN2ODUuMzMxNTNoLTg1LjMzMTUzdi04NS4zMzE1M3oiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjN2E3YTdhIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMjA2LjA1MjMxLDE2Mi41NjM4M2MwLC02LjkzMDQ5IDUuNjE4MjcsLTEyLjU0ODc2IDEyLjU0ODc2LC0xMi41NDg3NmM2LjkzMDQ5LDAgMTIuNTQ4NzYsNS42MTgyNyAxMi41NDg3NiwxMi41NDg3NmMwLDYuOTMwNDkgLTUuNjE4MjcsMTIuNTQ4NzYgLTEyLjU0ODc2LDEyLjU0ODc2Yy02LjkzMDQ5LDAgLTEyLjU0ODc2LC01LjYxODI3IC0xMi41NDg3NiwtMTIuNTQ4NzZ6IiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMjM4LjI4MjgsMTkyLjE1MjQ4YzAsLTkuODQ4NTggNy45ODM4NSwtMTcuODMyNDQgMTcuODMyNDQsLTE3LjgzMjQ0YzkuODQ4NTgsMCAxNy44MzI0NCw3Ljk4Mzg1IDE3LjgzMjQ0LDE3LjgzMjQ0YzAsOS44NDg1OCAtNy45ODM4NiwxNy44MzI0NCAtMTcuODMyNDQsMTcuODMyNDRjLTkuODQ4NTgsMCAtMTcuODMyNDQsLTcuOTgzODYgLTE3LjgzMjQ0LC0xNy44MzI0NHoiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yMjEuMTQwOTcsMTY0LjMxODg3bDMwLjM4MTIsMjUuNjI1ODgiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSI1IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L2c+PC9nPjwvc3ZnPg=="; + + class SPanimations { + constructor() { + Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => { + allAnimations.forEach(animationObject => { + const args = { NAME: Object.keys(animationObject)[0] }; + this.stopAnimation(args); + }); + }); + } + + getInfo() { + return { + id: "SPanimations", + name: "Animations", + color1: "#7a7a7a", + color2: "#444444", + color3: "#232323", + menuIconURI, + blockIconURI, + blocks: [ + { + opcode: "createAnimation", + blockType: Scratch.BlockType.COMMAND, + text: "make new animation named [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + opcode: "removeAnimation", + blockType: Scratch.BlockType.COMMAND, + text: "delete animation named [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + opcode: "removeAll", + blockType: Scratch.BlockType.COMMAND, + text: "delete all animations", + }, + { + opcode: "isExists", + blockType: Scratch.BlockType.BOOLEAN, + text: "animation [NAME] exists?", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + blockType: Scratch.BlockType.LABEL, + text: "Frames", + }, + { + opcode: "addFrame", + blockType: Scratch.BlockType.COMMAND, + text: "add [COSTUME] to animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + COSTUME: { + type: Scratch.ArgumentType.COSTUME, + }, + }, + }, + { + opcode: "removeFrame", + blockType: Scratch.BlockType.COMMAND, + text: "remove [COSTUME] from animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + COSTUME: { + type: Scratch.ArgumentType.COSTUME, + }, + }, + }, + { + opcode: "addAllFrames", + blockType: Scratch.BlockType.COMMAND, + text: "add costumes from [COS1] to [COS2] to animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + COS1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + COS2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 5, + }, + }, + }, + { + opcode: "removeAllFrames", + blockType: Scratch.BlockType.COMMAND, + text: "remove frames [COS1] to [COS2] from animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + COS1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + COS2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 5, + }, + }, + }, + { + opcode: "addPause", + blockType: Scratch.BlockType.COMMAND, + text: "add a [SECOND] second pause to animation [NAME] with ID [ID]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + SECOND: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 3, + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "pause1", + }, + }, + }, + { + opcode: "removePause", + blockType: Scratch.BlockType.COMMAND, + text: "remove pause frame from animation [NAME] with ID [ID]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "pause1", + }, + }, + }, + + "---", + + { + opcode: "numFrames", + blockType: Scratch.BlockType.REPORTER, + text: "number of frames in animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + opcode: "frameNames", + blockType: Scratch.BlockType.REPORTER, + text: "all frames in animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + opcode: "frameName", + blockType: Scratch.BlockType.REPORTER, + text: "frame [FRAME] in animation [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + FRAME: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + }, + }, + { + blockType: Scratch.BlockType.LABEL, + text: "Playback", + }, + { + opcode: "setFPS", + blockType: Scratch.BlockType.COMMAND, + text: "set FPS of animation [NAME] to [FPS]", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + FPS: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 30, + }, + }, + }, + { + opcode: "playBack", + blockType: Scratch.BlockType.COMMAND, + text: "play animation [NAME] [TYPE]", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "playBack", + defaultValue: "normally", + }, + }, + }, + { + opcode: "stopAnimation", + blockType: Scratch.BlockType.COMMAND, + text: "stop animation [NAME]", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + + "---", + + { + opcode: "currentFPS", + blockType: Scratch.BlockType.REPORTER, + text: "FPS of animation [NAME]", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + opcode: "isPlaying", + blockType: Scratch.BlockType.BOOLEAN, + text: "is animation [NAME] playing?", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, + { + blockType: Scratch.BlockType.LABEL, + text: "Keyframes", + }, + { + opcode: "addPosition", + blockType: Scratch.BlockType.COMMAND, + text: "add keyframe position to animation [NAME] with ID [ID] starting at x [x] y [y] and ending at x [x2] y [y2]", + blockIconURI: keyIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "key1", + }, + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0, + }, + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0, + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 100, + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0, + }, + }, + }, + { + opcode: "addDirection", + blockType: Scratch.BlockType.COMMAND, + text: "add keyframe direction to animation [NAME] with ID [ID] starting at [DIR1] and ending at [DIR2]", + blockIconURI: keyIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "key1", + }, + DIR1: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 90, + }, + DIR2: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 0, + }, + }, + }, + { + opcode: "addScale", + blockType: Scratch.BlockType.COMMAND, + text: "add keyframe scale to animation [NAME] with ID [ID] starting at [scale]% and ending [scale2]%", + blockIconURI: keyIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "key1", + }, + scale: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 100, + }, + scale2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 150, + }, + }, + }, + { + opcode: "deleteKeyframe", + blockType: Scratch.BlockType.COMMAND, + text: "remove keyframe with ID [ID] from animation [NAME]", + blockIconURI: keyIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "key1", + }, + }, + }, + ], + menus: { + playBack: { + acceptReporters: false, + items: [ + "normally", + "in reverse", + "looped normally", + "looped reversed" + ], + }, + } + }; + } + + createAnimation(args, util) { + allAnimations.push({ + [args.NAME]: { + name: args.NAME, + target: util.target, + fps: 10, + frames: [], + playing: false, + } + }); + } + + removeAnimation(args) { + this.stopPlayingAnimation(args.NAME); + const indexToRemove = allAnimations.findIndex((animation) => Object.keys(animation)[0] === args.NAME); + if (indexToRemove !== -1) { + allAnimations.splice(indexToRemove, 1); + } + } + + removeAll() { + allAnimations.forEach(animationObject => { + const args = { NAME: Object.keys(animationObject)[0] }; + this.stopAnimation(args); + }); + allAnimations = []; + } + + isExists(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + return Boolean(animation); + } + + addFrame(args, util) { + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (util.target !== animation[args.NAME].target) { + console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + return; + } + if (animation) { + animation[args.NAME].frames.push(args.COSTUME); + } + } + + addAllFrames(args, util) { + const maxCos = util.target.sprite.costumes_.length; + args.COS1 = args.COS1 < 1 ? 1 : Math.round(args.COS1) - 1; + args.COS2 = args.COS2 < 1 ? 1 : args.COS2 > util.target.sprite.costumes_.length ? util.target.sprite.costumes_.length : Math.round(args.COS2); + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (util.target !== animation[args.NAME].target) { + console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + return; + } + if (animation) { + for (let i = args.COS1; i < args.COS2; i++) { + animation[args.NAME].frames.push(util.target.getCostumes()[i].name); + } + } + } + + removeAllFrames(args, util) { + const maxCos = util.target.sprite.costumes_.length; + args.COS1 = args.COS1 < 1 ? 1 : Math.round(args.COS1) - 1; + args.COS2 = args.COS2 < 1 ? 1 : args.COS2 > util.target.sprite.costumes_.length ? util.target.sprite.costumes_.length : Math.round(args.COS2); + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + if (util.target !== animation[args.NAME].target) { + console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + return; + } + animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => { + const frameIndex = util.target.getCostumes().findIndex(costume => costume.name === frame); + return frameIndex < args.COS1 || frameIndex >= args.COS2; + }); + } + } + + removeFrame(args, util) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + if (util.target !== animation[args.NAME].target) { + console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + return; + } + animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => frame !== args.COSTUME); + } + } + + addPause(args, util) { + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (animation) { + const SECS = Math.abs(args.SECOND) * 1000; + animation[args.NAME].frames.push({ + [`spKF4!PZ${args.ID}`]: { + secs: SECS, + } + }); + } + } + + removePause(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => !frame.includes(args.ID)); + } + } + + numFrames(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + return animation[args.NAME].frames.length; + } else { + return "Animation Doesnt Exist!"; + } + } + + frameNames(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + return JSON.stringify(animation[args.NAME].frames); + } else { + return "Animation Doesnt Exist!"; + } + } + + frameName(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + const newFrame = Math.abs(Math.round(args.FRAME)) - 1; + return (animation[args.NAME].frames[newFrame]) ? JSON.stringify(animation[args.NAME].frames[newFrame]) : ""; + } else { + return "Animation Doesnt Exist!"; + } + } + + setFPS(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + animation[args.NAME].fps = args.FPS === 0 ? 1 : Math.abs(Scratch.Cast.toNumber(args.FPS)); + } + } + + playBack(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + this.stopPlayingAnimation(args.NAME); + //stoping animation is not instant, this is to prevent dual animations + setTimeout(() => { + const myAnimation = animation[args.NAME]; + myAnimation.playing = true; + const target = myAnimation.target; + let frameIndex = args.TYPE.includes("reverse") ? myAnimation.frames.length - 1 : 0; + const numFrames = myAnimation.frames.length; + + const playNextFrame = () => { + if (myAnimation.playing === true) { + if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("spKF4!"))) { + if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("PZ"))) { + const keys = Object.keys(myAnimation.frames[frameIndex]); + for (const key of keys) { + const delayTime = myAnimation.frames[frameIndex][key].secs; + setTimeout(() => { + handleNextFrame(); + }, delayTime); + return; + } + } else { + this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation); + } + } else { + this._setCostume(target, myAnimation.frames[frameIndex]); + } + handleNextFrame(); + } else { + myAnimation.playing = false; + } + }; + + const handleNextFrame = () => { + if (args.TYPE.includes("reverse")) { + frameIndex--; + if (frameIndex < 0) { + if (args.TYPE.includes("looped")) { + frameIndex = numFrames - 1; + } else { + myAnimation.playing = false; + return; + } + } + } else { + frameIndex++; + if (frameIndex >= numFrames) { + if (args.TYPE.includes("looped")) { + frameIndex = 0; + } else { + myAnimation.playing = false; + return; + } + } + } + setTimeout(playNextFrame, 1000 / myAnimation.fps); + }; + playNextFrame(); + }, 100); + } + } + + stopAnimation(args) { + this.stopPlayingAnimation(args.NAME); + } + + stopPlayingAnimation(name) { + const animation = allAnimations.find((animation) => animation[name]); + if (animation) { + animation[name].playing = false; + } + } + + currentFPS(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + return animation[args.NAME].fps; + } else { + return "Animation Doesnt Exist!"; + } + } + + isPlaying(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + return animation[args.NAME].playing; + } else { + return "Animation Doesnt Exist!"; + } + } + + _setCostume(target, requestedCostume, optZeroIndex) { + // ripped from vm + if (typeof requestedCostume === "number") { + target.setCostume(optZeroIndex ? requestedCostume : requestedCostume - 1); + } else { + const costumeIndex = target.getCostumeIndexByName(requestedCostume.toString()); + if (costumeIndex !== -1) { + target.setCostume(costumeIndex); + } else if (requestedCostume === "next costume") { + target.setCostume(target.currentCostume + 1); + } else if (requestedCostume === "previous costume") { + target.setCostume(target.currentCostume - 1); + } else if (!(isNaN(requestedCostume) || Cast.isWhiteSpace(requestedCostume))) { + target.setCostume(optZeroIndex ? Number(requestedCostume) : Number(requestedCostume) - 1); + } + } + return []; + } + + addPosition(args, util) { + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (animation) { + const keyframe = { + [`spKF4!XY${args.ID}`]: { + x1: args.x, + y1: args.y, + x2: args.x2, + y2: args.y2, + } + }; + animation[args.NAME].frames.push(keyframe); + } + } + + addDirection(args, util) { + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (animation) { + const keyframe = { + [`spKF4!DIR${args.ID}`]: { + dir1: args.DIR1, + dir2: args.DIR2, + } + }; + animation[args.NAME].frames.push(keyframe); + } + } + + addScale(args, util) { + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (animation) { + const keyframe = { + [`spKF4!SZ${args.ID}`]: { + size1: args.scale, + size2: args.scale2, + } + }; + animation[args.NAME].frames.push(keyframe); + } + } + + deleteKeyframe(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => { + for (const key in frame) { + if (key.startsWith("spKF4!") && key.endsWith(args.ID)) { + return false; + } + } + return true; + }); + } + } + + _setKeyframe(target, keyframe, data) { + if (!keyFramesPlaying.some(item => JSON.stringify(item) === JSON.stringify([data, keyframe]))) { + keyFramesPlaying.push([data, keyframe]); + } + keyFramesPlaying.push([data, keyframe]); + const key = keyframe[Object.keys(keyframe)[0]]; + const animationDuration = data.fps * 20; + let startTime; + if (JSON.stringify(keyframe).includes("XY")) { + const startX = key.x1; + const startY = key.y1; + const deltaX = key.x2 - key.x1; + const deltaY = key.y2 - key.y1; + const animateXY = (timestamp) => { + if (!startTime) { + startTime = timestamp; + } + const elapsedTime = timestamp - startTime; + const progress = Math.min(elapsedTime / animationDuration, 1); + const newX = startX + deltaX * progress; + const newY = startY + deltaY * progress; + target.setXY(newX, newY); + if (progress < 1) { + requestAnimationFrame(animateXY); + } + }; + requestAnimationFrame(animateXY); + } else if (JSON.stringify(keyframe).includes("DIR")) { + const startDir = key.dir1; + const deltaDir = key.dir2 - key.dir1; + const animateDirection = (timestamp) => { + if (!startTime) { + startTime = timestamp; + } + const elapsedTime = timestamp - startTime; + const progress = Math.min(elapsedTime / animationDuration, 1); + const newDirection = startDir + deltaDir * progress; + target.setDirection(newDirection); + if (progress < 1) { + requestAnimationFrame(animateDirection); + } + }; + requestAnimationFrame(animateDirection); + } else if (JSON.stringify(keyframe).includes("SZ")) { + const startSize = key.size1; + const deltaSize = key.size2 - key.size1; + const animateSize = (timestamp) => { + if (!startTime) { + startTime = timestamp; + } + const elapsedTime = timestamp - startTime; + const progress = Math.min(elapsedTime / animationDuration, 1); + const newSize = startSize + deltaSize * progress; + target.setSize(newSize); + if (progress < 1) { + requestAnimationFrame(animateSize); + } + }; + requestAnimationFrame(animateSize); + } + keyFramesPlaying = keyFramesPlaying.filter(item => JSON.stringify(item) !== JSON.stringify([data, keyframe])); + } + } + + Scratch.extensions.register(new SPanimations()); +})(Scratch); From 4730307135e55f40a6b5efadd5d6c6f7e7510d0c Mon Sep 17 00:00:00 2001 From: SharkPool-SP <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 15 Oct 2023 14:26:48 -0700 Subject: [PATCH 02/17] Update Animations.js --- extensions/SharkPool/Animations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 116ba6e7f1..403c8163a0 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -682,7 +682,7 @@ target.setCostume(target.currentCostume + 1); } else if (requestedCostume === "previous costume") { target.setCostume(target.currentCostume - 1); - } else if (!(isNaN(requestedCostume) || Cast.isWhiteSpace(requestedCostume))) { + } else if (!(isNaN(requestedCostume) || Scratch.Cast.isWhiteSpace(requestedCostume))) { target.setCostume(optZeroIndex ? Number(requestedCostume) : Number(requestedCostume) - 1); } } From c07c8d9e488e8a371c67f8623d70c6d625384b9f Mon Sep 17 00:00:00 2001 From: SharkPool-SP <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 15 Oct 2023 14:32:57 -0700 Subject: [PATCH 03/17] Create Animations.svg --- images/SharkPool/Animations.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 images/SharkPool/Animations.svg diff --git a/images/SharkPool/Animations.svg b/images/SharkPool/Animations.svg new file mode 100644 index 0000000000..59aed2e475 --- /dev/null +++ b/images/SharkPool/Animations.svg @@ -0,0 +1 @@ + From 289e25aa6b9544c3e820dd9d0a7f748802bd9594 Mon Sep 17 00:00:00 2001 From: SharkPool-SP <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 15 Oct 2023 15:27:14 -0700 Subject: [PATCH 04/17] Update Animations.js --- extensions/SharkPool/Animations.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 403c8163a0..848d7fc4fc 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -3,7 +3,7 @@ // Description: Play Animations for your Sprites // By: SharkPool -// Version V.1.0.0 +// Version V.1.1.0 (function (Scratch) { "use strict"; @@ -307,6 +307,18 @@ }, }, }, + { + opcode: "currentFrame", + blockType: Scratch.BlockType.REPORTER, + text: "current frame of animation [NAME]", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + }, + }, { blockType: Scratch.BlockType.LABEL, text: "Keyframes", @@ -430,6 +442,7 @@ fps: 10, frames: [], playing: false, + currentFrame: 0, } }); } @@ -550,6 +563,15 @@ } } + currentFrame(args) { + const animation = allAnimations.find((animation) => animation[args.NAME]); + if (animation) { + return animation[args.NAME].currentFrame; + } else { + return "Animation Doesnt Exist!"; + } + } + frameNames(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); if (animation) { @@ -586,10 +608,12 @@ myAnimation.playing = true; const target = myAnimation.target; let frameIndex = args.TYPE.includes("reverse") ? myAnimation.frames.length - 1 : 0; + myAnimation.currentFrame = frameIndex; const numFrames = myAnimation.frames.length; const playNextFrame = () => { if (myAnimation.playing === true) { + myAnimation.currentFrame = frameIndex; if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("spKF4!"))) { if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("PZ"))) { const keys = Object.keys(myAnimation.frames[frameIndex]); From 6bc1babdd19b0f39624d5bc11ae060ac4b3f5628 Mon Sep 17 00:00:00 2001 From: SharkPool-SP <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 20 Oct 2023 01:34:15 -0700 Subject: [PATCH 05/17] New blocks --- extensions/SharkPool/Animations.js | 37 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 848d7fc4fc..1a27f9b162 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -3,7 +3,7 @@ // Description: Play Animations for your Sprites // By: SharkPool -// Version V.1.1.0 +// Version V.1.2.0 (function (Scratch) { "use strict"; @@ -87,6 +87,18 @@ }, }, }, + { + opcode: "allAnimationsX", + blockType: Scratch.BlockType.REPORTER, + text: "all [TYPE] animations", + arguments: { + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "pullTypes", + defaultValue: "existing", + }, + }, + }, { blockType: Scratch.BlockType.LABEL, text: "Frames", @@ -430,6 +442,13 @@ "looped reversed" ], }, + pullTypes: { + acceptReporters: true, + items: [ + "existing", + "playing" + ], + }, } }; } @@ -484,7 +503,6 @@ } addAllFrames(args, util) { - const maxCos = util.target.sprite.costumes_.length; args.COS1 = args.COS1 < 1 ? 1 : Math.round(args.COS1) - 1; args.COS2 = args.COS2 < 1 ? 1 : args.COS2 > util.target.sprite.costumes_.length ? util.target.sprite.costumes_.length : Math.round(args.COS2); let animation = allAnimations.find((animation) => animation[args.NAME]); @@ -504,7 +522,6 @@ } removeAllFrames(args, util) { - const maxCos = util.target.sprite.costumes_.length; args.COS1 = args.COS1 < 1 ? 1 : Math.round(args.COS1) - 1; args.COS2 = args.COS2 < 1 ? 1 : args.COS2 > util.target.sprite.costumes_.length ? util.target.sprite.costumes_.length : Math.round(args.COS2); let animation = allAnimations.find((animation) => animation[args.NAME]); @@ -591,6 +608,15 @@ } } + allAnimationsX(args) { + if (args.TYPE === "playing") { + const playingAnimations = allAnimations.filter(animationObj => animationObj[Object.keys(animationObj)[0]].playing === true); + return JSON.stringify(playingAnimations.map(animationObj => Object.keys(animationObj)[0])); + } else { + return JSON.stringify(allAnimations.map(animationObj => Object.keys(animationObj)[0])); + } + } + setFPS(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); if (animation) { @@ -610,6 +636,9 @@ let frameIndex = args.TYPE.includes("reverse") ? myAnimation.frames.length - 1 : 0; myAnimation.currentFrame = frameIndex; const numFrames = myAnimation.frames.length; + if (numFrames === 0) { + return; + } const playNextFrame = () => { if (myAnimation.playing === true) { @@ -690,7 +719,7 @@ if (animation) { return animation[args.NAME].playing; } else { - return "Animation Doesnt Exist!"; + return false; } } From 13c2c326166586d945770b54ff98903f0c685afc Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 22 Oct 2023 22:51:00 -0700 Subject: [PATCH 06/17] Update Animations.js --- extensions/SharkPool/Animations.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 1a27f9b162..31f80d079a 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -646,6 +646,7 @@ if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("spKF4!"))) { if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("PZ"))) { const keys = Object.keys(myAnimation.frames[frameIndex]); + /* eslint-disable */ for (const key of keys) { const delayTime = myAnimation.frames[frameIndex][key].secs; setTimeout(() => { @@ -653,6 +654,7 @@ }, delayTime); return; } + /* eslint-enable */ } else { this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation); } From b37f55cacf026eb272fb868deb782198a3a34b2a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:28:12 -0700 Subject: [PATCH 07/17] Update Animations.js --- extensions/SharkPool/Animations.js | 77 ++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 31f80d079a..69a12a6673 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -3,7 +3,7 @@ // Description: Play Animations for your Sprites // By: SharkPool -// Version V.1.2.0 +// Version V.1.3.0 (function (Scratch) { "use strict"; @@ -415,6 +415,38 @@ }, }, }, + { + opcode: "addStretch", + blockType: Scratch.BlockType.COMMAND, + text: "add keyframe stretch to animation [NAME] with ID [ID] starting at width [x] height [y] and ending at width [x2] height [y2]", + blockIconURI: keyIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "key1", + }, + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 100, + }, + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 100, + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 200, + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 50, + }, + }, + }, { opcode: "deleteKeyframe", blockType: Scratch.BlockType.COMMAND, @@ -646,7 +678,6 @@ if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("spKF4!"))) { if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("PZ"))) { const keys = Object.keys(myAnimation.frames[frameIndex]); - /* eslint-disable */ for (const key of keys) { const delayTime = myAnimation.frames[frameIndex][key].secs; setTimeout(() => { @@ -654,7 +685,6 @@ }, delayTime); return; } - /* eslint-enable */ } else { this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation); } @@ -763,6 +793,25 @@ } } + addStretch(args, util) { + let animation = allAnimations.find((animation) => animation[args.NAME]); + if (!animation) { + this.createAnimation(args, util); + animation = allAnimations.find((animation) => animation[args.NAME]); + } + if (animation) { + const keyframe = { + [`spKF4!WH${args.ID}`]: { + x1: args.x, + y1: args.y, + x2: args.x2, + y2: args.y2, + } + }; + animation[args.NAME].frames.push(keyframe); + } + } + addDirection(args, util) { let animation = allAnimations.find((animation) => animation[args.NAME]); if (!animation) { @@ -838,6 +887,28 @@ } }; requestAnimationFrame(animateXY); + } else if (JSON.stringify(keyframe).includes("WH")) { + console.log(target); + const startX = key.x1; + const startY = key.y1; + const deltaX = key.x2 - key.x1; + const deltaY = key.y2 - key.y1; + const animateXY = (timestamp) => { + if (!startTime) { + startTime = timestamp; + } + const elapsedTime = timestamp - startTime; + const progress = Math.min(elapsedTime / animationDuration, 1); + const newX = startX + deltaX * progress; + const newY = startY + deltaY * progress; + target.stretch["0"] = newX; + target.stretch["1"] = newY; + target.setDirection(target.direction); + if (progress < 1) { + requestAnimationFrame(animateXY); + } + }; + requestAnimationFrame(animateXY); } else if (JSON.stringify(keyframe).includes("DIR")) { const startDir = key.dir1; const deltaDir = key.dir2 - key.dir1; From 4e23d6291d92e521fcf4f3ec55a68d521426a638 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:31:16 -0700 Subject: [PATCH 08/17] Update Animations.js --- extensions/SharkPool/Animations.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 69a12a6673..b48934b339 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -3,7 +3,7 @@ // Description: Play Animations for your Sprites // By: SharkPool -// Version V.1.3.0 +// Version V.1.2.0 (function (Scratch) { "use strict"; @@ -678,6 +678,7 @@ if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("spKF4!"))) { if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("PZ"))) { const keys = Object.keys(myAnimation.frames[frameIndex]); + /* eslint-disable */ for (const key of keys) { const delayTime = myAnimation.frames[frameIndex][key].secs; setTimeout(() => { @@ -685,6 +686,7 @@ }, delayTime); return; } + /* eslint-enable */ } else { this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation); } From 258926ed1159778e8f6f19ae2ff63a58538f8357 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 1 Nov 2023 22:15:10 -0700 Subject: [PATCH 09/17] Update Animations.js --- extensions/SharkPool/Animations.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index b48934b339..10d3d53aff 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -420,6 +420,7 @@ blockType: Scratch.BlockType.COMMAND, text: "add keyframe stretch to animation [NAME] with ID [ID] starting at width [x] height [y] and ending at width [x2] height [y2]", blockIconURI: keyIconURI, + hideFromPalette: true, arguments: { NAME: { type: Scratch.ArgumentType.STRING, From ca7784292c347d46452d4e0844c5978473e02a85 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 12 Nov 2023 23:33:38 -0800 Subject: [PATCH 10/17] Update Animations.js --- extensions/SharkPool/Animations.js | 111 +++++++++++++---------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 10d3d53aff..8b2399bf10 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -3,7 +3,7 @@ // Description: Play Animations for your Sprites // By: SharkPool -// Version V.1.2.0 +// Version V.1.4.0 (function (Scratch) { "use strict"; @@ -203,9 +203,7 @@ }, }, }, - "---", - { opcode: "numFrames", blockType: Scratch.BlockType.REPORTER, @@ -280,6 +278,23 @@ }, }, }, + { + opcode: "playBackWait", + blockType: Scratch.BlockType.COMMAND, + text: "play animation [NAME] [TYPE] until done", + blockIconURI: playIconURI, + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation 1", + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "playBack", + defaultValue: "normally", + }, + }, + }, { opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, @@ -292,9 +307,7 @@ }, }, }, - "---", - { opcode: "currentFPS", blockType: Scratch.BlockType.REPORTER, @@ -420,7 +433,7 @@ blockType: Scratch.BlockType.COMMAND, text: "add keyframe stretch to animation [NAME] with ID [ID] starting at width [x] height [y] and ending at width [x2] height [y2]", blockIconURI: keyIconURI, - hideFromPalette: true, + hideFromPalette: !Scratch.extensions.isPenguinMod, arguments: { NAME: { type: Scratch.ArgumentType.STRING, @@ -448,6 +461,7 @@ }, }, }, + "---", { opcode: "deleteKeyframe", blockType: Scratch.BlockType.COMMAND, @@ -468,19 +482,11 @@ menus: { playBack: { acceptReporters: false, - items: [ - "normally", - "in reverse", - "looped normally", - "looped reversed" - ], + items: ["normally", "in reverse", "looped normally", "looped reversed"], }, pullTypes: { acceptReporters: true, - items: [ - "existing", - "playing" - ], + items: ["existing", "playing"], }, } }; @@ -502,9 +508,7 @@ removeAnimation(args) { this.stopPlayingAnimation(args.NAME); const indexToRemove = allAnimations.findIndex((animation) => Object.keys(animation)[0] === args.NAME); - if (indexToRemove !== -1) { - allAnimations.splice(indexToRemove, 1); - } + if (indexToRemove !== -1) { allAnimations.splice(indexToRemove, 1) } } removeAll() { @@ -517,7 +521,7 @@ isExists(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); - return Boolean(animation); + return Scratch.Cast.toBoolean(animation); } addFrame(args, util) { @@ -530,9 +534,7 @@ console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); return; } - if (animation) { - animation[args.NAME].frames.push(args.COSTUME); - } + if (animation) { animation[args.NAME].frames.push(args.COSTUME) } } addAllFrames(args, util) { @@ -590,9 +592,7 @@ if (animation) { const SECS = Math.abs(args.SECOND) * 1000; animation[args.NAME].frames.push({ - [`spKF4!PZ${args.ID}`]: { - secs: SECS, - } + [`spKF4!PZ${args.ID}`]: { secs: SECS } }); } } @@ -657,6 +657,16 @@ } } + playBackWait(args) { + this.playBack(args); + return new Promise((resolve, reject) => { + const animationPlay = () => { + this.isPlaying(args) ? setTimeout(animationPlay, 100) : resolve(); + }; + setTimeout(animationPlay, 100); + }); + } + playBack(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); if (animation) { @@ -669,9 +679,7 @@ let frameIndex = args.TYPE.includes("reverse") ? myAnimation.frames.length - 1 : 0; myAnimation.currentFrame = frameIndex; const numFrames = myAnimation.frames.length; - if (numFrames === 0) { - return; - } + if (numFrames === 0) return; const playNextFrame = () => { if (myAnimation.playing === true) { @@ -682,9 +690,7 @@ /* eslint-disable */ for (const key of keys) { const delayTime = myAnimation.frames[frameIndex][key].secs; - setTimeout(() => { - handleNextFrame(); - }, delayTime); + setTimeout(() => { handleNextFrame() }, delayTime); return; } /* eslint-enable */ @@ -729,15 +735,11 @@ } } - stopAnimation(args) { - this.stopPlayingAnimation(args.NAME); - } + stopAnimation(args) { this.stopPlayingAnimation(args.NAME) } stopPlayingAnimation(name) { const animation = allAnimations.find((animation) => animation[name]); - if (animation) { - animation[name].playing = false; - } + if (animation) animation[name].playing = false; } currentFPS(args) { @@ -877,29 +879,22 @@ const deltaX = key.x2 - key.x1; const deltaY = key.y2 - key.y1; const animateXY = (timestamp) => { - if (!startTime) { - startTime = timestamp; - } + if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); const newX = startX + deltaX * progress; const newY = startY + deltaY * progress; target.setXY(newX, newY); - if (progress < 1) { - requestAnimationFrame(animateXY); - } + if (progress < 1) requestAnimationFrame(animateXY); }; requestAnimationFrame(animateXY); } else if (JSON.stringify(keyframe).includes("WH")) { - console.log(target); const startX = key.x1; const startY = key.y1; const deltaX = key.x2 - key.x1; const deltaY = key.y2 - key.y1; const animateXY = (timestamp) => { - if (!startTime) { - startTime = timestamp; - } + if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); const newX = startX + deltaX * progress; @@ -907,41 +902,31 @@ target.stretch["0"] = newX; target.stretch["1"] = newY; target.setDirection(target.direction); - if (progress < 1) { - requestAnimationFrame(animateXY); - } + if (progress < 1) requestAnimationFrame(animateXY); }; requestAnimationFrame(animateXY); } else if (JSON.stringify(keyframe).includes("DIR")) { const startDir = key.dir1; const deltaDir = key.dir2 - key.dir1; const animateDirection = (timestamp) => { - if (!startTime) { - startTime = timestamp; - } + if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); const newDirection = startDir + deltaDir * progress; target.setDirection(newDirection); - if (progress < 1) { - requestAnimationFrame(animateDirection); - } + if (progress < 1) requestAnimationFrame(animateDirection); }; requestAnimationFrame(animateDirection); } else if (JSON.stringify(keyframe).includes("SZ")) { const startSize = key.size1; const deltaSize = key.size2 - key.size1; const animateSize = (timestamp) => { - if (!startTime) { - startTime = timestamp; - } + if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); const newSize = startSize + deltaSize * progress; target.setSize(newSize); - if (progress < 1) { - requestAnimationFrame(animateSize); - } + if (progress < 1) requestAnimationFrame(animateSize); }; requestAnimationFrame(animateSize); } From 0685b9c5acf9909ffa7eeccfefbd3ab6dddcb514 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 2 Feb 2024 16:25:09 -0800 Subject: [PATCH 11/17] Update Animations.js --- extensions/SharkPool/Animations.js | 391 ++++++++++------------------- 1 file changed, 136 insertions(+), 255 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 8b2399bf10..393dd4b3d7 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -1,16 +1,13 @@ // Name: Animations // ID: SPanimations // Description: Play Animations for your Sprites -// By: SharkPool +// By: SharkPool -// Version V.1.4.0 +// Version V.1.5.0 (function (Scratch) { "use strict"; - - if (!Scratch.extensions.unsandboxed) { - throw new Error("Animations must run unsandboxed"); - } + if (!Scratch.extensions.unsandboxed) throw new Error("Animations must run unsandboxed"); const vm = Scratch.vm; const runtime = vm.runtime; @@ -38,7 +35,6 @@ }); }); } - getInfo() { return { id: "SPanimations", @@ -56,7 +52,7 @@ arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, @@ -67,14 +63,14 @@ arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, { opcode: "removeAll", blockType: Scratch.BlockType.COMMAND, - text: "delete all animations", + text: "delete all animations" }, { opcode: "isExists", @@ -83,7 +79,7 @@ arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, @@ -94,112 +90,93 @@ arguments: { TYPE: { type: Scratch.ArgumentType.STRING, - menu: "pullTypes", - defaultValue: "existing", + menu: "pullTypes" }, }, }, - { - blockType: Scratch.BlockType.LABEL, - text: "Frames", - }, + { blockType: Scratch.BlockType.LABEL, text: "Frames" }, { opcode: "addFrame", blockType: Scratch.BlockType.COMMAND, - text: "add [COSTUME] to animation [NAME]", + text: "add [COSTUME] to [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", - }, - COSTUME: { - type: Scratch.ArgumentType.COSTUME, + defaultValue: "animation 1" }, + COSTUME: { type: Scratch.ArgumentType.COSTUME }, }, }, { opcode: "removeFrame", blockType: Scratch.BlockType.COMMAND, - text: "remove [COSTUME] from animation [NAME]", + text: "remove [COSTUME] from [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", - }, - COSTUME: { - type: Scratch.ArgumentType.COSTUME, + defaultValue: "animation 1" }, + COSTUME: { type: Scratch.ArgumentType.COSTUME }, }, }, { opcode: "addAllFrames", blockType: Scratch.BlockType.COMMAND, - text: "add costumes from [COS1] to [COS2] to animation [NAME]", + text: "add costumes from [COS1] to [COS2] to [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", - }, - COS1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1, - }, - COS2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 5, + defaultValue: "animation 1" }, + COS1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, + COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, }, }, { opcode: "removeAllFrames", blockType: Scratch.BlockType.COMMAND, - text: "remove frames [COS1] to [COS2] from animation [NAME]", + text: "remove frames [COS1] to [COS2] from [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", - }, - COS1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1, - }, - COS2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 5, + defaultValue: "animation 1" }, + COS1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, + COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, }, }, + "---", { opcode: "addPause", blockType: Scratch.BlockType.COMMAND, - text: "add a [SECOND] second pause to animation [NAME] with ID [ID]", + text: "add a [SECOND] second pause to [NAME] with ID [ID]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, SECOND: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 3, + defaultValue: 3 }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "pause1", + defaultValue: "pause1" }, }, }, { opcode: "removePause", blockType: Scratch.BlockType.COMMAND, - text: "remove pause frame from animation [NAME] with ID [ID]", + text: "remove pause frame from [NAME] with ID [ID]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "pause1", + defaultValue: "pause1" }, }, }, @@ -207,57 +184,54 @@ { opcode: "numFrames", blockType: Scratch.BlockType.REPORTER, - text: "number of frames in animation [NAME]", + text: "number of frames in [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, { opcode: "frameNames", blockType: Scratch.BlockType.REPORTER, - text: "all frames in animation [NAME]", + text: "all frames in [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation 1", - }, + } }, }, { opcode: "frameName", blockType: Scratch.BlockType.REPORTER, - text: "frame [FRAME] in animation [NAME]", + text: "frame # [FRAME] in [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, FRAME: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 1, + defaultValue: 1 }, }, }, - { - blockType: Scratch.BlockType.LABEL, - text: "Playback", - }, + { blockType: Scratch.BlockType.LABEL, text: "Playback" }, { opcode: "setFPS", blockType: Scratch.BlockType.COMMAND, - text: "set FPS of animation [NAME] to [FPS]", + text: "set FPS of [NAME] to [FPS]", blockIconURI: playIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, FPS: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 30, + defaultValue: 30 }, }, }, @@ -269,12 +243,11 @@ arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, TYPE: { type: Scratch.ArgumentType.STRING, - menu: "playBack", - defaultValue: "normally", + menu: "playBack" }, }, }, @@ -286,12 +259,11 @@ arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, TYPE: { type: Scratch.ArgumentType.STRING, - menu: "playBack", - defaultValue: "normally", + menu: "playBack" }, }, }, @@ -303,7 +275,7 @@ arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, @@ -311,179 +283,136 @@ { opcode: "currentFPS", blockType: Scratch.BlockType.REPORTER, - text: "FPS of animation [NAME]", + text: "FPS of [NAME]", blockIconURI: playIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, { opcode: "isPlaying", blockType: Scratch.BlockType.BOOLEAN, - text: "is animation [NAME] playing?", + text: "is [NAME] playing?", blockIconURI: playIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, { opcode: "currentFrame", blockType: Scratch.BlockType.REPORTER, - text: "current frame of animation [NAME]", + text: "current frame of [NAME]", blockIconURI: playIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, }, }, - { - blockType: Scratch.BlockType.LABEL, - text: "Keyframes", - }, + { blockType: Scratch.BlockType.LABEL, text: "Keyframes" }, { opcode: "addPosition", blockType: Scratch.BlockType.COMMAND, - text: "add keyframe position to animation [NAME] with ID [ID] starting at x [x] y [y] and ending at x [x2] y [y2]", + text: "add keyframe position to [NAME] with ID [ID] start x [x] y [y] end x [x2] y [y2]", blockIconURI: keyIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "key1", - }, - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0, - }, - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0, - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 100, - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0, + defaultValue: "key1" }, + x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, { opcode: "addDirection", blockType: Scratch.BlockType.COMMAND, - text: "add keyframe direction to animation [NAME] with ID [ID] starting at [DIR1] and ending at [DIR2]", + text: "add keyframe direction to [NAME] with ID [ID] start [DIR1] end [DIR2]", blockIconURI: keyIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "key1", - }, - DIR1: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: 90, - }, - DIR2: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: 0, + defaultValue: "key1" }, + DIR1: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 }, + DIR2: { type: Scratch.ArgumentType.ANGLE, defaultValue: 0 }, }, }, { opcode: "addScale", blockType: Scratch.BlockType.COMMAND, - text: "add keyframe scale to animation [NAME] with ID [ID] starting at [scale]% and ending [scale2]%", + text: "add keyframe scale to [NAME] with ID [ID] start [scale]% end [scale2]%", blockIconURI: keyIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "key1", - }, - scale: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 100, - }, - scale2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 150, + defaultValue: "key1" }, + scale: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + scale2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 150 }, }, }, { opcode: "addStretch", blockType: Scratch.BlockType.COMMAND, - text: "add keyframe stretch to animation [NAME] with ID [ID] starting at width [x] height [y] and ending at width [x2] height [y2]", + text: "add keyframe stretch to [NAME] with ID [ID] start width [x] height [y] end width [x2] height [y2]", blockIconURI: keyIconURI, - hideFromPalette: !Scratch.extensions.isPenguinMod, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "key1", - }, - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 100, - }, - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 100, - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 200, - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 50, + defaultValue: "key1" }, + x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 200 }, + y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, "---", { opcode: "deleteKeyframe", blockType: Scratch.BlockType.COMMAND, - text: "remove keyframe with ID [ID] from animation [NAME]", + text: "remove keyframe with ID [ID] from [NAME]", blockIconURI: keyIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", + defaultValue: "animation 1" }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: "key1", + defaultValue: "key1" }, }, }, ], menus: { - playBack: { - acceptReporters: false, - items: ["normally", "in reverse", "looped normally", "looped reversed"], - }, + playBack: ["normally", "in reverse", "looped normally", "looped reversed"], pullTypes: { acceptReporters: true, items: ["existing", "playing"], @@ -495,12 +424,9 @@ createAnimation(args, util) { allAnimations.push({ [args.NAME]: { - name: args.NAME, - target: util.target, - fps: 10, - frames: [], - playing: false, - currentFrame: 0, + name: args.NAME, target: util.target, + fps: 10, frames: [], + playing: false, currentFrame: 0 } }); } @@ -531,7 +457,7 @@ animation = allAnimations.find((animation) => animation[args.NAME]); } if (util.target !== animation[args.NAME].target) { - console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + console.error(`This Animation can only be ran in "${animation[args.NAME].target.sprite.name}"`); return; } if (animation) { animation[args.NAME].frames.push(args.COSTUME) } @@ -546,7 +472,7 @@ animation = allAnimations.find((animation) => animation[args.NAME]); } if (util.target !== animation[args.NAME].target) { - console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + console.error(`This Animation can only be ran in "${animation[args.NAME].target.sprite.name}"`); return; } if (animation) { @@ -576,7 +502,7 @@ const animation = allAnimations.find((animation) => animation[args.NAME]); if (animation) { if (util.target !== animation[args.NAME].target) { - console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); + console.error(`This Animation can only be ran in "${animation[args.NAME].target.sprite.name}"`); return; } animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => frame !== args.COSTUME); @@ -606,29 +532,20 @@ numFrames(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - return animation[args.NAME].frames.length; - } else { - return "Animation Doesnt Exist!"; - } + if (animation) return animation[args.NAME].frames.length; + else return "Animation Doesnt Exist!"; } currentFrame(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - return animation[args.NAME].currentFrame; - } else { - return "Animation Doesnt Exist!"; - } + if (animation) return animation[args.NAME].currentFrame; + else return "Animation Doesnt Exist!"; } frameNames(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - return JSON.stringify(animation[args.NAME].frames); - } else { - return "Animation Doesnt Exist!"; - } + if (animation) return JSON.stringify(animation[args.NAME].frames); + else return "Animation Doesnt Exist!"; } frameName(args) { @@ -636,9 +553,7 @@ if (animation) { const newFrame = Math.abs(Math.round(args.FRAME)) - 1; return (animation[args.NAME].frames[newFrame]) ? JSON.stringify(animation[args.NAME].frames[newFrame]) : ""; - } else { - return "Animation Doesnt Exist!"; - } + } else { return "Animation Doesnt Exist!" } } allAnimationsX(args) { @@ -680,7 +595,6 @@ myAnimation.currentFrame = frameIndex; const numFrames = myAnimation.frames.length; if (numFrames === 0) return; - const playNextFrame = () => { if (myAnimation.playing === true) { myAnimation.currentFrame = frameIndex; @@ -694,25 +608,17 @@ return; } /* eslint-enable */ - } else { - this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation); - } - } else { - this._setCostume(target, myAnimation.frames[frameIndex]); - } + } else { this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation) } + } else { this._setCostume(target, myAnimation.frames[frameIndex]) } handleNextFrame(); - } else { - myAnimation.playing = false; - } + } else { myAnimation.playing = false } }; - const handleNextFrame = () => { if (args.TYPE.includes("reverse")) { frameIndex--; if (frameIndex < 0) { - if (args.TYPE.includes("looped")) { - frameIndex = numFrames - 1; - } else { + if (args.TYPE.includes("looped")) frameIndex = numFrames - 1; + else { myAnimation.playing = false; return; } @@ -720,9 +626,8 @@ } else { frameIndex++; if (frameIndex >= numFrames) { - if (args.TYPE.includes("looped")) { - frameIndex = 0; - } else { + if (args.TYPE.includes("looped")) frameIndex = 0; + else { myAnimation.playing = false; return; } @@ -744,35 +649,25 @@ currentFPS(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - return animation[args.NAME].fps; - } else { - return "Animation Doesnt Exist!"; - } + if (animation) return animation[args.NAME].fps; + else return "Animation Doesnt Exist!"; } isPlaying(args) { const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - return animation[args.NAME].playing; - } else { - return false; - } + if (animation) return animation[args.NAME].playing; + else return false; } _setCostume(target, requestedCostume, optZeroIndex) { - // ripped from vm if (typeof requestedCostume === "number") { target.setCostume(optZeroIndex ? requestedCostume : requestedCostume - 1); } else { const costumeIndex = target.getCostumeIndexByName(requestedCostume.toString()); - if (costumeIndex !== -1) { - target.setCostume(costumeIndex); - } else if (requestedCostume === "next costume") { - target.setCostume(target.currentCostume + 1); - } else if (requestedCostume === "previous costume") { - target.setCostume(target.currentCostume - 1); - } else if (!(isNaN(requestedCostume) || Scratch.Cast.isWhiteSpace(requestedCostume))) { + if (costumeIndex !== -1) target.setCostume(costumeIndex); + else if (requestedCostume === "next costume") target.setCostume(target.currentCostume + 1); + else if (requestedCostume === "previous costume") target.setCostume(target.currentCostume - 1); + else if (!(isNaN(requestedCostume) || Scratch.Cast.isWhiteSpace(requestedCostume))) { target.setCostume(optZeroIndex ? Number(requestedCostume) : Number(requestedCostume) - 1); } } @@ -788,10 +683,8 @@ if (animation) { const keyframe = { [`spKF4!XY${args.ID}`]: { - x1: args.x, - y1: args.y, - x2: args.x2, - y2: args.y2, + x1: args.x, y1: args.y, + x2: args.x2, y2: args.y2, } }; animation[args.NAME].frames.push(keyframe); @@ -807,10 +700,8 @@ if (animation) { const keyframe = { [`spKF4!WH${args.ID}`]: { - x1: args.x, - y1: args.y, - x2: args.x2, - y2: args.y2, + x1: args.x, y1: args.y, + x2: args.x2, y2: args.y2, } }; animation[args.NAME].frames.push(keyframe); @@ -826,8 +717,7 @@ if (animation) { const keyframe = { [`spKF4!DIR${args.ID}`]: { - dir1: args.DIR1, - dir2: args.DIR2, + dir1: args.DIR1, dir2: args.DIR2, } }; animation[args.NAME].frames.push(keyframe); @@ -843,8 +733,7 @@ if (animation) { const keyframe = { [`spKF4!SZ${args.ID}`]: { - size1: args.scale, - size2: args.scale2, + size1: args.scale, size2: args.scale2, } }; animation[args.NAME].frames.push(keyframe); @@ -856,9 +745,7 @@ if (animation) { animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => { for (const key in frame) { - if (key.startsWith("spKF4!") && key.endsWith(args.ID)) { - return false; - } + if (key.startsWith("spKF4!") && key.endsWith(args.ID)) return false; } return true; }); @@ -866,66 +753,60 @@ } _setKeyframe(target, keyframe, data) { + let startTime, startX, startY, deltaX, deltaY, startSize, deltaSize, startDir, deltaDir; if (!keyFramesPlaying.some(item => JSON.stringify(item) === JSON.stringify([data, keyframe]))) { keyFramesPlaying.push([data, keyframe]); } keyFramesPlaying.push([data, keyframe]); const key = keyframe[Object.keys(keyframe)[0]]; const animationDuration = data.fps * 20; - let startTime; + if (JSON.stringify(keyframe).includes("DIR")) { + startDir = key.dir1; + deltaDir = key.dir2 - key.dir1; + } else if (JSON.stringify(keyframe).includes("SZ")) { + startSize = key.size1; + deltaSize = key.size2 - key.size1; + } else { + startX = key.x1; + startY = key.y1; + deltaX = key.x2 - key.x1; + deltaY = key.y2 - key.y1; + } if (JSON.stringify(keyframe).includes("XY")) { - const startX = key.x1; - const startY = key.y1; - const deltaX = key.x2 - key.x1; - const deltaY = key.y2 - key.y1; const animateXY = (timestamp) => { if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); - const newX = startX + deltaX * progress; - const newY = startY + deltaY * progress; - target.setXY(newX, newY); + target.setXY(startX + deltaX * progress, startY + deltaY * progress); if (progress < 1) requestAnimationFrame(animateXY); }; requestAnimationFrame(animateXY); } else if (JSON.stringify(keyframe).includes("WH")) { - const startX = key.x1; - const startY = key.y1; - const deltaX = key.x2 - key.x1; - const deltaY = key.y2 - key.y1; const animateXY = (timestamp) => { if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); - const newX = startX + deltaX * progress; - const newY = startY + deltaY * progress; - target.stretch["0"] = newX; - target.stretch["1"] = newY; - target.setDirection(target.direction); + vm.renderer._allDrawables[target.drawableID].updateScale([ + startX + deltaX * progress, startY + deltaY * progress + ]); if (progress < 1) requestAnimationFrame(animateXY); }; requestAnimationFrame(animateXY); } else if (JSON.stringify(keyframe).includes("DIR")) { - const startDir = key.dir1; - const deltaDir = key.dir2 - key.dir1; const animateDirection = (timestamp) => { if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); - const newDirection = startDir + deltaDir * progress; - target.setDirection(newDirection); + target.setDirection(startDir + deltaDir * progress); if (progress < 1) requestAnimationFrame(animateDirection); }; requestAnimationFrame(animateDirection); } else if (JSON.stringify(keyframe).includes("SZ")) { - const startSize = key.size1; - const deltaSize = key.size2 - key.size1; const animateSize = (timestamp) => { if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; const progress = Math.min(elapsedTime / animationDuration, 1); - const newSize = startSize + deltaSize * progress; - target.setSize(newSize); + target.setSize(startSize + deltaSize * progress); if (progress < 1) requestAnimationFrame(animateSize); }; requestAnimationFrame(animateSize); From 058a917a89a2283b3357a78e0039f2e815933ffe Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:04:44 -0700 Subject: [PATCH 12/17] Animations V2 --- extensions/SharkPool/Animations.js | 804 +++++++++++------------------ 1 file changed, 301 insertions(+), 503 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 393dd4b3d7..787c400cfe 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -2,8 +2,9 @@ // ID: SPanimations // Description: Play Animations for your Sprites // By: SharkPool +// Licence: MIT -// Version V.1.5.0 +// Version V.2.0.0 (function (Scratch) { "use strict"; @@ -11,27 +12,77 @@ const vm = Scratch.vm; const runtime = vm.runtime; - let allAnimations = []; - let keyFramesPlaying = []; + const render = vm.renderer; + const specialID = `SPspecialID${Math.random()}`; + const playTypes = { + "normally": 1, + "in reverse": 2, + "looped normally": 3, + "looped reversed": 4 + }; - const menuIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjcuNTA5OTIiIGhlaWdodD0iMTI3LjUwOTkyIiB2aWV3Qm94PSIwLDAsMTI3LjUwOTkyLDEyNy41MDk5MiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE3Ni4yNDUwNCwtMTE2LjI0NTA0KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTc2LjI0NTA0LDE4MGMwLC0zNS4yMTA4OSAyOC41NDQwNywtNjMuNzU0OTYgNjMuNzU0OTYsLTYzLjc1NDk2YzM1LjIxMDg5LDAgNjMuNzU0OTYsMjguNTQ0MDcgNjMuNzU0OTYsNjMuNzU0OTZjMCwzNS4yMTA4OSAtMjguNTQ0MDcsNjMuNzU0OTYgLTYzLjc1NDk2LDYzLjc1NDk2Yy0zNS4yMTA4OSwwIC02My43NTQ5NiwtMjguNTQ0MDcgLTYzLjc1NDk2LC02My43NTQ5NnoiIGZpbGw9IiM0NDQ0NDQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIi8+PHBhdGggZD0iTTE4Mi4xMTk0OSwxODBjMCwtMzEuOTY2NTMgMjUuOTEzOTksLTU3Ljg4MDUxIDU3Ljg4MDUxLC01Ny44ODA1MWMzMS45NjY1MywwIDU3Ljg4MDUxLDI1LjkxMzk5IDU3Ljg4MDUxLDU3Ljg4MDUxYzAsMzEuOTY2NTMgLTI1LjkxMzk5LDU3Ljg4MDUxIC01Ny44ODA1MSw1Ny44ODA1MWMtMzEuOTY2NTMsMCAtNTcuODgwNTEsLTI1LjkxMzk5IC01Ny44ODA1MSwtNTcuODgwNTF6IiBmaWxsPSIjN2E3YTdhIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0xOTkuOTQwMTEsMjA2Ljc3NDU5Yy01LjYxMjgsLTguMTI0OCAtMy41NzY0MywtMTkuMjYxMzMgNC41NDgzNywtMjQuODc0MTRjOC4xMjQ4LC01LjYxMjggMTkuMjYxMzMsLTMuNTc2NDMgMjQuODc0MTMsNC41NDgzN2M1LjYxMjgsOC4xMjQ4IDMuNTc2NDMsMTkuMjYxMzMgLTQuNTQ4MzYsMjQuODc0MTRjLTguMTI0OCw1LjYxMjggLTE5LjI2MTMzLDMuNTc2NDMgLTI0Ljg3NDE0LC00LjU0ODM3eiIgZmlsbD0iIzg5ODk4OSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik0yMDQuMzU1MDMsMTk2LjU4ODg2Yy02LjMzMDY1LC05LjE2MzkyIC00LjAzMzg0LC0yMS43MjQ3NSA1LjEzMDA4LC0yOC4wNTU0YzkuMTYzOTIsLTYuMzMwNjUgMjEuNzI0NzUsLTQuMDMzODQgMjguMDU1NCw1LjEzMDA4YzYuMzMwNjUsOS4xNjM5MiA0LjAzMzg0LDIxLjcyNDc1IC01LjEzMDA4LDI4LjA1NTRjLTkuMTYzOTIsNi4zMzA2NSAtMjEuNzI0NzUsNC4wMzM4NCAtMjguMDU1NDEsLTUuMTMwMDh6IiBmaWxsPSIjOWQ5ZDlkIiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTIxMi4zOTAwMiwxODkuNTcyNTVjLTcuNDE2MTIsLTEwLjczNTE4IC00LjcyNTQ4LC0yNS40NDk3MSA2LjAwOTcsLTMyLjg2NTgyYzEwLjczNTE4LC03LjQxNjEyIDI1LjQ0OTcxLC00LjcyNTQ5IDMyLjg2NTgyLDYuMDA5NjljNy40MTYxMiwxMC43MzUxOCA0LjcyNTQ4LDI1LjQ0OTcxIC02LjAwOTY5LDMyLjg2NTgzYy0xMC43MzUxOCw3LjQxNjEyIC0yNS40NDk3MSw0LjcyNTQ4IC0zMi44NjU4MywtNi4wMDk2OXoiIGZpbGw9IiNhZWFlYWUiIHN0cm9rZT0iIzdhN2E3YSIgc3Ryb2tlLXdpZHRoPSIyLjUiLz48cGF0aCBkPSJNMjIzLjU1NTkxLDE4NS43NzU4OWMtOC4wMzQxMiwtMTEuNjI5NzcgLTUuMTE5MjcsLTI3LjU3MDUyIDYuNTEwNSwtMzUuNjA0NjRjMTEuNjI5NzcsLTguMDM0MTIgMjcuNTcwNTIsLTUuMTE5MjcgMzUuNjA0NjQsNi41MTA1YzguMDM0MTIsMTEuNjI5NzcgNS4xMTkyNywyNy41NzA1MiAtNi41MTA1LDM1LjYwNDY0Yy0xMS42Mjk3Nyw4LjAzNDEyIC0yNy41NzA1Miw1LjExOTI3IC0zNS42MDQ2NCwtNi41MTA1eiIgZmlsbD0iI2JhYmFiYSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik0yMzEuOTYyODUsMTg5LjYxOTY5Yy04LjgyODcxLC0xMi43Nzk5NyAtNS42MjU1OCwtMzAuMjk3MjcgNy4xNTQzOSwtMzkuMTI1OTdjMTIuNzc5OTcsLTguODI4NzEgMzAuMjk3MjcsLTUuNjI1NTggMzkuMTI1OTcsNy4xNTQzOWM4LjgyODcxLDEyLjc3OTk3IDUuNjI1NTgsMzAuMjk3MjcgLTcuMTU0MzksMzkuMTI1OTdjLTEyLjc3OTk3LDguODI4NzEgLTMwLjI5NzI3LDUuNjI1NTggLTM5LjEyNTk3LC03LjE1NDM5eiIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiLz48L2c+PC9nPjwvc3ZnPg=="; + let allAnimations = {}; + const menuIconURI = +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjcuNTEiIGhlaWdodD0iMTI3LjUxIiB2aWV3Qm94PSIwIDAgMTI3LjUxIDEyNy41MSI+PHBhdGggZD0iTTAgNjMuNzU1QzAgMjguNTQ1IDI4LjU0NCAwIDYzLjc1NSAwczYzLjc1NSAyOC41NDQgNjMuNzU1IDYzLjc1NS0yOC41NDQgNjMuNzU1LTYzLjc1NSA2My43NTVTMCA5OC45NjYgMCA2My43NTUiIGZpbGw9IiM0NDQiLz48cGF0aCBkPSJNNS44NzUgNjMuNzU1YzAtMzEuOTY3IDI1LjkxMy01Ny44OCA1Ny44OC01Ny44OHM1Ny44OCAyNS45MTMgNTcuODggNTcuODgtMjUuOTEzIDU3Ljg4LTU3Ljg4IDU3Ljg4LTU3Ljg4LTI1LjkxMy01Ny44OC01Ny44OCIgZmlsbD0iIzdhN2E3YSIvPjxwYXRoIGQ9Ik0yMy42OTUgOTAuNTNjLTUuNjEzLTguMTI1LTMuNTc2LTE5LjI2MiA0LjU0OC0yNC44NzVzMTkuMjYyLTMuNTc2IDI0Ljg3NSA0LjU0OSAzLjU3NiAxOS4yNjEtNC41NDkgMjQuODc0LTE5LjI2MSAzLjU3Ni0yNC44NzQtNC41NDh6IiBmaWxsPSIjODk4OTg5IiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTI4LjExIDgwLjM0NGMtNi4zMy05LjE2NC00LjAzNC0yMS43MjUgNS4xMy0yOC4wNTZzMjEuNzI1LTQuMDMzIDI4LjA1NiA1LjEzIDQuMDMzIDIxLjcyNS01LjEzIDI4LjA1Ni0yMS43MjUgNC4wMzQtMjguMDU2LTUuMTN6IiBmaWxsPSIjOWQ5ZDlkIiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTM2LjE0NSA3My4zMjhjLTcuNDE2LTEwLjczNi00LjcyNS0yNS40NSA2LjAxLTMyLjg2NnMyNS40NS00LjcyNiAzMi44NjYgNi4wMWM3LjQxNiAxMC43MzUgNC43MjUgMjUuNDUtNi4wMSAzMi44NjVzLTI1LjQ1IDQuNzI2LTMyLjg2Ni02LjAxeiIgZmlsbD0iI2FlYWVhZSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik00Ny4zMTEgNjkuNTMxYy04LjAzNC0xMS42My01LjEyLTI3LjU3IDYuNTEtMzUuNjA1czI3LjU3LTUuMTE5IDM1LjYwNSA2LjUxYzguMDM0IDExLjYzIDUuMTIgMjcuNTcxLTYuNTEgMzUuNjA1LTExLjYzIDguMDM1LTI3LjU3MSA1LjEyLTM1LjYwNS02LjUxeiIgZmlsbD0iI2JhYmFiYSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik01NS43MTggNzMuMzc1Yy04LjgyOS0xMi43OC01LjYyNi0zMC4yOTggNy4xNTQtMzkuMTI2IDEyLjc4LTguODI5IDMwLjI5OC01LjYyNiAzOS4xMjYgNy4xNTQgOC44MjkgMTIuNzggNS42MjYgMzAuMjk3LTcuMTU0IDM5LjEyNnMtMzAuMjk3IDUuNjI2LTM5LjEyNi03LjE1NCIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg=="; const blockIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4NS4zMzE1MyIgaGVpZ2h0PSI4NS4zMzE1MyIgdmlld0JveD0iMCwwLDg1LjMzMTUzLDg1LjMzMTUzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk3LjMzNDIzLC0xMzcuMzM0MjQpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjgyLjY2NTc2LDEzNy4zMzQyNHY4NS4zMzE1M2gtODUuMzMxNTN2LTg1LjMzMTUzeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiM3YTdhN2EiLz48cGF0aCBkPSJNMjUyLjMwNDA3LDE2Mi42OTU5NWMyLjc2MTQzLDAgNSwyLjIzODU3IDUsNXY0MC40NTkyYzAsMi43NjE0MyAtMi4yMzg1OCw1IC01LDVoLTQwLjQ1OTJjLTIuNzYxNDMsMCAtNSwtMi4yMzg1NyAtNSwtNXYtNDAuNDU5MmMwLC0yLjc2MTQzIDIuMjM4NTcsLTUgNSwtNXoiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjOTI5MjkyIi8+PHBhdGggZD0iTTI2MC4yMjk2LDE1NC43NzA0MmMyLjc2MTQyLDAgNSwyLjIzODU3IDUsNXY0MC40NTkyYzAsMi43NjE0MyAtMi4yMzg1OCw1IC01LDVoLTQwLjQ1OTJjLTIuNzYxNDMsMCAtNSwtMi4yMzg1NyAtNSwtNXYtNDAuNDU5MmMwLC0yLjc2MTQzIDIuMjM4NTcsLTUgNSwtNXoiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjYzBjMGMwIi8+PHBhdGggZD0iTTI2OC4xNTUxMywxNDYuODQ0ODljMi43NjE0MiwwIDUsMi4yMzg1OCA1LDV2NDAuNDU5MmMwLDIuNzYxNDIgLTIuMjM4NTgsNSAtNSw1aC00MC40NTkyYy0yLjc2MTQyLDAgLTUsLTIuMjM4NTggLTUsLTV2LTQwLjQ1OTJjMCwtMi43NjE0MiAyLjIzODU4LC01IDUsLTV6IiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpbmRleCZxdW90OzpudWxsfSIgZmlsbD0iI2ZmZmZmZiIvPjwvZz48L2c+PC9zdmc+"; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0iTTU0Ljk3IDI1LjM2MmE1IDUgMCAwIDEgNSA1djQwLjQ2YTUgNSAwIDAgMS01IDVIMTQuNTFhNSA1IDAgMCAxLTUtNXYtNDAuNDZhNSA1IDAgMCAxIDUtNXoiIGZpbGw9IiM5MjkyOTIiLz48cGF0aCBkPSJNNjIuODk2IDE3LjQzNmE1IDUgMCAwIDEgNSA1djQwLjQ2YTUgNSAwIDAgMS01IDVoLTQwLjQ2YTUgNSAwIDAgMS01LTV2LTQwLjQ2YTUgNSAwIDAgMSA1LTV6IiBmaWxsPSJzaWx2ZXIiLz48cGF0aCBkPSJNNzAuODIxIDkuNTExYTUgNSAwIDAgMSA1IDV2NDAuNDZhNSA1IDAgMCAxLTUgNWgtNDAuNDZhNSA1IDAgMCAxLTUtNXYtNDAuNDZhNSA1IDAgMCAxIDUtNXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4="; const playIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4NS4zMzE1MyIgaGVpZ2h0PSI4NS4zMzE1MyIgdmlld0JveD0iMCwwLDg1LjMzMTUzLDg1LjMzMTUzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk3LjMzNDIzLC0xMzcuMzM0MjUpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTk3LjMzNDI0LDIyMi42NjU3OHYtODUuMzMxNTNoODUuMzMxNTN2ODUuMzMxNTN6IiBmaWxsPSIjN2E3YTdhIi8+PHBhdGggZD0iTTIyNC43NjQ3MSwxNTMuNTYxNDdjNS40MjY0MSwzLjEzMjk0IDI2Ljc0NzY2LDE1LjQ0Mjc3IDM4LjMwODU4LDIyLjExNzQ3YzQuNTIyMTIsMi42MTA4NSA0LjU4MjEyLDYuMDczNTIgMC4xNDI5Myw4LjYzNjQ5Yy0xMS4zMDM3NCw2LjUyNjIyIC0zMi4xNDE1OSwxOC41NTY5NiAtMzcuNzc1NDcsMjEuODA5NjhjLTIuNzYzODEsMS41OTU2OSAtNS42MDYsMC43MzAwNiAtNS42MDYsLTMuMTk2ODhjMCwtMTIuMjEwNjkgMCwtMzguMTU0NjIgMCwtNDUuMTc1NTJjMCwtMy40MjY0MiAyLjQ1NDg1LC01LjYyMDI2IDQuOTI5OTYsLTQuMTkxMjV6IiBmaWxsPSIjZmZmZmZmIi8+PC9nPjwvZz48L3N2Zz4="; - +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0ibTI3LjQzMSAxNi4yMjcgMzguMzA4IDIyLjExOGM0LjUyMiAyLjYxIDQuNTgyIDYuMDczLjE0MyA4LjYzNmwtMzcuNzc1IDIxLjgxYy0yLjc2NCAxLjU5Ni01LjYwNi43My01LjYwNi0zLjE5N1YyMC40MTljMC0zLjQyNyAyLjQ1NS01LjYyIDQuOTMtNC4xOTIiIGZpbGw9IiNmZmYiLz48L3N2Zz4="; const keyIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4NS4zMzE1MyIgaGVpZ2h0PSI4NS4zMzE1MyIgdmlld0JveD0iMCwwLDg1LjMzMTUzLDg1LjMzMTUzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk3LjMzNDIzLC0xMzcuMzM0MjMpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4Mi42NjU3NywxMzcuMzM0MjN2ODUuMzMxNTNoLTg1LjMzMTUzdi04NS4zMzE1M3oiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjN2E3YTdhIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMjA2LjA1MjMxLDE2Mi41NjM4M2MwLC02LjkzMDQ5IDUuNjE4MjcsLTEyLjU0ODc2IDEyLjU0ODc2LC0xMi41NDg3NmM2LjkzMDQ5LDAgMTIuNTQ4NzYsNS42MTgyNyAxMi41NDg3NiwxMi41NDg3NmMwLDYuOTMwNDkgLTUuNjE4MjcsMTIuNTQ4NzYgLTEyLjU0ODc2LDEyLjU0ODc2Yy02LjkzMDQ5LDAgLTEyLjU0ODc2LC01LjYxODI3IC0xMi41NDg3NiwtMTIuNTQ4NzZ6IiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMjM4LjI4MjgsMTkyLjE1MjQ4YzAsLTkuODQ4NTggNy45ODM4NSwtMTcuODMyNDQgMTcuODMyNDQsLTE3LjgzMjQ0YzkuODQ4NTgsMCAxNy44MzI0NCw3Ljk4Mzg1IDE3LjgzMjQ0LDE3LjgzMjQ0YzAsOS44NDg1OCAtNy45ODM4NiwxNy44MzI0NCAtMTcuODMyNDQsMTcuODMyNDRjLTkuODQ4NTgsMCAtMTcuODMyNDQsLTcuOTgzODYgLTE3LjgzMjQ0LC0xNy44MzI0NHoiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yMjEuMTQwOTcsMTY0LjMxODg3bDMwLjM4MTIsMjUuNjI1ODgiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSI1IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L2c+PC9nPjwvc3ZnPg=="; - +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0iTTguNzE4IDI1LjIzYzAtNi45MyA1LjYxOS0xMi41NDkgMTIuNTUtMTIuNTQ5IDYuOTMgMCAxMi41NDggNS42MTggMTIuNTQ4IDEyLjU0OXMtNS42MTggMTIuNTQ5LTEyLjU0OSAxMi41NDlTOC43MTggMzIuMTYgOC43MTggMjUuMjI5bTMyLjIzMSAyOS41ODljMC05Ljg0OCA3Ljk4NC0xNy44MzIgMTcuODMyLTE3LjgzMnMxNy44MzMgNy45ODQgMTcuODMzIDE3LjgzMmMwIDkuODUtNy45ODQgMTcuODMzLTE3LjgzMyAxNy44MzNzLTE3LjgzMi03Ljk4NC0xNy44MzItMTcuODMzIiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTIzLjgwNyAyNi45ODUgMzAuMzgxIDI1LjYyNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg=="; + + function doForEachAnimation(func) { + const targets = Object.values(allAnimations); + for (let i = 0; i < targets.length; i++) { + const targetCache = targets[i]; + const targAnims = Object.values(targetCache); + for (let j = 0; j < targAnims.length; j++) func(targAnims[j]); + } + } + class SPanimations { constructor() { - Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => { - allAnimations.forEach(animationObject => { - const args = { NAME: Object.keys(animationObject)[0] }; - this.stopAnimation(args); + runtime.on("PROJECT_STOP_ALL", () => { + doForEachAnimation((anim) => { anim.playing = false }); + }); + runtime.on("PROJECT_START", () => { + doForEachAnimation((anim) => { anim.playing = false }); + }); + runtime.on("AFTER_EXECUTE", () => { + doForEachAnimation((anim) => { + if (anim.playing) { + const isReverse = anim.playType === 2 || anim.playType === 4; + anim.timer += anim.fps / 1000; + if (anim.timer > anim.buffer) { + anim.buffer += anim.fps / 1000; + if (isReverse) anim.currentFrame--; + else anim.currentFrame++; + + // determine if this frame is a keyframe/costume + const thisFrame = anim.frames[anim.currentFrame] ?? ""; + if (thisFrame.startsWith(specialID)) { + const keyframe = anim.specialFrames[thisFrame]; + if (keyframe.type === "pause") anim.buffer += keyframe.secs; + else this.keyframeUpdate(keyframe, anim, true); + } else if (thisFrame) { + const index = anim.target.getCostumeIndexByName(thisFrame); + if (index > -1) anim.target.setCostume(index); + else console.warn(`Animations -- Invalid Costume (${thisFrame})`); + } + } + // complete any unfinished keyframes + anim.keyBuffers.forEach((key) => { this.keyframeUpdate(key, anim, false) }); + + const frameCnt = anim.frames.length; + const frameCheck = isReverse ? anim.currentFrame <= 0: anim.currentFrame >= frameCnt; + if (frameCheck && anim.keyBuffers.length === 0) { + this.resetAnimPlayer(anim); + if (isReverse) anim.currentFrame = frameCnt; + if (anim.playType < 3) anim.playing = false; + } + } }); }); } @@ -50,10 +101,7 @@ blockType: Scratch.BlockType.COMMAND, text: "make new animation named [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -61,10 +109,7 @@ blockType: Scratch.BlockType.COMMAND, text: "delete animation named [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -77,10 +122,7 @@ blockType: Scratch.BlockType.BOOLEAN, text: "animation [NAME] exists?", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -88,10 +130,7 @@ blockType: Scratch.BlockType.REPORTER, text: "all [TYPE] animations", arguments: { - TYPE: { - type: Scratch.ArgumentType.STRING, - menu: "pullTypes" - }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "pullTypes" } }, }, { blockType: Scratch.BlockType.LABEL, text: "Frames" }, @@ -100,11 +139,8 @@ blockType: Scratch.BlockType.COMMAND, text: "add [COSTUME] to [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - COSTUME: { type: Scratch.ArgumentType.COSTUME }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + COSTUME: { type: Scratch.ArgumentType.COSTUME } }, }, { @@ -112,11 +148,8 @@ blockType: Scratch.BlockType.COMMAND, text: "remove [COSTUME] from [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - COSTUME: { type: Scratch.ArgumentType.COSTUME }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + COSTUME: { type: Scratch.ArgumentType.COSTUME } }, }, { @@ -124,12 +157,9 @@ blockType: Scratch.BlockType.COMMAND, text: "add costumes from [COS1] to [COS2] to [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, COS1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, - COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } }, }, { @@ -137,12 +167,9 @@ blockType: Scratch.BlockType.COMMAND, text: "remove frames [COS1] to [COS2] from [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, COS1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, - COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, + COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } }, }, "---", @@ -151,18 +178,9 @@ blockType: Scratch.BlockType.COMMAND, text: "add a [SECOND] second pause to [NAME] with ID [ID]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - SECOND: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 3 - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "pause1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + SECOND: { type: Scratch.ArgumentType.NUMBER, defaultValue: 3 }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "pause1" } }, }, { @@ -170,14 +188,8 @@ blockType: Scratch.BlockType.COMMAND, text: "remove pause frame from [NAME] with ID [ID]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "pause1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "pause1" } }, }, "---", @@ -186,10 +198,7 @@ blockType: Scratch.BlockType.REPORTER, text: "number of frames in [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -197,10 +206,7 @@ blockType: Scratch.BlockType.REPORTER, text: "all frames in [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1", - } + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -208,14 +214,8 @@ blockType: Scratch.BlockType.REPORTER, text: "frame # [FRAME] in [NAME]", arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - FRAME: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + FRAME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 } }, }, { blockType: Scratch.BlockType.LABEL, text: "Playback" }, @@ -225,14 +225,8 @@ text: "set FPS of [NAME] to [FPS]", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - FPS: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 30 - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + FPS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 30 } }, }, { @@ -241,14 +235,8 @@ text: "play animation [NAME] [TYPE]", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - TYPE: { - type: Scratch.ArgumentType.STRING, - menu: "playBack" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "playBack" } }, }, { @@ -257,14 +245,8 @@ text: "play animation [NAME] [TYPE] until done", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - TYPE: { - type: Scratch.ArgumentType.STRING, - menu: "playBack" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "playBack" } }, }, { @@ -273,10 +255,7 @@ text: "stop animation [NAME]", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, "---", @@ -286,10 +265,7 @@ text: "FPS of [NAME]", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -298,10 +274,7 @@ text: "is [NAME] playing?", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { @@ -310,10 +283,7 @@ text: "current frame of [NAME]", blockIconURI: playIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } }, }, { blockType: Scratch.BlockType.LABEL, text: "Keyframes" }, @@ -323,18 +293,12 @@ text: "add keyframe position to [NAME] with ID [ID] start x [x] y [y] end x [x2] y [y2]", blockIconURI: keyIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "key1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, - y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } }, }, { @@ -343,16 +307,10 @@ text: "add keyframe direction to [NAME] with ID [ID] start [DIR1] end [DIR2]", blockIconURI: keyIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "key1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, DIR1: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 }, - DIR2: { type: Scratch.ArgumentType.ANGLE, defaultValue: 0 }, + DIR2: { type: Scratch.ArgumentType.ANGLE, defaultValue: 0 } }, }, { @@ -361,16 +319,10 @@ text: "add keyframe scale to [NAME] with ID [ID] start [scale]% end [scale2]%", blockIconURI: keyIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "key1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, scale: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, - scale2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 150 }, + scale2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 150 } }, }, { @@ -379,18 +331,12 @@ text: "add keyframe stretch to [NAME] with ID [ID] start width [x] height [y] end width [x2] height [y2]", blockIconURI: keyIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "key1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 200 }, - y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } }, }, "---", @@ -400,418 +346,270 @@ text: "remove keyframe with ID [ID] from [NAME]", blockIconURI: keyIconURI, arguments: { - NAME: { - type: Scratch.ArgumentType.STRING, - defaultValue: "animation 1" - }, - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "key1" - }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" } }, }, ], menus: { - playBack: ["normally", "in reverse", "looped normally", "looped reversed"], - pullTypes: { - acceptReporters: true, - items: ["existing", "playing"], - }, + playBack: Object.keys(playTypes), + pullTypes: { acceptReporters: true, items: ["existing", "playing"] } } }; } - createAnimation(args, util) { - allAnimations.push({ - [args.NAME]: { - name: args.NAME, target: util.target, - fps: 10, frames: [], - playing: false, currentFrame: 0 - } - }); + // Helper Funcs + getAnim(name, util, forAll) { + if (forAll) { + let animSearch = undefined; + doForEachAnimation((anim) => { + if (anim.name === name) animSearch = anim; + }); + return animSearch; + } + const anim = allAnimations[util.target.id]?.[name]; + if (anim) return anim; + return this.createAnimation({ NAME: name, SECRET: true }, util); } - removeAnimation(args) { - this.stopPlayingAnimation(args.NAME); - const indexToRemove = allAnimations.findIndex((animation) => Object.keys(animation)[0] === args.NAME); - if (indexToRemove !== -1) { allAnimations.splice(indexToRemove, 1) } + resetAnimPlayer(anim) { + anim.buffer = anim.fps / 1000; + anim.timer = 0; + anim.currentFrame = -1; } - removeAll() { - allAnimations.forEach(animationObject => { - const args = { NAME: Object.keys(animationObject)[0] }; - this.stopAnimation(args); - }); - allAnimations = []; + keyframeUpdate(data, anim, init) { + const target = anim.target; + let { type, start, end, x1, x2, y1, y2 } = data; + if (data.startStamp === undefined) data.startStamp = Date.now(); + const elapsedTime = Date.now() - data.startStamp; + const progress = Math.min(elapsedTime / (anim.fps * 20), 1); + let delta1, delta2; + switch (type) { + case "pos": + delta1 = x2 - x1; + delta2 = y2 - y1; + target.setXY(x1 + delta1 * progress, y1 + delta2 * progress); + break; + case "dir": + delta1 = end - start; + target.setDirection(start + delta1 * progress); + break; + case "size": + delta1 = end - start; + target.setSize(start + delta1 * progress); + break; + case "stretch": + delta1 = x2 - x1; + delta2 = y2 - y1; + render._allDrawables[target.drawableID].updateScale([ + x1 + delta1 * progress, y1 + delta2 * progress + ]); + break; + } + if (progress === 1) { + delete data.startStamp; + anim.keyBuffers.splice(anim.keyBuffers.indexOf(data), 1); + } else if (init) anim.keyBuffers.push(data); } - isExists(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - return Scratch.Cast.toBoolean(animation); + // Block Funcs + createAnimation(args, util) { + const name = Scratch.Cast.toString(args.NAME); + const id = util.target.id; + if (allAnimations[id] === undefined) allAnimations[id] = {}; + const obj = { + name, buffer: 0, timer: 0, + playing: false, playType: 0, + fps: 10, target: util.target, + frames: [], specialFrames: {}, + keyBuffers: [], currentFrame: -1 + }; + allAnimations[id][name] = obj; + if (args.SECRET) return obj; } - addFrame(args, util) { - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (util.target !== animation[args.NAME].target) { - console.error(`This Animation can only be ran in "${animation[args.NAME].target.sprite.name}"`); - return; - } - if (animation) { animation[args.NAME].frames.push(args.COSTUME) } + removeAnimation(args, util) { + const name = Scratch.Cast.toString(args.NAME); + if (this.isExists(args, util)) delete allAnimations[util.target.id][name]; } - addAllFrames(args, util) { - args.COS1 = args.COS1 < 1 ? 1 : Math.round(args.COS1) - 1; - args.COS2 = args.COS2 < 1 ? 1 : args.COS2 > util.target.sprite.costumes_.length ? util.target.sprite.costumes_.length : Math.round(args.COS2); - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (util.target !== animation[args.NAME].target) { - console.error(`This Animation can only be ran in "${animation[args.NAME].target.sprite.name}"`); - return; - } - if (animation) { - for (let i = args.COS1; i < args.COS2; i++) { - animation[args.NAME].frames.push(util.target.getCostumes()[i].name); - } - } + removeAll() { allAnimations = {} } + + isExists(args, util) { + const name = Scratch.Cast.toString(args.NAME); + return Scratch.Cast.toBoolean(this.getAnim(args.NAME, "", true)); } - removeAllFrames(args, util) { - args.COS1 = args.COS1 < 1 ? 1 : Math.round(args.COS1) - 1; - args.COS2 = args.COS2 < 1 ? 1 : args.COS2 > util.target.sprite.costumes_.length ? util.target.sprite.costumes_.length : Math.round(args.COS2); - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - if (util.target !== animation[args.NAME].target) { - console.error(`This Animation belongs to and can only be ran in "${animation[args.NAME].target.sprite.name}"`); - return; - } - animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => { - const frameIndex = util.target.getCostumes().findIndex(costume => costume.name === frame); - return frameIndex < args.COS1 || frameIndex >= args.COS2; - }); - } + addFrame(args, util) { + const anim = this.getAnim(args.NAME, util); + anim.frames.push(Scratch.Cast.toString(args.COSTUME)); } removeFrame(args, util) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - if (util.target !== animation[args.NAME].target) { - console.error(`This Animation can only be ran in "${animation[args.NAME].target.sprite.name}"`); - return; - } - animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => frame !== args.COSTUME); - } + const anim = this.getAnim(args.NAME, util); + const ind = anim.frames.indexOf(Scratch.Cast.toString(args.COSTUME)); + if (ind > -1) anim.frames.splice(ind, 1); } - addPause(args, util) { - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (animation) { - const SECS = Math.abs(args.SECOND) * 1000; - animation[args.NAME].frames.push({ - [`spKF4!PZ${args.ID}`]: { secs: SECS } - }); - } + addAllFrames(args, util) { + const costumes = util.target.getCostumes(); + const start = Math.min(costumes.length, Math.max(1, Math.round(Scratch.Cast.toNumber(args.COS1)))); + const end = Math.min(costumes.length, Math.max(start, Math.round(Scratch.Cast.toNumber(args.COS2)))); + const anim = this.getAnim(args.NAME, util); + for (let i = start - 1; i < end; i++) anim.frames.push(costumes[i].name); } - removePause(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => !frame.includes(args.ID)); - } + removeAllFrames(args, util) { + const costumes = util.target.getCostumes(); + const start = Math.min(costumes.length, Math.max(1, Math.round(Scratch.Cast.toNumber(args.COS1)))); + const end = Math.min(costumes.length, Math.max(start, Math.round(Scratch.Cast.toNumber(args.COS2)))); + const anim = this.getAnim(args.NAME, util); + anim.frames.splice(start - 1, end - (start - 1)); } - numFrames(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) return animation[args.NAME].frames.length; - else return "Animation Doesnt Exist!"; + addPause(args, util) { + const id = `${specialID}pause-${Scratch.Cast.toString(args.ID)}`; + const anim = this.getAnim(args.NAME, util); + anim.frames.push(id); + anim.specialFrames[id] = { + type: "pause", + secs: Scratch.Cast.toNumber(args.SECOND) + }; } - currentFrame(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) return animation[args.NAME].currentFrame; - else return "Animation Doesnt Exist!"; + removePause(args, util) { + this.deleteKeyframe(args, util); // works the same way + } + + numFrames(args) { + const anim = this.getAnim(args.NAME, "", true); + return anim ? anim.frames.length : 0; } frameNames(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) return JSON.stringify(animation[args.NAME].frames); - else return "Animation Doesnt Exist!"; + const anim = this.getAnim(args.NAME, "", true); + if (!anim) return "[]"; + const rawFrames = structuredClone(anim.frames); + rawFrames.forEach((frame, i) => { rawFrames[i] = frame.replace(specialID, "") }); + return anim ? JSON.stringify(rawFrames) : "[]"; } frameName(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - const newFrame = Math.abs(Math.round(args.FRAME)) - 1; - return (animation[args.NAME].frames[newFrame]) ? JSON.stringify(animation[args.NAME].frames[newFrame]) : ""; - } else { return "Animation Doesnt Exist!" } + const anim = this.getAnim(args.NAME, "", true); + const ind = Scratch.Cast.toNumber(args.FRAME) - 1; + return anim ? anim.frames[ind].replace(specialID, "") ?? "" : ""; } allAnimationsX(args) { - if (args.TYPE === "playing") { - const playingAnimations = allAnimations.filter(animationObj => animationObj[Object.keys(animationObj)[0]].playing === true); - return JSON.stringify(playingAnimations.map(animationObj => Object.keys(animationObj)[0])); - } else { - return JSON.stringify(allAnimations.map(animationObj => Object.keys(animationObj)[0])); + let array = []; + if (args.TYPE === "existing") doForEachAnimation((anim) => { array.push(anim.name) }); + else { + doForEachAnimation((anim) => { + if (anim.playing) array.push(anim.name); + }); } + return JSON.stringify(array); } - setFPS(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - animation[args.NAME].fps = args.FPS === 0 ? 1 : Math.abs(Scratch.Cast.toNumber(args.FPS)); - } + setFPS(args, util) { + const anim = this.getAnim(args.NAME, util); + anim.fps = Scratch.Cast.toNumber(args.FPS); } - playBackWait(args) { - this.playBack(args); - return new Promise((resolve, reject) => { - const animationPlay = () => { - this.isPlaying(args) ? setTimeout(animationPlay, 100) : resolve(); - }; - setTimeout(animationPlay, 100); - }); + playBack(args, util) { + const anim = this.getAnim(args.NAME, util); + this.resetAnimPlayer(anim); + anim.playType = playTypes[args.TYPE] ?? 1; + if (anim.playType === 2 || anim.playType === 4) anim.currentFrame = anim.frames.length; + anim.playing = true; } - playBack(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - this.stopPlayingAnimation(args.NAME); - //stoping animation is not instant, this is to prevent dual animations - setTimeout(() => { - const myAnimation = animation[args.NAME]; - myAnimation.playing = true; - const target = myAnimation.target; - let frameIndex = args.TYPE.includes("reverse") ? myAnimation.frames.length - 1 : 0; - myAnimation.currentFrame = frameIndex; - const numFrames = myAnimation.frames.length; - if (numFrames === 0) return; - const playNextFrame = () => { - if (myAnimation.playing === true) { - myAnimation.currentFrame = frameIndex; - if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("spKF4!"))) { - if (Object.keys(myAnimation.frames[frameIndex]).some(key => key.includes("PZ"))) { - const keys = Object.keys(myAnimation.frames[frameIndex]); - /* eslint-disable */ - for (const key of keys) { - const delayTime = myAnimation.frames[frameIndex][key].secs; - setTimeout(() => { handleNextFrame() }, delayTime); - return; - } - /* eslint-enable */ - } else { this._setKeyframe(target, myAnimation.frames[frameIndex], myAnimation) } - } else { this._setCostume(target, myAnimation.frames[frameIndex]) } - handleNextFrame(); - } else { myAnimation.playing = false } - }; - const handleNextFrame = () => { - if (args.TYPE.includes("reverse")) { - frameIndex--; - if (frameIndex < 0) { - if (args.TYPE.includes("looped")) frameIndex = numFrames - 1; - else { - myAnimation.playing = false; - return; - } - } - } else { - frameIndex++; - if (frameIndex >= numFrames) { - if (args.TYPE.includes("looped")) frameIndex = 0; - else { - myAnimation.playing = false; - return; - } - } - } - setTimeout(playNextFrame, 1000 / myAnimation.fps); - }; - playNextFrame(); - }, 100); - } + playBackWait(args, util) { + const anim = this.getAnim(args.NAME, util); + if (util.stackFrame.executed === undefined) { + this.resetAnimPlayer(anim); + anim.playType = playTypes[args.TYPE] ?? 1; + if (anim.playType === 2 || anim.playType === 4) anim.currentFrame = anim.frames.length; + anim.playing = true; + util.stackFrame.executed = true; + util.yield(); + } else if (anim.playing) util.yield(); } - stopAnimation(args) { this.stopPlayingAnimation(args.NAME) } - - stopPlayingAnimation(name) { - const animation = allAnimations.find((animation) => animation[name]); - if (animation) animation[name].playing = false; + stopAnimation(args, util) { + const anim = this.getAnim(args.NAME, util); + anim.playing = false; } currentFPS(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) return animation[args.NAME].fps; - else return "Animation Doesnt Exist!"; + const anim = this.getAnim(args.NAME, "", true); + return anim ? anim.fps : ""; } isPlaying(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) return animation[args.NAME].playing; - else return false; + const anim = this.getAnim(args.NAME, "", true); + return anim ? anim.playing : false; } - _setCostume(target, requestedCostume, optZeroIndex) { - if (typeof requestedCostume === "number") { - target.setCostume(optZeroIndex ? requestedCostume : requestedCostume - 1); - } else { - const costumeIndex = target.getCostumeIndexByName(requestedCostume.toString()); - if (costumeIndex !== -1) target.setCostume(costumeIndex); - else if (requestedCostume === "next costume") target.setCostume(target.currentCostume + 1); - else if (requestedCostume === "previous costume") target.setCostume(target.currentCostume - 1); - else if (!(isNaN(requestedCostume) || Scratch.Cast.isWhiteSpace(requestedCostume))) { - target.setCostume(optZeroIndex ? Number(requestedCostume) : Number(requestedCostume) - 1); - } - } - return []; + currentFrame(args) { + const anim = this.getAnim(args.NAME, "", true); + return anim ? Math.max(1, Math.min(anim.currentFrame + 1, anim.frames.length)) : 0; } addPosition(args, util) { - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (animation) { - const keyframe = { - [`spKF4!XY${args.ID}`]: { - x1: args.x, y1: args.y, - x2: args.x2, y2: args.y2, - } - }; - animation[args.NAME].frames.push(keyframe); - } - } - - addStretch(args, util) { - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (animation) { - const keyframe = { - [`spKF4!WH${args.ID}`]: { - x1: args.x, y1: args.y, - x2: args.x2, y2: args.y2, - } - }; - animation[args.NAME].frames.push(keyframe); - } + const id = `${specialID}pos-${Scratch.Cast.toString(args.ID)}`; + const anim = this.getAnim(args.NAME, util); + anim.frames.push(id); + anim.specialFrames[id] = { + type: "pos", + x1: Scratch.Cast.toNumber(args.x), x2: Scratch.Cast.toNumber(args.x2), + y1: Scratch.Cast.toNumber(args.y), y2: Scratch.Cast.toNumber(args.y2) + }; } addDirection(args, util) { - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (animation) { - const keyframe = { - [`spKF4!DIR${args.ID}`]: { - dir1: args.DIR1, dir2: args.DIR2, - } - }; - animation[args.NAME].frames.push(keyframe); - } + const id = `${specialID}dir-${Scratch.Cast.toString(args.ID)}`; + const anim = this.getAnim(args.NAME, util); + anim.frames.push(id); + anim.specialFrames[id] = { + type: "dir", + start: Scratch.Cast.toNumber(args.DIR1), + end: Scratch.Cast.toNumber(args.DIR2) + }; } addScale(args, util) { - let animation = allAnimations.find((animation) => animation[args.NAME]); - if (!animation) { - this.createAnimation(args, util); - animation = allAnimations.find((animation) => animation[args.NAME]); - } - if (animation) { - const keyframe = { - [`spKF4!SZ${args.ID}`]: { - size1: args.scale, size2: args.scale2, - } - }; - animation[args.NAME].frames.push(keyframe); - } + const id = `${specialID}size-${Scratch.Cast.toString(args.ID)}`; + const anim = this.getAnim(args.NAME, util); + anim.frames.push(id); + anim.specialFrames[id] = { + type: "size", + start: Scratch.Cast.toNumber(args.scale), + end: Scratch.Cast.toNumber(args.scale2) + }; } - deleteKeyframe(args) { - const animation = allAnimations.find((animation) => animation[args.NAME]); - if (animation) { - animation[args.NAME].frames = animation[args.NAME].frames.filter(frame => { - for (const key in frame) { - if (key.startsWith("spKF4!") && key.endsWith(args.ID)) return false; - } - return true; - }); - } + addStretch(args, util) { + const id = `${specialID}stretch-${Scratch.Cast.toString(args.ID)}`; + const anim = this.getAnim(args.NAME, util); + anim.frames.push(id); + anim.specialFrames[id] = { + type: "stretch", + x1: Scratch.Cast.toNumber(args.x), x2: Scratch.Cast.toNumber(args.x2), + y1: Scratch.Cast.toNumber(args.y), y2: Scratch.Cast.toNumber(args.y2) + }; } - _setKeyframe(target, keyframe, data) { - let startTime, startX, startY, deltaX, deltaY, startSize, deltaSize, startDir, deltaDir; - if (!keyFramesPlaying.some(item => JSON.stringify(item) === JSON.stringify([data, keyframe]))) { - keyFramesPlaying.push([data, keyframe]); - } - keyFramesPlaying.push([data, keyframe]); - const key = keyframe[Object.keys(keyframe)[0]]; - const animationDuration = data.fps * 20; - if (JSON.stringify(keyframe).includes("DIR")) { - startDir = key.dir1; - deltaDir = key.dir2 - key.dir1; - } else if (JSON.stringify(keyframe).includes("SZ")) { - startSize = key.size1; - deltaSize = key.size2 - key.size1; - } else { - startX = key.x1; - startY = key.y1; - deltaX = key.x2 - key.x1; - deltaY = key.y2 - key.y1; - } - if (JSON.stringify(keyframe).includes("XY")) { - const animateXY = (timestamp) => { - if (!startTime) startTime = timestamp; - const elapsedTime = timestamp - startTime; - const progress = Math.min(elapsedTime / animationDuration, 1); - target.setXY(startX + deltaX * progress, startY + deltaY * progress); - if (progress < 1) requestAnimationFrame(animateXY); - }; - requestAnimationFrame(animateXY); - } else if (JSON.stringify(keyframe).includes("WH")) { - const animateXY = (timestamp) => { - if (!startTime) startTime = timestamp; - const elapsedTime = timestamp - startTime; - const progress = Math.min(elapsedTime / animationDuration, 1); - vm.renderer._allDrawables[target.drawableID].updateScale([ - startX + deltaX * progress, startY + deltaY * progress - ]); - if (progress < 1) requestAnimationFrame(animateXY); - }; - requestAnimationFrame(animateXY); - } else if (JSON.stringify(keyframe).includes("DIR")) { - const animateDirection = (timestamp) => { - if (!startTime) startTime = timestamp; - const elapsedTime = timestamp - startTime; - const progress = Math.min(elapsedTime / animationDuration, 1); - target.setDirection(startDir + deltaDir * progress); - if (progress < 1) requestAnimationFrame(animateDirection); - }; - requestAnimationFrame(animateDirection); - } else if (JSON.stringify(keyframe).includes("SZ")) { - const animateSize = (timestamp) => { - if (!startTime) startTime = timestamp; - const elapsedTime = timestamp - startTime; - const progress = Math.min(elapsedTime / animationDuration, 1); - target.setSize(startSize + deltaSize * progress); - if (progress < 1) requestAnimationFrame(animateSize); - }; - requestAnimationFrame(animateSize); + deleteKeyframe(args, util) { + const id = Scratch.Cast.toString(args.ID); + const anim = this.getAnim(args.NAME, util); + const specialFrames = anim.frames.filter((frame) => { return frame.startsWith(specialID) }); + const frame = specialFrames.find((frame) => { return frame.endsWith(id) }); + if (frame !== undefined) { + anim.frames.splice(anim.frames.indexOf(frame), 1); + delete anim.specialFrames[frame]; } - keyFramesPlaying = keyFramesPlaying.filter(item => JSON.stringify(item) !== JSON.stringify([data, keyframe])); } } From 672f7462f9d61b7f1f6099471c5c1eb3c4620acb Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:32:13 -0700 Subject: [PATCH 13/17] Animations -- Small Bug Fix --- extensions/SharkPool/Animations.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 787c400cfe..76a8c5221b 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -4,7 +4,7 @@ // By: SharkPool // Licence: MIT -// Version V.2.0.0 +// Version V.2.0.01 (function (Scratch) { "use strict"; @@ -51,10 +51,11 @@ doForEachAnimation((anim) => { anim.playing = false }); }); runtime.on("AFTER_EXECUTE", () => { + if (runtime.ioDevices.clock._paused) return; doForEachAnimation((anim) => { if (anim.playing) { const isReverse = anim.playType === 2 || anim.playType === 4; - anim.timer += anim.fps / 1000; + anim.timer += 0.01; if (anim.timer > anim.buffer) { anim.buffer += anim.fps / 1000; if (isReverse) anim.currentFrame--; From d3e53d586ff9df05410624104d7f4bf9a9dfd953 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:33:39 -0700 Subject: [PATCH 14/17] Animations -- Lint Shut --- extensions/SharkPool/Animations.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 76a8c5221b..35c406e523 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -438,7 +438,6 @@ removeAll() { allAnimations = {} } isExists(args, util) { - const name = Scratch.Cast.toString(args.NAME); return Scratch.Cast.toBoolean(this.getAnim(args.NAME, "", true)); } From 4aca57e86e3dad3276e26ac14410f55dc60a0497 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:11:28 -0800 Subject: [PATCH 15/17] Animations -- Fix Animation End Pause + Current Frame --- extensions/SharkPool/Animations.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index 35c406e523..bb8bd8baf0 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -4,7 +4,7 @@ // By: SharkPool // Licence: MIT -// Version V.2.0.01 +// Version V.2.0.02 (function (Scratch) { "use strict"; @@ -77,7 +77,7 @@ anim.keyBuffers.forEach((key) => { this.keyframeUpdate(key, anim, false) }); const frameCnt = anim.frames.length; - const frameCheck = isReverse ? anim.currentFrame <= 0: anim.currentFrame >= frameCnt; + const frameCheck = isReverse ? anim.currentFrame <= 1 : anim.currentFrame >= frameCnt - 1; if (frameCheck && anim.keyBuffers.length === 0) { this.resetAnimPlayer(anim); if (isReverse) anim.currentFrame = frameCnt; @@ -554,7 +554,7 @@ currentFrame(args) { const anim = this.getAnim(args.NAME, "", true); - return anim ? Math.max(1, Math.min(anim.currentFrame + 1, anim.frames.length)) : 0; + return anim ? anim.currentFrame + 2 : 0; // + 2 since we start at -1 instead of 0 } addPosition(args, util) { From f95385c381a6fd8c858fab4e9191caee31ef7d2f Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 26 Jan 2025 23:05:34 -0800 Subject: [PATCH 16/17] Animations.js -- allow clones to access parent animations --- extensions/SharkPool/Animations.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index bb8bd8baf0..d562c4cad9 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -368,8 +368,20 @@ }); return animSearch; } - const anim = allAnimations[util.target.id]?.[name]; + const target = util.target; + let anim = allAnimations[target.id]?.[name]; if (anim) return anim; + else if (!target.isOriginal) { + anim = allAnimations[target.sprite.clones[0].id]?.[name]; + if (anim) { + if (allAnimations[target.id] === undefined) allAnimations[target.id] = {}; + const cloneCopy = { ...anim }; + cloneCopy.target = target; + + allAnimations[target.id][name] = cloneCopy; + return cloneCopy; + } + } return this.createAnimation({ NAME: name, SECRET: true }, util); } From bec42194099140bee9f1074f3836881c0ea155da Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Mon, 27 Jan 2025 16:24:19 +0000 Subject: [PATCH 17/17] [Automated] Format code --- extensions/SharkPool/Animations.js | 281 ++++++++++++++++++++--------- 1 file changed, 200 insertions(+), 81 deletions(-) diff --git a/extensions/SharkPool/Animations.js b/extensions/SharkPool/Animations.js index d562c4cad9..15e0eb6139 100644 --- a/extensions/SharkPool/Animations.js +++ b/extensions/SharkPool/Animations.js @@ -8,30 +8,31 @@ (function (Scratch) { "use strict"; - if (!Scratch.extensions.unsandboxed) throw new Error("Animations must run unsandboxed"); + if (!Scratch.extensions.unsandboxed) + throw new Error("Animations must run unsandboxed"); const vm = Scratch.vm; const runtime = vm.runtime; const render = vm.renderer; const specialID = `SPspecialID${Math.random()}`; const playTypes = { - "normally": 1, + normally: 1, "in reverse": 2, "looped normally": 3, - "looped reversed": 4 + "looped reversed": 4, }; let allAnimations = {}; const menuIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjcuNTEiIGhlaWdodD0iMTI3LjUxIiB2aWV3Qm94PSIwIDAgMTI3LjUxIDEyNy41MSI+PHBhdGggZD0iTTAgNjMuNzU1QzAgMjguNTQ1IDI4LjU0NCAwIDYzLjc1NSAwczYzLjc1NSAyOC41NDQgNjMuNzU1IDYzLjc1NS0yOC41NDQgNjMuNzU1LTYzLjc1NSA2My43NTVTMCA5OC45NjYgMCA2My43NTUiIGZpbGw9IiM0NDQiLz48cGF0aCBkPSJNNS44NzUgNjMuNzU1YzAtMzEuOTY3IDI1LjkxMy01Ny44OCA1Ny44OC01Ny44OHM1Ny44OCAyNS45MTMgNTcuODggNTcuODgtMjUuOTEzIDU3Ljg4LTU3Ljg4IDU3Ljg4LTU3Ljg4LTI1LjkxMy01Ny44OC01Ny44OCIgZmlsbD0iIzdhN2E3YSIvPjxwYXRoIGQ9Ik0yMy42OTUgOTAuNTNjLTUuNjEzLTguMTI1LTMuNTc2LTE5LjI2MiA0LjU0OC0yNC44NzVzMTkuMjYyLTMuNTc2IDI0Ljg3NSA0LjU0OSAzLjU3NiAxOS4yNjEtNC41NDkgMjQuODc0LTE5LjI2MSAzLjU3Ni0yNC44NzQtNC41NDh6IiBmaWxsPSIjODk4OTg5IiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTI4LjExIDgwLjM0NGMtNi4zMy05LjE2NC00LjAzNC0yMS43MjUgNS4xMy0yOC4wNTZzMjEuNzI1LTQuMDMzIDI4LjA1NiA1LjEzIDQuMDMzIDIxLjcyNS01LjEzIDI4LjA1Ni0yMS43MjUgNC4wMzQtMjguMDU2LTUuMTN6IiBmaWxsPSIjOWQ5ZDlkIiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTM2LjE0NSA3My4zMjhjLTcuNDE2LTEwLjczNi00LjcyNS0yNS40NSA2LjAxLTMyLjg2NnMyNS40NS00LjcyNiAzMi44NjYgNi4wMWM3LjQxNiAxMC43MzUgNC43MjUgMjUuNDUtNi4wMSAzMi44NjVzLTI1LjQ1IDQuNzI2LTMyLjg2Ni02LjAxeiIgZmlsbD0iI2FlYWVhZSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik00Ny4zMTEgNjkuNTMxYy04LjAzNC0xMS42My01LjEyLTI3LjU3IDYuNTEtMzUuNjA1czI3LjU3LTUuMTE5IDM1LjYwNSA2LjUxYzguMDM0IDExLjYzIDUuMTIgMjcuNTcxLTYuNTEgMzUuNjA1LTExLjYzIDguMDM1LTI3LjU3MSA1LjEyLTM1LjYwNS02LjUxeiIgZmlsbD0iI2JhYmFiYSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik01NS43MTggNzMuMzc1Yy04LjgyOS0xMi43OC01LjYyNi0zMC4yOTggNy4xNTQtMzkuMTI2IDEyLjc4LTguODI5IDMwLjI5OC01LjYyNiAzOS4xMjYgNy4xNTQgOC44MjkgMTIuNzggNS42MjYgMzAuMjk3LTcuMTU0IDM5LjEyNnMtMzAuMjk3IDUuNjI2LTM5LjEyNi03LjE1NCIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg=="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjcuNTEiIGhlaWdodD0iMTI3LjUxIiB2aWV3Qm94PSIwIDAgMTI3LjUxIDEyNy41MSI+PHBhdGggZD0iTTAgNjMuNzU1QzAgMjguNTQ1IDI4LjU0NCAwIDYzLjc1NSAwczYzLjc1NSAyOC41NDQgNjMuNzU1IDYzLjc1NS0yOC41NDQgNjMuNzU1LTYzLjc1NSA2My43NTVTMCA5OC45NjYgMCA2My43NTUiIGZpbGw9IiM0NDQiLz48cGF0aCBkPSJNNS44NzUgNjMuNzU1YzAtMzEuOTY3IDI1LjkxMy01Ny44OCA1Ny44OC01Ny44OHM1Ny44OCAyNS45MTMgNTcuODggNTcuODgtMjUuOTEzIDU3Ljg4LTU3Ljg4IDU3Ljg4LTU3Ljg4LTI1LjkxMy01Ny44OC01Ny44OCIgZmlsbD0iIzdhN2E3YSIvPjxwYXRoIGQ9Ik0yMy42OTUgOTAuNTNjLTUuNjEzLTguMTI1LTMuNTc2LTE5LjI2MiA0LjU0OC0yNC44NzVzMTkuMjYyLTMuNTc2IDI0Ljg3NSA0LjU0OSAzLjU3NiAxOS4yNjEtNC41NDkgMjQuODc0LTE5LjI2MSAzLjU3Ni0yNC44NzQtNC41NDh6IiBmaWxsPSIjODk4OTg5IiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTI4LjExIDgwLjM0NGMtNi4zMy05LjE2NC00LjAzNC0yMS43MjUgNS4xMy0yOC4wNTZzMjEuNzI1LTQuMDMzIDI4LjA1NiA1LjEzIDQuMDMzIDIxLjcyNS01LjEzIDI4LjA1Ni0yMS43MjUgNC4wMzQtMjguMDU2LTUuMTN6IiBmaWxsPSIjOWQ5ZDlkIiBzdHJva2U9IiM3YTdhN2EiIHN0cm9rZS13aWR0aD0iMi41Ii8+PHBhdGggZD0iTTM2LjE0NSA3My4zMjhjLTcuNDE2LTEwLjczNi00LjcyNS0yNS40NSA2LjAxLTMyLjg2NnMyNS40NS00LjcyNiAzMi44NjYgNi4wMWM3LjQxNiAxMC43MzUgNC43MjUgMjUuNDUtNi4wMSAzMi44NjVzLTI1LjQ1IDQuNzI2LTMyLjg2Ni02LjAxeiIgZmlsbD0iI2FlYWVhZSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik00Ny4zMTEgNjkuNTMxYy04LjAzNC0xMS42My01LjEyLTI3LjU3IDYuNTEtMzUuNjA1czI3LjU3LTUuMTE5IDM1LjYwNSA2LjUxYzguMDM0IDExLjYzIDUuMTIgMjcuNTcxLTYuNTEgMzUuNjA1LTExLjYzIDguMDM1LTI3LjU3MSA1LjEyLTM1LjYwNS02LjUxeiIgZmlsbD0iI2JhYmFiYSIgc3Ryb2tlPSIjN2E3YTdhIiBzdHJva2Utd2lkdGg9IjIuNSIvPjxwYXRoIGQ9Ik01NS43MTggNzMuMzc1Yy04LjgyOS0xMi43OC01LjYyNi0zMC4yOTggNy4xNTQtMzkuMTI2IDEyLjc4LTguODI5IDMwLjI5OC01LjYyNiAzOS4xMjYgNy4xNTQgOC44MjkgMTIuNzggNS42MjYgMzAuMjk3LTcuMTU0IDM5LjEyNnMtMzAuMjk3IDUuNjI2LTM5LjEyNi03LjE1NCIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg=="; const blockIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0iTTU0Ljk3IDI1LjM2MmE1IDUgMCAwIDEgNSA1djQwLjQ2YTUgNSAwIDAgMS01IDVIMTQuNTFhNSA1IDAgMCAxLTUtNXYtNDAuNDZhNSA1IDAgMCAxIDUtNXoiIGZpbGw9IiM5MjkyOTIiLz48cGF0aCBkPSJNNjIuODk2IDE3LjQzNmE1IDUgMCAwIDEgNSA1djQwLjQ2YTUgNSAwIDAgMS01IDVoLTQwLjQ2YTUgNSAwIDAgMS01LTV2LTQwLjQ2YTUgNSAwIDAgMSA1LTV6IiBmaWxsPSJzaWx2ZXIiLz48cGF0aCBkPSJNNzAuODIxIDkuNTExYTUgNSAwIDAgMSA1IDV2NDAuNDZhNSA1IDAgMCAxLTUgNWgtNDAuNDZhNSA1IDAgMCAxLTUtNXYtNDAuNDZhNSA1IDAgMCAxIDUtNXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0iTTU0Ljk3IDI1LjM2MmE1IDUgMCAwIDEgNSA1djQwLjQ2YTUgNSAwIDAgMS01IDVIMTQuNTFhNSA1IDAgMCAxLTUtNXYtNDAuNDZhNSA1IDAgMCAxIDUtNXoiIGZpbGw9IiM5MjkyOTIiLz48cGF0aCBkPSJNNjIuODk2IDE3LjQzNmE1IDUgMCAwIDEgNSA1djQwLjQ2YTUgNSAwIDAgMS01IDVoLTQwLjQ2YTUgNSAwIDAgMS01LTV2LTQwLjQ2YTUgNSAwIDAgMSA1LTV6IiBmaWxsPSJzaWx2ZXIiLz48cGF0aCBkPSJNNzAuODIxIDkuNTExYTUgNSAwIDAgMSA1IDV2NDAuNDZhNSA1IDAgMCAxLTUgNWgtNDAuNDZhNSA1IDAgMCAxLTUtNXYtNDAuNDZhNSA1IDAgMCAxIDUtNXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4="; const playIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0ibTI3LjQzMSAxNi4yMjcgMzguMzA4IDIyLjExOGM0LjUyMiAyLjYxIDQuNTgyIDYuMDczLjE0MyA4LjYzNmwtMzcuNzc1IDIxLjgxYy0yLjc2NCAxLjU5Ni01LjYwNi43My01LjYwNi0zLjE5N1YyMC40MTljMC0zLjQyNyAyLjQ1NS01LjYyIDQuOTMtNC4xOTIiIGZpbGw9IiNmZmYiLz48L3N2Zz4="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0ibTI3LjQzMSAxNi4yMjcgMzguMzA4IDIyLjExOGM0LjUyMiAyLjYxIDQuNTgyIDYuMDczLjE0MyA4LjYzNmwtMzcuNzc1IDIxLjgxYy0yLjc2NCAxLjU5Ni01LjYwNi43My01LjYwNi0zLjE5N1YyMC40MTljMC0zLjQyNyAyLjQ1NS01LjYyIDQuOTMtNC4xOTIiIGZpbGw9IiNmZmYiLz48L3N2Zz4="; const keyIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0iTTguNzE4IDI1LjIzYzAtNi45MyA1LjYxOS0xMi41NDkgMTIuNTUtMTIuNTQ5IDYuOTMgMCAxMi41NDggNS42MTggMTIuNTQ4IDEyLjU0OXMtNS42MTggMTIuNTQ5LTEyLjU0OSAxMi41NDlTOC43MTggMzIuMTYgOC43MTggMjUuMjI5bTMyLjIzMSAyOS41ODljMC05Ljg0OCA3Ljk4NC0xNy44MzIgMTcuODMyLTE3LjgzMnMxNy44MzMgNy45ODQgMTcuODMzIDE3LjgzMmMwIDkuODUtNy45ODQgMTcuODMzLTE3LjgzMyAxNy44MzNzLTE3LjgzMi03Ljk4NC0xNy44MzItMTcuODMzIiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTIzLjgwNyAyNi45ODUgMzAuMzgxIDI1LjYyNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg=="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzIiIGhlaWdodD0iODUuMzMyIiB2aWV3Qm94PSIwIDAgODUuMzMyIDg1LjMzMiI+PHBhdGggZD0iTTguNzE4IDI1LjIzYzAtNi45MyA1LjYxOS0xMi41NDkgMTIuNTUtMTIuNTQ5IDYuOTMgMCAxMi41NDggNS42MTggMTIuNTQ4IDEyLjU0OXMtNS42MTggMTIuNTQ5LTEyLjU0OSAxMi41NDlTOC43MTggMzIuMTYgOC43MTggMjUuMjI5bTMyLjIzMSAyOS41ODljMC05Ljg0OCA3Ljk4NC0xNy44MzIgMTcuODMyLTE3LjgzMnMxNy44MzMgNy45ODQgMTcuODMzIDE3LjgzMmMwIDkuODUtNy45ODQgMTcuODMzLTE3LjgzMyAxNy44MzNzLTE3LjgzMi03Ljk4NC0xNy44MzItMTcuODMzIiBmaWxsPSIjZmZmIi8+PHBhdGggZD0ibTIzLjgwNyAyNi45ODUgMzAuMzgxIDI1LjYyNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg=="; function doForEachAnimation(func) { const targets = Object.values(allAnimations); @@ -45,10 +46,14 @@ class SPanimations { constructor() { runtime.on("PROJECT_STOP_ALL", () => { - doForEachAnimation((anim) => { anim.playing = false }); + doForEachAnimation((anim) => { + anim.playing = false; + }); }); runtime.on("PROJECT_START", () => { - doForEachAnimation((anim) => { anim.playing = false }); + doForEachAnimation((anim) => { + anim.playing = false; + }); }); runtime.on("AFTER_EXECUTE", () => { if (runtime.ioDevices.clock._paused) return; @@ -70,14 +75,19 @@ } else if (thisFrame) { const index = anim.target.getCostumeIndexByName(thisFrame); if (index > -1) anim.target.setCostume(index); - else console.warn(`Animations -- Invalid Costume (${thisFrame})`); + else + console.warn(`Animations -- Invalid Costume (${thisFrame})`); } } // complete any unfinished keyframes - anim.keyBuffers.forEach((key) => { this.keyframeUpdate(key, anim, false) }); + anim.keyBuffers.forEach((key) => { + this.keyframeUpdate(key, anim, false); + }); const frameCnt = anim.frames.length; - const frameCheck = isReverse ? anim.currentFrame <= 1 : anim.currentFrame >= frameCnt - 1; + const frameCheck = isReverse + ? anim.currentFrame <= 1 + : anim.currentFrame >= frameCnt - 1; if (frameCheck && anim.keyBuffers.length === 0) { this.resetAnimPlayer(anim); if (isReverse) anim.currentFrame = frameCnt; @@ -102,7 +112,10 @@ blockType: Scratch.BlockType.COMMAND, text: "make new animation named [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { @@ -110,20 +123,26 @@ blockType: Scratch.BlockType.COMMAND, text: "delete animation named [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { opcode: "removeAll", blockType: Scratch.BlockType.COMMAND, - text: "delete all animations" + text: "delete all animations", }, { opcode: "isExists", blockType: Scratch.BlockType.BOOLEAN, text: "animation [NAME] exists?", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { @@ -131,7 +150,7 @@ blockType: Scratch.BlockType.REPORTER, text: "all [TYPE] animations", arguments: { - TYPE: { type: Scratch.ArgumentType.STRING, menu: "pullTypes" } + TYPE: { type: Scratch.ArgumentType.STRING, menu: "pullTypes" }, }, }, { blockType: Scratch.BlockType.LABEL, text: "Frames" }, @@ -140,8 +159,11 @@ blockType: Scratch.BlockType.COMMAND, text: "add [COSTUME] to [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - COSTUME: { type: Scratch.ArgumentType.COSTUME } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + COSTUME: { type: Scratch.ArgumentType.COSTUME }, }, }, { @@ -149,8 +171,11 @@ blockType: Scratch.BlockType.COMMAND, text: "remove [COSTUME] from [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - COSTUME: { type: Scratch.ArgumentType.COSTUME } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + COSTUME: { type: Scratch.ArgumentType.COSTUME }, }, }, { @@ -158,9 +183,12 @@ blockType: Scratch.BlockType.COMMAND, text: "add costumes from [COS1] to [COS2] to [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, COS1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, - COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, }, }, { @@ -168,9 +196,12 @@ blockType: Scratch.BlockType.COMMAND, text: "remove frames [COS1] to [COS2] from [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, COS1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, - COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + COS2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, }, }, "---", @@ -179,9 +210,12 @@ blockType: Scratch.BlockType.COMMAND, text: "add a [SECOND] second pause to [NAME] with ID [ID]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, SECOND: { type: Scratch.ArgumentType.NUMBER, defaultValue: 3 }, - ID: { type: Scratch.ArgumentType.STRING, defaultValue: "pause1" } + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "pause1" }, }, }, { @@ -189,8 +223,11 @@ blockType: Scratch.BlockType.COMMAND, text: "remove pause frame from [NAME] with ID [ID]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - ID: { type: Scratch.ArgumentType.STRING, defaultValue: "pause1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "pause1" }, }, }, "---", @@ -199,7 +236,10 @@ blockType: Scratch.BlockType.REPORTER, text: "number of frames in [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { @@ -207,7 +247,10 @@ blockType: Scratch.BlockType.REPORTER, text: "all frames in [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { @@ -215,8 +258,11 @@ blockType: Scratch.BlockType.REPORTER, text: "frame # [FRAME] in [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - FRAME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + FRAME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, }, }, { blockType: Scratch.BlockType.LABEL, text: "Playback" }, @@ -226,8 +272,11 @@ text: "set FPS of [NAME] to [FPS]", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - FPS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 30 } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + FPS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 30 }, }, }, { @@ -236,8 +285,11 @@ text: "play animation [NAME] [TYPE]", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "playBack" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "playBack" }, }, }, { @@ -246,8 +298,11 @@ text: "play animation [NAME] [TYPE] until done", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "playBack" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "playBack" }, }, }, { @@ -256,7 +311,10 @@ text: "stop animation [NAME]", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, "---", @@ -266,7 +324,10 @@ text: "FPS of [NAME]", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { @@ -275,7 +336,10 @@ text: "is [NAME] playing?", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { @@ -284,7 +348,10 @@ text: "current frame of [NAME]", blockIconURI: playIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, }, }, { blockType: Scratch.BlockType.LABEL, text: "Keyframes" }, @@ -294,12 +361,15 @@ text: "add keyframe position to [NAME] with ID [ID] start x [x] y [y] end x [x2] y [y2]", blockIconURI: keyIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, - y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, { @@ -308,10 +378,13 @@ text: "add keyframe direction to [NAME] with ID [ID] start [DIR1] end [DIR2]", blockIconURI: keyIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, DIR1: { type: Scratch.ArgumentType.ANGLE, defaultValue: 90 }, - DIR2: { type: Scratch.ArgumentType.ANGLE, defaultValue: 0 } + DIR2: { type: Scratch.ArgumentType.ANGLE, defaultValue: 0 }, }, }, { @@ -320,10 +393,13 @@ text: "add keyframe scale to [NAME] with ID [ID] start [scale]% end [scale2]%", blockIconURI: keyIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, scale: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, - scale2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 150 } + scale2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 150 }, }, }, { @@ -332,12 +408,15 @@ text: "add keyframe stretch to [NAME] with ID [ID] start width [x] height [y] end width [x2] height [y2]", blockIconURI: keyIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 200 }, - y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, "---", @@ -347,15 +426,18 @@ text: "remove keyframe with ID [ID] from [NAME]", blockIconURI: keyIconURI, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "animation-1" }, - ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "animation-1", + }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "key1" }, }, }, ], menus: { playBack: Object.keys(playTypes), - pullTypes: { acceptReporters: true, items: ["existing", "playing"] } - } + pullTypes: { acceptReporters: true, items: ["existing", "playing"] }, + }, }; } @@ -374,10 +456,11 @@ else if (!target.isOriginal) { anim = allAnimations[target.sprite.clones[0].id]?.[name]; if (anim) { - if (allAnimations[target.id] === undefined) allAnimations[target.id] = {}; + if (allAnimations[target.id] === undefined) + allAnimations[target.id] = {}; const cloneCopy = { ...anim }; cloneCopy.target = target; - + allAnimations[target.id][name] = cloneCopy; return cloneCopy; } @@ -416,7 +499,8 @@ delta1 = x2 - x1; delta2 = y2 - y1; render._allDrawables[target.drawableID].updateScale([ - x1 + delta1 * progress, y1 + delta2 * progress + x1 + delta1 * progress, + y1 + delta2 * progress, ]); break; } @@ -432,11 +516,17 @@ const id = util.target.id; if (allAnimations[id] === undefined) allAnimations[id] = {}; const obj = { - name, buffer: 0, timer: 0, - playing: false, playType: 0, - fps: 10, target: util.target, - frames: [], specialFrames: {}, - keyBuffers: [], currentFrame: -1 + name, + buffer: 0, + timer: 0, + playing: false, + playType: 0, + fps: 10, + target: util.target, + frames: [], + specialFrames: {}, + keyBuffers: [], + currentFrame: -1, }; allAnimations[id][name] = obj; if (args.SECRET) return obj; @@ -447,7 +537,9 @@ if (this.isExists(args, util)) delete allAnimations[util.target.id][name]; } - removeAll() { allAnimations = {} } + removeAll() { + allAnimations = {}; + } isExists(args, util) { return Scratch.Cast.toBoolean(this.getAnim(args.NAME, "", true)); @@ -466,16 +558,28 @@ addAllFrames(args, util) { const costumes = util.target.getCostumes(); - const start = Math.min(costumes.length, Math.max(1, Math.round(Scratch.Cast.toNumber(args.COS1)))); - const end = Math.min(costumes.length, Math.max(start, Math.round(Scratch.Cast.toNumber(args.COS2)))); + const start = Math.min( + costumes.length, + Math.max(1, Math.round(Scratch.Cast.toNumber(args.COS1))) + ); + const end = Math.min( + costumes.length, + Math.max(start, Math.round(Scratch.Cast.toNumber(args.COS2))) + ); const anim = this.getAnim(args.NAME, util); for (let i = start - 1; i < end; i++) anim.frames.push(costumes[i].name); } removeAllFrames(args, util) { const costumes = util.target.getCostumes(); - const start = Math.min(costumes.length, Math.max(1, Math.round(Scratch.Cast.toNumber(args.COS1)))); - const end = Math.min(costumes.length, Math.max(start, Math.round(Scratch.Cast.toNumber(args.COS2)))); + const start = Math.min( + costumes.length, + Math.max(1, Math.round(Scratch.Cast.toNumber(args.COS1))) + ); + const end = Math.min( + costumes.length, + Math.max(start, Math.round(Scratch.Cast.toNumber(args.COS2))) + ); const anim = this.getAnim(args.NAME, util); anim.frames.splice(start - 1, end - (start - 1)); } @@ -486,7 +590,7 @@ anim.frames.push(id); anim.specialFrames[id] = { type: "pause", - secs: Scratch.Cast.toNumber(args.SECOND) + secs: Scratch.Cast.toNumber(args.SECOND), }; } @@ -503,7 +607,9 @@ const anim = this.getAnim(args.NAME, "", true); if (!anim) return "[]"; const rawFrames = structuredClone(anim.frames); - rawFrames.forEach((frame, i) => { rawFrames[i] = frame.replace(specialID, "") }); + rawFrames.forEach((frame, i) => { + rawFrames[i] = frame.replace(specialID, ""); + }); return anim ? JSON.stringify(rawFrames) : "[]"; } @@ -515,7 +621,10 @@ allAnimationsX(args) { let array = []; - if (args.TYPE === "existing") doForEachAnimation((anim) => { array.push(anim.name) }); + if (args.TYPE === "existing") + doForEachAnimation((anim) => { + array.push(anim.name); + }); else { doForEachAnimation((anim) => { if (anim.playing) array.push(anim.name); @@ -533,7 +642,8 @@ const anim = this.getAnim(args.NAME, util); this.resetAnimPlayer(anim); anim.playType = playTypes[args.TYPE] ?? 1; - if (anim.playType === 2 || anim.playType === 4) anim.currentFrame = anim.frames.length; + if (anim.playType === 2 || anim.playType === 4) + anim.currentFrame = anim.frames.length; anim.playing = true; } @@ -542,7 +652,8 @@ if (util.stackFrame.executed === undefined) { this.resetAnimPlayer(anim); anim.playType = playTypes[args.TYPE] ?? 1; - if (anim.playType === 2 || anim.playType === 4) anim.currentFrame = anim.frames.length; + if (anim.playType === 2 || anim.playType === 4) + anim.currentFrame = anim.frames.length; anim.playing = true; util.stackFrame.executed = true; util.yield(); @@ -575,8 +686,10 @@ anim.frames.push(id); anim.specialFrames[id] = { type: "pos", - x1: Scratch.Cast.toNumber(args.x), x2: Scratch.Cast.toNumber(args.x2), - y1: Scratch.Cast.toNumber(args.y), y2: Scratch.Cast.toNumber(args.y2) + x1: Scratch.Cast.toNumber(args.x), + x2: Scratch.Cast.toNumber(args.x2), + y1: Scratch.Cast.toNumber(args.y), + y2: Scratch.Cast.toNumber(args.y2), }; } @@ -587,7 +700,7 @@ anim.specialFrames[id] = { type: "dir", start: Scratch.Cast.toNumber(args.DIR1), - end: Scratch.Cast.toNumber(args.DIR2) + end: Scratch.Cast.toNumber(args.DIR2), }; } @@ -598,7 +711,7 @@ anim.specialFrames[id] = { type: "size", start: Scratch.Cast.toNumber(args.scale), - end: Scratch.Cast.toNumber(args.scale2) + end: Scratch.Cast.toNumber(args.scale2), }; } @@ -608,16 +721,22 @@ anim.frames.push(id); anim.specialFrames[id] = { type: "stretch", - x1: Scratch.Cast.toNumber(args.x), x2: Scratch.Cast.toNumber(args.x2), - y1: Scratch.Cast.toNumber(args.y), y2: Scratch.Cast.toNumber(args.y2) + x1: Scratch.Cast.toNumber(args.x), + x2: Scratch.Cast.toNumber(args.x2), + y1: Scratch.Cast.toNumber(args.y), + y2: Scratch.Cast.toNumber(args.y2), }; } deleteKeyframe(args, util) { const id = Scratch.Cast.toString(args.ID); const anim = this.getAnim(args.NAME, util); - const specialFrames = anim.frames.filter((frame) => { return frame.startsWith(specialID) }); - const frame = specialFrames.find((frame) => { return frame.endsWith(id) }); + const specialFrames = anim.frames.filter((frame) => { + return frame.startsWith(specialID); + }); + const frame = specialFrames.find((frame) => { + return frame.endsWith(id); + }); if (frame !== undefined) { anim.frames.splice(anim.frames.indexOf(frame), 1); delete anim.specialFrames[frame];