Spaces:
Build error
Build error
const BlockType = require('../../extension-support/block-type'); | |
const BlockShape = require('../../extension-support/block-shape'); | |
const ArgumentType = require('../../extension-support/argument-type'); | |
const ArgumentAlignment = require('../../extension-support/argument-alignment'); | |
const Cast = require('../../util/cast'); | |
const MathUtil = require('../../util/math-util'); | |
const test_indicator = require('./test_indicator.png'); | |
const pathToMedia = 'static/blocks-media'; | |
/** | |
* Class for Dev blocks | |
* @constructor | |
*/ | |
class JgDevBlocks { | |
constructor(runtime) { | |
/** | |
* The runtime instantiating this block package. | |
* @type {Runtime} | |
*/ | |
this.runtime = runtime; | |
// register compiled blocks | |
this.runtime.registerCompiledExtensionBlocks('jgDev', this.getCompileInfo()); | |
} | |
// util | |
/** | |
* @returns {object} metadata for this extension and its blocks. | |
*/ | |
getInfo() { | |
return { | |
id: 'jgDev', | |
name: 'Test Extension', | |
color1: '#4275f5', | |
color2: '#425df5', | |
blocks: [ | |
{ | |
opcode: 'stopSound', | |
text: 'stop sound [ID]', | |
blockType: BlockType.COMMAND, | |
arguments: { | |
ID: { type: ArgumentType.STRING, defaultValue: "id" } | |
} | |
}, | |
{ | |
opcode: 'starttimeSound', | |
text: 'start sound [ID] at seconds [SEX]', | |
blockType: BlockType.COMMAND, | |
arguments: { | |
ID: { type: ArgumentType.SOUND, defaultValue: "name or index" }, | |
SEX: { type: ArgumentType.NUMBER, defaultValue: 0 } | |
} | |
}, | |
{ | |
opcode: 'transitionSound', | |
text: 'set sound [ID] volume transition to seconds [SEX]', | |
blockType: BlockType.COMMAND, | |
arguments: { | |
ID: { type: ArgumentType.SOUND, defaultValue: "sound to set fade out effect on" }, | |
SEX: { type: ArgumentType.NUMBER, defaultValue: 1 } | |
} | |
}, | |
{ | |
opcode: 'logArgs1', | |
text: 'costume input [INPUT] sound input [INPUT2]', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
INPUT: { type: ArgumentType.COSTUME }, | |
INPUT2: { type: ArgumentType.SOUND } | |
} | |
}, | |
{ | |
opcode: 'logArgs2', | |
text: 'variable input [INPUT] list input [INPUT2]', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
INPUT: { type: ArgumentType.VARIABLE }, | |
INPUT2: { type: ArgumentType.LIST } | |
} | |
}, | |
{ | |
opcode: 'logArgs3', | |
text: 'broadcast input [INPUT]', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
INPUT: { type: ArgumentType.BROADCAST } | |
} | |
}, | |
{ | |
opcode: 'logArgs4', | |
text: 'color input [INPUT]', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
INPUT: { type: ArgumentType.COLOR } | |
} | |
}, | |
{ | |
opcode: 'setEffectName', | |
text: 'set [EFFECT] to [VALUE]', | |
blockType: BlockType.COMMAND, | |
arguments: { | |
EFFECT: { type: ArgumentType.STRING, defaultValue: "color" }, | |
VALUE: { type: ArgumentType.NUMBER, defaultValue: 0 } | |
} | |
}, | |
{ | |
opcode: 'setBlurEffect', | |
text: 'set blur [PX]px', | |
blockType: BlockType.COMMAND, | |
arguments: { | |
PX: { type: ArgumentType.NUMBER, defaultValue: 0 } | |
} | |
}, | |
{ | |
opcode: 'restartFromTheTop', | |
text: 'restart from the top [ICON]', | |
blockType: BlockType.COMMAND, | |
isTerminal: true, | |
arguments: { | |
ICON: { | |
type: ArgumentType.IMAGE, | |
dataURI: pathToMedia + "/repeat.svg" | |
} | |
} | |
}, | |
{ | |
opcode: 'doodooBlockLolol', | |
text: 'ignore blocks inside [INPUT]', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL, | |
arguments: { | |
INPUT: { type: ArgumentType.BOOLEAN } | |
} | |
}, | |
{ | |
opcode: 'ifFalse', | |
text: 'if [INPUT] is false', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL, | |
arguments: { | |
INPUT: { type: ArgumentType.BOOLEAN } | |
} | |
}, | |
{ | |
opcode: 'multiplyTest', | |
text: 'multiply [VAR] by [MULT] then', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL, | |
arguments: { | |
VAR: { type: ArgumentType.STRING, menu: "variable" }, | |
MULT: { type: ArgumentType.NUMBER, defaultValue: 4 } | |
} | |
}, | |
{ | |
opcode: 'compiledIfNot', | |
text: 'if not [CONDITION] then (compiled)', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL, | |
arguments: { | |
CONDITION: { type: ArgumentType.BOOLEAN } | |
} | |
}, | |
{ | |
opcode: 'compiledReturn', | |
text: 'return [RETURN]', | |
blockType: BlockType.COMMAND, | |
isTerminal: true, | |
arguments: { | |
RETURN: { type: ArgumentType.STRING, defaultValue: '1' } | |
} | |
}, | |
{ | |
opcode: 'compiledOutput', | |
text: 'compiled code', | |
blockType: BlockType.REPORTER, | |
disableMonitor: true | |
}, | |
{ | |
opcode: 'branchNewThread', | |
text: 'new thread', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL | |
}, | |
{ | |
opcode: 'whatthescallop', | |
text: 'bruh [numtypeableDropdown] [typeableDropdown] overriden: [overridennumtypeableDropdown] [overridentypeableDropdown]', | |
arguments: { | |
numtypeableDropdown: { | |
menu: 'numericTypeableTest' | |
}, | |
typeableDropdown: { | |
menu: 'typeableTest' | |
}, | |
overridennumtypeableDropdown: { | |
menu: 'numericTypeableTest', | |
defaultValue: 5 | |
}, | |
overridentypeableDropdown: { | |
menu: 'typeableTest', | |
defaultValue: 'your mom' | |
} | |
}, | |
blockType: BlockType.REPORTER | |
}, | |
{ | |
opcode: 'booleanMonitor', | |
text: 'boolean monitor', | |
blockType: BlockType.BOOLEAN | |
}, | |
{ | |
opcode: 'ifFalseReturned', | |
text: 'if [INPUT] is false (return)', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL, | |
arguments: { | |
INPUT: { type: ArgumentType.BOOLEAN } | |
} | |
}, | |
{ | |
opcode: 'turbrowaorploop', | |
blockType: BlockType.LOOP, | |
text: 'my repeat [TIMES]', | |
arguments: { | |
TIMES: { | |
type: ArgumentType.NUMBER, | |
defaultValue: 10 | |
} | |
} | |
}, | |
{ | |
opcode: 'alignmentTestate', | |
blockType: BlockType.CONDITIONAL, | |
text: [ | |
'this block tests alignments', | |
'left', | |
'middle', | |
'right' | |
], | |
alignments: [ | |
null, | |
null, | |
ArgumentAlignment.LEFT, | |
null, | |
ArgumentAlignment.CENTER, | |
null, | |
ArgumentAlignment.RIGHT | |
], | |
branchCount: 3 | |
}, | |
{ | |
opcode: 'squareReporter', | |
text: 'square boy', | |
blockType: BlockType.REPORTER, | |
blockShape: BlockShape.SQUARE | |
}, | |
{ | |
opcode: 'branchIndicatorTest', | |
text: 'this has a custom branchIndicator', | |
branchCount: 1, | |
blockType: BlockType.CONDITIONAL, | |
branchIndicator: test_indicator | |
}, | |
{ | |
opcode: 'givesAnError', | |
text: 'throw an error', | |
blockType: BlockType.COMMAND | |
}, | |
{ | |
opcode: 'hiddenBoolean', | |
text: 'im actually a boolean output', | |
blockType: BlockType.REPORTER, | |
forceOutputType: 'Boolean', | |
disableMonitor: true | |
}, | |
{ | |
opcode: 'varvarvavvarvarvar', | |
text: 'varibles!?!?!??!?!?!?!?!!!?!?! [variable]', | |
arguments: { | |
variable: { | |
menu: 'variableInternal' | |
} | |
}, | |
blockType: BlockType.REPORTER | |
}, | |
{ | |
opcode: 'green', | |
text: 'im literally just green', | |
blockType: BlockType.REPORTER, | |
color1: '#00ff00', | |
color2: '#000000', | |
color3: '#000000', | |
disableMonitor: true | |
}, | |
{ | |
opcode: 'duplicato', | |
text: 'duplicato', | |
blockType: BlockType.REPORTER, | |
canDragDuplicate: true, | |
disableMonitor: true, | |
hideFromPalette: true | |
}, | |
{ | |
opcode: 'theheheuoihew9h9', | |
blockType: BlockType.COMMAND, | |
text: 'This block will appear in the penguinmod wiki [SEP] [DUPLIC]', | |
arguments: { | |
SEP: { | |
type: ArgumentType.SEPERATOR, | |
}, | |
DUPLIC: { | |
type: ArgumentType.STRING, | |
fillIn: 'duplicato', | |
} | |
} | |
}, | |
{ | |
opcode: 'costumeTypeTest', | |
blockType: BlockType.REPORTER, | |
text: 'test custom type updating/rendering (new instance)' | |
}, | |
{ | |
opcode: 'costumeTypeTestSame', | |
blockType: BlockType.REPORTER, | |
text: 'test custom type updating/rendering (same instance)' | |
}, | |
{ | |
opcode: 'spriteDefaultType', | |
blockType: BlockType.REPORTER, | |
text: 'get this target' | |
}, | |
{ | |
opcode: 'spriteDefaultTypeOther', | |
blockType: BlockType.REPORTER, | |
text: 'get stage target' | |
}, | |
{ | |
opcode: 'costumeDefaultType', | |
blockType: BlockType.REPORTER, | |
text: 'get current costume' | |
}, | |
{ | |
opcode: 'soundDefaultType', | |
blockType: BlockType.REPORTER, | |
text: 'get first sound' | |
} | |
], | |
menus: { | |
variableInternal: { | |
variableType: 'scalar' | |
}, | |
variable: "getVariablesMenu", | |
numericTypeableTest: { | |
items: [ | |
'item1', | |
'item2', | |
'item3' | |
], | |
isTypeable: true, | |
isNumeric: true | |
}, | |
typeableTest: { | |
items: [ | |
'item1', | |
'item2', | |
'item3' | |
], | |
isTypeable: true, | |
isNumeric: false | |
} | |
} | |
}; | |
} | |
spriteDefaultType(args, util) { | |
return util.target; | |
} | |
spriteDefaultTypeOther(args, util) { | |
return this.runtime.getTargetForStage(); | |
} | |
costumeDefaultType(args, util) { | |
return util.target.getCostumeType(util.target.currentCostume); | |
} | |
soundDefaultType(args, util) { | |
return util.target.getSoundType(0); | |
} | |
costumeTypeTest() { | |
return { | |
_monitorUpToDate: false, | |
costumId: 'thing', | |
num: Math.sin(Date.now() / 1000), | |
toReporterContent() { | |
const el = document.createElement('span'); | |
el.style.color = '#F00'; | |
el.textContent = this.num; | |
return el; | |
}, | |
toMonitorContent() { | |
this._monitorUpToDate = true; | |
const el = document.createElement('span'); | |
el.style.color = '#0F0'; | |
el.textContent = this.num; | |
return el; | |
}, | |
toListItem() { | |
this._monitorUpToDate = true; | |
const el = document.createElement('span'); | |
el.style.color = '#00F'; | |
el.textContent = this.num; | |
return el; | |
}, | |
toListEditor() { | |
return `[num ${this.num}]`; | |
}, | |
fromListEditor(thing) { | |
this.num = Number(thing.slice(5, -1)); | |
return this; | |
} | |
}; | |
} | |
costumeTypeTestSame() { | |
if (!this.custom) this.custom = this.costumeTypeTest(); | |
this.custom.num = Math.sin(Date.now() / 1000); | |
this.custom._monitorUpToDate = false; | |
return this.custom; | |
} | |
/** | |
* This function is used for any compiled blocks in the extension if they exist. | |
* Data in this function is given to the IR & JS generators. | |
* Data must be valid otherwise errors may occur. | |
* @returns {object} functions that create data for compiled blocks. | |
*/ | |
getCompileInfo() { | |
return { | |
ir: { | |
compiledIfNot: (generator, block) => ({ | |
kind: 'stack', /* this gets replaced but we still need to say what type of block this is */ | |
condition: generator.descendInputOfBlock(block, 'CONDITION'), | |
whenTrue: generator.descendSubstack(block, 'SUBSTACK'), | |
whenFalse: [] | |
}), | |
compiledReturn: (generator, block) => ({ | |
kind: 'stack', | |
return: generator.descendInputOfBlock(block, 'RETURN') | |
}), | |
restartFromTheTop: () => ({ | |
kind: 'stack' | |
}), | |
compiledOutput: () => ({ | |
kind: 'input' /* input is output :troll: (it makes sense in the ir & jsgen implementation ok) */ | |
}) | |
}, | |
js: { | |
compiledIfNot: (node, compiler, imports) => { | |
compiler.source += `if (!(${compiler.descendInput(node.condition).asBoolean()})) {\n`; | |
compiler.descendStack(node.whenTrue, new imports.Frame(false)); | |
// only add the else branch if it won't be empty | |
// this makes scripts have a bit less useless noise in them | |
if (node.whenFalse.length) { | |
compiler.source += `} else {\n`; | |
compiler.descendStack(node.whenFalse, new imports.Frame(false)); | |
} | |
compiler.source += `}\n`; | |
}, | |
compiledReturn: (node, compiler) => { | |
compiler.source += `return ${compiler.descendInput(node.return).asString()};`; | |
}, | |
restartFromTheTop: (_, compiler) => { | |
compiler.source += `runtime._restartThread(thread);`; | |
compiler.source += `return;`; | |
}, | |
compiledOutput: (_, compiler, imports) => { | |
const code = Cast.toString(compiler.source); | |
return new imports.TypedInput(JSON.stringify(code), imports.TYPE_STRING); | |
} | |
} | |
}; | |
} | |
varvarvavvarvarvar(args) { | |
return JSON.stringify(args); | |
} | |
// menu | |
getVariablesMenu() { | |
// menus can only be opened in the editor so use editingTarget | |
const target = vm.editingTarget; | |
const emptyMenu = [{ text: "", value: "" }]; | |
if (!target) return emptyMenu; | |
if (!target.variables) return emptyMenu; | |
const menu = Object.getOwnPropertyNames(target.variables).map(variableId => { | |
const variable = target.variables[variableId]; | |
return { | |
text: variable.name, | |
value: variable.name | |
}; | |
}); | |
// check if menu has 0 items because pm throws an error if theres no items | |
return (menu.length > 0) ? menu : emptyMenu; | |
} | |
branchIndicatorTest() { | |
return; // dude logs wont shut up because i didnt define this func | |
} | |
// util | |
_getSoundIndex(soundName, util) { | |
// if the sprite has no sounds, return -1 | |
const len = util.target.sprite.sounds.length; | |
if (len === 0) { | |
return -1; | |
} | |
// look up by name first | |
const index = this._getSoundIndexByName(soundName, util); | |
if (index !== -1) { | |
return index; | |
} | |
// then try using the sound name as a 1-indexed index | |
const oneIndexedIndex = parseInt(soundName, 10); | |
if (!isNaN(oneIndexedIndex)) { | |
return MathUtil.wrapClamp(oneIndexedIndex - 1, 0, len - 1); | |
} | |
// could not be found as a name or converted to index, return -1 | |
return -1; | |
} | |
_getSoundIndexByName(soundName, util) { | |
const sounds = util.target.sprite.sounds; | |
for (let i = 0; i < sounds.length; i++) { | |
if (sounds[i].name === soundName) { | |
return i; | |
} | |
} | |
// if there is no sound by that name, return -1 | |
return -1; | |
} | |
// blocks | |
branchNewThread(_, util) { | |
// CubesterYT probably | |
if (util.thread.target.blocks.getBranch(util.thread.peekStack(), 0)) { | |
util.sequencer.runtime._pushThread( | |
util.thread.target.blocks.getBranch(util.thread.peekStack(), 0), | |
util.target, | |
{} | |
); | |
} | |
} | |
booleanMonitor() { | |
return Math.round(Math.random()) == 1; | |
} | |
stopSound(args, util) { | |
const target = util.target; | |
const sprite = target.sprite; | |
if (!sprite) return; | |
const soundBank = sprite.soundBank; | |
if (!soundBank) return; | |
const id = Cast.toString(args.ID); | |
soundBank.stop(target, id); | |
} | |
starttimeSound(args, util) { | |
const id = Cast.toString(args.ID); | |
const index = this._getSoundIndex(id, util); | |
if (index < 0) return; | |
const target = util.target; | |
const sprite = target.sprite; | |
if (!sprite) return; | |
if (!sprite.sounds) return; | |
const { soundId } = sprite.sounds[index]; | |
const soundBank = sprite.soundBank; | |
if (!soundBank) return; | |
soundBank.playSound(target, soundId, Cast.toNumber(args.SEX)); | |
} | |
transitionSound(args, util) { | |
const id = Cast.toString(args.ID); | |
const index = this._getSoundIndex(id, util); | |
if (index < 0) return; | |
const target = util.target; | |
const sprite = target.sprite; | |
if (!sprite) return; | |
if (!sprite.sounds) return; | |
const { soundId } = sprite.sounds[index]; | |
const soundBank = sprite.soundBank; | |
if (!soundBank) return; | |
soundBank.soundPlayers[soundId].stopFadeDecay = Cast.toNumber(args.SEX); | |
} | |
green() { | |
return 'g'; | |
} | |
logArgs1(args) { | |
console.log(args); | |
return JSON.stringify(args); | |
} | |
logArgs2(args) { | |
console.log(args); | |
return JSON.stringify(args); | |
} | |
logArgs3(args) { | |
console.log(args); | |
return JSON.stringify(args); | |
} | |
logArgs4(args) { | |
console.log(args); | |
return JSON.stringify(args); | |
} | |
setEffectName(args, util) { | |
const PX = Cast.toNumber(args.VALUE); | |
util.target.setEffect(args.EFFECT, PX); | |
} | |
setBlurEffect(args, util) { | |
const PX = Cast.toNumber(args.PX); | |
util.target.setEffect("blur", PX); | |
} | |
doodooBlockLolol(args, util) { | |
if (args.INPUT === true) return; | |
console.log(args, util); | |
util.startBranch(1, false); | |
console.log(util.target.getCurrentCostume()); | |
} | |
ifFalse(args, util) { | |
console.log(args, util); | |
if (!args.INPUT) { | |
util.startBranch(1, false); | |
} | |
} | |
ifFalseReturned(args) { | |
if (!args.INPUT) { | |
return 1; | |
} | |
} | |
turbrowaorploop ({TIMES}, util) { | |
const times = Math.round(Cast.toNumber(TIMES)); | |
if (typeof util.stackFrame.loopCounter === 'undefined') { | |
util.stackFrame.loopCounter = times; | |
} | |
util.stackFrame.loopCounter--; | |
if (util.stackFrame.loopCounter >= 0) { | |
return true; | |
} | |
} | |
// compiled blocks should have interpreter versions | |
compiledIfNot(args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
if (!condition) { | |
util.startBranch(1, false); | |
} | |
} | |
compiledReturn() { | |
return 'noop'; | |
} | |
restartFromTheTop() { | |
return 'noop'; | |
} | |
compiledOutput() { | |
return '<unavailable without compiler>'; | |
} | |
hiddenBoolean() { | |
return true; | |
} | |
multiplyTest(args, util) { | |
const target = util.target; | |
Object.getOwnPropertyNames(target.variables).forEach(variableId => { | |
const variable = target.variables[variableId]; | |
if (variable.name !== Cast.toString(args.VAR)) return; | |
console.log(variable); | |
if (typeof variable.value !== 'number') { | |
variable.value = 0; | |
} | |
variable.value *= Cast.toNumber(args.MULT); | |
}); | |
} | |
whatthescallop(args) { | |
return JSON.stringify(args); | |
} | |
squareReporter() { | |
return 0; | |
} | |
alignmentTestate() { | |
return; | |
} | |
givesAnError() { | |
throw new Error('woah an error'); | |
} | |
} | |
module.exports = JgDevBlocks; | |