Skip to content

Instantly share code, notes, and snippets.

@apollolux
Created June 3, 2013 22:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apollolux/5702052 to your computer and use it in GitHub Desktop.
Save apollolux/5702052 to your computer and use it in GitHub Desktop.
persist.js, a JavaScript for persistent data storage and retrieval between maps in the Sphere engine originally written by @tung ; created from version archived at http://web.archive.org/web/20100628133859/http://tunginobi.spheredev.org/download/persist.js
/*
FILE ARCHIVED ON 13:38:59 Jun 28, 2010 AND RETRIEVED FROM THE
INTERNET ARCHIVE ON 22:26:43 Jun 3, 2013.
JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
SECTION 108(a)(3)).
*/
/**
* persist.js - store persistent data with maps and persons.
*
* persist.js lets you store variables with persons and maps. It also
* keeps them, even when you switch maps. Data is kept in one place,
* making it easy to save and load.
*
* persist.js also lets you write map scripts inside Sphere's code
* editor, giving you F7 syntax checking, syntax highlighting, and
* full editing comfort.
*
* Wiki: http://wiki.spheredev.org/Script:Persist.js
*/
var persist = (function() {
var mapEvents = [{
fn: 'enter',
event: SCRIPT_ON_ENTER_MAP
}, {
fn: 'leave',
event: SCRIPT_ON_LEAVE_MAP
}, {
fn: 'leaveNorth',
event: SCRIPT_ON_LEAVE_MAP_NORTH
}, {
fn: 'leaveSouth',
event: SCRIPT_ON_LEAVE_MAP_SOUTH
}, {
fn: 'leaveEast',
event: SCRIPT_ON_LEAVE_MAP_EAST
}, {
fn: 'leaveWest',
event: SCRIPT_ON_LEAVE_MAP_WEST
}];
var personEvents = [{
fn: 'create',
event: SCRIPT_ON_CREATE
}, {
fn: 'destroy',
event: SCRIPT_ON_DESTROY
}, {
fn: 'touch',
event: SCRIPT_ON_ACTIVATE_TOUCH
}, {
fn: 'talk',
event: SCRIPT_ON_ACTIVATE_TALK
}, {
fn: 'generator',
event: SCRIPT_COMMAND_GENERATOR
}];
/************************************************************
* Log layer: provide optional debug output to Sphere logs. *
************************************************************/
/* Output log for debugging and tracing. */
var log = null;
var logging = false;
var takeNotes = false;
/**
* Set an output log for debugging persist.js and its use.
* @param debuglog {SphereLog} the log to output messages to
*/
function setLog(debuglog) {
log = debuglog;
}
/**
* Begin logging of persist.js.
* @param takenotes {boolean} include trace notes
* @param debuglog {SphereLog} optional, the log to output messages to
*/
function startLogging(takenotes, debuglog) {
logging = true;
takeNotes = takenotes || false;
if (debuglog) log = debuglog;
}
/**
* Stop sending
*/
function stopLogging() {
logging = false;
}
/* Write an error message to the assigned log, or the default. */
function logError(message) {
if (!log) log = OpenLog("persist.js.log");
log.write("Error: " + message);
}
/* Write a warning to the assigned log if logging is enabled. */
function logWarn(message) {
if (log && logging) log.write("Warning: " + message);
}
/* Write a note to the assigned log if logging is enabled. */
function logNote(message) {
if (log && logging && takeNotes) log.write("Note: " + message);
}
/****************************************
* File layer: process paths and files. *
****************************************/
/* Read in all the text in a file. */
function readFile(path) {
logNote("readFile: Opening " + path);
var bytes;
var f = OpenRawFile(path);
try {
bytes = f.read(f.getSize());
} finally {
f.close();
}
return CreateStringFromByteArray(bytes);
}
/* Path of the map script directory, relative to 'other/'.
* Include the slash at the end. */
var scriptPath = "../scripts/maps/";
/**
* Get the path of script files for maps, relative to 'other/'.
* @returns {string} the script path
*/
function getScriptPath() {
return scriptPath;
}
/**
* Set the path of script files for maps, relative to 'other/'.
* @param newPath {string} the new path for map scripts
*/
function setScriptPath(newPath) {
scriptPath = newPath;
}
/* Split a path into file and directory components */
function splitPath(path) {
var filename = path.replace('\\', '/', 'g').split('/').reverse()[0];
var directory = path.replace(filename, '');
return {
dir: directory,
file: filename
};
}
/* Check if a file exists in the directory. */
function fileExists(path) {
// relative to 'other/' because OpenRawFile defaults to it
var pathBits = splitPath(path);
var files = GetFileList('other/' + pathBits.dir);
for (var f = 0; f < files.length; ++f) {
if (pathBits.file == files[f]) return true;
}
return false;
}
/* Get the map script file, given the map filename. */
function scriptFromMap(map) {
// Lose the '.rmp' extension
var mapExt = '.rmp';
var basePath = map.substring(0, map.length - mapExt.length);
return scriptPath + basePath + '.js';
}
/* Load the script of the map into a string. */
function loadMapScriptFile(map) {
var script = scriptFromMap(map);
logNote("loadMapScriptFile: Loading " + script + " for " + map);
if (fileExists(script)) return readFile(script);
logWarn("loadMapScriptFile: Couldn't load " + script);
return '({})';
}
/*******************************************************
* State layer: manage world/map/person state objects. *
*******************************************************/
/* Tie an existing object to a prototype. */
function tieToPrototype(obj, proto) {
var protoTie = function() {};
protoTie.prototype = proto;
var newObj = new protoTie();
for (var p in obj) {
if (p in proto && typeof proto[p] == 'object') newObj[p] = tieToPrototype(obj[p], proto[p]);
else newObj[p] = obj[p];
}
return newObj;
}
/* Holds all world/map/person state variables. */
var world = {};
/**
* Get the state of the world.
* @returns {Object} the world state
*/
function getWorldState() {
return world;
}
/**
* Set the state of the world.
* @param newWorld {Object} the new world state
*/
function setWorldState(newWorld) {
for (var map in newWorld)
loadMapScript(map);
// EVIL ABSTRACTION BREAKING USE OF scriptCache!
world = tieToPrototype(newWorld, scriptCache);
}
/* List the persons to whom events should be attached. */
function getImportantPersons() {
var persons = [];
for (var p = 0, plist = GetPersonList(); p < plist.length; ++p) {
if (plist[p] != "" && (!IsInputAttached() || plist[p] != GetInputPerson())) persons.push(plist[p]);
}
return persons;
}
/* Check if a map state exists in the world. */
function mapStateExists(map) {
return map in getWorldState();
}
/* Make sure current map (and person) state variables are set. */
function ensureState(mapname) {
var map = mapname || GetCurrentMap();
// If the map state exists, chances are its person states do too.
// Also cuts this ugly circular call dependency:
// ensureState -> initPersonState -> setPersonState -> getMapState -> ensureState
// *whew*
if (mapStateExists(map)) return;
logNote("ensureState: Ensuring state of " + map);
var template = loadMapScript(map);
logNote("ensureState: Preparing state of " + map + " with loaded script");
initMapState(map, template);
for (var p = 0, persons = getImportantPersons(); p < persons.length; ++p) {
if (persons[p] in template) {
logNote("ensureState: Preparing state of " + persons[p] + " in " + map + " with loaded script");
initPersonState(map, persons[p], template[persons[p]]);
}
}
}
/**
* Get the map state. Use via the alias 'persist.map(map)'.
* @param map {string} optional, the name of the map
* @returns reference to the map state object
*/
function getMapState(map) {
var mapChosen = map || GetCurrentMap();
var ws = getWorldState();
if (!mapStateExists(mapChosen)) ensureState(mapChosen);
return ws[mapChosen];
}
/* Set the state of a map. */
function setMapState(map, newState) {
var ws = getWorldState();
ws[map] = newState;
}
/* Check if a person state exists in a map. */
function personStateExists(map, person) {
return mapStateExists(map) && (person in getMapState(map));
}
/**
* Get the person state. Use via the alias 'persist.person(person, map)'.
* @param map {string} optional, name of the map where the person is
* @param person {string} optional, name of the person
* @returns reference to the person state object
*/
function getPersonState(map, person) {
var personChosen = person || GetCurrentPerson();
var ms = getMapState(map);
if (personChosen in ms) return ms[personChosen];
return ms[personChosen] = {};
}
/* Set the state of a person. */
function setPersonState(map, person, newState) {
var ms = getMapState(map);
ms[person] = newState;
}
/* Check that a member of an object is not internal or a prototype member. */
function isCustomMember(object, member) {
return Object.prototype.hasOwnProperty.call(object, member) && Object.prototype.propertyIsEnumerable.call(object, member);
}
/* Check if a member is a map variable. */
function isMapVariable(member) {
for (var me = 0; me < mapEvents.length; ++me) {
if (member == mapEvents[me].fn) return false;
}
for (var p in GetPersonList()) {
if (member == p) return false;
}
return true;
}
/* Copy map variables from a map script object. */
function initMapState(map, script) {
var mapState = {};
for (var element in script) {
if (script[element] != 'function' && isCustomMember(script, element) && isMapVariable(element)) mapState[element] = script[element];
}
setMapState(map, tieToPrototype(mapState, script));
}
/* Check if a member is a person variable. */
function isPersonVariable(member) {
for (var pe = 0; pe < personEvents.length; ++pe) {
if (member == personEvents[pe].fn) return false;
}
return true;
}
/* Copy person variables from a person script object */
function initPersonState(map, person, script) {
var personState = {};
for (var element in script) {
if (typeof script[element] != 'function' && isCustomMember(script, element) && isPersonVariable(element)) personState[element] = script[element];
}
setPersonState(map, person, tieToPrototype(personState, script));
}
/***********************************************************
* Evaluation layer: eval files and manage loaded scripts. *
***********************************************************/
/* Loaded map script objects. */
var scriptCache = {};
/* Evaluate the matching script for a map. */
function loadMapScript(map) {
if (map in scriptCache) return scriptCache[map];
try {
scriptCache[map] = eval(loadMapScriptFile(map));
} catch (e) {
logError("In script for " + map + ": " + e);
}
return scriptCache[map];
}
/* Run a map event script. */
function runMapScript(map, event) {
logNote("runMapScript: Preparing to run " + event + " of " + map);
var mapScript = loadMapScript(map);
if (event in mapScript) {
logNote("runMapScript: Running " + event + " of " + map);
mapScript[event](world[map], world);
} else {
logWarn("runMapScript: Couldn't find " + event + " for " + map);
}
}
/* Run a person event script. */
function runPersonScript(map, person, event) {
if (event != 'generator') logNote("runPersonScript: Preparing to run " + event + " of " + person + " in " + map);
var mapScript = loadMapScript(map);
if (person in mapScript && event in mapScript[person]) {
if (event != 'generator') logNote("runPersonScript: Running " + event + " of " + person + " in " + map);
mapScript[person][event](world[map][person], world[map], world);
} else {
if (event != 'generator') logWarn("runPersonScript: Couldn't find " + event + " for " + person + " in " + map);
}
}
/**********************************************
* Hook layer: define what happens in events. *
**********************************************/
/* Run a map event for the current map. */
function triggerMapEvent(which) {
if (!IsMapEngineRunning()) return;
var map = GetCurrentMap();
try {
runMapScript(map, which);
} catch (e) {
logError("triggerMapEvent: event " + which + " for " + map + ": " + e);
Abort(e);
}
}
/* Run a person event for the current person. */
function triggerPersonEvent(which) {
if (!IsMapEngineRunning()) return;
var map = GetCurrentMap();
var person = GetCurrentPerson();
try {
runPersonScript(GetCurrentMap(), GetCurrentPerson(), which);
} catch (e) {
logError("triggerPersonEvent: event " + which + " for " + person + " on " + map + ": " + e);
Abort(e);
}
}
/* Re-run the create scripts of each person on the current map. */
function recreatePersons() {
logNote("recreatePersons: Re-running person SCRIPT_ON_CREATE events");
var persons = getImportantPersons();
for (var p = 0; p < persons.length; ++p)
CallPersonScript(persons[p], SCRIPT_ON_CREATE);
}
/****************************************
* Binding layer: wire events to hooks. *
****************************************/
/* Connect map events to scripts. */
function bindMapEvents() {
logNote("bindMapEvents: Binding map events");
for (var e = 0; e < mapEvents.length; ++e)
SetDefaultMapScript(mapEvents[e].event, 'persist.triggerMapEvent("' + mapEvents[e].fn + '");');
// Overwrite map enter event, it needs special handling.
SetDefaultMapScript(SCRIPT_ON_ENTER_MAP, 'persist.ensureState(); ' + 'persist.bindPersonsEvents(); ' + 'persist.recreatePersons(); ' + 'persist.triggerMapEvent("enter"); ');
}
/* Disconnect map event scripts. */
function unbindMapEvents() {
logNote("unbindMapEvents: Unbinding map events");
for (var e = 0; e < mapEvents.length; ++e)
SetDefaultMapScript(mapEvents[e].event, '');
}
/* Connect events of all persons (sic) to scripts. */
function bindPersonsEvents() {
logNote("bindPersonsEvents: Binding person events");
var mapScript = getMapState();
var persons = getImportantPersons();
for (var p = 0; p < persons.length; ++p) {
for (var e = 0; e < personEvents.length; ++e) {
if (persons[p] in mapScript && personEvents[e].fn in mapScript[persons[p]]) {
logNote("bindPersonsEvents: Binding " + personEvents[e].fn + " for " + persons[p]);
SetPersonScript(persons[p], personEvents[e].event, 'persist.triggerPersonEvent("' + personEvents[e].fn + '");');
}
}
}
}
/* Disconnect events of all persons (sic) from scripts. */
function unbindPersonsEvents() {
if (!IsMapEngineRunning()) return;
logNote("unbindPersonsEvents: Unbinding person events");
var persons = getImportantPersons();
for (var p = 0; p < persons.length; ++p) {
for (var e = 0; e < personEvents.length; ++e) {
logNote("unbindPersonsEvents: Unbinding " + personEvents[e].fn + " from " + persons[p]);
SetPersonScript(persons[p], personEvents[e].event, '');
}
}
}
/**
* Activate the persist.js framework.
*/
function init() {
logNote("init: Preparing persist.js framework");
setWorldState({});
bindMapEvents();
}
/**
* Deactivate the persist.js framework.
*/
function stop() {
logNote("stop: Stopping persist.js framework");
unbindMapEvents();
unbindPersonsEvents();
}
/*************
* Interface *
*************/
return {
// Use these...
setLog: setLog,
startLogging: startLogging,
stopLogging: stopLogging,
getScriptPath: getScriptPath,
setScriptPath: setScriptPath,
getWorldState: getWorldState,
setWorldState: setWorldState,
map: getMapState,
person: function(person, map) {
return getPersonState(map, person);
},
init: init,
stop: stop,
// ... not these!
ensureState: ensureState,
triggerMapEvent: triggerMapEvent,
triggerPersonEvent: triggerPersonEvent,
recreatePersons: recreatePersons,
bindPersonsEvents: bindPersonsEvents,
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment