soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
24.7 kB
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;