2016-07-06 05:03:02 +00:00
/* 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 ;
2016-07-21 04:22:53 +00:00
/* UserAction definition */
2016-07-06 05:03:02 +00:00
// 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 ;
2016-07-10 18:11:23 +00:00
// Proplist containing option definitions. Indexed by option names.
local EvaluatorDefs ;
2016-07-06 05:03:02 +00:00
// Call this definition early (but after EditorBase) to allow EditorProp initialization
local DefinitionPriority = 99 ;
func Definition ( def )
{
// Typed evaluator base definitions
Evaluator = { } ;
2016-07-21 04:47:43 +00:00
Evaluator . Action = { Name = " $UserAction$ " , Type = " enum " , OptionKey = " Function " , Options = [ { Name = " $None$ " } ] } ;
Evaluator . Object = { Name = " $UserObject$ " , Type = " enum " , OptionKey = " Function " , Options = [ { Name = " $None$ " } ] } ;
Evaluator . Player = { Name = " $UserPlayer$ " , Type = " enum " , OptionKey = " Function " , Options = [ { Name = " $Noone$ " } ] } ;
Evaluator . PlayerList = { Name = " $UserPlayerList$ " , Type = " enum " , OptionKey = " Function " , Options = [ { Name = " $Noone$ " } ] } ;
Evaluator . Boolean = { Name = " $UserBoolean$ " , Type = " enum " , OptionKey = " Function " , Options = [ ] } ;
Evaluator . Condition = { Name = " $UserCondition$ " , Type = " enum " , OptionKey = " Function " , Options = [ { Name = " $None$ " } ] } ;
2016-07-06 05:03:02 +00:00
// Action evaluators
EvaluatorCallbacks = { } ;
2016-07-10 18:11:23 +00:00
EvaluatorDefs = { } ;
2016-07-13 21:18:08 +00:00
AddEvaluator ( " Action " , " $Sequence$ " , " $Sequence$ " , " sequence " , [ def , def . EvalAct_Sequence ] , { Actions = [ ] } , { Type = " proplist " , DescendPath = " Actions " , Display = " {{Actions}} " , EditorProps = {
Actions = { Name = " $Actions$ " , Type = " array " , Elements = Evaluator . Action } ,
2016-07-06 05:03:02 +00:00
} } ) ;
2016-07-13 21:18:08 +00:00
AddEvaluator ( " Action " , " $Sequence$ " , " $Goto$ " , " goto " , [ def , def . EvalAct_Goto ] , { Index = 0 } , { Type = " proplist " , Display = " {{Index}} " , EditorProps = {
Index = { Name = " $Index$ " , Type = " int " , Min = 0 }
2016-07-06 05:03:02 +00:00
} } ) ;
AddEvaluator ( " Action " , " $Sequence$ " , " $StopSequence$ " , " stop_sequence " , [ def , def . EvalAct_StopSequence ] ) ;
AddEvaluator ( " Action " , " $Sequence$ " , " $SuspendSequence$ " , " suspend_sequence " , [ def , def . EvalAct_SuspendSequence ] ) ;
2016-07-13 21:18:08 +00:00
AddEvaluator ( " Action " , " $Sequence$ " , " $Wait$ " , " wait " , [ def , def . EvalAct_Wait ] , { Time = 60 } , { Type = " proplist " , Display = " {{Time}} " , EditorProps = {
Time = { Name = " $Time$ " , Type = " int " , Min = 1 }
2016-07-06 05:03:02 +00:00
} } ) ;
// Object evaluators
AddEvaluator ( " Object " , nil , " $ActionObject$ " , " action_object " , [ def , def . EvalObj_ActionObject ] ) ;
2016-07-24 14:50:39 +00:00
AddEvaluator ( " Object " , nil , " $TriggerClonk$ " , " triggering_clonk " , [ def , def . EvalObj_TriggeringClonk ] ) ;
2016-07-06 05:03:02 +00:00
AddEvaluator ( " Object " , nil , " $TriggerObject$ " , " triggering_object " , [ def , def . EvalObj_TriggeringObject ] ) ;
2016-07-13 06:17:12 +00:00
AddEvaluator ( " Object " , nil , " $ConstantObject$ " , " object_constant " , [ def , def . EvalConstant ] , { Value = nil } , { Type = " object " , Name = " $Value$ " } ) ;
2016-07-06 05:03:02 +00:00
// 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 ] ) ;
2016-07-13 06:17:12 +00:00
// Boolean (condition) evaluators
AddEvaluator ( " Boolean " , nil , " $Constant$ " , " bool_constant " , [ def , def . EvalConstant ] , { Value = true } , { Type = " bool " , Name = " $Value$ " } ) ;
2016-07-06 05:03:02 +00:00
// 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 ;
}
2016-07-13 06:17:12 +00:00
public func GetObjectEvaluator ( filter_def , name )
{
// Create copy of the Evaluator.Object delegate, but with the object_constant proplist replaced by a version with filter_def
var object_options = Evaluator . Object . Options [ : ] ;
var const_option = new EvaluatorDefs [ " object_constant " ] { } ;
const_option . Delegate = new const_option . Delegate { Filter = filter_def } ;
object_options [ const_option . OptionIndex ] = const_option ;
return new Evaluator . Object { Name = name , Options = object_options } ;
}
2016-07-06 05:03:02 +00:00
public func AddEvaluator ( string eval_type , string group , string name , string identifier , callback_data , default_val , proplist delegate )
{
2016-07-13 06:17:12 +00:00
// Add an evaluator for one of the data types. Evaluators allow users to write small action sequences and scripts in the editor using dropdown lists.
// eval_type: Return type of the evaluator (Action, Object, Boolean, Player, etc. as defined in UserAction.Evaluator)
// group [optional] Localized name of sub-group for larger enums (i.e. actions)
// name: Localized name as it appears in the dropdown list of evaluators
// identifier: Unique identifier that is used to store this action in savegames and look up the action def. Identifiers must be unique among all data types.
// callback_data: Array of [definition, definition.Function, parameter (optional)]. Function to be called when this evaluator is called
// default_val [optional]: Default value to be set when this evaluator is selected. Must be a proplist. Should contain values for all properties in the delegate
// delegate: Parameters for this evaluator
2016-07-06 05:03:02 +00:00
if ( ! default_val ) default_val = { } ;
var default_get ;
if ( GetType ( default_val ) = = C4V_Function )
{
default_get = default_val ;
default_val = Call ( default_get ) ;
}
2016-07-21 04:47:43 +00:00
default_val . Function = identifier ;
var action_def = { Name = name , Group = group , Value = default_val , OptionKey = " Function " , Delegate = delegate , Get = default_get } , n ;
2016-07-13 06:17:12 +00:00
if ( delegate )
2016-07-06 05:03:02 +00:00
{
2016-07-13 21:18:08 +00:00
if ( delegate . EditorProps )
2016-07-13 06:17:12 +00:00
{
// Proplist of array parameter for this evaluator: Descend path title should be name
2016-07-13 21:18:08 +00:00
delegate . Name = name ;
2016-07-13 06:17:12 +00:00
}
else
{
// Any other parameter type: Store in value
action_def . ValueKey = " Value " ;
}
2016-07-06 05:03:02 +00:00
}
2016-07-13 06:17:12 +00:00
Evaluator [ eval_type ] . Options [ n = GetLength ( Evaluator [ eval_type ] . Options ) ] = action_def ;
action_def . OptionIndex = n ;
2016-07-06 05:03:02 +00:00
EvaluatorCallbacks [ identifier ] = callback_data ;
2016-07-10 18:11:23 +00:00
EvaluatorDefs [ identifier ] = action_def ;
2016-07-21 04:22:53 +00:00
// Copy most boolean props to condition prop
if ( eval_type = = " Boolean " & & identifier ! = " bool_constant " )
AddEvaluator ( " Condition " , group , name , identifier , callback_data , default_val , delegate ) ;
2016-07-06 05:03:02 +00:00
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
2016-07-21 04:47:43 +00:00
var cb = EvaluatorCallbacks [ props . Function ] ;
2016-07-06 05:03:02 +00:00
return cb [ 0 ] - > Call ( cb [ 1 ] , props , context , cb [ 2 ] ) ;
}
2016-07-21 04:22:53 +00:00
public func EvaluateAction ( proplist props , object action_object , object triggering_object , int triggering_player , string progress_mode , bool allow_parallel , finish_callback )
2016-07-06 05:03:02 +00:00
{
2016-07-24 14:50:39 +00:00
// No action
if ( ! props ) if ( finish_callback ) return action_object - > Call ( finish_callback ) ; else return ;
2016-07-06 05:03:02 +00:00
// Determine context
var context ;
if ( ! progress_mode )
{
2016-07-21 04:22:53 +00:00
if ( ! ( context = props . _context ) )
props . _context = context = CreateObject ( UserAction ) ;
2016-07-06 05:03:02 +00:00
}
else if ( progress_mode = = " player " )
{
2016-07-21 04:22:53 +00:00
if ( ! props . _contexts ) props . _contexts = [ ] ;
2016-07-06 05:03:02 +00:00
var plr_id ;
if ( action_object ) plr_id = GetPlayerID ( action_object - > GetOwner ( ) ) ;
2016-07-21 04:22:53 +00:00
if ( ! ( context = props . _contexts [ plr_id ] ) )
props . _contexts [ plr_id ] = context = CreateObject ( UserAction ) ;
2016-07-06 05:03:02 +00:00
}
else // if (progress_mode == "session")
{
// Temporary context
context = CreateObject ( UserAction ) ;
context . temp = true ;
}
// Prevent duplicate parallel execution
2016-07-22 05:51:29 +00:00
if ( ! allow_parallel & & ( context . hold & & ! context . suspended ) ) return false ;
2016-07-06 05:03:02 +00:00
// Init context settings
2016-07-21 04:22:53 +00:00
context - > InitContext ( action_object , triggering_player , triggering_object , props ) ;
2016-07-06 05:03:02 +00:00
// Execute action
EvaluateValue ( " Action " , props , context ) ;
FinishAction ( context ) ;
2016-07-22 05:51:29 +00:00
return true ;
2016-07-06 05:03:02 +00:00
}
2016-07-21 04:22:53 +00:00
public func EvaluateCondition ( proplist props , object action_object , object triggering_object , int triggering_player )
{
// Build temp context
var context = CreateObject ( UserAction ) ;
context . temp = true ;
// Init context settings
context - > InitContext ( action_object , triggering_player , triggering_object , props ) ;
// Execute condition evaluator
var result = EvaluateValue ( " Condition " , props , context ) ;
// Cleanup
if ( context ) context - > RemoveObject ( ) ;
// Done
return result ;
}
2016-07-06 05:03:02 +00:00
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
2016-07-21 04:22:53 +00:00
if ( ! context . hold | | context . temp )
{
if ( context . action_object & & context . finish_callback ) context . action_object - > Call ( context . finish_callback , context ) ;
context - > RemoveObject ( ) ;
}
2016-07-06 05:03:02 +00:00
}
2016-07-13 06:17:12 +00:00
private func EvalConstant ( proplist props , proplist context ) { return props . Value ; }
2016-07-06 05:03:02 +00:00
private func EvalObj_ActionObject ( proplist props , proplist context ) { return context . action_object ; }
private func EvalObj_TriggeringObject ( proplist props , proplist context ) { return context . triggering_object ; }
2016-07-24 14:50:39 +00:00
private func EvalObj_TriggeringClonk ( proplist props , proplist context ) { return context . triggering_clonk ; }
2016-07-21 04:22:53 +00:00
private func EvalPlr_Trigger ( proplist props , proplist context ) { return context . triggering_player ; }
2016-07-06 05:03:02 +00:00
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
2016-07-13 21:39:36 +00:00
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 [ sid ] ? ? 0 ; progress < n ; + + progress )
2016-07-06 05:03:02 +00:00
{
//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.
2016-07-13 21:39:36 +00:00
context . sequence_progress [ sid ] = 0 ;
2016-07-06 05:03:02 +00:00
}
private func EvalAct_Goto ( proplist props , proplist context )
{
// Apply goto by jumping in most recently executed sequence
if ( context . last_sequence )
{
2016-07-24 14:50:39 +00:00
context . sequence_progress [ context . last_sequence . _sequence_id ] = props . Index ;
context . sequence_had_goto [ context . last_sequence . _sequence_id ] = true ;
2016-07-06 05:03:02 +00:00
}
}
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 ) ;
}
2016-07-21 04:22:53 +00:00
/* Context instance */
// Proplist holding progress in each sequence
local sequence_progress , sequence_had_goto ;
static UserAction_SequenceIDs ;
// Set to innermost sequence (for goto)
local last_sequence ;
// 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 ;
public func Initialize ( )
{
sequence_progress = { } ;
sequence_had_goto = { } ;
return true ;
}
public func InitContext ( object action_object , int triggering_player , object triggering_object , proplist props )
{
2016-07-24 14:50:39 +00:00
// Determine triggering player+objects
var triggering_clonk ;
// Triggering player unknown? Try fallback to the controller of the triggering object
if ( ! GetType ( triggering_player ) & & triggering_object )
2016-07-21 04:22:53 +00:00
{
2016-07-24 14:50:39 +00:00
triggering_player = triggering_object - > GetController ( ) ;
2016-07-21 04:22:53 +00:00
}
2016-07-24 14:50:39 +00:00
// Triggering clonk is the selected clonk of the triggering player
if ( GetType ( triggering_player ) )
2016-07-21 04:22:53 +00:00
{
2016-07-24 14:50:39 +00:00
triggering_clonk = GetCursor ( triggering_player ) ; ;
if ( ! triggering_clonk ) triggering_clonk = GetCrew ( triggering_player ) ;
2016-07-21 04:22:53 +00:00
}
2016-07-24 14:50:39 +00:00
// Triggering object: Fallback to triggering player clonk
if ( ! triggering_object ) triggering_object = triggering_clonk ;
2016-07-21 04:22:53 +00:00
// Init context settings
this . action_object = action_object ;
this . triggering_object = triggering_object ;
2016-07-24 14:50:39 +00:00
this . triggering_clonk = triggering_clonk ;
2016-07-21 04:22:53 +00:00
this . triggering_player = triggering_player ;
this . root_action = props ;
this . suspended = false ;
return true ;
}
2016-07-06 05:03:02 +00:00
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 )
{
2016-07-24 14:50:39 +00:00
sequence_progress [ last_sequence . _sequence_id ] = opt . Goto ;
2016-07-06 05:03:02 +00:00
hold = nil ;
}
UserAction - > ResumeAction ( this , hold ) ;
}
2016-07-21 04:22:53 +00:00
public func SaveScenarioObject ( props ) { return false ; } // temp. don't save.