/**
	Dialogue
	Attach to a non player charachter to provide a message interface.
	
	@author Sven
*/


local dlg_target;
local dlg_name;
local dlg_info;
local dlg_progress;
local dlg_section;   // if set, this string is included in progress callbacks (i.e., func Dlg_[Name]_[Section][Progress]() is called)
local dlg_status;
local dlg_interact;  // default true. can be set to false to deactivate the dialogue
local dlg_attention; // if set, a red attention mark is put above the clonk
local dlg_broadcast; // if set, all non-message (i.e. menu) MessageBox calls are called as MessageBoxBroadcast.
local dlg_last_opt_sel; // may contain array with recently selected options
local user_dialogue; // Dialogue action set by user in editor
local user_dialogue_allow_parallel;
local user_dialogue_progress_mode;

static const DLG_Status_Active = 0; // next interaction calls progress function
static const DLG_Status_Stop   = 1; // dialogue is done and menu closed on next interaction
static const DLG_Status_Remove = 2; // dialogue is removed on next interaction
static const DLG_Status_Wait   = 3; // dialogue is deactivated temporarily to prevent accidental restart after dialogue end

public func IsDialogue() { return true; }

/*-- Dialogue creation --*/

// Sets a new dialogue for a npc.
global func SetDialogue(string name, bool attention)
{
	if (!this)
		return;
	var dialogue = Dialogue->FindByTarget(this);
	if (!dialogue) dialogue = CreateObject(Dialogue);
	dialogue->InitDialogue(name, this, attention);
	
	dialogue->SetObjectLayer(nil);
	dialogue.Plane = this.Plane+1; // for proper placement of the attention symbol

	return dialogue;
}

// Removes the existing dialogue of an object.
global func RemoveDialogue()
{
	var dialogue = Dialogue->FindByTarget(this);
	if (dialogue) return dialogue->RemoveObject();
	return false;
}

// Find dialogue attached to a target (definition call, e.g. var dlg = Dialogue->FindByTarget(foo))
public func FindByTarget(object target)
{
	if (!target) return nil;
	return FindObject(Find_ID(Dialogue), Find_ActionTarget(target));
}

// Find dialogue with a given name.
public func FindByName(string name)
{
	if (!name) return nil;
	return FindObject(Find_ID(Dialogue), Find_Func("HasName", name));
}

public func HasName(string name)
{
	return name == dlg_name;
}

/*-- Dialogue properties --*/

protected func Initialize()
{
	// Dialogue progress to one.
	dlg_progress = 1;
	// Dialogue allows interaction by default.
	dlg_interact = true;
	// Dialogue is active by default.
	dlg_status = DLG_Status_Active;
	return;
}

public func InitDialogue(string name, object target, bool attention)
{
	dlg_target = target;
	dlg_name = name;

	// Attach dialogue object to target.
	if (attention)
	{
		// Attention: Show exclamation mark and glitter effect every five seconds
		AddAttention();
	}
	else
	{
		// No attention: Set invisible action
		SetAction("Dialogue", target);
		RemoveAttention();
	}
	
	// Update dialogue to target.
	UpdateDialogue();
	
	// Effect on targets to remove the dialogue when target dies or is removed
	AddEffect("IntDialogue", target, 1, 0, this);
	
	// Custom dialogue initialization
	if (!Call(Format("~Dlg_%s_Init", dlg_name), dlg_target))
		GameCall(Format("~Dlg_%s_Init", dlg_name), this, dlg_target);
	
	return true;
}

private func FxIntDialogueStop(object target, proplist fx, int reason, bool temp)
{
	// Target removed or died: Remove dialogue
	if (!temp) RemoveObject();
	return FX_OK;
}

public func AddAttention()
{
	// Attention: Show exclamation mark and glitter effect every five seconds
	if (!dlg_attention)
	{
		SetAction("DialogueAttention", dlg_target);
		RemoveTimer("AttentionEffect"); AddTimer("AttentionEffect", 36*5);
		dlg_attention = true;
	}
	return true;
}

public func RemoveAttention()
{
	// No longer show exclamation mark and glitter effects
	if (dlg_attention)
	{
		RemoveTimer("AttentionEffect");
		if (dlg_target) SetAction("Dialogue", dlg_target);
		dlg_attention = false;
	}
	return true;
}

public func SetAttention(bool to_val)
{
	if (to_val) return AddAttention(); else return RemoveAttention();
}

private func AttentionEffect() { return SetAction("DialogueAttentionEffect", dlg_target); }

private func UpdateDialogue()
{
	// Adapt size to target. Updateing to direction does not work well for NPCs that walk around 
	// It's also not very intuitive if the player just walks to the attention symbol anyway.
	var wdt = dlg_target->GetID()->GetDefWidth() + 10;
	var hgt = dlg_target->GetID()->GetDefHeight();
	//var dir = dlg_target->GetDir();
	SetShape(-wdt/2, -hgt/2, wdt, hgt);
	// Transfer position immediately so it's updated in paused mode
	SetPosition(dlg_target->GetX(), dlg_target->GetY());
	// Transfer target name.
	//SetName(Format("$MsgSpeak$", dlg_target->GetName()));
	return;
}

public func SetDialogueInfo()
{

	return;
}

public func SetInteraction(bool allow)
{
	dlg_interact = allow;
	return;
}

public func SetDialogueProgress(int progress, string section, bool add_attention)
{
	dlg_progress = Max(1, progress);
	dlg_section = section;
	if (add_attention) AddAttention();
	return;
}

public func SetDialogueStatus(int status)
{
	dlg_status = status;
	return;
}

public func GetDialogueTarget()
{
	return dlg_target;
}

public func SetDialogueTarget(object target)
{
	// Change dialogue target
	// Do not allow nil
	if (!target) return;
	// Update attachment and ! marker
	var had_attention = dlg_attention;
	RemoveAttention();
	dlg_target = target;
	if (had_attention) AddAttention(); else SetAction("Dialogue", dlg_target);
	// Update shape
	UpdateDialogue();
	return true;
}

// to be called from within dialogue after the last message
public func StopDialogue()
{
	// clear remembered positions
	dlg_last_opt_sel = nil;
	// put on wait for a while; then reenable
	SetDialogueStatus(DLG_Status_Stop);
	return true;
}

/*-- Interaction --*/

// Players can talk to NPC via the interaction bar.
public func IsInteractable() { return dlg_interact; }

// Adapt appearance in the interaction bar.
public func GetInteractionMetaInfo(object clonk)
{
	if (InDialogue(clonk))
		return { Description = Format("$MsgSpeak$", dlg_target->GetName()) , IconName = nil, IconID = Clonk, Selected = true };

	return { Description = Format("$MsgSpeak$", dlg_target->GetName()) , IconName = nil, IconID = Clonk, Selected = false };
}

// Advance dialogue from script
public func CallDialogue(object clonk, progress, string section)
{
	if (GetType(progress)) SetDialogueProgress(progress, section);
	return Interact(clonk);
}

// Called on player interaction.
public func Interact(object clonk)
{
	// Should not happen: not active -> stop interaction
	if (!dlg_interact)
		return true;	
	
	// Currently in a dialogue: abort that dialogue.
	if (InDialogue(clonk))
		clonk->CloseMenu();
		
	// User sequence provided in editor?
	if (user_dialogue)
	{
		UserAction->EvaluateAction(user_dialogue, this, clonk, nil, user_dialogue_progress_mode, user_dialogue_allow_parallel);
		return true;
	}
	
	// No conversation context: abort.
	if (!dlg_name)
		return true;
		
	// Dialogue still waiting? Do nothing then
	// (A sound might be nice here)
	if (dlg_status == DLG_Status_Wait)
	{
		return true;
	}
		
	// Stop dialogue?
	if (dlg_status == DLG_Status_Stop)
	{
		clonk->CloseMenu();
		dlg_status = DLG_Status_Wait;
		ScheduleCall(this, this.SetDialogueStatus, 30, 0, DLG_Status_Active);
		// Do a call on a closed dialogue as well.
		var fn_closed = Format("~Dlg_%s_Closed", dlg_name);
		if (!Call(fn_closed, clonk, dlg_target))
			GameCall(fn_closed, this, clonk, dlg_target);
		return true;
	}
	// Remove dialogue?
	if (dlg_status == DLG_Status_Remove)
	{
		clonk->CloseMenu();
		RemoveObject();
		return true;		
	}
	
	// Remove attention mark on first interaction
	RemoveAttention();
	
	// Have speakers face each other
	SetSpeakerDirs(dlg_target, clonk);

	// Start conversation context.
	// Update dialogue progress first.
	var progress = dlg_progress;
	dlg_progress++;
	// Then call relevant functions.
	// Call generic function first, then progress function
	var fn_generic = Format("~Dlg_%s", dlg_name);
	var fn_progress = Format("~Dlg_%s_%s%d", dlg_name, dlg_section ?? "", progress);
	if (!Call(fn_generic, clonk))
		if (!GameCall(fn_generic, this, clonk, dlg_target))
			if (!Call(fn_progress, clonk))
				GameCall(fn_progress, this, clonk, dlg_target);

	return true;
}

private func InDialogue(object clonk)
{
	return clonk->GetMenu() == Dialogue;
}

public func MessageBoxAll(string message, object talker, bool as_message)
{
	for(var i = 0; i < GetPlayerCount(C4PT_User); ++i)
	{
		var plr = GetPlayerByIndex(i, C4PT_User);
		MessageBox(message, GetCursor(plr), talker, plr, as_message);
	}
}

// Message box as dialog to player with a message copy to all other players
public func MessageBoxBroadcast(string message, object clonk, object talker, array options)
{
	// message copy to other players
	for(var i = 0; i < GetPlayerCount(C4PT_User); ++i)
	{
		var plr = GetPlayerByIndex(i, C4PT_User);
		if (GetCursor(plr) != clonk)
			MessageBox(message, GetCursor(plr), talker, plr, true);
	}
	// main message as dialog box
	return MessageBox(message, clonk, talker, nil, false, options);
}

static MessageBox_last_talker, MessageBox_last_pos;

private func MessageBox(string message, object clonk, object talker, int for_player, bool as_message, array options, proplist menu_target)
{
	// broadcast enabled: message copy to other players
	if (dlg_broadcast && !as_message)
	{
		for(var i = 0; i < GetPlayerCount(C4PT_User); ++i)
		{
			var other_plr = GetPlayerByIndex(i, C4PT_User);
			if (GetCursor(other_plr) != clonk)
				MessageBox(message, GetCursor(other_plr), talker, other_plr, true);
		}
	}
	// Use current NPC as talker if unspecified.
	// On definition call or without talker, just show the message without a source
	if (!talker && this != Dialogue) talker = dlg_target;
	if (talker) message = Format("<c %x>%s:</c> %s", talker->GetColor(), talker->GetName(), message);
	var portrait;
	if (talker) portrait = talker->~GetPortrait();
	
	// A target Clonk is given: Use a menu for this dialogue.
	if (clonk && !as_message)
	{
		var cmd;
		if (this != Dialogue) menu_target = this;
		if (menu_target) cmd = "MenuOK";
		clonk->CreateMenu(Dialogue, menu_target, C4MN_Extra_None, nil, nil, C4MN_Style_Dialog, false, Dialogue);
		var menu_item_offset = 0;
		
		// Add NPC portrait.
		//var portrait = Format("%i", talker->GetID()); //, Dialogue, talker->GetColor(), "1");
		if (talker)
			if (portrait)
				menu_item_offset += clonk->AddMenuItem("", nil, Dialogue, nil, clonk, nil, C4MN_Add_ImgPropListSpec, portrait);
			else
				menu_item_offset += clonk->AddMenuItem("", nil, Dialogue, nil, clonk, nil, C4MN_Add_ImgObject, talker);

		// Add NPC message.
		menu_item_offset += clonk->AddMenuItem(message, nil, nil, nil, clonk, nil, C4MN_Add_ForceNoDesc);
		
		// Add answers.
		if (options) for (var option in options)
		{
			var option_text, option_command;
			if (GetType(option) == C4V_Array)
			{
				// Text+Command given
				option_text = option[0];
				option_command = option[1];
				if (GetChar(option_command) == GetChar("#"))
				{
					// Command given as section name: Remove leading # and call section change
					var ichar=1, ocmd = "", c;
					while (c = GetChar(option_command, ichar++)) ocmd = Format("%s%c", ocmd, c);
					option_command = Format("CallDialogue(Object(%d), 1, \"%s\")", clonk->ObjectNumber(), ocmd);
				}
				else
				{
					// if only a command is given, the standard parameter is just the clonk
					if (!WildcardMatch(option_command, "*(*")) option_command = Format("%s(Object(%d))", option_command, clonk->ObjectNumber());
				}
			}
			else
			{
				// Only text given - command means regular dialogue advance
				option_text = option;
				option_command = cmd;
			}
			clonk->AddMenuItem(option_text, option_command, nil, nil, clonk, nil, C4MN_Add_ForceNoDesc);
		}
		
		// If there are no answers, add a next entry
		if (cmd && !options) clonk->AddMenuItem("$Next$", cmd, nil, nil, clonk, nil, C4MN_Add_ForceNoDesc);
		
		// When reaching the same options set while clicking through a dialogue, pre-select the next item
		if (options)
		{
			if (!menu_target.dlg_last_opt_sel) menu_target.dlg_last_opt_sel = [];
			var found_remembered_option = false, remembered_opts, i = 0;
			for (remembered_opts in menu_target.dlg_last_opt_sel)
			{
				if (DeepEqual(remembered_opts.options, options))
				{
					found_remembered_option = true;
					break;
				}
				++i;
			}
			if (found_remembered_option)
			{
				// We've seen this before. Pre-select the next option
				clonk->SelectMenuItem(++remembered_opts.sel);
			}
			else
			{
				// First encounter of this option set: Select first item.
				menu_target.dlg_last_opt_sel[i] = { options = options[:], sel = menu_item_offset };
			}
		}
		
		// Set menu decoration.
		clonk->SetMenuDecoration(GUI_MenuDeco);
		
		// Set text progress to NPC name.
		if (talker)
		{
			var name = talker->GetName();
			var n_length;
			while (GetChar(name, n_length))
				n_length++;
			clonk->SetMenuTextProgress(n_length + 1);
		}
	}
	else
	{
		// No target is given: Global (player) message
		if (!GetType(for_player)) for_player = NO_OWNER;
		// altenate left/right position as speakers change
		if (talker != MessageBox_last_talker) MessageBox_last_pos = !MessageBox_last_pos;
		MessageBox_last_talker = talker;
		var flags = 0, xoff = 150;
		if (!MessageBox_last_pos)
		{
			flags = MSG_Right;
			xoff *= -1;
			CustomMessage("", nil, for_player); // clear prev msg
		}
		else
		{
			CustomMessage("", nil, for_player, 0,0, nil, nil, nil, MSG_Right);  // clear prev msg
		}
		CustomMessage(message, nil, for_player, xoff,150, nil, GUI_MenuDeco, portrait ?? talker, flags);
	}

	return;
}

public func MenuOK(proplist menu_id, object clonk)
{
	// prevent the menu from closing when pressing MenuOK
	if (dlg_interact)
		Interact(clonk);
}

// Enable or disable message broadcasting to all players for important dialogues
public func SetBroadcast(bool to_val)
{
	dlg_broadcast = to_val;
	return true;
}

public func SetSpeakerDirs(object speaker1, object speaker2)
{
	// Force direction of two clonks to ace each other for dialogue
	if (!speaker1 || !speaker2) return false;
	speaker1->SetDir(speaker1->GetX() < speaker2->GetX());
	speaker2->SetDir(speaker1->GetX() > speaker2->GetX());
	return true;
}

public func SetUserDialogue(new_user_dialogue, new_user_dialogue_progress_mode, new_user_dialogue_allow_parallel)
{
	user_dialogue = new_user_dialogue;
	user_dialogue_progress_mode = new_user_dialogue_progress_mode;
	user_dialogue_allow_parallel = new_user_dialogue_allow_parallel;
	return true;
}

public func SetEnabled(bool to_val)
{
	dlg_interact = to_val;
	return true;
}

/* Scenario saving */

// Scenario saving
func SaveScenarioObject(props)
{
	if (!inherited(props, ...)) return false;
	if (!dlg_target) return false; // don't save dead dialogue object
	// Dialog has its own creation procedure
	props->RemoveCreation();
	props->Remove("Plane"); // updated when setting dialogue
	props->Add(SAVEOBJ_Creation, "%s->SetDialogue(%v,%v)", dlg_target->MakeScenarioSaveName(), dlg_name, !!dlg_attention);
	// Set properties
	if (user_dialogue || user_dialogue_progress_mode || user_dialogue_allow_parallel) props->AddCall("UserDialogue", this, "SetUserDialogue", user_dialogue, Format("%v", user_dialogue_progress_mode), user_dialogue_allow_parallel);
	if (!dlg_interact) props->AddCall("Enabled", this, "SetEnabled", dlg_interact);
	// Force dependency on all contained objects, so dialogue initialization procedure can access them
	var i=0, obj;
	while (obj = dlg_target->Contents(i++)) obj->MakeScenarioSaveName();
	return true;
}




/* Properties */

local ActMap = {
	Dialogue = {
		Prototype = Action,
		Name = "Dialogue",
		Procedure = DFA_ATTACH,
		Delay = 0,
		NextAction = "Dialogue",
	},
	DialogueAttention = {
		Prototype = Action,
		Name = "DialogueAttention",
		Procedure = DFA_ATTACH,
		X = 0, Y = 0, Wdt = 8, Hgt = 24, OffX = 0, OffY = -30,
		Delay = 0,
		NextAction = "DialogueAttention",
	},
	DialogueAttentionEffect = {
		Prototype = Action,
		Name = "DialogueAttentionEffect",
		Procedure = DFA_ATTACH,
		X = 0, Y = 0, Wdt = 8, Hgt = 24, OffX = 0, OffY = -30,
		Delay = 2,
		Length = 4,
		NextAction = "DialogueAttentionREffect",
	},
	DialogueAttentionREffect = {
		Prototype = Action,
		Name = "DialogueAttentionREffect",
		Procedure = DFA_ATTACH,
		X = 0, Y = 0, Wdt = 8, Hgt = 24, OffX = 0, OffY = -30,
		Delay = 2,
		Length = 4,
		Reverse = 1,
		NextAction = "DialogueAttention",
	}
};
local Name = "$Name$";
local Description = "$Description$";


/* EditorProps */

private func EvalObj_NPC(proplist props, proplist context) { if (context.action_object) return context.action_object.dlg_target; }

private func EvalAct_Message(proplist props, proplist context)
{
	//Log("EvalAct_Message %v %v", props, context);
	// Message parameters
	var after_message = props.AfterMessage;
	var speaker = UserAction->EvaluateValue("Object", props.Speaker, context);
	var n_options = 0, any_message = false;
	if (props.Options) n_options = GetLength(props.Options);
	if (n_options)
	{
		var options_msg = CreateArray(n_options), i=0;
		for (var opt in props.Options)
		{
			opt._goto = UserAction->EvaluateValue("Integer", opt.Goto, context); // evaluated in UserAction menu callback
			options_msg[i] = [UserAction->EvaluateString(opt.Text, context), Format("MenuSelectOption(%d)", i)];
			++i;
		}
		props._options_msg = options_msg;
	}
	var text = UserAction->EvaluateString(props.Text, context);
	// Show message to desired players
	for(var plr in UserAction->EvaluateValue("PlayerList", props.TargetPlayers, context))
	{
		Dialogue->MessageBox(text, context.triggering_object, speaker, plr, after_message != "next" && !n_options, props._options_msg, context);
		any_message = true;
	}
	// After-message-option
	if (GetType(after_message) == C4V_Int)
	{
		// Wait for some time
		context.hold = props;
		context->ScheduleCall(context, UserAction.ResumeAction, after_message, 1, context, props);
	}
	else if (after_message == "next" || n_options)
	{
		// Wait for user to press "Next" on dialogue or select an option
		// (If there are options, suspend or stop setting does not make sense)
		if (any_message) context.hold = props;
	}
	else if (after_message == "suspend")
	{
		// Wait for re-initiation of action
		context.suspended = true;
		context.hold = props;
	}
	else if (after_message == "stop")
	{
		// Reset action
		context.suspended = true;
		context.hold = nil;
	}
}

private func EvalAct_SetAttention(proplist props, proplist context)
{
	// User action: Set dialogue attention marker
	var target = UserAction->EvaluateValue("Object", props.Target, context);
	var status = UserAction->EvaluateValue("Boolean", props.Status, context);
	if (!target) return;
	if (target->GetID() != Dialogue)
		if (!(target = Dialogue->FindByTarget(target)))
			return;
	target->~SetAttention(status);
}

private func EvalAct_SetEnabled(proplist props, proplist context)
{
	// User action: Enable/disable dialogue
	var target = UserAction->EvaluateValue("Object", props.Target, context);
	var status = UserAction->EvaluateValue("Boolean", props.Status, context);
	if (!target) return;
	if (target->GetID() != Dialogue)
		if (!(target = Dialogue->FindByTarget(target)))
			return;
	target->~SetEnabled(status);
}

public func IsDialogue() { return true; }

public func Definition(def)
{
	// Actions provided by this definition
	UserAction->AddEvaluator("Action", "$Dialogue$", "$Message$", "$MessageDesc$", "message", [def, def.EvalAct_Message], def.GetDefaultMessageProp, { Type="proplist", Display="{{Speaker}}: \"{{Text}}\" {{Options}}", EditorProps = {
		Speaker = new UserAction.Evaluator.Object { Name = "$Speaker$" },
		Text = new UserAction.Evaluator.String { Name="$Text$", EditorHelp="$TextHelp$" },
		TargetPlayers = new UserAction.Evaluator.PlayerList { Name = "$TargetPlayers$" },
		AfterMessage = { Type="enum", Options = [{ Name="$ContinueAction$" }, { Name="$WaitForNext$", Value="next" }, { Name="$SuspendAction$", Value="suspend" }, { Name="$StopAction$", Value="stop" }, { Name="$WaitTime$", Value=60, Type=C4V_Int, Delegate={ Type="int", Min=1 } }] },
		Options = { Name="$Options$", Type="array", Display=3, DefaultValue = { Text = { Function="string_constant", Value="$DefaultOptionText$" }, Goto = { Function="int_constant", Value=0 } }, Elements = { Type="proplist", Display="({{Goto}}) {{Text}}", EditorProps = {
			Text = new UserAction.Evaluator.String { Name="$Text$", EditorHelp="$OptionTextHelp$" },
			Goto = new UserAction.Evaluator.Integer { Name="$Goto$", EditorHelp="$GotoHelp$" }
			} } }
		} } );
	UserAction->AddEvaluator("Action", "$Dialogue$", "$SetAttention$", "$SetAttentionDesc$", "dialogue_set_attention", [def, def.EvalAct_SetAttention], { Target = { Function="action_object" }, Status = { Function="bool_constant", Value=true } }, { Type="proplist", Display="{{Target}}: {{Status}}", EditorProps = {
		Target = UserAction->GetObjectEvaluator("IsDialogue", "$Dialogue$"),
		Status = new UserAction.Evaluator.Boolean { Name = "$Status$" }
		} } );
	UserAction->AddEvaluator("Action", "$Dialogue$", "$SetEnabled$", "$SetEnabledDesc$", "dialogue_set_enabled", [def, def.EvalAct_SetEnabled], { Target = { Function="action_object" }, Status = { Function="bool_constant", Value=true } }, { Type="proplist", Display="{{Target}}: {{Status}}", EditorProps = {
		Target = UserAction->GetObjectEvaluator("IsDialogue", "$Dialogue$"),
		Status = new UserAction.Evaluator.Boolean { Name = "$Status$" }
		} } );
	UserAction->AddEvaluator("Object", nil, "$NPC$", "$NPCDesc$", "npc", [def, def.EvalObj_NPC]);
	// Clonks can create a dialogue
	if (!Clonk.EditorActions) Clonk.EditorActions = {};
	Clonk.EditorActions.Dialogue = { Name="$Dialogue$", EditorHelp = "$DialogueHelp$", Command="SetDialogue(\"Editor\", true)", Select=true };
	// Dialogue EditorProps
	if (!def.EditorProps) def.EditorProps = {};
	def.EditorProps.dlg_target = { Name="$Target$", EditorHelp="$TargetDesc$", Type="object", Filter="IsClonk", Set="SetDialogueTarget" };
	def.EditorProps.user_dialogue = { Name="$Dialogue$", EditorHelp="$DialogueDesc$", Type="enum", OptionKey="Function", Options = [ { Name="$NoDialogue$" }, new UserAction.EvaluatorDefs.sequence { Group=nil, Value = { Function="sequence", Actions=[] } } ] };
	def.EditorProps.user_dialogue_allow_parallel = UserAction.PropParallel;
	def.EditorProps.user_dialogue_progress_mode = UserAction.PropProgressMode;
	def.EditorProps.dlg_attention = { Name="$Attention$ (!)", EditorHelp="$AttentionDesc$", Type="bool", Set="SetAttention" };
	def.EditorProps.dlg_interact = { Name="$Enabled$", EditorHelp="$EnabledDesc$", Type="bool", Set="SetEnabled" };
}

private func GetDefaultMessageProp(object target_object)
{
	if (target_object && target_object->~IsDialogue())
	{
		// Message prop for dialogue: Default is a NPC message with a "wait for next" option
		return { Function="message", Speaker = { Function="npc" }, TargetPlayers = { Function="triggering_player_list" }, Text = { Function="string_constant", Value="$DefaultDialogueMessage$" }, AfterMessage="next", Options=[] };
	}
	else
	{
		// Message prop for other dialogue: Default is a triggering clonk message with a "wait for next" option
		return { Function="message", Speaker = { Function="triggering_clonk" }, TargetPlayers = { Function="triggering_player_list" }, Text = { Function="string_constant", Value="$DefaultMessage$" }, AfterMessage=60, Options=[] };
	}
}

// Editor object drop happens easily - so move stuff directly to target
public func EditorCollection(obj)
{
	if (dlg_target && obj) obj->Enter(dlg_target);	
}
