/* Editor.js */
// Contains all the functions needed to load and use the editor
// Gives a UI for selecting Things and their options
// When done, it makes map based off what's still placed
/* Stuff to do:
---------------
* Add platforms back in (and a few other missing Things)
* Give options for different areas
*/
function loadEditor(noreset) {
// Make sure there aren't any other instances of this
editorClose();
// If you want to clear the current map
if(!noreset) {
window.canedit = true;
setMap(["Special", "Blank"]);
window.canedit = false;
}
// Set the library and controls
setEditorLibrary();
setEditorHTML();
setEditorControls();
setEditorTriggers();
setEditorLocalRetrieval();
// Visually update
classAdd(body, "editor");
classAdd(editor.sidebar, "expanded");
TimeHandler.addEvent(classRemove, 35, editor.sidebar, "expanded");
// Let the rest of the game know what's going on
map.shifting = false;
window.editing = true;
}
// To do: Merge this into the main library,
// and make Things.js use it for constructors
function setEditorLibrary() {
// The editor contains the placeable solids and characters
window.editor = {
xloc: 0,
yloc: 0,
playing: false,
canplace: true,
offset: { x: unitsizet2 }, // max is set upon running, if actually needed
settings: {
night: false,
setting: "Overworld",
alt: false
},
defaults: {
width: 8,
height: 8,
widthoff: 0,
heightoff: 0,
minimum: 1,
followerUpdate: editorFollowerUpdateStandard,
prefunc: pushPreThing,
outerok: true
},
placed: [],
characters: {
Goomba: {},
Koopa: {
height: 12,
arguments: {
smart: Boolean,
movement: ["moveSimple", "moveJumping", "moveFloating"]
},
followerUpdate: function(reference, pairs) {
var smart = pairs.smart == "True",
fname = pairs.movement,
func = fname == "moveJumping";
if(fname == "moveFloating") func = [8,72];
return [smart, func];
},
onadds: { nocollide: false }
},
Beetle: {
width: 8.5,
height: 8.5
},
HammerBro: { height: 12 },
CheepCheep: {
arguments: {
smart: Boolean
},
attributes: {
nofall: true
}
},
Lakitu: { height: 12 },
Podoboo: { width: 7 },
Blooper: {
height: 12,
onadds: {
nofall: true
}
},
Bowser: {
width: 16,
height: 16
}
},
solids: {
Floor: {
arguments: { width: 8 },
mydefaults: { width: 8 },
prefunc_custom: function(prestatement, placer, reference, args) {
var output = "Floor, " + prestatement.xloc + ", " + (prestatement.yloc);
if(args[1]) output += ", " + args[1];
return output;
}
},
Brick: {
arguments: {
contents: ["false", "Coins", "Star"]
},
followerUpdate: function(reference, pairs) {
var output = [],
content = pairs.contents;
output.push(window[pairs.contents]);
return output;
},
prefunc_custom: function(prestatement, placer, reference, args) {
var output = "Brick, ";
output += prestatement.xloc + ", " + (prestatement.yloc) + ", ";
output += placer.contents[0].name;
return output;
}
},
Block: {
arguments: {
contents: ["Coin", "Mushroom", "Star", "1Up Mushroom"],
hidden: Boolean
},
followerUpdate: function(reference, pairs) {
var output = [],
content = pairs.contents;
if(content == "1Up Mushroom") output.push([Mushroom, 1]);
else output.push(window[pairs.contents]);
if(pairs.hidden == "True") {
TimeHandler.addEvent(function() { editor.follower.hidden = true; });
output.push(1);
}
return output;
},
prefunc_custom: function(prestatement, placer, reference, args) {
var output = "Block, ",
contents = placer.contents,
contained = contents[0].name;
output += prestatement.xloc + ", " + prestatement.yloc;
// If it's got unusal contents, mark them
if(contained != "Coin") {
// Contents are generally just the function name (Coins by default), with 1Up and Death mushrooms being the exception
if(contained == "Mushroom" && contents[1]) {
output += ", [Mushroom, " + String(contents[1]) + "]";
}
else output += ", " + contained;
// Hidden is simply a bool
if(placer.hidden) output += ", true";
}
// Otherwise only add more if it's hidden
else if(placer.hidden) output += ", false, true";
return output;
}
},
Cannon: {
arguments: { height: 8 },
sprite_source: "top"
},
Pipe: {
width: 16,
prefunc: pushPrePipe,
prefunc_solo: true,
arguments: {
height: 8,
Pirhana: Boolean
},
followerUpdate: function(reference, pairs) {
var output = [];
output.push(Number(pairs.height));
output.push(Boolean(pairs.Pirhana));
return output;
},
sprite_source: "top"
},
Stone: {
arguments: {
width: 8,
height: 8
},
prefunc_custom: function(prestatement, placer, reference, args) {
var output = "Stone, " + prestatement.xloc + ", " + (prestatement.yloc);
output += ", " + args[1] + ", " + args[2];
return output;
}
},
Coral: {
arguments: { height: 8 }
},
CastleBlock: {
arguments: {
fireballs: 2,
direction: ["CW", "CCW"],
hidden: Boolean
},
followerUpdate: function(reference, pairs) {
var length = Number(pairs.fireballs),
dt = pairs.direction == "CW",
hidden = pairs.hidden == "True";
return [[length, dt], hidden];
}
},
// Disabled because of super glitches, but I really want to put it back in!!
/*Platform: {
height: 4,
arguments: {
width: 2,
movement: ["false", "Falling", "Floating", "Sliding", "Transport"]
},
mydefaults: { width: 4 },
previewsize: true,
followerUpdate: function(reference, pairs) {
var output = [],
movement = pairs.movement;
output.push(pairs.width);
switch(movement) {
case "Falling": output.push(moveFalling); break;
case "Floating": output.push(moveFloating); break;
case "Sliding": output.push(moveSliding); break;
case "Transport": output.push(collideTransport); break;
}
return output;
},
prefunc_custom: function(prestatement, placer, reference, args) {
var output = "Platform, " + prestatement.xloc + ", " + (prestatement.yloc),
movement = args[2];
switch(movement) {
case moveFalling: output += ", moveFalling"; break;
case moveFloating: output += ", [moveFloating, 8, 72]"; break;
case moveSliding:
var xloc = prestatement.xloc || 0;
output += ", [moveSliding";
output += ", " + (xloc - 24);
output += ", " + (xloc + 24);
output += "]";
break;
case collideTransport:
output += ", collideTransport";
break;
}
return output;
}
},*/
Springboard: {
height: 14.5,
heightoff: 1.5
}
},
scenery: {
Bush1: {width: 16},
Bush2: {width: 24},
Bush3: {width: 32},
Cloud1: {width: 16, height: 12},
Cloud2: {width: 24, height: 12},
Cloud3: {width: 32, height: 12},
HillSmall: {width: 24, height: 9.5, heightoff: -1.5},
HillLarge: {width: 40, height: 17.5, heightoff: -1.5},
PlantSmall: {width: 7, height: 15, heightoff: 1},
PlantLarge: {height: 23, heightoff: 1},
Fence: {},
Water: {
width: 4,
height: 4,
prefunc: fillPreWater,
prefunc_solo: true,
prefunc_custom: function(prestatement, placer, reference, args) {
return prestatement.xloc + ", " + (prestatement.yloc)/* + ", " + args[0]*/;
}
}
}
};
// Unfilled required properties are placed here
var load_paths = {},
defaults = editor.defaults,
group, me, locj, i, j;
// For each group in editor...
for(i in editor) {
group = editor[i];
// For each thing in that group...
for(j in editor[i]) {
me = group[j];
// Proliferate the defaults that don't exist
proliferate(me, editor.defaults, true);
}
}
// Note that everything in scenery is actually pushPreScenery(name, ...)
group = editor.scenery;
for(i in group) {
me = group[i];
// These are the secondary defaults
proliferate(me, {
createfunc: function(me) { return ThingCreate(Sprite, me.spritename); },
spritename: i,
prefunc_custom: function(prestatement, placer, reference, args) {
return "'" + reference.spritename + "', " + prestatement.xloc + ", " + (prestatement.yloc - reference.height);
}
}, true);
// These must happen
if(me.prefunc == pushPreThing) me.prefunc = pushPreScenery;
}
}
/* Initial HTML setup
*/
function setEditorHTML() {
createEditorGuideLines();
createEditorSidebar();
createEditorBottomBar();
createEditorScrollers();
// Set the bottom bar initially
editor.sectionselect.onchange();
}
// The sidebar contains the main options for placing thing
function createEditorSidebar() {
var optionNames = ["Solids", "Characters", "Scenery", "Settings"],
// The main elements
sidebar = editor.sidebar = createElement("div", {
id: "sidebar"
}),
category = editor.category = createElement("div", {
id: "category",
className: "group first"
}),
sectionselect = editor.sectionselect = createElement("select", {
id: "sectionselect",
className: "options big",
onchange: editorSelectSection
}),
options = editor.options = createElement("div", {
id: "options",
className: "options big"
}),
i;
// Dat element structure
sidebar.appendChild(category);
category.appendChild(sectionselect);
for(i in optionNames) {
sectionselect.appendChild(createElement("option", {
innerText: optionNames[i]
}));
}
sidebar.appendChild(options);
// With it initially set, add the sidebar
body.appendChild(window.sidebar = sidebar);
}
// Lets the user choose which Thing to place
function createEditorBottomBar() {
var bar = editor.bottombar = createElement("div", {
id: "bottombar",
things: {}
});
sidebar.appendChild(bar);
}
function createEditorScrollers() {
var names = ["right", "left"],
scrollers = {},
name, i, parent, div, top;
// Set the parent #scrollers to hold them
parent = createElement("div", {
id: "scrollers",
style: {
zIndex: 7,
width: (innerWidth - 32) + "px"
}
});
// Create the two scrollers
settings = {
className: "scroller",
style: {
zIndex: 7,
marginTop: innerHeight / 2 + "px"
},
onmouseover: editorFollowerHide,
onmouseout: editorFollowerShow,
onmousedown: editorScrollingStart,
onmouseup: editorScrollingStop
};
// Create each div
for(i = names.length - 1; i >= 0; --i) {
name = names[i];
div = scrollers[names[i]] = createElement("div", settings);
parent.appendChild(div);
}
// The left one should start hidden
proliferate(scrollers["left"], {
id: "left",
className: "scroller flipped off",
dx: -7
});
// The right one is at the right of the screen
proliferate(scrollers["right"], {
id: "right",
style: { right: "21px" },
dx: 7
});
editor.scrollers = scrollers;
body.appendChild(parent);
}
function editorFollowerHide() {
var follower = editor.follower;
follower.hiddenOld = follower.hidden;
follower.hidden = true;
}
function editorFollowerShow() {
var follower = editor.follower;
follower.hidden = follower.hiddenOld;
}
function editorScrollingStart(event) {
var scroller = event.target,
dx = scroller.dx;
editorPreventClicks();
editor.scrolling = TimeHandler.addEventInterval(editorScrolling, 1, Infinity, -dx);
classRemove(editor.scrollers["left"], "off");
}
function editorScrollingStop() {
TimeHandler.addEvent(editorClickOff, 3);
TimeHandler.clearEvent(editor.scrolling);
}
function editorScrolling(dx) {
scrollEditor(dx);
if(editor.xloc >= 0) {
scrollEditor(-editor.xloc);
editorScrollingStop();
classAdd(editor.scrollers["left"], "off");
return true;
}
}
// Guidelines display helpful boundaries
function createEditorGuideLines() {
var lines = {
floor: 0,
ceiling: ceillev,
jumplev1: jumplev1,
jumplev2: jumplev2
},
left = 16 * unitsize + "px",
floor = map.floor,
i, parent, line;
// The parent holds them and provides the marginLeft
window.maplines = parent = document.createElement("div");
parent.style.marginLeft = left;
parent.id = "maplines";
// Create each line and put it in the parent
for(i in lines) {
line = createElement("div", {
innerText: i,
className: "mapline",
id: i + "_line",
style: {
marginTop: (floor - lines[i]) * unitsize + "px",
marginLeft: "-" + left,
paddingLeft: left
}
});
parent.appendChild(line);
}
body.appendChild(parent);
}
// The controls supply typical ops like undo and save
function setEditorControls(names) {
names = names || ["load", "save", "reset", "undo"/*, "erase"*/];
var previous = document.getElementById("controls"),
container = createElement("div", {id: "controls"}),
controls = editor.controls = {container: container},
name, div, i;
// Clear anything previously existing
if(previous) previous.innerHTML = "";
// For each name, add a div
for(i in names) {
name = names[i];
div = createElement("div", {
id: name,
alt: name,
className: "control",
style: { backgroundImage: "url(Theme/" + name + ".gif)" },
innerHTML: "
" + name + "
",
onclick: editorClickControl
});
container.appendChild(div);
controls[name] = div;
}
sidebar.appendChild(container);
}
// What happens on mouse down/click/etc
function setEditorTriggers() {
// Everything that allows mouse triggers
var activators = [maplines, canvas],
me, i;
for(i = activators.length - 1; i >= 0; --i) {
me = activators[i];
me.onclick = editorMouseClick;
}
// Make sure the follower's position is updated!
document.onmousemove = editorFollowerFollowsCursor;
}
// Place a new thing at the editor's location
function editorMouseClick(event) {
if(!window.editing || editor.clicking) return;
editorPreventClicks();
// If erasing, do that instead
if(editor.erasing) return editorPlaceEraser(event);
// Don't do anything if you're in settings, or just clicked a control
if(editor.in_settings || !editor.canplace) return;
var section_name = editor.section_name,
current_section = window[section_name],
thing_name = editor.current_selected,
follower_old = editor.follower;
// Record this past follower in editor.placed
editor.placed.push(follower_old);
// To visualize, simply unattach the follower from the editor and make a new one
editor.follower = false;
editorSetCurrentThingFromName(null, true);
// (when paused, this doesn't happen otherwise)
if(paused) refillCanvas();
// No more follower manipulation is needed, because each Thing stores its own arguments
// Enabling .was_follower lets the eventual save function know this is a key Thing
follower_old.was_follower = true;
delete follower_old.onclick;
// If it's a live run-though, enable velocity & movement stuff
if(editor.playing) {
thingRetrieveVelocity(follower_old);
proliferate(follower_old, follower_old.reference.attributes);
}
}
// Selects solids or characters or etc.
function editorSelectSection() {
var selected = (this || editor.sectionselect).value.toLowerCase();
// Clear & load the bottom bar
// If it's settings, do something
if(editor.in_settings = selected == "settings") {
editorSetSection(selected, true);
editorSetSectionSettings();
}
else editorSetSection(selected);
}
function editorSetSection(name, nothing) {
var section = editor.section = editor[name],
bottombar = editor.bottombar,
num_kids = 0,
canv_sel, canv,
first, name;
editor.section_name = name;
// Clear and reset the bottom bar
bottombar.innerHTML = "";
if(!nothing) {
for(name in section) {
++num_kids;
canv = editorAddBottomPreview(bottombar, name, section[name]);
if(!canv_sel) // This makes it grab the first canvas (comment to get the last)
canv_sel = canv;
}
}
// Only show the bottom bar if there weren't any settings
if(num_kids) {
bottombar.style.visibility = "visible";
editorSetCurrentThingFromCanvas(canv_sel);
}
else bottombar.style.visibility = "hidden";
}
// Adds an equivalent object to the bottom bar
function editorAddBottomPreview(bottombar, name, ref) {
// Create a canvas based off name & ref
var width = ref.width,
height = ref.height,
thingfunc = window[name],
// The Thing is used for image drawing
// If the name doesn't exist as a member of window, it's a Scenery sprite
thing = thingfunc ? ThingCreate(thingfunc, ref.previewargs) : new Thing(Sprite, name),
// The holder holds the canvas for positioning reasons
holder = createElement("div", {
width: width * unitsize + "px",
height: height * unitsize + "px",
name: name,
className: "holder " + name,
onclick: editorSetCurrentThing
}),
maxWidth = { maxWidth: "100%" },
maxHeight = { maxHeight: "100%" },
canvas = proliferate(getCanvas(width * unitsizet2, height * unitsizet2), {
name: name,
reference: ref,
style: { marginLeft: -roundDigit(width / 2, scale) + "px" },
onclick: editorSetCurrentThing
}),
things = bottombar.things,
sizewidth = width * unitsizet2,
sizeheight = height * unitsizet2,
context = canvas.getContext("2d"),
csource;
// For superior sharpness
canvasDisableSmoothing(canvas);
// Update it visually
editor.bottombar.things[name] = canvas.thing = thing;
addClass(thing, "editor"); // Ones that need to know this is different get that here
// Know where to take from (multiple sprites need to be specified)
csource = thing.canvas;
if(thing.canvases) csource = thing.canvases[ref.sprite_source || "middle"].canvas;
// If there's a previewsize, pattern it on the canvas
if(ref.previewsize) {
context.fillStyle = context.createPattern(csource, "repeat");
context.fillRect(0, 0, sizewidth, sizeheight);
}
// Otherwise just draw it normally...
else {
context.drawImage(csource, 0, 0, sizewidth, sizeheight);
}
// Add the canvas to the holder, which is added to bottom bar
holder.appendChild(holder.canvas = canvas);
bottombar.appendChild(holder);
bottombar[name] = holder;
// Return the canvas (the last one will be set as the current)
return canvas;
}
// Displays the editor settings instead of Thing arguments
function editorSetSectionSettings() {
var settings = editor.settings,
html = "
",
rows;
html += "
Settings
";
// Add the options, some of which are non-standard
html += addArgumentOption("night", Boolean, settings.night);
html += addArgumentOption("setting", ["Overworld", "Underworld", "Underwater", "Castle", "Sky"], settings.setting);
html += addArgumentOption("alt", Boolean, settings.alt);
html += "
";
// Apply this to options
options.innerHTML = html;
// Make these valid, and call the setting supdate function
ensureOptionsAboveZero(editorUpdateSettingsOption);
// Knowing these options, reference them
rows = editor.sidebar.getElementsByTagName("table")[0].rows;
editor.settings.night_elem = rows[0].cells[1].firstChild;
editor.settings.setting_elem = rows[1].cells[1].firstChild;
editor.settings.alt_elem = rows[2].cells[1].firstChild;
// There's no follower here
if(editor.follower) killNormal(editor.follower);
editor.follower = false;
}
// Called instead of editorUpdateFollower for settings
function editorUpdateSettingsOption(event) {
var settings = editor.settings,
night = settings.night = settings.night_elem.value == "True",
alt = settings.alt = settings.alt_elem.value == "True",
setting = settings.setting = settings.setting_elem.value,
// // Get the new area setting from the editor settings elemes
newsetting = setting + (night ? " Night" : "") + (alt ? " " + alt : "");
setAreaSetting(area, newsetting, newsetting != area.setting);
}
// Called when something in the bottom bar is clicked
function editorSetCurrentThing(event, sameargs) {
// Set this as selected for the editor
var self = event.target,
name = editor.current_thing_name = self.name,
refs = editor.current_thing = editor.section[name];
if(!sameargs) updateCurrentArguments(name, refs);
editorUpdateFollower();
}
// Manually makes a fake event for editorSetCurrentThing using a bottom bar's canvas
function editorSetCurrentThingFromCanvas(canv, sameargs) {
editorSetCurrentThing({target: canv}, sameargs);
}
// Manually makes a fake event for editorSetCurrentThing using just a name
function editorSetCurrentThingFromName(name, sameargs) {
editorSetCurrentThing({target: {name: name || editor.current_thing_name}}, sameargs);
}
// Displays arguments of the selected Thing
function updateCurrentArguments(name, reference) {
reference = reference || {};
var options = editor.options,
html = "
",
mydefaults = reference.mydefaults || {},
arguments = reference.arguments || {},
i;
// Add this thing's name
html += "
" + name + "
";
// Size must be there, with or without the arguments
if(!arguments.width) html += addStaticOption("width", reference.width);
if(!arguments.height) html += addStaticOption("height", reference.height);
// Add any other arguments
for(i in arguments) {
html += addArgumentOption(i.replace("_", "-"), arguments[i], null, mydefaults);
}
// Submit the HTML
html += "
";
options.innerHTML = html;
ensureOptionsAboveZero();
}
// An option that can't be changed
function addStaticOption(name, value) {
if(value == Infinity) value = "Inf.";
return "
" + name + ":
" + value + "
";
}
// An option that can be changed (as an argument)
function addArgumentOption(name, value, ref, mydefaults) {
mydefaults = mydefaults || {};
var text = "
" + name + ":
";
switch(value) {
case Infinity: text += "Inf"; break;
case Boolean:
text += "";
break;
case Number:
text += "";
break;
default:
switch(typeof(value)) {
case "number": text += "" + value + "x"; break;
case "string": text += ""; break;
case "object":
text += "
";
}
// Ensures input & select elements are valid, and update when needed
function ensureOptionsAboveZero(updatefunc) {
updatefunc = updatefunc || editorUpdateFollower;
// For each input element, don't let it go below 0
var elements = editor.options.getElementsByTagName("input"),
element;
for(i = elements.length - 1; i >= 0; --i) {
element = elements[i];
element.onchange = element.onclick = element.onkeypress = editorInputEnsureAboveZero;
}
// Select elements also need to update the follower on change
elements = options.getElementsByTagName("select");
for(i = elements.length - 1; i >= 0; --i) {
element = elements[i];
element.onchange = element.onclick = element.onkeypress = editorUpdateFollower;
}
}
function editorInputEnsureAboveZero(event) {
// var me = event.target,
// min = editor.current_thing.minimum || 0,
// value = me.value = Number(me.value) || min;
// setTimeout(function() { if(value < me.min) me.value = min; }, 35);
editorUpdateFollower(event);
}
/* The Follower */
function editorUpdateFollower(event) {
// If settings are chosen, do that instead
if(editor.in_settings) return editorUpdateSettingsOption(event);
var current_thing = editor.current_thing,
args, follower;
// If there's already one, kill it
if(follower = editor.follower) {
follower.id = "";
killNormal(follower);
}
// If it has its own function (e.g. a Sprite currier), use that
if(current_thing.createfunc) {
follower = current_thing.createfunc(editor.current_thing, editorGetArguments());
}
// Otherwise pass it to ThingCreate with arguments
else follower = ThingCreate(
window[editor.current_thing_name],
current_thing.followerUpdate(editor.current_thing, editorGetArguments())
);
// Arguments set things such as size and adding
editor.follower = follower;
// Also give it things like the CSS ID and the onclick
proliferate(follower, {
id: "follower",
libtype: editor.section_name, // just in case (for scenery)
lookleft: true, // just in case (for HammerBro throwing)
nocollide: true,
reference: current_thing,
onclick: editorMouseClick
}, true);
// Add it, and give it the 'editor' class
addThing(follower);
addClass(follower, "editor");
// Don't let it do anything, unless this is a live run-through!
thingRetrieveVelocity(follower);
thingStoreVelocity(follower);
// Make sure it has a position
editorSetFollowerPosition(follower);
// Hide it if there's it's eraser
if(editor.erasing) follower.hidden = true;
}
// Grabs the inputs and their values from #options
function editorGetArguments() {
var inputs = arrayMake(editor.options.getElementsByTagName("input")),
selects = arrayMake(editor.options.getElementsByTagName("select")),
combined = inputs.concat(selects);
pairs = generateInputNameValuePairs(combined);
return pairs;
}
function generateInputNameValuePairs(inputs) {
var output = {}, i;
for(i in inputs) {
output[inputs[i].name] = inputs[i].value;
}
return output;
}
// Whenever the cursor moves, put it on that location
function editorFollowerFollowsCursor(event) {
var follower = editor.follower;
if(!follower) return;
var xloc = roundFollowerDigit(event.x) + (editor.current_thing.widthoff - editor.offset.x) * unitsize,
yloc = roundFollowerDigit(event.y) + editor.current_thing.heightoff * unitsize;
editorSetFollowerPosition(follower, xloc, yloc);
}
function editorSetFollowerPosition(follower, xloc, yloc) {
xloc = xloc || editor.xloc_old || 0;
yloc = yloc || editor.yloc_old || 0;
setLeft(follower, xloc);
setTop(follower, yloc);
editor.xloc_old = xloc;
editor.yloc_old = yloc;
}
// Because Things are placed based on a grid, despite the ability to be free-form.
function roundFollowerDigit(num) {
// var diff = 4;
var diff = editor.section_name == "solids" ? 8 : 4;
// switch(editor.section_name) {
// case "solids": diff = 8; break;
// case "characters": diff = 4; break;
// case "scenery": diff = 2; break;
// }
return unitsize * diff * round(num / (unitsize * diff));
}
function roundFollowerPosition(me, num) {
editorSetFollowerPosition(me,
roundFollowerDigit(me.left),
roundFollowerDigit(me.top)
);
}
/* Follower Update Methods */
// The default argument generator
// Passes in width and height, in that order, if they're in the arguments
function editorFollowerUpdateStandard(reference, pairs) {
// Hidden is a necessary check
if(pairs.hidden == "True") {
// This is an event because these are set before the new follower is made
TimeHandler.addEvent(function() { editor.follower.hidden = true; });
}
// More importantly, check for width and height
var output = [];
if(pairs.width) output.push(Number(pairs.width));
if(pairs.height) output.push(Number(pairs.height));
return output;
}
/* Editor Controls */
// Launches editorControlXXX, where X is what's clicked
function editorClickControl(event) {
// So the editor knows not to place anything
editorPreventClicks();
// This can either be the control or the control's firstChild
var target = event.target;
if(!target.id) target = target.parentNode;
window["editorControl" + capitalizeFirst(target.id)]();
// This should stop it from causing a placement
event.preventDefault();
}
function editorPreventClicks() {
editor.clicking = true;
TimeHandler.addEvent(editorClickOff, 3);
}
function editorClickOff() {
if(window.editor) editor.clicking = false;
}
// Deletes and pops the last thing in placed
function editorControlUndo() {
var placed = editor.placed,
last = placed.pop();
if(last && !last.mario) {
killNormal(last);
}
}
// Continuously undos until placed is empty
function editorControlReset() {
var placed = editor.placed,
len = placed.length,
timer = roundDigit(35 / len, 21);
TimeHandler.addEventInterval(editorControlUndo, timer, len);
}
// Creates the function and displays the submission window to the user
function editorControlSave() {
// if(editor.playing || editor.placed.length == 0) return;
// Display the input/submission window to the user
var rawfunc = editor.rawfunc = editorGetRawFunc(),
title = "Hit Submit below to start playing!",
p = "
This map will be resumed automatically the next time you use the editor on this computer. Alternately, you may copy this text to work on again later using Load (the button next to Save).
",
menu = editorCreateInputWindow(title + " " + p, rawfunc, editorSubmitGameFuncPlay);
return rawfunc;
}
// Stops playing (really just calls loadEditor)
function editorControlCancel() {
loadEditor();
}
// Gets the string version of the editor function
function editorGetRawFunc() {
var placed = editor.placed,
lenm1 = placed.length - 1,
statements = new Array(i),
// Using the simple Function constructor, the map is the first argument
rawfunc = " var map = arguments[0] || new Map();\n",
i;
// Start the raw func off with the time, location, and area
rawfunc += "\n map.time = " + data.time.amount + ";";
rawfunc += "\n map.locs = [ new Location(0, true) ];";
rawfunc += "\n map.areas = [";
rawfunc += "\n new Area('" + area.setting + "', function() {";
rawfunc += "\n setLocationGeneration(0);\n\n";
// Generate the pre-statements based on what's placed
for(i = lenm1; i >= 0; --i) {
// Manipulations are done by the editorPreStatement object
statements[i] = new editorPreStatement(placed[i]);
}
// Sort the pre-statements, for cleanliness
statements.sort(prethingsorter);
// With them sorted, turn them all into strings (with the 6 spaces in front)
for(i = lenm1; i >= 0; --i) {
// Manipulations are done by the editorPreStatement object
statements[i] = " " + statements[i].statement;
}
// So that annoying loading glitch doesn't happen
statements = removeDuplicates(statements);
// Add these statements to the raw function
rawfunc += statements.join("\n");
// Finish off the area function
rawfunc += "\n })";
rawfunc += "\n ];";
rawfunc += "\n return map;"
return rawfunc;
}
// Generates a prestatement based off a placed object
function editorPreStatement(placer) {
this.placer = placer;
// Positioning is important
this.xloc = (gamescreen.left + placer.left) / unitsize;
this.yloc = map.floor - placer.top / unitsize;
// Reference and arguments are used to make the statement
// Using them, get the statement
this.statement = editorGetStatement(this, placer, placer.reference, placer.args);
}
// Given a prestatement, this returns the string
function editorGetStatement(prestatement, placer, reference, args) {
// If there isn't a reference, try to get it from the library
if(!reference) {
reference = editor[placer.libtype][placer.title];
// If it's still not there, ignore this thing (like ScrollBlocker)
if(!reference) return "";
}
// The statement always starts with the reference's pre-function
var statement = (reference.prefunc || pushPreThing).name,
numargs = args.length,
argstrings, arg;
// If it has a custom prefunction creator (like scenery), do that
if(reference.prefunc_custom) {
statement += "(" + reference.prefunc_custom(prestatement, placer, reference, args) + ");";
}
// Otherwise generate the arguments
else {
argstrings = [];
// Unless reference specifies not to, add the placer's title
if(!reference.prefunc_solo) argstrings.push(placer.title);
// else argstrings.push(placer.prefunc);
// Start the argstrings off with the xloc and yloc
argstrings.push(String(prestatement.xloc));
argstrings.push(String(prestatement.yloc));
// For each argument (0 is the thing, so 1 onward), add it to the array of strings
for(var i = 1; i < numargs; ++i) {
// Add it, giving the thing apostrophes if needed
switch(typeof(arg = args[i])) {
case "undefined": break;
case "number": arg = String(round(arg)); break;
default: arg = String(arg); break;
}
// Don't add it if it's undefined
if(typeof(arg) != "undefined")
argstrings.push(arg);
}
// Make the statement use the arguments, joined by commas
statement += "(" + argstrings.join(", ") + ");";
}
return statement;
}
/* Erasing */
function editorControlErase() {
!editor.erasing ? editorControlEraseOn() : editorControlEraseOff();
}
function editorControlEraseOn() {
editor.erasing = editor.follower.hidden = true;
classAdd(body, "erasing");
classAdd(editor.controls.erase, "enabled");
}
function editorControlEraseOff() {
editor.erasing = editor.follower.hidden = false;
classRemove(body, "erasing");
classRemove(editor.controls.erase, "enabled");
}
function editorPlaceEraser(event) {
addThing(Eraser, event.x, event.y);
}
function Eraser(me) {
me.width = me.height = 2;
me.nocollide = me.nofall = true;
me.movement = eraserErases;
setCharacter(me, "eraser");
}
// Checks everything for collision (necessary because sceneries don't collide)
function eraserErases(me) {
if(!window.editor) return;
var placed = editor.placed,
arr = placed.concat(solids).concat(characters).concat(scenery),
other, i;
// If this touches anything in placed
for(i = arr.length - 1; i >= 0; --i) {
other = arr[i];
if(other.mario || other == editor.follower) continue;
// These ones touch:
if(objectsTouch(me, other)) {
// Kill the other
killNormal(other);
// Remove it from placed
placed.splice(placed.indexOf(other), 1);
break;
}
}
// ...and finally, kill me
killNormal(me);
}
/* Loading Previous Maps */
// Prompts the user to give part of a function
function editorControlLoad() {
var blurb = "Paste your work in progress here, and click Submit to continue it.";
editorCreateInputWindow(blurb, "", editorSubmitLoad);
}
// Takes solids, characters, and scenery, and puts them int oplaced
function addThingsToPlaced() {
var placed = editor.placed;
// Grab all the Things, and sort them
editor.placed = (editor.placed || []).concat(characters).concat(solids).concat(scenery);
placed.sort(prethingsorter);
// (don't include Mario in this)
placed.splice(placed.indexOf(mario), 1);
// Make the new placed all know their reference
for(i = placed.length - 1; i >= 0; --i) {
placer = placed[i];
placer.reference = editor[placer.libtype][placer.title];
}
}
/* Editor Input/Submission Window */
function editorCreateInputWindow(blurb, value, callback) {
// Create the elements
var bigwidth = gamescreen.unitwidth,
div = editor.input_window = createElement("div", {
id: "input_window",
innerHTML: blurb || "",
style: { width: bigwidth + "px" }
}),
input = div.input = editor.window_input = createElement("textarea", {
id: "window_input",
value: value || "",
style: { width: (bigwidth - 49) + "px" }
}),
submit = div.submit = createElement("div", {
id: "window_submit",
className: "window_button",
innerText: "Submit",
onclick: callback
}),
cancel = div.cancel = createElement("div", {
id: "window_cancel",
className: "window_button",
innerText: "Cancel",
onclick: editorCloseInputWindow
});
// Add them to each other, and the body
div.appendChild(input);
div.appendChild(submit);
div.appendChild(cancel);
body.appendChild(div);
// Remove the follower
killNormal(editor.follower = false);
editor.follower = false;
return div;
}
function editorCloseInputWindow(noedit) {
editorPreventClicks();
// Delete the input window
removeChildSafe(window.input_window, body);
if(!noedit) {
// Recreate the current thing
editorSetCurrentThingFromName();
// Pretty sure this is necessary.
window.editing = true;
}
editorUpdateFollower();
}
// It's as if it never happened.
function editorClose(inmap) {
if(!window.editor) return;
// Clear any visual changes
classRemove(body, "editor");
classRemove(body, "erasing");
// Remove the follower
// if(window.editor) {
killNormal(editor.follower);
editor.follower = false;
delete window.editor;
// }
// Remove the editor elements (safely)
var ids = ["maplines", "sidebar", "bottombar", "scrollers"], i;
for(i in ids) removeChildSafe(document.getElementById(ids[i]), body);
// Stop the mouse triggers
document.onmousemove = null;
window.editing = false;
// Unless this is being called by setMap, stop shifting
if(inmap && window.map) map.shifting = false;
}
// Called by scrollWindow while editing to update the follower
function scrollEditor(xinv, yinv) {
if(!window.editor) return;
var follower = editor.follower;
if(!follower) return;
xinv = xinv || 0;
yinv = yinv || 0;
// shiftBoth(follower, xinv, yinv);
shiftAll(scenery, xinv, yinv);
shiftAll(solids, xinv, yinv);
shiftAll(characters, xinv, yinv);
editor.xloc += xinv;
editor.yloc += yinv;
}
function editorStoreLocally() {
// Record this in localStorage
localStorage.editorLastFunc = editor.rawfunc;
}
// Checks for a previously saved function, and loads if found
function setEditorLocalRetrieval() {
var found = localStorage.editorLastFunc;
if(!found) return;
editor.rawfunc = round;
editorSubmitGameFunc();
}
// Starts the editor with a particular function from editor.rawfunc
function editorSubmitGameFunc() {
// If there's no raw function known, don't do anything
if(!window.editor || !editor.rawfunc) return loadEditor();
var rawfunc = editor.rawfunc,
mapfunc = window.custommapfunc = new Function(editor.rawfunc);
// Start the map
mapfuncs.Custom = { Map: mapfunc };
window.canedit = true;
setMap(["Custom", "Map"]);
window.canedit = editor.playing = false;
// Load mario and all things
entryBlank(mario);
addThingsToPlaced();
// Save and close
editorStoreLocally();
editorCloseInputWindow();
}
// Submits the game function, and starts playing
function editorSubmitGameFuncPlay() {
editorPreventClicks();
editorSubmitGameFunc();
editorStartPlaying();
}
// Grabs the raw function from the input window
function editorSubmitLoad() {
if(!window.editor || !editor.window_input) return;
editorPreventClicks();
var rawfunc = editor.window_input.value;
loadEditor();
editor.rawfunc = rawfunc;
editorSubmitGameFunc();
}
// Allows Mario to roam the world
function editorStartPlaying() {
editorPreventClicks();
editor.playing = true;
// Place a Mario normally
placeMario();
entryPlain(mario);
nokeys = false;
// Retrieve each thing
var placed = editor.placed,
placer, ref, i;
for(i in placed) {
placer = placed[i];
thingRetrieveVelocity(placer);
// If it needs, give it back some defaults
ref = editor[placer.libtype][placer.title];
if(ref)
proliferate(placer, ref.onadds);
}
// Only have the cancel option now
setEditorControls(["Cancel"]);
}
// Checks for a rawfunc in localStorage, and loads it if possible
function setEditorLocalRetrieval() {
var found = localStorage.editorLastFunc;
if(!found) return;
editor.rawfunc = found;
editorSubmitGameFunc();
editorStoreLocally();
// For each thing now placed:
var placed = editor.placed, i;
for(i in placed) {
// thingRetrieveVelocity(placed[i]);
thingStoreVelocity(placed[i]);
}
}