Created
June 3, 2013 22:34
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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