/// var MapsCreatr; (function (MapsCreatr_1) { "use strict"; /** * Basic storage container for a single Thing to be stored in an Area's * PreThings member. A PreThing stores an actual Thing along with basic * sizing and positioning information, so that a MapsHandler may accurately * spawn or unspawn it as needed. */ var PreThing = (function () { /** * @param {Thing} thing The Thing, freshly created by ObjectMaker.make. * @param {IPreThingSettings} reference The creation Object instruction * used to create the Thing. */ function PreThing(thing, reference, ObjectMaker) { this.thing = thing; this.title = thing.title; this.reference = reference; this.spawned = false; this.left = reference.x || 0; this.top = reference.y || 0; this.right = this.left + (reference.width || ObjectMaker.getFullPropertiesOf(this.title).width); this.bottom = this.top + (reference.height || ObjectMaker.getFullPropertiesOf(this.title).height); if (reference.position) { this.position = reference.position; } } return PreThing; })(); MapsCreatr_1.PreThing = PreThing; /** * Storage container and lazy loader for GameStarter maps that is the back-end * counterpart to MapsHandlr. Maps are created with their custom Location and * Area members, which are initialized the first time the map is retrieved. * Areas contain a "creation" Object[] detailing the instructions on creating * that Area's "PreThing" objects, which store Things along with basic position * information. * * In short, a Map contains a set of Areas, each of which knows its size and the * steps to create its contents. Each Map also contains a set of Locations, * which are entry points into one Area each. * * See Schema.txt for the minimum recommended format for Maps, Locations, * Areas, and creation commands. * * @author "Josh Goldberg" */ var MapsCreatr = (function () { /** * @param {IMapsCreatrSettings} settings */ function MapsCreatr(settings) { if (!settings) { throw new Error("No settings object given to MapsCreatr."); } if (!settings.ObjectMaker) { throw new Error("No ObjectMakr given to MapsCreatr."); } if (typeof settings.ObjectMaker.getFullProperties() === "undefined") { throw new Error("MapsCreatr's ObjectMaker must store full properties."); } if (!settings.groupTypes) { throw new Error("No groupTypes given to MapsCreatr."); } this.ObjectMaker = settings.ObjectMaker; this.groupTypes = settings.groupTypes; this.keyGroupType = settings.keyGroupType || "groupType"; this.keyEntrance = settings.keyEntrance || "entrance"; this.macros = settings.macros || {}; this.scope = settings.scope || this; this.entrances = settings.entrances; this.requireEntrance = settings.requireEntrance; this.mapsRaw = {}; this.maps = {}; if (settings.maps) { this.storeMaps(settings.maps); } } /* Simple gets */ /** * @return {ObjectMakr} The internal ObjectMakr. */ MapsCreatr.prototype.getObjectMaker = function () { return this.ObjectMaker; }; /** * @return {String[]} The allowed group types. */ MapsCreatr.prototype.getGroupTypes = function () { return this.groupTypes; }; /** * @return {String} The key under which Things are to store their group. */ MapsCreatr.prototype.getKeyGroupType = function () { return this.keyGroupType; }; /** * @return {String} The key under which Things declare themselves an entrance. */ MapsCreatr.prototype.getKeyEntrance = function () { return this.keyEntrance; }; /** * @return {Object} The allowed macro Functions. */ MapsCreatr.prototype.getMacros = function () { return this.macros; }; /** * @return {Mixed} The scope to give as a last parameter to macros. */ MapsCreatr.prototype.getScope = function () { return this.scope; }; /** * @return {Boolean} Whether Locations must have an entrance Function. */ MapsCreatr.prototype.getRequireEntrance = function () { return this.requireEntrance; }; /** * @return {Object} The Object storing raw maps, keyed by name. */ MapsCreatr.prototype.getMapsRaw = function () { return this.mapsRaw; }; /** * @return {Object} The Object storing maps, keyed by name. */ MapsCreatr.prototype.getMaps = function () { return this.maps; }; /** * @param {Mixed} name A key to find the map under. This will typically be * a String. * @return {Map} The raw map keyed by the given name. */ MapsCreatr.prototype.getMapRaw = function (name) { var mapRaw = this.mapsRaw[name]; if (!mapRaw) { throw new Error("No map found under: " + name); } return mapRaw; }; /** * Getter for a map under the maps container. If the map has not yet been * initialized (had its areas and locations set), that is done here as lazy * loading. * * @param {Mixed} name A key to find the map under. This will typically be * a String. * @return {Map} */ MapsCreatr.prototype.getMap = function (name) { var map = this.maps[name]; if (!map) { throw new Error("No map found under: " + name); } if (!map.initialized) { // Set the one-to-many Map->Area relationships within the Map this.setMapAreas(map); // Set the one-to-many Area->Location relationships within the Map this.setMapLocations(map); map.initialized = true; } return map; }; /** * Creates and stores a set of new maps based on the key/value pairs in a * given Object. These will be stored as maps by their string keys via * this.storeMap. * * @param {Object} maps An Object containing a set of key/map pairs to * store as maps. * @return {Object} The newly created maps object. */ MapsCreatr.prototype.storeMaps = function (maps) { var i; for (i in maps) { if (maps.hasOwnProperty(i)) { this.storeMap(i, maps[i]); } } }; /** * Creates and stores a new map. The internal ObjectMaker factory is used to * auto-generate it based on a given settings object. The actual loading of * Areas and Locations is deferred to this.getMap. * * @param {Mixed} name A name under which the map should be stored, * commonly a String or Array. * @param {Object} settings An Object containing arguments to be sent to * the ObjectMakr being used as a Maps factory. * @return {Map} The newly created Map. */ MapsCreatr.prototype.storeMap = function (name, mapRaw) { if (!name) { throw new Error("Maps cannot be created with no name."); } var map = this.ObjectMaker.make("Map", mapRaw); this.mapsRaw[name] = mapRaw; if (!map.areas) { throw new Error("Maps cannot be used with no areas: " + name); } if (!map.locations) { throw new Error("Maps cannot be used with no locations: " + name); } this.maps[name] = map; return map; }; /* Area setup (PreThing analysis) */ /** * Given a Area, this processes and returns the PreThings that are to * inhabit the Area per its creation instructions. * * Each reference (which is a JSON object taken from an Area's .creation * Array) is an instruction to this script to switch to a location, push * some number of PreThings to the PreThings object via a predefined macro, * or push a single PreThing to the PreThings object. * * Once those PreThing objects are obtained, they are filtered for validity * (e.g. location setter commands are irrelevant after a single use), and * sorted on .xloc and .yloc. * * @param {Area} area * @return {Object} An associative array of PreThing containers. The keys * will be the unique group types of all the allowed * Thing groups, which will be stored in the parent * EightBittr's GroupHoldr. Each container stores Arrays * of the PreThings sorted by .xloc and .yloc in both * increasing and decreasing order. */ MapsCreatr.prototype.getPreThings = function (area) { var map = area.map, creation = area.creation, prethings = this.fromKeys(this.groupTypes), i; area.collections = {}; for (i = 0; i < creation.length; i += 1) { this.analyzePreSwitch(creation[i], prethings, area, map); } return this.processPreThingsArrays(prethings); }; /** * PreThing switcher: Given a JSON representation of a PreThing, this * determines what to do with it. It may be a location setter (to switch the * x- and y- location offset), a macro (to repeat some number of actions), * or a raw PreThing. * Any modifications done in a called function will be to push some number * of PreThings to their respective group in the output PreThings Object. * * @param {Object} reference A JSON mapping of some number of PreThings. * @param {Object} PreThings An associative array of PreThing Arrays, * keyed by the allowed group types. * @param {Area} area The Area object to be populated by these PreThings. * @param {Map} map The Map object containing the Area object. */ MapsCreatr.prototype.analyzePreSwitch = function (reference, prethings, area, map) { // Case: macro (unless it's undefined) if (reference.macro) { return this.analyzePreMacro(reference, prethings, area, map); } else { // Case: default (a regular PreThing) return this.analyzePreThing(reference, prethings, area, map); } }; /** * PreThing case: Macro instruction. This calls the macro on the same input, * captures the output, and recursively repeats the analyzePreSwitch driver * function on the output(s). * * @param {Object} reference A JSON mapping of some number of PreThings. * @param {Object} PreThings An associative array of PreThing Arrays, * keyed by the allowed group types. * @param {Area} area The Area object to be populated by these PreThings. * @param {Map} map The Map object containing the Area object. */ MapsCreatr.prototype.analyzePreMacro = function (reference, prethings, area, map) { var macro = this.macros[reference.macro], outputs, i; if (!macro) { console.warn("A non-existent macro is referenced. It will be ignored:", macro, reference, prethings, area, map); return; } // Avoid modifying the original macro by creating a new object in its // place, while submissively proliferating any default macro settings outputs = macro(reference, prethings, area, map, this.scope); // If there is any output, recurse on all components of it, Array or not if (outputs) { if (outputs instanceof Array) { for (i = 0; i < outputs.length; i += 1) { this.analyzePreSwitch(outputs[i], prethings, area, map); } } else { this.analyzePreSwitch(outputs, prethings, area, map); } } return outputs; }; /** * Macro case: PreThing instruction. This creates a PreThing from the * given reference and adds it to its respective group in PreThings (based * on the PreThing's [keyGroupType] variable). * * @param {Object} reference A JSON mapping of some number of PreThings. * @param {Object} PreThings An associative array of PreThing Arrays, * keyed by the allowed group types. * @param {Area} area The Area object to be populated by these PreThings. * @param {Map} map The Map object containing the Area object. */ MapsCreatr.prototype.analyzePreThing = function (reference, prethings, area, map) { var title = reference.thing, thing, prething; if (!this.ObjectMaker.hasFunction(title)) { console.warn("A non-existent Thing type is referenced. It will be ignored:", title, reference, prethings, area, map); return; } prething = new PreThing(this.ObjectMaker.make(title, reference), reference, this.ObjectMaker); thing = prething.thing; if (!prething.thing[this.keyGroupType]) { console.warn("A Thing does not contain a " + this.keyGroupType + ". It will be ignored:", prething, "\n", arguments); return; } if (this.groupTypes.indexOf(prething.thing[this.keyGroupType]) === -1) { console.warn("A Thing contains an unknown " + this.keyGroupType + ". It will be ignored:", thing[this.keyGroupType], prething, reference, prethings, area, map); return; } prethings[prething.thing[this.keyGroupType]].push(prething); if (!thing.noBoundaryStretch && area.boundaries) { this.stretchAreaBoundaries(prething, area); } // If a Thing is an entrance, then the location it is an entrance to // must know it and its position. Note that this will have to be changed // for Pokemon/Zelda style games. if (thing[this.keyEntrance] !== undefined && typeof thing[this.keyEntrance] !== "object") { if (typeof map.locations[thing[this.keyEntrance]] !== "undefined") { if (typeof map.locations[thing[this.keyEntrance]].xloc === "undefined") { map.locations[thing[this.keyEntrance]].xloc = prething.left; } if (typeof map.locations[thing[this.keyEntrance]].yloc === "undefined") { map.locations[thing[this.keyEntrance]].yloc = prething.top; } map.locations[thing[this.keyEntrance]].entrance = prething.thing; } } if (reference.collectionName && area.collections) { this.ensureThingCollection(prething, reference.collectionName, reference.collectionKey, area); } return prething; }; /** * Converts the raw area settings in a Map into Area objects. * * These areas are typically stored as an Array or Object inside the Map * containing some number of attribute keys (such as "settings") along with * an Array under "Creation" that stores some number of commands for * populating that area in MapsHandlr::spawnMap. * * @param {Map} map */ MapsCreatr.prototype.setMapAreas = function (map) { var areasRaw = map.areas, locationsRaw = map.locations, // The parsed containers should be the same types as the originals areasParsed = new areasRaw.constructor(), locationsParsed = new locationsRaw.constructor(), area, location, i; // Parse all the Area objects (works for both Arrays and Objects) for (i in areasRaw) { if (areasRaw.hasOwnProperty(i)) { area = this.ObjectMaker.make("Area", areasRaw[i]); areasParsed[i] = area; area.map = map; area.name = i; area.boundaries = { "top": 0, "right": 0, "bottom": 0, "left": 0 }; } } // Parse all the Location objects (works for both Arrays and Objects) for (i in locationsRaw) { if (locationsRaw.hasOwnProperty(i)) { location = this.ObjectMaker.make("Location", locationsRaw[i]); locationsParsed[i] = location; location.entryRaw = locationsRaw[i].entry; location.name = i; location.area = locationsRaw[i].area || 0; if (this.requireEntrance) { if (!this.entrances.hasOwnProperty(location.entryRaw)) { throw new Error("Location " + i + " has unknown entry string: " + location.entryRaw); } } if (this.entrances && location.entryRaw) { location.entry = this.entrances[location.entryRaw]; } else if (location.entry && location.entry.constructor === String) { location.entry = this.entrances[String(location.entry)]; } } } // Store the output object in the Map, and keep the raw settings for the // sake of debugging / user interest map.areas = areasParsed; map.locations = locationsParsed; }; /** * Converts the raw location settings in a Map into Location objects. * * These locations typically have very little information, generally just a * container Area, x-location, y-location, and spawning function. * * @param {Map} map */ MapsCreatr.prototype.setMapLocations = function (map) { var locsRaw = map.locations, // The parsed container should be the same type as the original locsParsed = new locsRaw.constructor(), location, i; // Parse all the keys in locasRaw (works for both Arrays and Objects) for (i in locsRaw) { if (locsRaw.hasOwnProperty(i)) { location = this.ObjectMaker.make("Location", locsRaw[i]); locsParsed[i] = location; // The area should be an object reference, under the Map's areas location.area = map.areas[locsRaw[i].area || 0]; if (!locsParsed[i].area) { throw new Error("Location " + i + " references an invalid area:" + locsRaw[i].area); } } } // Store the output object in the Map, and keep the old settings for the // sake of debugging / user interest map.locations = locsParsed; }; /** * "Stretches" an Area's boundaries based on a PreThing. For each direction, * if the PreThing has a more extreme version of it (higher top, etc.), the * boundary is updated. * * @param {PreThing} prething * @param {Area} area */ MapsCreatr.prototype.stretchAreaBoundaries = function (prething, area) { var boundaries = area.boundaries; boundaries.top = Math.min(prething.top, boundaries.top); boundaries.right = Math.max(prething.right, boundaries.right); boundaries.bottom = Math.max(prething.bottom, boundaries.bottom); boundaries.left = Math.min(prething.left, boundaries.left); }; /** * Adds a Thing to the specified collection in the Map's Area. * * @param {PreThing} prething * @param {String} collectionName * @param {String} collectionKey * @param {Area} area */ MapsCreatr.prototype.ensureThingCollection = function (prething, collectionName, collectionKey, area) { var thing = prething.thing, collection = area.collections[collectionName]; if (!collection) { collection = area.collections[collectionName] = {}; } thing.collection = collection; collection[collectionKey] = thing; }; /** * Creates an Object wrapper around a PreThings Object with versions of * each child PreThing[] sorted by xloc and yloc, in increasing and * decreasing order. * * @param {Object} prethings * @return {Object} A PreThing wrapper with the keys "xInc", "xDec", * "yInc", and "yDec". */ MapsCreatr.prototype.processPreThingsArrays = function (prethings) { var scope = this, output = {}, i; for (i in prethings) { if (prethings.hasOwnProperty(i)) { var children = prethings[i], array = { "xInc": this.getArraySorted(children, this.sortPreThingsXInc), "xDec": this.getArraySorted(children, this.sortPreThingsXDec), "yInc": this.getArraySorted(children, this.sortPreThingsYInc), "yDec": this.getArraySorted(children, this.sortPreThingsYDec) }; // Adding in a "push" lambda allows MapsCreatr to interact with // this using the same .push syntax as Arrays. array.push = (function (prething) { scope.addArraySorted(this.xInc, prething, scope.sortPreThingsXInc); scope.addArraySorted(this.xDec, prething, scope.sortPreThingsXDec); scope.addArraySorted(this.yInc, prething, scope.sortPreThingsYInc); scope.addArraySorted(this.yDec, prething, scope.sortPreThingsYDec); }).bind(array); output[i] = array; } } return output; }; /* Utilities */ /** * Creates an Object pre-populated with one key for each of the Strings in * the input Array, each pointing to a new Array. * * @param {String[]} arr * @return {Object} * @remarks This is a rough opposite of Object.keys, which takes in an * Object and returns an Array of Strings. */ MapsCreatr.prototype.fromKeys = function (arr) { var output = {}, i; for (i = 0; i < arr.length; i += 1) { output[arr[i]] = []; } return output; }; /** * Returns a shallow copy of an Array, in sorted order based on a given * sorter Function. * * @param {Array} array * @param {Function} sorter * @ */ MapsCreatr.prototype.getArraySorted = function (array, sorter) { var copy = array.slice(); copy.sort(sorter); return copy; }; /** * Adds an element into an Array using a sorter Function. * * @param {Array} array * @param {Mixed} element * @param {Function} sorter A Function that returns the difference between * two elements (for example, a Numbers sorter * given (a,b) would return a - b). */ MapsCreatr.prototype.addArraySorted = function (array, element, sorter) { var lower = 0, upper = array.length, index; while (lower !== upper) { index = ((lower + upper) / 2) | 0; // Case: element is less than the index if (sorter(element, array[index]) < 0) { upper = index; } else { // Case: element is higher than the index lower = index + 1; } } if (lower === upper) { array.splice(lower, 0, element); return; } }; /** * Sorter for PreThings that results in increasing horizontal order. * * @param {PreThing} a * @param {PreThing} b */ MapsCreatr.prototype.sortPreThingsXInc = function (a, b) { return a.left === b.left ? a.top - b.top : a.left - b.left; }; /** * Sorter for PreThings that results in decreasing horizontal order. * * @param {PreThing} a * @param {PreThing} b */ MapsCreatr.prototype.sortPreThingsXDec = function (a, b) { return b.right === a.right ? b.bottom - a.bottom : b.right - a.right; }; /** * Sorter for PreThings that results in increasing vertical order. * * @param {PreThing} a * @param {PreThing} b */ MapsCreatr.prototype.sortPreThingsYInc = function (a, b) { return a.top === b.top ? a.left - b.left : a.top - b.top; }; /** * Sorter for PreThings that results in decreasing vertical order. * * @param {PreThing} a * @param {PreThing} b */ MapsCreatr.prototype.sortPreThingsYDec = function (a, b) { return b.bottom === a.bottom ? b.right - a.right : b.bottom - a.bottom; }; return MapsCreatr; })(); MapsCreatr_1.MapsCreatr = MapsCreatr; })(MapsCreatr || (MapsCreatr = {}));