Skip to content

Instantly share code, notes, and snippets.

@gamedevsam
Created December 16, 2014 16:18
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 gamedevsam/ae19b5ebdf4175385987 to your computer and use it in GitHub Desktop.
Save gamedevsam/ae19b5ebdf4175385987 to your computer and use it in GitHub Desktop.
sams-addons level scripting
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;
}
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() { }
}
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