ref: 86a1bffad7063d1f51dfe44c2126bbdf8b7fe5c8
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"]],
])