ref: 81d1e4e01d7001e6a0107792e6167136787295d6
dir: /external.js/
import {append, flatten} from "./model.js" import {Forever, Variable, List, Define, TextParameter, BooleanParameter, Else, BooleanSlot, compareName} from "./core.js" import * as core from "./core.js" import {Stage} from "./stage.js" import {MD5} from "./md5.js" import {zip, unzip} from "./zip.js" let decoder = new TextDecoder() let missing = new Set() export async function load(id, options = {}) { let response1 = await fetch(`https://trampoline.turbowarp.org/proxy/projects/${id}`) if (!response1.ok) return let {project_token, title} = await response1.json() let response2 = await fetch(`https://projects.scratch.mit.edu/${id}?token=${project_token}`) if (!response2.ok) return let project = await response2.json() let stage = await fromExternal(project, ({md5ext}) => fetch(`https://assets.scratch.mit.edu/internalapi/asset/${md5ext}/get/`).then(response => response.blob()), options) stage.name = title return stage } export async function fromSB3(bytes, options = {}) { let files = await unzip(bytes) let project = files?.get("project.json") if (!project) return let types = {png: "image/png", jpg: "image/jpeg", svg: "image/svg+xml"} return fromExternal(JSON.parse(decoder.decode(project)), ({md5ext, type}) => new Blob([files.get(md5ext)], {type: types[type] ?? "application/octet-stream"}), options) } async function fromExternal(project, getBlob, options = {}) { let stage = new Stage() for (let name of project.extensions) { if (!await stage.addExtension(name)) { console.log("could not load extension: " + name) } } for (let target of project.targets) { target = {...target, costumes: target.costumes.map(costume => ({...costume, blob: getBlob(costume)}))} let sprite if (target.isStage) { for (let backdrop of target.costumes) { stage.makeBackdrop({name: backdrop.name, blob: await backdrop.blob, x: backdrop.rotationCenterX, y: backdrop.rotationCenterY}) } stage.backdrops[0].remove() stage.backdrop = stage.backdrops[target.currentCostume] for (let [name, value] of Object.values(target.variables)) { stage.variables.set(name, {value}) } for (let [name, list] of Object.values(target.lists)) { stage.lists.set(name, list.slice()) } } else { sprite = stage.makeSprite({name: target.name}) sprite.direction = target.direction sprite.size = target.size sprite.x = target.x sprite.y = target.y if (!target.visible) sprite.hide() if (target.draggable) sprite.draggable = true for (let costume of target.costumes) { sprite.makeCostume({name: costume.name, blob: await costume.blob, x: costume.rotationCenterX, y: costume.rotationCenterY}) } sprite.costumes[0].remove() sprite.costume = sprite.costumes[target.currentCostume] for (let [name, value] of Object.values(target.variables)) { sprite.selfVariables.set(name, {value}) } for (let [name, list] of Object.values(target.lists)) { sprite.selfLists.set(name, list.slice()) } } let options1 = { ...options, MakeBlock: opcode => options.MakeBlock?.(opcode) ?? stageConversions[opcode]?.({stage, sprite, scratch: stage.scratch}), variables: (sprite ?? stage).variables, lists: (sprite ?? stage).lists, } let scripts = fromExternalTarget(target, options1).filter(block => block.type.hat) if (target.isStage) stage.scripts.push(...scripts) else sprite.scripts.push(...scripts) } let ordered = project.targets.filter(target => !target.isStage).sort((a, b) => a.layerOrder - b.layerOrder) for (let {name} of ordered) { stage.getSprite(name).gotoFront() } if (missing.size > 0) console.log("missing:", ...missing) return stage } function fromExternalTarget(target, options = {}) { let blocks = [] for (let info of Object.values(target.blocks)) { if (!info.topLevel) continue let stack = [] convert(info, target.blocks, options, stack) if (stack.length === 0) continue if (typeof stack[0] !== "object") continue if (stack[0].type.shape === undefined && !stack[0].type.hat) Forever(...stack) blocks.push(stack[0]) } for (let block of blocks.flatMap(flatten)) { if (block.type.shape === undefined && !block.type.hat && block.type.category === "custom" && !blocks.some(other => other.type === Define.type && compareName(other.names[0], block.names[0]))) { blocks.push(Define(block.names[0].map((part, i) => typeof part === "string" ? part : {type: part.type, name: "input " + (i + 1)}))) } } return blocks } function convert(info, infos, options, stack0) { if (!info) return let convert1 = options.MakeBlock?.(info.opcode) ?? conversions[info.opcode] if (!convert1) { missing.add(info.opcode) if (info.next) convert(infos[info.next], infos, options, stack0) return } let inputs = {} for (let [name, [_type, value]] of Object.entries(info.inputs)) { if (value === null) continue if (info.opcode !== "procedures_call") name = name.toLowerCase() if (name.startsWith("substack")) { let stack = [] convert(infos[value], infos, options, stack) inputs[name] = stack continue } if (value instanceof Array) { if (value[0] === 12) { inputs[name] = Variable(toName(value[1])) continue } if (value[0] === 13) { inputs[name] = List(toName(value[1])) continue } inputs[name] = String(value[1]) continue } let stack = [] convert(infos[value], infos, options, stack) if (stack.length !== 1 || typeof stack[0] === "object" && stack[0].type.shape === undefined) { inputs[name] = "" continue } inputs[name] = stack[0] } for (let [name, [value]] of Object.entries(info.fields)) inputs[name.toLowerCase()] = value let block = convert1(inputs, info, infos) stack0.push(block) if (typeof block === "object" && block.type.hat) { let stack = [] if (info.next) convert(infos[info.next], infos, options, stack) for (let other of stack) append(block, other) return } if (!info.next || typeof block === "object" && block.type.cap) return convert(infos[info.next], infos, options, stack0) } function toName(name) { return name.normalize().replace(/\s+/ug, " ").trim() } function customName(name, names) { let i = 0 return name.split(/(%b|%s|%n)/g).map(part => { if (part === "%b") return {type: "boolean", name: toName(names[i++] ?? "")} if (part === "%s") return {type: "text", name: toName(names[i++] ?? "")} if (part === "%n") return {type: "text", name: toName(names[i++] ?? "")} return toName(part) }).filter(Boolean) } export async function toSB3(stage) { let missing = new Set() let files = [] let result = {monitors: [], extensions: Object.keys(stage.extensions), meta: {semver: "3.0.0", vm: "0.0.0", agent: "Scrax"}, targets: []} let fns = [] fns.push(async () => result.targets[0] = await toExternalStage(stage, files, missing)) for (let [i, sprite] of stage.sprites.entries()) fns.push(async () => result.targets[i + 1] = await toExternalSprite(sprite, files, missing)) await Promise.all(fns.map(fn => fn())) files.push(new File([JSON.stringify(result)], "project.json")) if (missing.size > 0) console.log("missing:", ...missing) return zip(files) } function toExternalSprite(sprite, files, missing) { let escaped = sprite.name.replaceAll("|", "||").replaceAll("/", "|") let blocks = {} let every = sprite.scripts.flatMap(flatten) for (let block of every) toExternalBlock(block, blocks, {scripts: sprite.scripts, variables: sprite.selfVariables, lists: sprite.selfLists, name: escaped, missing, conversions: spriteConversions(sprite)}) let variables = {} let lists = {} let broadcasts = {} let costumes = [] let sounds = [] let fns = [] for (let [i, costume] of sprite.costumes.entries()) fns.push(async () => costumes[i] = await toExternalCostume(costume, files)) for (let [name, {value}] of sprite.selfVariables) variables[escaped + "/var/" + name] = [name, value] for (let [name, list] of sprite.selfLists) lists[escaped + "/list/" + name] = [name, list.slice()] let result = { isStage: false, name: sprite.name, variables, lists, blocks, broadcasts, costumes, sounds, comments: {}, currentCostume: sprite.costumes.indexOf(sprite.costume), volume: sprite.volume, visible: sprite.visible, draggable: sprite.draggable, x: sprite.x, y: sprite.y, size: sprite.size, direction: sprite.direction, rotationStyle: {full: "all around", none: "don't rotate", "left-right": "left-right"}[sprite.rotationStyle], } return Promise.all(fns.map(fn => fn())).then(() => result) } function toExternalStage(stage, files, missing) { let blocks = {} let every = stage.scripts.flatMap(flatten) for (let block of every) toExternalBlock(block, blocks, {scripts: stage.scripts, missing, conversions: stageConversions1(stage)}) let variables = {} let lists = {} let broadcasts = {} let costumes = [] let sounds = [] let fns = [] for (let [i, backdrop] of stage.backdrops.entries()) fns.push(async () => costumes[i] = await toExternalCostume(backdrop, files)) for (let [name, {value}] of stage.variables) variables["stage/var/" + name] = [name, value] for (let [name, list] of stage.lists) lists["stage/list/" + name] = [name, list.slice()] let result = { isStage: true, name: "Stage", variables, lists, blocks, broadcasts, costumes, sounds, comments: {}, currentCostume: stage.backdrops.indexOf(stage.backdrop), volume: stage.volume, tempo: stage.extensions.music?.tempo ?? 60, } return Promise.all(fns.map(fn => fn())).then(() => result) } async function toExternalCostume(costume, files) { let md5 = MD5(new Uint8Array(await costume.blob.arrayBuffer())) let ext = {"image/png": "png", "image/jpeg": "jpg", "image/svg+xml": "svg"}[costume.blob.type] ?? "bin" files.push(new File([costume.blob], md5 + "." + ext)) return { name: costume.name, assetId: md5, md5ext: md5 + "." + ext, dataFormat: ext, rotationCenterX: costume.x, rotationCenterY: costume.y, } } function toExternalBlock(block, blocks, {scripts, variables, lists, name, missing, conversions}) { if (block.type === Variable.type) return if (block.type === List.type) return if (block.type.shape === "slot") return if (block.type === Define.type) { let inputs = {} let values = block.names[0].filter(part => typeof part !== "string") for (let [i, part] of values.entries()) { inputs[`param/${part.type}/${part.name}`] = [1, block.id + "/" + i] blocks[block.id + "/" + i] = { opcode: part.type === "boolean" ? "argument_reporter_boolean" : "argument_reporter_string_number", parent: block.id + "/x", inputs: {}, fields: {VALUE: [part.name, null]}, shadow: true, topLevel: false, } } blocks[block.id] = {opcode: "procedures_definition", inputs: {custom_block: [1, block.id + "/x"]}, fields: {}, topLevel: true, parent: null} blocks[block.id + "/x"] = { opcode: "procedures_prototype", inputs, fields: {}, parent: String(block.id), shadow: true, topLevel: true, mutation: { tagName: "mutation", children: [], proccode: block.names[0].map(part => typeof part === "string" ? part : part.type === "boolean" ? "%b" : "%s").join(" "), argumentids: JSON.stringify(values.map(part => `param/${part.type}/${part.name}`)), argumentnames: JSON.stringify(values.map(part => part.name)), argumentdefaults: JSON.stringify(values.map(() => "")), warp: JSON.stringify(block.atomic), }, } if (block.stack.length > 0) blocks[block.id].next = String(block.stack[0].id) return } function toVariable(name1) { return [name1, (variables?.has(name1) ? name + "/var/" : "stage/var/") + name1] } function toList(name1) { return [name1, (lists?.has(name1) ? name + "/list/" : "stage/list/") + name1] } let previous = block.parent?.stack?.[block.parent.stack.indexOf(block) - 1] previous ??= block.parent if (block.type.shape === undefined && !block.type.hat && block.type.category === "custom") { let block0 = scripts.find(block0 => block0.type === Define.type && compareName(block0.names[0], block.names[0])) let values = block0.names[0].filter(part => typeof part !== "string") let ids = values.map(part => `param/${part.type}/${part.name}`) let inputs = {} blocks[block.id] = { opcode: "procedures_call", inputs, fields: {}, topLevel: false, mutation: { tagName: "mutation", children: [], proccode: block.names[0].map(part => typeof part === "string" ? part : part.type === "boolean" ? "%b" : "%s").join(" "), argumentids: JSON.stringify(ids), warp: JSON.stringify(block0.atomic), }, parent: String(previous.id), } for (let [i, input] of block.inputs.entries()) { inputs[ids[i]] = [3, String(input.id), [10, ""]] if (input.type === Variable.type) inputs[ids[i]] = [3, [12, ...toVariable(input.names[0])], [10, ""]] if (input.type === List.type) inputs[ids[i]] = [3, [13, ...toList(input.names[0])], [10, ""]] if (input.type.shape === "slot") inputs[ids[i]] = [1, [10, input.value]] if (input.type.shape === "slot" && values[i].type === "boolean") delete inputs[ids[i]] } let next = block.parent.stack[block.parent.stack.indexOf(block) + 1] if (next) blocks[block.id].next = String(next.id) return } if (block.type === TextParameter.type || block.type === BooleanParameter.type) { let opcode = block.type === BooleanParameter.type ? "argument_reporter_boolean" : "argument_reporter_string_number" blocks[block.id] = {opcode, parent: String(block.parent.id), inputs: {}, fields: {VALUE: [block.names[0], null]}, topLevel: false} return } let conversion = conversions1.get(block.type) ?? conversions.get(block.type) if (!conversion) { missing.add(block.type) return } let [opcode, ...args] = conversion let inputs = {} let fields = {} blocks[block.id] = {opcode, inputs, fields, topLevel: block.type.hat, parent: null} if (block.type.hat) { if (block.stack.length > 0) blocks[block.id].next = String(block.stack[0].id) } else { if (block.stack) { if (block.stack.length > 0) inputs.SUBSTACK = [2, String(block.stack[0].id)] } blocks[block.id].parent = String(previous.id) if (block.type.shape === undefined) { if (block.complement?.type === Else.type) { blocks[block.id].opcode = "control_if_else" if (block.complement.stack.length > 0) inputs.SUBSTACK2 = [2, String(block.complement.stack[0].id)] } let next = block.parent.stack[block.parent.stack.indexOf(block) + 1] if (next) blocks[block.id].next = String(next.id) } } for (let [i, reference] of block.type.references?.entries() ?? []) { if (reference === "variable") fields.VARIABLE = toVariable(block.names[i]) if (reference === "list") fields.LIST = toList(block.names[i]) } let i = 0 for (let arg of args) { if (typeof arg === "string") { let block1 = block.inputs[i] if (arg.endsWith(":u")) { fields[arg.slice(0, -2)] = [block1.value.toUpperCase(), null] i++ continue } inputs[arg] = [3, String(block1.id), [10, ""]] if (block1.type === Variable.type) inputs[arg] = [3, [12, ...toVariable(block1.names[0])], [10, ""]] if (block1.type === List.type) inputs[arg] = [3, [13, ...toList(block1.names[0])], [10, ""]] if (block1.type.shape === "slot") inputs[arg] = [1, [10, block1.value]] if (block1.type.shape === "slot" && block.type.slots[i].type === BooleanSlot.type) delete inputs[arg] i++ continue } if (arg.length === 1) { let block1 = block.inputs[i] if (block1.type.shape === "slot") fields[arg[0]] = [block1.value, null] else fields[arg[0]] = ["", null] i++ continue } if (arg.length === 2) { fields[arg[0]] = [arg[1], null] continue } if (arg.length === 4) { inputs[arg[0]] = [1, block.id + "/x"] blocks[block.id + "/x"] = {opcode: arg[1], fields: {[arg[2]]: [arg[3], null]}, topLevel: false, shadow: true} continue } let block1 = block.inputs[i] inputs[arg[0]] = [3, String(block1.id), [10, ""]] if (block1.type === Variable.type) inputs[arg[0]] = [3, [12, ...toVariable(block1.names[0])], [10, ""]] if (block1.type === List.type) inputs[arg[0]] = [3, [13, ...toList(block1.names[0])], [10, ""]] if (block1.type.shape === "slot" && block.type.slots[i].type === BooleanSlot.type) delete inputs[arg[0]] if (block1.type.shape === "slot" && block.type.slots[i].type !== BooleanSlot.type) { inputs[arg[0]] = [1, String(block1.id)] blocks[block1.id] = {opcode: arg[1], fields: {[arg[2]]: [block1.value, null]}, topLevel: false, shadow: true} } i++ } } let conversions = { event_whenflagclicked: () => core.WhenFlagClicked(), control_forever: inputs => core.Forever(...inputs.substack ?? []), control_repeat: inputs => core.Repeat(inputs.times, ...inputs.substack ?? []), control_if: inputs => core.If(inputs.condition, ...inputs.substack ?? []), control_if_else: inputs => { let block = core.IfElse(inputs.condition, ...inputs.substack ?? []) append(block.complement, ...inputs.substack2 ?? []) return block }, control_wait_until: inputs => core.WaitUntil(inputs.condition), control_repeat_until: inputs => core.RepeatUntil(inputs.condition, ...inputs.substack ?? []), operator_add: inputs => core.Add(inputs.num1, inputs.num2), operator_subtract: inputs => core.Subtract(inputs.num1, inputs.num2), operator_multiply: inputs => core.Multiply(inputs.num1, inputs.num2), operator_divide: inputs => core.Divide(inputs.num1, inputs.num2), operator_lt: inputs => core.LessThan(inputs.operand1, inputs.operand2), operator_equals: inputs => core.Equals(inputs.operand1, inputs.operand2), operator_gt: inputs => core.GreaterThan(inputs.operand1, inputs.operand2), operator_and: inputs => core.And(inputs.operand1, inputs.operand2), operator_or: inputs => core.Or(inputs.operand1, inputs.operand2), operator_not: inputs => core.Not(inputs.operand), operator_join: inputs => core.Join(inputs.string1, inputs.string2), operator_letter_of: inputs => core.TextIndex(inputs.string, inputs.letter), operator_length: inputs => core.TextLength(inputs.string), operator_contains: inputs => core.TextContains(inputs.string1, inputs.string2), operator_mod: inputs => core.Modulo(inputs.num1, inputs.num2), data_setvariableto: inputs => core.SetVariable(toName(inputs.variable), inputs.value), data_changevariableby: inputs => core.ChangeVariable(toName(inputs.variable), inputs.value), data_addtolist: inputs => core.Append(toName(inputs.list), inputs.item), data_deleteoflist: inputs => { if (inputs.index === "all") return core.Clear(toName(inputs.list)) if (inputs.index === "last") return core.Delete(toName(inputs.list), core.Length(toName(inputs.list))) return core.Delete(toName(inputs.list), inputs.index) }, data_deletealloflist: inputs => core.Clear(toName(inputs.list)), data_insertatlist: inputs => core.Insert(toName(inputs.list), inputs.index, inputs.item), data_replaceitemoflist: inputs => core.Replace(toName(inputs.list), inputs.index, inputs.item), data_itemoflist: inputs => core.Index(toName(inputs.list), inputs.index), data_itemnumoflist: inputs => core.Find(toName(inputs.list), inputs.item), data_lengthoflist: inputs => core.Length(toName(inputs.list)), data_listcontainsitem: inputs => core.Contains(toName(inputs.list), inputs.item), procedures_definition: (_inputs, info, infos) => { let {mutation} = infos[info.inputs.custom_block[1]] let block = core.Define(customName(mutation.proccode, JSON.parse(mutation.argumentnames))) if (mutation.warp === "true") block.atomic = true return block }, procedures_call: (inputs, info) => core.Custom(customName(info.mutation.proccode, []), ...JSON.parse(info.mutation.argumentids).map(n => inputs[n])), procedures_prototype: () => { }, argument_reporter_string_number: inputs => core.TextParameter(toName(inputs.value)), argument_reporter_boolean: inputs => core.BooleanParameter(toName(inputs.value)), } let stageConversions = { control_wait: ({scratch}) => inputs => scratch.Wait(inputs.duration), sensing_timer: ({scratch}) => () => scratch.Timer(), sensing_resettimer: ({scratch}) => () => scratch.ResetTimer(), sensing_current: ({scratch}) => inputs => { if (inputs.currentmenu === "YEAR") return scratch.Year() if (inputs.currentmenu === "MONTH") return scratch.Month() if (inputs.currentmenu === "DATE") return scratch.Day() if (inputs.currentmenu === "DAYOFWEEK") return scratch.DayOfWeek() if (inputs.currentmenu === "HOUR") return scratch.Hour() if (inputs.currentmenu === "MINUTE") return scratch.Minute() if (inputs.currentmenu === "SECOND") return scratch.Second() }, sensing_dayssince2000: ({scratch}) => () => scratch.DaysSince2000(), sensing_username: ({scratch}) => () => scratch.Username(), operator_random: ({scratch}) => inputs => scratch.PickRandom(inputs.from, inputs.to), operator_round: ({scratch}) => inputs => scratch.Round(inputs.num), operator_mathop: ({scratch}) => inputs => { let ops = {abs: scratch.Abs, ceiling: scratch.Ceiling, floor: scratch.Floor, sqrt: scratch.Sqrt, sin: scratch.Sin, cos: scratch.Cos, tan: scratch.Tan, asin: scratch.ArcSin, acos: scratch.ArcCos, atan: scratch.ArcTan, ln: scratch.Ln, log: scratch.Log, "e ^": scratch.Exp, "10 ^": scratch.Exp10} return ops[inputs.operator]?.(inputs.num) }, control_stop: ({scratch}) => inputs => { if (inputs.stop_option === "this script") return core.Stop() if (inputs.stop_option === "all") return scratch.StopAll() if (inputs.stop_option === "other scripts in sprite") return scratch.StopOther() }, motion_movesteps: ({sprite}) => inputs => sprite.Move(inputs.steps), motion_turnright: ({sprite}) => inputs => sprite.RotateRight(inputs.degrees), motion_turnleft: ({sprite}) => inputs => sprite.RotateLeft(inputs.degrees), motion_pointindirection: ({sprite}) => inputs => sprite.SetDirection(inputs.direction), // motion_pointtowards: ..., motion_gotoxy: ({sprite}) => inputs => sprite.GotoXY(inputs.x, inputs.y), // motion_goto: ..., // motion_glidesecstoxy: ..., // motion_glideto: ..., motion_changexby: ({sprite}) => inputs => sprite.ChangeX(inputs.dx), motion_setx: ({sprite}) => inputs => sprite.SetX(inputs.x), motion_changeyby: ({sprite}) => inputs => sprite.ChangeY(inputs.dy), motion_sety: ({sprite}) => inputs => sprite.SetY(inputs.y), // motion_ifonedgebounce: ..., motion_setrotationstyle: ({sprite}) => inputs => { if (inputs.style === "don't rotate") return sprite.DisableRotation() if (inputs.style === "all around") return sprite.EnableRotation() if (inputs.style === "left-right") return sprite.EnableFlipping() }, motion_xposition: ({sprite}) => () => sprite.GetX(), motion_yposition: ({sprite}) => () => sprite.GetY(), motion_direction: ({sprite}) => () => sprite.GetDirection(), looks_say: ({sprite}) => inputs => sprite.Say(inputs.message), looks_think: ({sprite}) => inputs => sprite.Think(inputs.message), looks_sayforsecs: ({sprite}) => inputs => sprite.SayFor(inputs.message, inputs.secs), looks_thinkforsecs: ({sprite}) => inputs => sprite.ThinkFor(inputs.message, inputs.secs), looks_show: ({sprite}) => () => sprite.Show(), looks_hide: ({sprite}) => () => sprite.Hide(), looks_changeeffectby: ({sprite}) => inputs => sprite.ChangeEffect(inputs.effect.toLowerCase(), inputs.change), looks_seteffectto: ({sprite}) => inputs => sprite.SetEffect(inputs.effect.toLowerCase(), inputs.value), looks_cleargraphiceffects: ({sprite}) => () => sprite.ClearEffects(), looks_changesizeby: ({sprite}) => inputs => sprite.ChangeSize(inputs.change), looks_setsizeto: ({sprite}) => inputs => sprite.SetSize(inputs.size), looks_size: ({sprite}) => () => sprite.GetSize(), looks_gotofrontback: ({sprite}) => inputs => { if (inputs.front_back === "front") return sprite.GotoFront() if (inputs.front_back === "back") return sprite.GotoBack() }, looks_goforwardbackwardlayers: ({sprite}) => inputs => { if (inputs.forward_backward === "forward") return sprite.GoForward(inputs.num) if (inputs.forward_backward === "backward") return sprite.GoBackward(inputs.num) }, looks_switchcostumeto: ({sprite}) => inputs => sprite.SetCostume(inputs.costume), looks_nextcostume: ({sprite}) => () => sprite.NextCostume(), looks_costumenumbername: ({sprite}) => inputs => { if (inputs.number_name === "number") return sprite.CostumeNumber() if (inputs.number_name === "name") return sprite.CostumeName() }, looks_costume: () => inputs => String(inputs.costume), looks_switchbackdropto: ({stage}) => inputs => stage.SetBackdrop(inputs.backdrop), // looks_switchbackdroptoandwait: ..., looks_backdrops: () => inputs => String(inputs.backdrop), looks_nextbackdrop: ({stage}) => () => stage.NextBackdrop(), looks_backdropnumbername: ({stage}) => inputs => { if (inputs.number_name === "number") return stage.BackdropNumber() if (inputs.number_name === "name") return stage.BackdropName() }, // sound_play: ..., // sound_playuntildone: ..., // sound_stopallsounds: ..., // sound_seteffectto: ..., // sound_changeeffectby: ..., // sound_cleareffects: ..., // sound_changevolumeby: ..., // sound_setvolumeto: ..., // sound_volume: ..., event_whenthisspriteclicked: ({sprite}) => () => sprite.WhenClicked(), event_whenstageclicked: ({stage}) => () => stage.WhenClicked(), event_whenbroadcastreceived: ({stage}) => inputs => stage.WhenReceived(inputs.broadcast_option), // event_whengreaterthan: ..., // event_whenbackdropswitchesto: ..., event_broadcast: ({stage}) => inputs => stage.Broadcast(inputs.broadcast_input), event_broadcastandwait: ({stage}) => inputs => stage.BroadcastAndWait(inputs.broadcast_input), event_whenkeypressed: ({stage}) => inputs => stage.WhenKeyPressed(inputs.key_option), control_start_as_clone: ({sprite}) => () => sprite.WhenCloned(), control_create_clone_of: ({stage, sprite}) => inputs => { if (inputs.clone_option === "_myself_") return sprite.Clone() return stage.CloneSprite(inputs.clone_option) }, control_create_clone_of_menu: () => inputs => inputs.clone_option, control_delete_this_clone: ({sprite}) => () => sprite.Delete(), sensing_touchingobject: ({sprite}) => inputs => { // todo: allow sprite selection if (inputs.touchingobjectmenu === "_mouse_") return sprite.TouchingMouse() }, sensing_touchingobjectmenu: () => inputs => inputs.touchingobjectmenu, // sensing_touchingcolor: ..., // sensing_coloristouchingcolor: ..., sensing_distanceto: ({sprite}) => inputs => sprite.DistanceToSprite(inputs.distancetomenu), sensing_distancetomenu: () => inputs => inputs.distancetomenu, // sensing_askandwait: ..., // sensing_answer: ..., sensing_keypressed: ({stage}) => inputs => stage.KeyPressed(inputs.key_option), sensing_mousedown: ({stage}) => () => stage.MouseDown(), sensing_mousex: ({stage}) => () => stage.GetMouseX(), sensing_mousey: ({stage}) => () => stage.GetMouseY(), // sensing_setdragmode: ..., // sensing_loudness: ..., // sensing_of: ..., sensing_keyoptions: () => inputs => String(inputs.key_option), // data_showlist: ..., // data_hidelist: ..., pen_clear: ({sprite}) => () => sprite.extensions.pen.blocks.Clear(), pen_stamp: ({sprite}) => () => sprite.extensions.pen.blocks.Stamp(), pen_penDown: ({sprite}) => () => sprite.extensions.pen.blocks.PenDown(), pen_penUp: ({sprite}) => () => sprite.extensions.pen.blocks.PenUp(), pen_setPenColorToColor: ({sprite}) => inputs => sprite.extensions.pen.blocks.SetPenColor(inputs.color), pen_changePenSizeBy: ({sprite}) => inputs => sprite.extensions.pen.blocks.ChangePenSize(inputs.size), pen_setPenSizeTo: ({sprite}) => inputs => sprite.extensions.pen.blocks.SetPenSize(inputs.size), // pen_setPenColorParamTo: ..., // pen_changePenColorParamBy: ..., // pen_menu_colorParam: ..., // music_playDrumForBeats: ..., // music_restForBeats: ..., music_playNoteForBeats: ({sprite, stage}) => inputs => (sprite ?? stage).extensions.music.blocks.PlayNote(inputs.note, inputs.beats), // music_setInstrument: ..., // music_setTempo: ..., // music_changeTempo: ..., note: () => inputs => String(inputs.note), // music_getTempo: ..., // videoSensing_whenMotionGreaterThan: ..., // videoSensing_videoOn: ..., // videoSensing_videoToggle: ..., // videoSensing_setVideoTransparency: ..., // text2speech_speakAndWait: ..., // text2speech_setVoice: ..., // text2speech_setLanguage: ..., // translate_getTranslate: ..., // translate_getViewerLanguage: ..., // boost_motorOnFor: ..., // boost_motorOnForRotation: ..., // boost_motorOn: ..., // boost_motorOff: ..., // boost_setMotorPower: ..., // boost_setMotorDirection: ..., // boost_getMotorPosition: ..., // boost_whenColor: ..., // boost_seeingColor: ..., // boost_whenTilted: ..., // boost_getTiltAngle: ..., // boost_setLightHue: ..., // ev3_motorTurnClockwise: ..., // ev3_motorTurnCounterClockwise: ..., // ev3_motorSetPower: ..., // ev3_getMotorPosition: ..., // ev3_whenButtonPressed: ..., // ev3_whenDistanceLessThan: ..., // ev3_whenBrightnessLessThan: ..., // ev3_buttonPressed: ..., // ev3_getDistance: ..., // ev3_getBrightness: ..., // ev3_beep: ..., // gdxfor_whenGesture: ..., // gdxfor_whenForcePushedOrPulled: ..., // gdxfor_getForce: ..., // gdxfor_whenTilted: ..., // gdxfor_isTilted: ..., // gdxfor_getTilt: ..., // gdxfor_isFreeFalling: ..., // gdxfor_getSpinSpeed: ..., // gdxfor_getAcceleration: ..., // makeymakey_whenMakeyKeyPressed: ..., // makeymakey_whenCodePressed: ..., // microbit_whenButtonPressed: ..., // microbit_isButtonPressed: ..., // microbit_whenGesture: ..., // microbit_displaySymbol: ..., // microbit_displayText: ..., // microbit_displayClear: ..., // microbit_whenTilted: ..., // microbit_isTilted: ..., // microbit_getTiltAngle: ..., // microbit_whenPinConnected: ..., // wedo2_motorOnFor: ..., // wedo2_motorOn: ..., // wedo2_motorOff: ..., // wedo2_startMotorPower: ..., // wedo2_setMotorDirection: ..., // wedo2_setLightHue: ..., // wedo2_playNoteFor: ..., // wedo2_whenDistance: ..., // wedo2_whenTilted: ..., // wedo2_getDistance: ..., // wedo2_isTilted: ..., // wedo2_getTiltAngle: ..., } let conversions1 = new Map([ [core.WhenFlagClicked.type, ["event_whenflagclicked"]], [core.If.type, ["control_if", "CONDITION"]], [core.Forever.type, ["control_forever"]], [core.WaitUntil.type, ["control_wait_until", "CONDITION"]], [core.RepeatUntil.type, ["control_repeat_until", "CONDITION"]], [core.Repeat.type, ["control_repeat", "TIMES"]], [core.Stop.type, ["control_stop", ["STOP_OPTION", "this script"]]], [core.Add.type, ["operator_add", "NUM1", "NUM2"]], [core.Subtract.type, ["operator_subtract", "NUM1", "NUM2"]], [core.Multiply.type, ["operator_multiply", "NUM1", "NUM2"]], [core.Divide.type, ["operator_divide", "NUM1", "NUM2"]], [core.Modulo.type, ["operator_mod", "NUM1", "NUM2"]], [core.LessThan.type, ["operator_lt", "OPERAND1", "OPERAND2"]], [core.GreaterThan.type, ["operator_gt", "OPERAND1", "OPERAND2"]], [core.Equals.type, ["operator_equals", "OPERAND1", "OPERAND2"]], [core.And.type, ["operator_and", "OPERAND1", "OPERAND2"]], [core.Or.type, ["operator_or", "OPERAND1", "OPERAND2"]], [core.Not.type, ["operator_not", "OPERAND"]], [core.Join.type, ["operator_join", "STRING1", "STRING2"]], [core.TextIndex.type, ["operator_letter_of", "LETTER", "STRING"]], [core.TextLength.type, ["operator_length", "STRING"]], [core.TextContains.type, ["operator_contains", "STRING1", "STRING2"]], [core.SetVariable.type, ["data_setvariableto", "VALUE"]], [core.ChangeVariable.type, ["data_changevariableby", "VALUE"]], [core.Append.type, ["data_addtolist", "ITEM"]], [core.Delete.type, ["data_deleteoflist", "INDEX"]], [core.Clear.type, ["data_deletealloflist"]], [core.Insert.type, ["data_insertatlist", "INDEX", "ITEM"]], [core.Replace.type, ["data_replaceitemoflist", "INDEX", "ITEM"]], [core.Index.type, ["data_itemoflist", "INDEX"]], [core.Find.type, ["data_itemnumoflist", "ITEM"]], [core.Length.type, ["data_lengthoflist"]], [core.Contains.type, ["data_listcontainsitem", "ITEM"]], ]) let stageConversions1 = stage => new Map([ [stage.scratch.Wait.type, ["control_wait", "DURATION"]], [stage.scratch.Timer.type, ["sensing_timer"]], [stage.scratch.ResetTimer.type, ["sensing_resettimer"]], [stage.scratch.Username.type, ["sensing_username"]], [stage.scratch.Round.type, ["operator_round", "NUM"]], [stage.scratch.DaysSince2000.type, ["sensing_dayssince2000"]], [stage.scratch.Abs.type, ["operator_mathop", "NUM", ["OPERATOR", "abs"]]], [stage.scratch.Ceiling.type, ["operator_mathop", "NUM", ["OPERATOR", "ceiling"]]], [stage.scratch.Floor.type, ["operator_mathop", "NUM", ["OPERATOR", "floor"]]], [stage.scratch.Sqrt.type, ["operator_mathop", "NUM", ["OPERATOR", "sqrt"]]], [stage.scratch.Sin.type, ["operator_mathop", "NUM", ["OPERATOR", "sin"]]], [stage.scratch.Cos.type, ["operator_mathop", "NUM", ["OPERATOR", "cos"]]], [stage.scratch.Tan.type, ["operator_mathop", "NUM", ["OPERATOR", "tan"]]], [stage.scratch.ArcSin.type, ["operator_mathop", "NUM", ["OPERATOR", "asin"]]], [stage.scratch.ArcCos.type, ["operator_mathop", "NUM", ["OPERATOR", "acos"]]], [stage.scratch.ArcTan.type, ["operator_mathop", "NUM", ["OPERATOR", "atan"]]], [stage.scratch.Ln.type, ["operator_mathop", "NUM", ["OPERATOR", "ln"]]], [stage.scratch.Log.type, ["operator_mathop", "NUM", ["OPERATOR", "log"]]], [stage.scratch.Exp.type, ["operator_mathop", "NUM", ["OPERATOR", "e ^"]]], [stage.scratch.Exp10.type, ["operator_mathop", "NUM", ["OPERATOR", "10 ^"]]], [stage.scratch.Year.type, ["sensing_current", ["CURRENTMENU", "YEAR"]]], [stage.scratch.Month.type, ["sensing_current", ["CURRENTMENU", "MONTH"]]], [stage.scratch.Day.type, ["sensing_current", ["CURRENTMENU", "DATE"]]], [stage.scratch.DayOfWeek.type, ["sensing_current", ["CURRENTMENU", "DAYOFWEEK"]]], [stage.scratch.Hour.type, ["sensing_current", ["CURRENTMENU", "HOUR"]]], [stage.scratch.Minute.type, ["sensing_current", ["CURRENTMENU", "MINUTE"]]], [stage.scratch.Second.type, ["sensing_current", ["CURRENTMENU", "SECOND"]]], [stage.scratch.PickRandom.type, ["operator_random", "FROM", "TO"]], [stage.scratch.StopOther.type, ["control_stop", ["STOP_OPTION", "other scripts in sprite"]]], [stage.scratch.StopAll.type, ["control_stop", ["STOP_OPTION", "all"]]], [stage.Broadcast.type, ["event_broadcast", ["BROADCAST_INPUT", "event_broadcast_menu", "BROADCAST_OPTION"]]], [stage.BroadcastAndWait.type, ["event_broadcastandwait", ["BROADCAST_INPUT", "event_broadcast_menu", "BROADCAST_OPTION"]]], [stage.MouseDown.type, ["sensing_mousedown"]], [stage.getMouseX.type, ["sensing_mousex"]], [stage.getMouseY.type, ["sensing_mousey"]], [stage.SpriteX.type, ["sensing_of", ["PROPERTY", "x position"], ["OBJECT", "sensing_of_object_menu", "OBJECT"]]], [stage.SpriteY.type, ["sensing_of", ["PROPERTY", "y position"], ["OBJECT", "sensing_of_object_menu", "OBJECT"]]], [stage.SpriteDirection.type, ["sensing_of", ["PROPERTY", "direction"], ["OBJECT", "sensing_of_object_menu", "OBJECT"]]], [stage.SpriteSize.type, ["sensing_of", ["PROPERTY", "size"], ["OBJECT", "sensing_of_object_menu", "OBJECT"]]], [stage.SpriteCostumeName.type, ["sensing_of", ["PROPERTY", "costume name"], ["OBJECT", "sensing_of_object_menu", "OBJECT"]]], [stage.SpriteCostumeNumber.type, ["sensing_of", ["PROPERTY", "costume #"], ["OBJECT", "sensing_of_object_menu", "OBJECT"]]], ]) let spriteConversions = sprite => new Map([ ...stageConversions1(sprite.stage), [sprite.Move.type, ["motion_movesteps", "STEPS"]], [sprite.RotateRight.type, ["motion_turnleft", "DEGREES"]], [sprite.RotateLeft.type, ["motion_turnright", "DEGREES"]], [sprite.GotoXY.type, ["motion_gotoxy", "X", "Y"]], [sprite.SetDirection.type, ["motion_pointindirection", "DIRECTION"]], [sprite.SetX.type, ["motion_setx", "X"]], [sprite.SetY.type, ["motion_sety", "Y"]], [sprite.ChangeX.type, ["motion_changexby", "DX"]], [sprite.ChangeY.type, ["motion_changeyby", "DY"]], [sprite.SetSize.type, ["looks_setsizeto", "SIZE"]], [sprite.ChangeSize.type, ["looks_changesizeby", "CHANGE"]], [sprite.GetX.type, ["motion_xposition"]], [sprite.GetY.type, ["motion_yposition"]], [sprite.GetDirection.type, ["motion_direction"]], [sprite.GetSize.type, ["looks_size"]], [sprite.Show.type, ["looks_show"]], [sprite.Hide.type, ["looks_hide"]], [sprite.SetCostume.type, ["looks_switchcostumeto", ["COSTUME", "looks_costume", "COSTUME"]]], [sprite.NextCostume.type, ["looks_nextcostume"]], [sprite.CostumeName.type, ["looks_costumenumbername", ["NUMBER_NAME", "name"]]], [sprite.CostumeNumber.type, ["looks_costumenumbername", ["NUMBER_NAME", "number"]]], [sprite.GotoFront.type, ["looks_gotofrontback", ["FRONT_BACK", "front"]]], [sprite.GotoBack.type, ["looks_gotofrontback", ["FRONT_BACK", "back"]]], [sprite.GoForward.type, ["looks_goforwardbackwardlayers", ["FORWARD_BACKWARD", "forward"]]], [sprite.GoBackward.type, ["looks_goforwardbackwardlayers", ["FORWARD_BACKWARD", "backward"]]], [sprite.TouchingMouse.type, ["sensing_touchingobject", ["TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu", "TOUCHINGOBJECTMENU", "_mouse_"]]], [sprite.SetEffect.type, ["looks_seteffectto", "EFFECT:u", "VALUE"]], [sprite.ChangeEffect.type, ["looks_changeeffectby", "EFFECT:u", "CHANGE"]], [sprite.ClearEffects.type, ["looks_cleargraphiceffects"]], [sprite.EnableRotation.type, ["motion_setrotationstyle", ["STYLE", "all around"]]], [sprite.EnableFlipping.type, ["motion_setrotationstyle", ["STYLE", "left-right"]]], [sprite.DisableRotation.type, ["motion_setrotationstyle", ["STYLE", "don't rotate"]]], [sprite.Say.type, ["looks_say", "MESSAGE"]], [sprite.Think.type, ["looks_think", "MESSAGE"]], [sprite.SayFor.type, ["looks_sayforsecs", "MESSAGE", "SECS"]], [sprite.ThinkFor.type, ["looks_thinkforsecs", "MESSAGE", "SECS"]], [sprite.DistanceToSprite.type, ["sensing_distanceto", ["DISTANCETOMENU", "sensing_distancetomenu", "DISTANCETOMENU"]]], [sprite.Clone.type, ["control_create_clone_of", ["CLONE_OPTION", "control_create_clone_of_menu", "CLONE_OPTION", "_myself_"]]], [sprite.WhenCloned.type, ["control_start_as_clone"]], [sprite.extensions.pen?.blocks.Clear.type, ["pen_clear"]], [sprite.extensions.pen?.blocks.PenDown.type, ["pen_penDown"]], [sprite.extensions.pen?.blocks.PenUp.type, ["pen_penUp"]], [sprite.extensions.pen?.blocks.SetPenColor.type, ["pen_setPenColorToColor", "COLOR"]], [sprite.extensions.pen?.blocks.SetPenSize.type, ["pen_setPenSizeTo", "SIZE"]], [sprite.extensions.pen?.blocks.Stamp.type, ["pen_stamp"]], ])