Created
December 16, 2014 16:18
-
-
Save gamedevsam/ae19b5ebdf4175385987 to your computer and use it in GitHub Desktop.
sams-addons level scripting
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
package levels.scripting; | |
/** | |
* ... | |
* @author Samuel Batista | |
*/ | |
interface IScriptHandler | |
{ | |
/* | |
* WARNING: | |
* All level objects that receive events must implement the following functions (COPY AND PASTE THIS CODE): | |
* | |
* | |
* override public function destroy():Void | |
* { | |
* super.destroy(); | |
* if(handlesScriptEvents) | |
* LevelLogic.objectDestroyed(this); | |
* } | |
* public var handlesScriptEvents:Bool; | |
* | |
* | |
* !!! YOU MUST CALL -- LevelLogic.objectDestroyed(this); -- IN THE OBJECT'S -- destroy() -- FUNCTION !!! | |
*/ | |
public function destroy():Void; | |
public var handlesScriptEvents:Bool; | |
} |
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
package levels.scripting; | |
/** | |
* ... | |
* @author Samuel Batista | |
*/ | |
import flixel.FlxG; | |
class ScriptLogic | |
{ | |
private var commands:String; | |
private var eventTrigger:String; | |
private var object:IScriptHandler; | |
// Passing null to eventTrigger will cause this sequence to execute immediately | |
public static function run(object:IScriptHandler, commands:String, ?eventTrigger:String = null):Void | |
{ | |
// Bail out if there are no commands to execute | |
if (commands == null || commands == "") | |
return; | |
// Ensure this is valid event trigger, or assign it to null to execute immediately | |
if (eventTrigger != null && eventTrigger == "") | |
eventTrigger = null; | |
// Reuse LevelLogic objects that have completed their execution | |
var eventSequence:ScriptLogic; | |
if (inactiveScriptEvents.length > 0) | |
eventSequence = inactiveScriptEvents.pop(); | |
else | |
eventSequence = new ScriptLogic(); | |
eventSequence.object = object; | |
eventSequence.commands = commands; | |
eventSequence.eventTrigger = eventTrigger; | |
if (eventTrigger != null) | |
{ | |
// Verify this object doesn't already have an event handler for this exact event | |
#if !FLX_NO_DEBUG | |
for (es in activeScriptEvents) | |
{ | |
if (es.object == object && es.eventTrigger == eventTrigger) | |
throw "LevelLogic ERROR: Duplicate event handler for '" + eventTrigger + "' detected. A single object cannot have two entries for the same event. Use the pipe | symbol to chain commands in one event row"; | |
} | |
#end | |
object.handlesScriptEvents = true; | |
activeScriptEvents.push(eventSequence); | |
Events.addHandler(object, eventTrigger, eventSequence.onExecute); | |
} | |
else | |
{ | |
// Execute immediately if it doesn't provide an event trigger | |
eventSequence.execute(); | |
} | |
} | |
// This must be called when an object that listens to level logic events is destroyed | |
public static function objectDestroyed(o:IScriptHandler):Void | |
{ | |
var i = activeScriptEvents.length - 1; | |
while (i >= 0) | |
{ | |
if (activeScriptEvents[i].object == o) | |
{ | |
activeScriptEvents[i].dispose(true); | |
activeScriptEvents[i] = activeScriptEvents[activeScriptEvents.length - 1]; | |
activeScriptEvents.pop(); | |
} | |
i--; | |
} | |
} | |
// Call this to stop handling a specific event | |
public static function stopHandlingEvent(o:IScriptHandler, eventTrigger:String) | |
{ | |
var i = activeScriptEvents.length - 1; | |
while (i >= 0) | |
{ | |
if (activeScriptEvents[i].object == o && activeScriptEvents[i].eventTrigger == eventTrigger) | |
{ | |
activeScriptEvents[i].dispose(true); | |
activeScriptEvents[i] = activeScriptEvents[activeScriptEvents.length - 1]; | |
activeScriptEvents.pop(); | |
return; | |
} | |
i--; | |
} | |
} | |
private function execute():Void | |
{ | |
// you can execute multiple commands on an object by separating them with pipe symbol '|' | |
var commandList:Array<String> = commands.split("|"); | |
for (c in commandList) | |
{ | |
var commandData:Array<String> = c.split("_"); | |
var command = StringTools.ltrim(commandData[0].toLowerCase()); | |
switch(command) | |
{ | |
case "e", "event": | |
// verify command format | |
if (commandData.length != 2) | |
throw "LevelLogic ERROR: Improperly formatted Command Sequence '" + c + "'. 'Event' command must be formatted this way: 'event_EventName'"; | |
// dispatch event | |
Events.dispatch(StringTools.rtrim(commandData[1]), object); | |
case "f", "func": | |
// find func | |
var funcName = StringTools.rtrim(commandData[1]); | |
var func = Reflect.field(object, funcName); | |
if (func == null) | |
throw "LevelLogic ERROR: Could not find function '" + funcName + "' in " + Type.getClass(object) + ". Make sure you didn't misspell the function name (and that it exists in the class)."; | |
// find args | |
var finalArgs:Array<Dynamic> = new Array<Dynamic>(); | |
var stringArgs:Array<Dynamic> = commandData.slice(2, commandData.length); | |
// remove any whitespace and parse args for bools | |
for (i in 0 ... stringArgs.length) | |
{ | |
finalArgs.push(formatDataArg(StringTools.rtrim(stringArgs[i]))); | |
//trace("typeof args[i]=" + Type.typeof(finalArgs[finalArgs.length - 1])); | |
} | |
// call func | |
//trace("callFunc: " + funcName + "(" + finalArgs + ")"); | |
Reflect.callMethod(object, func, finalArgs); | |
case "v", "var": | |
// find var (remove whitespace) | |
var field = StringTools.rtrim(commandData[1]); | |
if (Reflect.getProperty(object, field) == null) | |
throw "LevelLogic ERROR: Could not find variable '" + field + "' in " + Type.getClass(object) + ". Make sure you didn't misspell the variable name (and that it exists in the class)."; | |
// verify command format | |
if (commandData.length != 3) | |
throw "LevelLogic ERROR: Improperly formatted Command Sequence '" + c + "'. 'var' command must be formatted this way: 'var_VariableName_Value'"; | |
// set var | |
//trace("setProperty [" + field + "] = " + formatDataArg(commandData[2])); | |
Reflect.setProperty(object, field, formatDataArg(commandData[2])); | |
default: | |
throw "LevelLogic ERROR: Unknown command type '" + command + "'. Valid commands are 'event_' or 'e_', 'var_' or 'v_' and 'func_' or 'f_'"; | |
} | |
} | |
if(eventTrigger == null) | |
dispose(); | |
} | |
public static function formatDataArg(o:String):Dynamic { | |
var lower = o.toLowerCase(); | |
if ( lower == "true" || lower == "yes" ) | |
return true; | |
else if ( lower == "false" || lower == "no" ) | |
return false; | |
else | |
return o; | |
} | |
private function onExecute(Dynamic):Void { | |
this.execute(); | |
} | |
private function dispose(?removeFromManager:Bool):Void | |
{ | |
inactiveScriptEvents.push(this); | |
if (removeFromManager) | |
{ | |
Events.removeObject(object); | |
} | |
} | |
private static var activeScriptEvents:Array<ScriptLogic> = new Array<ScriptLogic>(); | |
private static var inactiveScriptEvents:Array<ScriptLogic> = new Array<ScriptLogic>(); | |
// Do not instanciate events yourself, call ScriptLogic.run() | |
private function new() { } | |
} |
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
package levels.scripting; | |
/** | |
* ... | |
* @author Samuel Batista | |
*/ | |
import flixel.addons.editors.tiled.TiledObject; | |
import flixel.addons.editors.tiled.TiledPropertySet; | |
class ScriptParser | |
{ | |
public static function parseLogic(o:Dynamic, tmxo:TiledObject):Void | |
{ | |
parseProperties(o, tmxo.custom, tmxo); | |
if(tmxo.shared != null) | |
parseProperties(o, tmxo.shared, tmxo); | |
} | |
public static function parseProperties(o:Dynamic, props:TiledPropertySet, tmxo:TiledObject):Void | |
{ | |
var iterator = props.keysIterator(); | |
var key = iterator.next(); | |
while (key != null) | |
{ | |
key = StringTools.trim(key); | |
if ( StringTools.startsWith(key, "_") ) | |
{ | |
#if (!cpp && !neko) | |
if ( !Reflect.hasField(o, key) ) | |
throw "LevelLogic ERROR: Could not find special event '" + key + "' in object [" + tmxo.name + "] of type '" + tmxo.type + "'. Please check " + Type.getClass(o) + " for a list of valid special events."; | |
#end | |
Reflect.setField(o, key, props.get(key)); | |
} | |
else | |
{ | |
var llh:IScriptHandler = cast o; | |
if (llh == null) | |
throw "LevelLogic ERROR: Only ILevelLogicHandler objects can receive custom events. NEED IMMEDIATE PROGRAMMER ASSISTANCE."; | |
var lowerCase = key.toLowerCase(); | |
if ( StringTools.startsWith(lowerCase, "e_") ) | |
ScriptLogic.run(llh, props.get(key), lowerCase.substr("e_".length)); | |
else if ( StringTools.startsWith(lowerCase, "event_") ) | |
ScriptLogic.run(llh, props.get(key), lowerCase.substr("event_".length)); | |
else if(lowerCase != "") | |
throw "LevelLogic ERROR: Unknown command type '" + key + "' in NAME field in object [" + tmxo.name + "] of type '" + tmxo.type + "'. Valid commands are 'event', and special events that start with '_'"; | |
} | |
key = iterator.next(); | |
} | |
} | |
public static function isScriptProperty(prop:String):Bool | |
{ | |
if ( StringTools.startsWith(prop, "_") ) | |
return true; | |
var lowerCase = prop.toLowerCase(); | |
if ( StringTools.startsWith(lowerCase, "e_") ) | |
return true; | |
else if ( StringTools.startsWith(lowerCase, "event_") ) | |
return true; | |
return false; | |
} | |
// Private constructor, static class only | |
private function new() {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment