(() => { globalThis.ovoLevelEditor = { init() { let sdk_runtime = cr_getC2Runtime(); if (!sdk_runtime) return; let setLayout = (name) => { if (sdk_runtime.layouts.hasOwnProperty(name)) { sdk_runtime.changelayout = sdk_runtime.layouts[name]; return sdk_runtime.layouts[name]; } }; let baseLayoutName = "Level Base"; let baseLayout = sdk_runtime.layouts[baseLayoutName]; let oldFn = baseLayout.startRunning.bind(baseLayout); baseLayout.startRunning = () => { console.log("start"); globalThis.ovoLevelEditor.applySetup(); oldFn(); globalThis.ovoLevelEditor.applyCurrentLevel(); }; let setLayoutToBase = () => { sdk_runtime.changelayout = baseLayout; }; const types = { Solid: sdk_runtime.types_by_index.find( (x) => x.name === "Solid" || (x.plugin instanceof cr.plugins_.TiledBg && x.texture_file && x.texture_file.includes("/solid.png") && x.behs_count === 2) ), SolidWhite: sdk_runtime.types_by_index.find( (x) => x.name === "SolidWhite" || (x.plugin instanceof cr.plugins_.TiledBg && x.texture_file && x.texture_file.includes("/solidwhite.png")) ), SolidRed: sdk_runtime.types_by_index.find( (x) => x.name === "SolidMove" || (x.plugin instanceof cr.plugins_.TiledBg && x.texture_file && x.texture_file.includes("/solidmove.png")) ), Spike: sdk_runtime.types_by_index.find( (x) => x.name === "Spike" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("spike-")) ), SpikeWhite: sdk_runtime.types_by_index.find( (x) => x.name === "Spike2" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("spike2-")) ), Flag: sdk_runtime.types_by_index.find( (x) => x.name === "EndFlag" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("endflag")) ), JumpThrough: sdk_runtime.types_by_index.find( (x) => x.name === "JumpThrough" || (x.plugin instanceof cr.plugins_.TiledBg && x.texture_file && x.texture_file.includes("jumpthrough") && x.families.length === 2) ), GroundPoundSolid: sdk_runtime.types_by_index.find( (x) => x.name === "GroundPoundSolid" || (x.plugin instanceof cr.plugins_.TiledBg && x.texture_file && x.texture_file.includes("groundpoundsolid") && x.families.length === 2) ), RocketLauncher: sdk_runtime.types_by_index.find( (x) => x.name === "RocketLauncher" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("rocketlauncher")) ), JumpBoost: sdk_runtime.types_by_index.find( (x) => x.name === "JumpBoost" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("jumpboost")) ), Gradient: sdk_runtime.types_by_index.find( (x) => x.name === "Gradient" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("gradient")) ), LayoutNameHolder: sdk_runtime.types_by_index.find( (x) => x.name === "LayoutNameHolder" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("layoutnameholder")) ), LayoutNumber: sdk_runtime.types_by_index.find( (x) => x.name === "LayoutNumber" || (x.plugin instanceof cr.plugins_.SkymenSFPlusPLus && x.texture_file && x.texture_file.includes("layoutnumber")) ), LayoutSubtitle: sdk_runtime.types_by_index.find( (x) => x.name === "LayoutSubtitle" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("layoutsubtitle")) ), Text: sdk_runtime.types_by_index.find( (x) => x.name === "TextAlign" || (x.plugin instanceof cr.plugins_.TextModded && x.vars_count === 8 && !x.is_family) ), PlayerRig: sdk_runtime.types_by_index.find( (x) => x.name === "PlayerRig" || (x.is_family && x.members.length === 10) ), Portal: sdk_runtime.types_by_index.find( (x) => x.name === "Portal" || (x.plugin instanceof cr.plugins_.Sprite && x.all_frames && x.all_frames[0].texture_file.includes("portal")) ), }; const initInstVars = { RocketLauncher: ( inst, { Range = 300, RateOfFire = 2, RotateSpeed = 180, ProjectileRotateSpeed = 180, ProjectileSpeed = 150, PredictiveAim = false, ConeOfView = 360, } = {} ) => { inst.instance_vars[1] = ProjectileSpeed; inst.instance_vars[2] = ProjectileRotateSpeed; let turretBehavior = inst.behavior_insts.find( (beh) => beh.behavior instanceof cr.behaviors.Turret ); turretBehavior.rateOfFire = RateOfFire; turretBehavior.range = Range; turretBehavior.rotateSpeed = cr.to_radians(RotateSpeed); turretBehavior.projectileSpeed = ProjectileSpeed; turretBehavior.predictiveAim = PredictiveAim; let losBehavior = inst.behavior_insts.find( (beh) => beh.behavior instanceof cr.behaviors.LOS ); losBehavior.cone = cr.to_radians(ConeOfView); }, JumpBoost: ( inst, { Force = 0.7, SetDecelerationToZero = false } = {} ) => { inst.instance_vars[0] = Force; inst.instance_vars[1] = SetDecelerationToZero ? 1 : 0; }, Portal: ( inst, { ID = 0, Target = 0, ForceAngle = false, ForcedAngle = 0, ForceSpeed = false, ForcedSpeed = 0, InversePortal = false, } = {}, { color: [r = 1, g = 1, b = 1] = [] } = {} ) => { inst.instance_vars[0] = ID; inst.instance_vars[1] = Target; inst.instance_vars[2] = ForceAngle ? 1 : 0; inst.instance_vars[3] = ForcedAngle; inst.instance_vars[4] = ForceSpeed ? 1 : 0; inst.instance_vars[5] = ForcedSpeed; inst.instance_vars[6] = InversePortal ? 1 : 0; inst.effect_params[0][3] = r * 255; inst.effect_params[0][4] = g * 255; inst.effect_params[0][5] = b * 255; }, SolidWhite: (inst, _, { color: [r = 1, g = 1, b = 1] = [] } = {}) => { inst.effect_params[0][0] = r; inst.effect_params[0][1] = g; inst.effect_params[0][2] = b; }, Gradient: (inst, _, { color: [r = 1, g = 1, b = 1] = [] } = {}) => { inst.effect_params[0][0] = r; inst.effect_params[0][1] = g; inst.effect_params[0][2] = b; }, Text: ( inst, _, { text, fontSize, fontFace, wordWrapMode, horizontalAlign, verticalAlign, isBold, isItalic, fontColor: [r = 0, g = 0, b = 0], lineHeight, } = {} ) => { inst.facename = fontFace; inst.text = text; inst.ptSize = fontSize; let wrap = ["word", "character"].findIndex((x) => x === wordWrapMode); inst.wrapbyword = wrap === 0; inst.nowrap = wrap === 2; inst.wrap = wrap; // 0=word, 1=character 2=none inst.halign = ["left", "center", "right"].findIndex( (x) => x === horizontalAlign ) * 50; inst.valign = ["top", "center", "bottom"].findIndex((x) => x === verticalAlign) * 50; inst.fontStyle = `${isBold && "bold"} ${isItalic && "italic"}`; inst.line_height_offset = lineHeight; inst.color = `rgb(${r * 255},${g * 255},${b * 255})`; inst.updateFont(); }, Player: ( inst, { SlideTime = 0.8, SlideRefresh = 0.5, Gravity = 1500 } = {} ) => { inst.instance_vars[5] = SlideTime; inst.instance_vars[6] = SlideRefresh; let platformerBehavior = inst.behavior_insts.find( (beh) => beh.behavior instanceof cr.behaviors.Platform ); cr.behaviors.Platform.prototype.acts.__proto__.SetGravity.call( platformerBehavior, Gravity ); }, }; const layers = { "Layer 0": baseLayout.layers.find((x) => x.name === "Layer 0"), "Layer 1": baseLayout.layers.find((x) => x.name === "Layer 1"), "Layer 2": baseLayout.layers.find((x) => x.name === "Layer 2"), "Layer 3": baseLayout.layers.find((x) => x.name === "Layer 3"), "Layer 4": baseLayout.layers.find((x) => x.name === "Layer 4"), Background: baseLayout.layers.find((x) => x.name === "Background"), }; let create = ( type, layer, { x, y, visible, opacity, collisionsEnabled, width, height, angle, instVars, extra, } ) => { if (!types.hasOwnProperty(type)) return; let inst = sdk_runtime.createInstance(types[type], layers[layer], x, y); inst.width = width ?? inst.width; inst.height = height ?? inst.height; inst.angle = angle ?? inst.angle; inst.visible = visible ?? inst.visible; inst.opacity = opacity ?? inst.opacity; inst.collisionsEnabled = collisionsEnabled ?? inst.collisionsEnabled; inst.set_bbox_changed(); if (initInstVars.hasOwnProperty(type)) initInstVars[type](inst, instVars, extra); return inst; }; //create("Solid", "Layer 0", {x:100, y:100, angle: 45, height:8, width: 50}) //create("Spike", "Layer 0", {x:100, y:500}) globalThis.ovoLevelEditor = { startLevel(json) { sdk_runtime.types_by_index.find( (x) => x.name === "Globals" || (x.plugin instanceof cr.plugins_.Globals && x.instvar_sids.length > 20) ).instances[0].instance_vars[3] = 1; this.curLevel = json; setLayoutToBase(); }, wipeAllInstances() { Object.values(layers).forEach((layer) => { if (!layer) return; console.log("wiping " + layer); layer.instances .filter((x) => Object.values(types).includes(x.type)) .forEach(sdk_runtime.DestroyInstance.bind(sdk_runtime)); }); }, getPlayer() { return sdk_runtime.types_by_index .filter( (x) => !!x.animations && x.animations[0].frames[0].texture_file.includes("collider") )[0] .instances.filter( (x) => x.instance_vars[17] === "" && x.behavior_insts[0].enabled )[0]; }, async awaitForPlayer() { let player = this.getPlayer(); while (!player) { await new Promise((resolve) => setTimeout(resolve, 20)); player = this.getPlayer(); } return player; }, async setPlayerPosition(x, y, layer) { let player = this.getPlayer(); while (!player) { await new Promise((resolve) => setTimeout(resolve, 20)); player = this.getPlayer(); } if (x) player.x = x; if (y) player.y = y; player.type.plugin.acts.MoveToLayer.call(player, layer); let instances = [...types.PlayerRig.instances]; instances.sort((a, b) => a.instance_vars[0] - b.instance_vars[0]); instances.forEach((inst) => { inst.type.plugin.acts.MoveToLayer.call(inst, layer); }); player.set_bbox_changed(); }, applySetup() { if (!this.curLevel) return; if (this.curLevel.layout) { if (this.curLevel.layout.width) { baseLayout.originalWidth = this.curLevel.layout.width; baseLayout.width = this.curLevel.layout.width; } if (this.curLevel.layout.height) { baseLayout.originalHeight = this.curLevel.layout.height; baseLayout.height = this.curLevel.layout.height; } } }, async applyCurrentLevel() { if (!this.curLevel) return; this.wipeAllInstances(); let player = await this.awaitForPlayer(); let playerInitialised = false; // if (this.curLevel.player) { // initInstVars.Player(player, this.curLevel.player.instVars); // this.setPlayerPosition( // this.curLevel.player.x, // this.curLevel.player.y, // layers[this.curLevel.player.layer] // ); // } Object.keys(this.curLevel.layers).forEach((layer) => { if (!layers[layer]) return; let allInstances = Object.keys(this.curLevel.layers[layer]) .map((type) => this.curLevel.layers[layer][type].map((x) => ({ ...x, type })) ) .flat() .sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0)); for (let i = 0; i < allInstances.length; i++) { const inst = allInstances[i]; let zIndex = inst.zIndex ?? i; if ( !playerInitialised && this.curLevel.player && this.curLevel.player.layer === layer && (this.curLevel.player.zIndex ?? 0) <= zIndex ) { playerInitialised = true; initInstVars.Player(player, this.curLevel.player.instVars); this.setPlayerPosition( this.curLevel.player.x, this.curLevel.player.y, layers[this.curLevel.player.layer] ); } let newInst = create(inst.type, layer, inst); sdk_runtime.trigger(newInst.type.plugin.cnds.OnCreated, newInst); } // Object.keys(this.curLevel.layers[layer]).forEach((type) => { // if (!types[type]) return; // this.curLevel.layers[layer][type].forEach((inst) => { // let newInst = create(type, layer, inst); // sdk_runtime.trigger( // newInst.type.plugin.cnds.OnCreated, // newInst // ); // }); // }); }); if (!playerInitialised && this.curLevel.player) { initInstVars.Player(player, this.curLevel.player.instVars); this.setPlayerPosition( this.curLevel.player.x, this.curLevel.player.y, layers[this.curLevel.player.layer] ); } sdk_runtime.trigger(sdk_runtime.system.cnds.OnLayoutStart); if ( this.curLevel.layout.holder && typeof this.curLevel.layout.holder.x === "number" ) { let text = create( "LayoutNumber", "Layer 0", this.curLevel.layout.holder ); sdk_runtime.trigger(text.type.plugin.cnds.OnCreated, text); if (typeof this.curLevel.layout.number === "number") text.text = this.curLevel.layout.number.toString(); else text.text = "1"; } let holder = create("LayoutNameHolder", "Layer 0", { x: -500, y: -500, }); holder.instance_vars[0] = this.curLevel.layout.name || ""; holder.instance_vars[2] = !!this.curLevel.layout.useSlope; holder.instance_vars[3] = true; sdk_runtime.trigger(holder.type.plugin.cnds.OnCreated, holder); }, handleDrop(ev) { console.log("File(s) dropped"); // Prevent default behavior (Prevent file from being opened) ev.preventDefault(); let playFile = (file) => { console.log(file); file.text().then((text) => { console.log(text); try { let json = JSON.parse(text); if (globalThis.ovoLevelEditor.startLevel) globalThis.ovoLevelEditor.startLevel(json); } catch (error) { alert("not a valid level file"); } }); }; if (ev.dataTransfer.items) { // Use DataTransferItemList interface to access the file(s) for (var i = 0; i < ev.dataTransfer.items.length; i++) { // If dropped items aren't files, reject them if (ev.dataTransfer.items[i].kind === "file") { var file = ev.dataTransfer.items[i].getAsFile(); playFile(file); break; } } } else { // Use DataTransfer interface to access the file(s) playFile(ev.dataTransfer.files[0]); } }, }; sdk_runtime.canvas.setAttribute("ondragover", "event.preventDefault();"); sdk_runtime.canvas.setAttribute( "ondrop", "ovoLevelEditor.handleDrop(event)" ); }, }; let previewBuffer = 3; let messageHandler = (event) => { if (!event.data.isLevelEditor || !event.data.messageType) return; if (event.data.messageType.toLowerCase() === "isready") { if (!!globalThis.cr_is_preview) previewBuffer--; console.log(previewBuffer); if (previewBuffer <= 0) globalThis.ovoLevelEditor.init(); event.source.postMessage( { isReady: !globalThis.ovoLevelEditor.hasOwnProperty("init"), isLevelEditor: true, messageType: event.data.messageType, }, event.origin ); } if (event.data.messageType.toLowerCase() === "startlevel") { if (globalThis.ovoLevelEditor.hasOwnProperty("init")) { event.source.postMessage( { levelStarted: false, isLevelEditor: true, messageType: event.data.messageType, }, event.origin ); return; } globalThis.ovoLevelEditor.startLevel(event.data.level); event.source.postMessage( { levelStarted: true, isLevelEditor: true, messageType: event.data.messageType, }, event.origin ); } }; globalThis.window.addEventListener("message", messageHandler); })();