Add UserAction scripted editor property delegates

UserActions will be used to click small dialogues and scripts directly in the editor.
qteditor
Sven Eberhardt 2016-07-06 01:03:02 -04:00
parent 990b6ea8f1
commit 5c6da07c51
4 changed files with 315 additions and 0 deletions

View File

@ -0,0 +1,7 @@
[DefCore]
id=UserAction
Version=8,0
Category=C4D_StaticBack
Width=8
Height=8
Offset=-4,-4

View File

@ -0,0 +1,262 @@
/* User action execution handler */
// Handles actions set in editor e.g. for dialogues, switches, etc.
// An object is sometimes needed to show a menu or start a timer, so this definition is created whenever a user action is run
local Name = "UserAction";
local Plane=0;
// Base classes for EditorProps using actions
local Evaluator;
// EditorProps for generic user action callbacks
local Prop, PropProgressMode, PropParallel;
// Base props for action execution conditions
local ActionEvaluation ;
// Proplist containing callback function. Indexed by option names.
local EvaluatorCallbacks;
// If this action is paused and will be resumed by a callback or by re-execution of the action, this property is set to the props of the holding action
local hold;
// Set to true if action is on hold but waiting for re-execution
local suspended;
// Return value if a value-providing evaluator is held
local hold_result;
// Call this definition early (but after EditorBase) to allow EditorProp initialization
local DefinitionPriority=99;
// Proplist holding progress in each sequence
local sequence_progress, sequence_had_goto;
static UserAction_SequenceIDs;
// Set to innermost sequence (for goto)
local last_sequence;
public func Initialize()
{
sequence_progress = {};
sequence_had_goto = {};
return true;
}
public func SaveScenarioObject(props) { return false; } // temp. don't save.
func Definition(def)
{
// Typed evaluator base definitions
Evaluator = {};
Evaluator.Action = { Name="$UserAction$", Type="enum", OptionKey="Option", Options = [ { Name="$None$" } ] };
Evaluator.Object = { Name="$UserObject$", Type="enum", OptionKey="Option", Options = [ { Name="$None$" } ] };
Evaluator.Player = { Name="$UserPlayer$", Type="enum", OptionKey="Option", Options = [ { Name="$Noone$" } ] };
Evaluator.PlayerList = { Name="$UserPlayerList$", Type="enum", OptionKey="Option", Options = [ { Name="$Noone$" } ] };
// Action evaluators
EvaluatorCallbacks = {};
AddEvaluator("Action", "$Sequence$", "$Sequence$", "sequence", [def, def.EvalAct_Sequence], { Actions=[] }, { Type="proplist", DescendPath="Actions", Display="{{Actions}}", Elements = {
EditorProp_Actions = { Name="$Actions$", Type="array", Elements=Evaluator.Action },
} } );
AddEvaluator("Action", "$Sequence$", "$Goto$", "goto", [def, def.EvalAct_Goto], { Index=0 }, { Type="proplist", Display="{{Index}}", Elements = {
EditorProp_Index = { Name="$Index$", Type="int", Min=0 }
} } );
AddEvaluator("Action", "$Sequence$", "$StopSequence$", "stop_sequence", [def, def.EvalAct_StopSequence]);
AddEvaluator("Action", "$Sequence$", "$SuspendSequence$", "suspend_sequence", [def, def.EvalAct_SuspendSequence]);
AddEvaluator("Action", "$Sequence$", "$Wait$", "wait", [def, def.EvalAct_Wait], { Time=60 }, { Type="proplist", Display="{{Time}}", Elements = {
EditorProp_Time = { Name="$Time$", Type="int", Min=1 }
} } );
// Object evaluators
AddEvaluator("Object", nil, "$ActionObject$", "action_object", [def, def.EvalObj_ActionObject]);
AddEvaluator("Object", nil, "$TriggerObject$", "triggering_object", [def, def.EvalObj_TriggeringObject]);
// Player evaluators
AddEvaluator("Player", nil, "$TriggeringPlayer$", "triggering_player", [def, def.EvalPlr_Trigger]);
AddEvaluator("PlayerList", nil, "$TriggeringPlayer$", "triggering_player_list", [def, def.EvalPlrList_Single, def.EvalPlr_Trigger]);
AddEvaluator("PlayerList", nil, "$AllPlayers$", "all_players", [def, def.EvalPlrList_All]);
// User action editor props
Prop = Evaluator.Action;
PropProgressMode = { Name="$UserActionProgressMode$", Type="enum", Options = [ { Name="$Session$", Value="session" }, { Name="$Player$", Value="player" }, { Name="$Global$" } ] };
PropParallel = { Name="$ParallelAction$", Type="bool" };
return true;
}
public func AddEvaluator(string eval_type, string group, string name, string identifier, callback_data, default_val, proplist delegate)
{
if (!default_val) default_val = {};
var default_get;
if (GetType(default_val) == C4V_Function)
{
default_get = default_val;
default_val = Call(default_get);
}
if (delegate && delegate.Elements)
{
delegate.Elements.Name = name;
}
default_val.Option = identifier;
var action_def = { Name=name, Group=group, Value=default_val, OptionKey="Option", Delegate=delegate, Get=default_get };
Evaluator[eval_type].Options[GetLength(Evaluator[eval_type].Options)] = action_def;
EvaluatorCallbacks[identifier] = callback_data;
return action_def;
}
public func EvaluateValue(string eval_type, proplist props, proplist context)
{
//Log("EvaluateValue %v %v %v", eval_type, props, context);
if (!props) return nil;
// Finish any hold-action
if (context.hold == props)
{
context.hold = nil;
return context.hold_result;
}
// Not on hold: Perform evaluation
var cb = EvaluatorCallbacks[props.Option];
return cb[0]->Call(cb[1], props, context, cb[2]);
}
public func EvaluateAction(proplist props, object action_object, object triggering_object, string progress_mode, bool allow_parallel)
{
// Determine context
var context;
if (!progress_mode)
{
if (!(context = props.context))
props.context = context = CreateObject(UserAction);
}
else if (progress_mode == "player")
{
if (!props.contexts) props.contexts = [];
var plr_id;
if (action_object) plr_id = GetPlayerID(action_object->GetOwner());
if (!(context = props.contexts[plr_id]))
props.contexts[plr_id] = context = CreateObject(UserAction);
}
else // if (progress_mode == "session")
{
// Temporary context
context = CreateObject(UserAction);
context.temp = true;
}
// Prevent duplicate parallel execution
if (!allow_parallel && (context.hold && !context.suspended)) return nil;
// Init context settings
context.action_object = action_object;
context.triggering_object = triggering_object;
context.root_action = props;
context.suspended = false;
// Execute action
EvaluateValue("Action", props, context);
FinishAction(context);
}
private func ResumeAction(proplist context, proplist resume_props)
{
//Log("ResumeAction %v %v", context, resume_props);
// Resume only if on hold for the same entry
if (context.hold != resume_props) return;
// Resume action
EvaluateValue("Action", context.root_action, context);
// Cleanup action object (unless it ran into another hold)
FinishAction(context);
}
private func FinishAction(proplist context)
{
// Cleanup action object (unless it's kept around for callbacks or to store sequence progress)
// Note that context.root_action.contexts is checked to kill session-contexts that try to suspend
// There would be no way to resume so just kill the context
if (!context.hold || context.temp) context->RemoveObject();
}
private func EvalObj_ActionObject(proplist props, proplist context) { return context.action_object; }
private func EvalObj_TriggeringObject(proplist props, proplist context) { return context.triggering_object; }
private func EvalPlr_Trigger(proplist props, proplist context) { if (context.triggering_object) return context.triggering_object->GetOwner(); }
private func EvalPlrList_Single(proplist props, proplist context, fn) { return [Call(fn, props, context)]; }
private func EvalPlrList_All(proplist props, proplist context, fn)
{
var n = GetPlayerCount(C4PT_User);
var result = CreateArray(n);
for (var i=0; i<n; ++i) result[i] = GetPlayerByIndex(i);
return result;
}
private func EvalAct_Sequence(proplist props, proplist context)
{
// Sequence execution: Iterate over actions until one action puts the context on hold
var n = GetLength(props.Actions), sid = props.sequence_id;
if (!sid) sid = props.sequence_id = Format("%d", ++UserAction_SequenceIDs);
for (var progress = context.sequence_progress[props.sequence_id] ?? 0; progress < n; ++progress)
{
//Log("Sequence progress exec %v %v", progress, context.hold);
// goto preparations
context.sequence_had_goto[sid] = false;
context.last_sequence = props;
// Evaluate next sequence step
EvaluateValue("Action", props.Actions[progress], context);
if (context.hold || context.suspended)
{
// Execution on hold (i.e. wait action). Stop execution for now
if (!context.hold) progress = 0; // No hold specified: Stop with sequence reset
context.sequence_progress[sid] = progress;
return;
}
// Apply jump in the sequence
if (context.sequence_had_goto[sid]) progress = context.sequence_progress[sid] - 1;
}
// Sequence finished
context.last_sequence = nil;
// Reset for next execution.
context.sequence_progress[props.sequence_id] = 0;
}
private func EvalAct_Goto(proplist props, proplist context)
{
// Apply goto by jumping in most recently executed sequence
if (context.last_sequence)
{
context.sequence_progress[context.last_sequence.sequence_id] = props.Index;
context.sequence_had_goto[context.last_sequence.sequence_id] = true;
}
}
private func EvalAct_StopSequence(proplist props, proplist context)
{
// Stop: Suspend without hold props, which causes all sequences to reset
context.hold = nil;
context.suspended = true;
}
private func EvalAct_SuspendSequence(proplist props, proplist context)
{
// Suspend: Remember hold position and stop action execution
context.hold = props;
context.suspended = true;
}
private func EvalAct_Wait(proplist props, proplist context)
{
// Wait for specified number of frames
context.hold = props;
ScheduleCall(context, UserAction.ResumeAction, props.Time, 1, context, props);
}
public func MenuOK(proplist menu_id, object clonk)
{
// Pressed 'Next' in dialogue: Resume in user action
UserAction->ResumeAction(this, this.hold);
}
public func MenuSelectOption(int index)
{
// Selected an option in dialogue: Resume at specified position in innermost sequence
if (!hold || !hold.Options) return;
var opt = this.hold.Options[index];
if (opt && last_sequence)
{
sequence_progress[last_sequence.sequence_id] = opt.Goto;
hold = nil;
}
UserAction->ResumeAction(this, hold);
}

View File

@ -0,0 +1,23 @@
UserAction=Aktion
UserObject=Objekt
UserPlayer=Spieler
UserPlayerList=Spieler
None=Nichts
Noone=Niemand
AllPlayers=Alle Spieler
Sequence=Sequenz
Wait=Warten
Time=Zeit (Frames)
ActionObject=Aktionsobjekt (this)
TriggerObject=Ausloesender Clonk
TriggeringPlayer=Ausloesender Spieler
UserActionProgressMode=Fortschritt speichern...
Session=Nicht speichern
Player=Pro Spieler
Global=Global
ParallelAction=Parallele Mehrfachausfuehrung
Goto=Gehe zu Sequenzindex
StopSequence=Sequenz beenden
SuspendSequence=Sequenz anhalten
Index=Index
Actions=Aktionen

View File

@ -0,0 +1,23 @@
UserAction=Action
UserObject=Object
UserPlayer=Player
UserPlayerList=Players
None=Nothing
Noone=Noone
AllPlayers=All players
Sequence=Sequence
Wait=Wait
Time=Time (Frames)
ActionObject=Action object(this)
TriggerObject=Triggering clonk
TriggeringPlayer=Triggering player
UserActionProgressMode=Save progress...
Session=Don't save
Player=Per player
Global=Globally
ParallelAction=Parallel multi-execution
Goto=Go to sequence index
StopSequence=Quit sequence
SuspendSequence=Suspend sequence
Index=Index
Actions=Actions