#include maps\mp\_utility;
#include common_scripts\utility;

/*script_brushmodel_mover (1 0.25 0.5) ?
script_brushmodel_movers will move and/or rotate to script_struct targets using the move 
parameters defined in script_parameters. Script_structs can target additional script 
structs to create paths and loops. Move parameters can be overridden with 
script_parameters on script structs.
--------------------------------------------------------------------------------

Call level thread maps\mp\_movers::main() before _load::main(); in your level script.

---------------------------------------------------------------------------

Valid parameters that can be included in script_parameters:
move_time: Time to move to target
accel_time: Time spent accelerating. accel_time+decel_time must be less then move_time.
decel_time: Time spent decelerating. accel_time+decel_time must be less then move_time.

move_speed: Move speed of the mover. Overrides move_time.
accel_frac: The faction of the total move time spent accelerating [0.0 to 1.0]
decel_frac: The faction of the total move time spent decelerating [0.0 to 1.0]

delay_time: Time the mover will wait before moving to the next position
wait_time: Time the mover will wait after reaching to it's position

delay_till: Wait till this level notify before moving to the next position
wait_till: Wait till this level notify after reaching position

usable: Sets the mover to usable, used in combination with delay_till_trigger
delay_till_trigger:  Will not move till target till mover receives "trigger" notify

solid: Set the brush to solid or or not

script_params: Allows parameters to be defined/added through script. Use
maps\mp\_movers::script_mover_add_parameters("example_set", "move_speed=5;accel_frac=.1")
in the level script to create a named set of parameters. The named parameters can be 
included by setting script_params=name_of_set

--------------------------------------------------------------------------------

Advanced Stuff:
-To set the "angles" of a mover have the mover target a script_struct with angles
set and script_noteworthy=origin. This helps when rotating to angles on targeted structs
-Script_models can be linked to the mover by having the mover target them and setting
script_notewothy=link
-Movers can target other movers. A mover will send "trigger" to its targeted movers when
it started moving. Targeted movers should have delay_till_trigger.
-Script_structs can also target movers and will trigger all targeted movers when a mover
arrives at the script_struct
-Move parameters can be randomized with this syntax: <param_name>=<min_value>,<max_value>;
-Move parameters can be set back to default with this syntax: <param_name>=default
-If a mover targets a trigger with script_noteworthy=use_trigger_link or 
script_noteworthy=use_trigger that trigger will trigger the mover when triggered. use_trigger_link
will link the trigger to the mover and use_trigger will not.
-Entities targeted by the mover or mover goals can modified. The targeted entity needs script_noteworthy
set to <action_name>_on_<notify>. Example script_noteworthy=show_on_arrive or script_noteworthy=hide_on_arrive;connectpaths_on_arrive
	Notifies:
		"arrive": Fires action when mover arrives at a goal
		"depart": Fires action when mover leaves a position
	Actions:
		"connectpaths": Calls connect paths on the entity. Entity needs to be DYNAMIC_PATH
		"disconnectpaths": Calls disconnect paths on the entity. Entity needs to be DYNAMIC_PATH
		"solid": Makes the entity solid (makes entity notSolid on map start)
		"notsolid": Makes the entity NotSolid
		"delete": Deletes the entity
		"hide": Hides the entity
		"show": Shows the entity (hides entity on map start)
		"triggerhide": Hides the entity by placing it under the world
		"triggershow": Shows the entity by moving it into the world (ent is moved under the world at startup)
		 

-----------------------------------------------------------------

default:"script_parameters" "move_time=5;accel_time=0;decel_time=0"
*/

/*script_model_mover (1 0.25 0.5) (-16 -16 -16) (16 16 16)
script_model_movers will move and/or rotate to script_struct targets using the move 
parameters defined in script_parameters. Script_structs can target additional script 
structs to create paths and loops. Move parameters can be overridden with 
script_parameters on script structs.
--------------------------------------------------------------------------------

Call level thread maps\mp\_movers::main() before _load::main(); in your level script.

---------------------------------------------------------------------------

Valid parameters that can be included in script_parameters:
move_time: Time to move to target
accel_time: Time spent accelerating. accel_time+decel_time must be less then move_time.
decel_time: Time spent decelerating. accel_time+decel_time must be less then move_time.

move_speed: Move speed of the mover. Overrides move_time.
accel_frac: The faction of the total move time spent accelerating [0.0 to 1.0]
decel_frac: The faction of the total move time spent decelerating [0.0 to 1.0]

delay_time: Time the mover will wait before moving to the next position
wait_time: Time the mover will wait after reaching to it's position

delay_till: Wait till this level notify before moving to the next position
wait_till: Wait till this level notify after reaching position

usable: Sets the mover to usable, used in combination with delay_till_trigger
delay_till_trigger:  Will not move till target till mover receives "trigger" notify

script_params: Allows parameters to be defined/added through script. Use
maps\mp\_movers::script_mover_add_parameters("example_set", "move_speed=5;accel_frac=.1")
in the level script to create a named set of parameters. The named parameters can be 
included by setting script_params=name_of_set

--------------------------------------------------------------------------------

Advanced Stuff:
-To set the "angles" of a mover have the mover target a script_struct with angles
set and script_noteworthy=origin. This helps when rotating to angles on targeted structs
-Script_models can be linked to the mover by having the mover target them and setting
script_notewothy=link
-Movers can target other movers. A mover will send "trigger" to its targeted movers when
it started moving. Targeted movers should have delay_till_trigger.
-Script_structs can also target movers and will trigger all targeted movers when a mover
arrives at the script_struct
-Move parameters can be randomized with this syntax: <param_name>=<min_value>,<max_value>;
-If a mover targets a trigger with script_noteworthy=use_trigger_link or 
script_noteworthy=use_trigger that trigger will trigger the mover when triggered. use_trigger_link
will link the trigger to the mover and use_trigger will not.
-Entities targeted by the mover or mover goals can modified. The targeted entity needs script_noteworthy
set to <action_name>_on_<notify>. Example script_noteworthy=show_on_arrive or script_noteworthy=hide_on_arrive;connectpaths_on_arrive

	Notifies:
		"arrive": Fires action when mover arrives at a goal
		"depart": Fires action when mover leaves a position
	Actions:
		"connectpaths": Calls connect paths on the entity. Entity needs to be DYNAMIC_PATH
		"disconnectpaths": Calls disconnect paths on the entity. Entity needs to be DYNAMIC_PATH
		"solid": Makes the entity solid (makes entity notSolid on map start)
		"notsolid": Makes the entity NotSolid
		"delete": Deletes the entity
		"hide": Hides the entity
		"show": Shows the entity (hides entity on map start)
		"triggerhide": Hides the entity by placing it under the world
		"triggershow": Shows the entity by moving it into the world (ent is moved under the world at startup)
		
-----------------------------------------------------------------

default:"script_parameters" "move_time=5;accel_time=0;decel_time=0"
*/

SCRIPT_MOVER_NOTIFY_NAME = "script_mover_anim";

///////
// Movers
///////
main()
{
	if( getDvar( "r_reflectionProbeGenerate" ) == "1" )
		return;
	
	level.script_mover_defaults = [];
	level.script_mover_defaults["move_time"] 	= 5;
	level.script_mover_defaults["accel_time"] 	= 0;
	level.script_mover_defaults["decel_time"] 	= 0;
	level.script_mover_defaults["wait_time"] 	= 0;
	level.script_mover_defaults["delay_time"] 	= 0;
	level.script_mover_defaults["usable"] 		= 0;
	level.script_mover_defaults["hintstring"] = "activate";
	
	script_mover_add_hintString("activate", &"MP_ACTIVATE_MOVER");
	
	script_mover_add_parameters("none", "");
	
	level.script_mover_named_goals = [];
	
	level.script_mover_animations = [];
	
	waitframe();
	
	movers = [];
	classnames = script_mover_classnames();
	foreach(class in classnames)
	{
		movers = array_combine(movers, GetEntArray(class, "classname"));
	}
	array_thread(movers, ::script_mover_init);
}

script_mover_classnames()
{
	return ["script_model_mover", "script_brushmodel_mover"];
}

script_mover_is_script_mover()
{
	if(IsDefined(self.script_mover))
		return self.script_mover;
	
	classnames = script_mover_classnames();
	foreach(class in classnames)
	{
		if(self.classname == class)
		{
			self.script_mover = true;
			return true;
		}
	}
	
	return false;
}

script_mover_add_hintString(name, hintString)
{
	if(!IsDefined(level.script_mover_hintstrings))	
		level.script_mover_hintstrings = [];
	
	level.script_mover_hintstrings[name] = hintString;
}
	
script_mover_add_parameters(name, parameters)
{
	if(!IsDefined(level.script_mover_parameters))
		level.script_mover_parameters = [];
	level.script_mover_parameters[name] = parameters;
}

script_mover_add_animation(scene_name, animation_string, animation_ref, optional_type)
{
	if(!IsDefined(level.script_mover_animations))
		level.script_mover_animations = [];
	
	if(!IsDefined(optional_type))
		optional_type = "default";
	
	if(!IsDefined(level.script_mover_animations[scene_name]))
		level.script_mover_animations[scene_name] = [];
	
	anim_info = spawnStruct();
	anim_info.animname = animation_string;
	anim_info.animref = animation_ref;
	
	level.script_mover_animations[scene_name][optional_type] = anim_info;
}

script_mover_init()
{
	self.script_mover = true;
	self.moving = false;
	
	self.origin_ent = self;
	
	self.use_triggers = [];
	self.linked_ents = [];

	structs = [];
	if(IsDefined(self.target))
		structs = GetStructArray(self.target, "targetname");
	
	
	foreach(target in structs)
	{
		if(!IsDefined(target.script_noteworthy))
			continue;
		
		switch(target.script_noteworthy)
		{
			case "origin":
				if(!IsDefined(target.angles))
					target.angles = (0,0,0);
				self.origin_ent = spawn("script_model", target.origin);
				self.origin_ent.angles = target.angles;
				self.origin_ent SetModel("tag_origin");
				self.origin_ent LinkTo(self);
				break;
			case "scripted_node":
			case "scene_node":
				if(!IsDefined(target.angles))
					target.angles = (0,0,0);
				self.scripted_node = target;
				break;
			default:
				break;
		}
	}
	
	ents = [];
	if(IsDefined(self.target))
		ents = GetEntArray(self.target, "targetname");
	foreach(target in ents)
	{
		if(!IsDefined(target.script_noteworthy))
			continue;
		
		toks = StrTok(target.script_noteworthy, ";");
		foreach(tok in toks)
		{
			switch(tok)
			{
				case "use_trigger_link":
					target EnableLinkTo();
					target LinkTo(self);
					//Fallthrough
				case "use_trigger":
					target script_mover_parse_targets();
					self thread script_mover_use_trigger(target);
					self.use_triggers[self.use_triggers.size] = target;
					break;
				case "link":
					target LinkTo(self);
					self.linked_ents[self.linked_ents.size] = target;
					break;
				default:
					break;
			}
		}
	}

	self thread script_mover_parse_targets();
	self thread script_mover_init_move_parameters();
	self thread script_mover_save_default_move_parameters();
	self thread script_mover_set_defaults();
	self thread script_mover_apply_move_parameters(self);
	self thread script_mover_reset_init();
	
	script_mover_start();
	
	foreach(trigger in self.use_triggers)
	{
		self script_mover_set_usable(trigger, true);
	}
	
	self.script_mover_init = true;
	self notify("script_mover_init");
}

script_mover_start()
{
	if(self script_mover_is_animated())
	{
		self thread script_mover_animate();
	}
	else
	{
		self thread script_mover_move_to_target();
	}
}

script_mover_reset_init()
{
	self.mover_reset_origin = self.origin;
	self.mover_reset_angles = self.angles;
}

script_mover_reset(unused)
{
	self notify("mover_reset");
	if(self script_mover_is_animated())
		self ScriptModelClearAnim();
	self.origin = self.mover_reset_origin;
	self.angles = self.mover_reset_angles;
	self notify("new_path");
	
	waitframe();
	
	self script_mover_start();
}

script_mover_use_trigger(trigger)
{
	self endon("death");
	while(1)
	{
		trigger waittill("trigger");
		
		if(trigger.goals.size>0)
		{
			self notify("new_path");
			self thread script_mover_move_to_target(trigger);
		}
		else
		{
			self notify("trigger");
		}
	}
}

script_mover_move_to_named_goal(goal_name)
{
	if(IsDefined(level.script_mover_named_goals[goal_name]))
	{
		self notify("new_path");
		self.goals = level.script_mover_named_goals[goal_name];
		self thread script_mover_move_to_target();
	}
}

anglesClamp180(angles)
{
	return (AngleClamp180(angles[0]),AngleClamp180(angles[1]),AngleClamp180(angles[2]));
}

script_mover_parse_targets()
{
	if(IsDefined(self.parsed) && self.parsed)
		return;
	
	self.parsed = true;
	
	self.goals = [];
	self.movers = [];
	
	structs = [];
	ents = [];
	if(IsDefined(self.target))
	{
		structs = GetStructArray(self.target, "targetname");
		ents = GetEntArray(self.target, "targetname");
	}
	
	for(i=0;i<structs.size;i++)
	{
		target = structs[i];
		if(!IsDefined(target.script_noteworthy))
			target.script_noteworthy = "goal"; //Assume goal this is a mover goal pos if not set
		
		switch(target.script_noteworthy)
		{
			case "ignore":
				if(IsDefined(target.target))
				{
					add_structs = GetStructArray(target.target, "targetname");
					foreach(add in add_structs)
					{
						structs[structs.size] = add;
					}
				}
				break;
			case "goal":
				target script_mover_init_move_parameters();
				target script_mover_parse_targets();
				self.goals[self.goals.size] = target;
				if(IsDefined(target.params["name"]))
				{
					if(!IsDefined(level.script_mover_named_goals[target.params["name"]]))
						level.script_mover_named_goals[target.params["name"]] = [];
					
					size = level.script_mover_named_goals[target.params["name"]].size;
					level.script_mover_named_goals[target.params["name"]][size] = target;
				}
				break;
			default:
				break;
		}
	}
	
	foreach(ent in ents)
	{
		if(ent script_mover_is_script_mover())
		{
			self.movers[self.movers.size] = ent;
		}
		
		self thread script_mover_parse_ent( ent );
	}
}

script_mover_parse_ent( ent )
{
	if(!IsDefined(ent.script_noteworthy))
		return;
	
	if( ent script_mover_is_script_mover() && !IsDefined(ent.script_mover_init) )
	{
		ent waittill("script_mover_init");
	}
	
	notes = StrTok(ent.script_noteworthy, ";");
	foreach(note in notes)
	{
		toks = StrTok(note, "_");
		if(toks.size<3 || toks[1] != "on")
			continue;
		
		action = ToLower(toks[0]);
		action_notify = toks[2]; //arrive or depart
		for(i=3; i<toks.size; i++)
		{
			action_notify = action_notify + "_" + toks[i];
		}

		switch(action)
		{
			case "connectpaths":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_connectpaths, ::script_mover_disconnectpaths);
				break;
			case "disconnectpaths":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_disconnectpaths, ::script_mover_connectpaths);
				break;
			case "solid":
				ent NotSolid();
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_solid, ::script_mover_notsolid);
				break;
			case "notsolid":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_notsolid, ::script_mover_solid);
				break;
			case "delete":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_delete);
				break;
			case "hide":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_hide, ::script_mover_show);
				break;
			case "show":
				ent Hide();
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_show, ::script_mover_hide);
				break;
			case "triggerhide":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_trigger_off, ::script_mover_trigger_on);
				break;
			case "triggershow":
				ent trigger_off();
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_trigger_on, ::script_mover_trigger_off);
				break;
			case "trigger":
				self thread script_mover_func_on_notify(ent, action_notify, ::script_mover_trigger, ::script_mover_reset);
				break;
			default:
				break;
		}
	}
}

script_mover_trigger_off(mover)
{
	self DontInterpolate();
	self trigger_off();
}

script_mover_trigger_on(mover)
{
	self DontInterpolate();
	self trigger_on();
}

script_mover_notify(mover, note)
{
	mover notify(note);
}

script_mover_levelnotify(mover, note)
{
	level notify(note);
}

script_mover_connectpaths(mover)
{
	self ConnectPaths();
}

script_mover_disconnectpaths(mover)
{
	self DisconnectPaths(mover);
}

script_mover_solid(mover)
{
	self solid();
}

script_mover_notsolid(mover)
{
	self NotSolid();
}

script_mover_delete(mover)
{
	self delete();
}

script_mover_hide(mover)
{
	self Hide();
}

script_mover_show(mover)
{
	self show();
}

script_mover_trigger(mover)
{
	self notify("trigger");
}

script_mover_func_on_notify(ent, note, func, reset_func)
{
	self endon("death");
	ent endon("death");

	while(1)
	{
		self waittill(note, mover);
		
		ent [[func]](mover);

		if(IsDefined(reset_func) && IsDefined(mover))
		{
			mover script_mover_watch_for_reset(ent, reset_func);
		}
		else
		{
			break;
		}
	}
}

//childthread of script_mover_move_to_target
script_mover_update_paths()
{
	dynamic_path_ents = [];
	if(self script_mover_is_dynamic_path())
		dynamic_path_ents[dynamic_path_ents.size] = self;
	
	foreach(linked_ent in self.linked_ents)
	{
		if(linked_ent script_mover_is_dynamic_path())
			dynamic_path_ents[dynamic_path_ents.size] = linked_ent;
	}
	
	if ( dynamic_path_ents.size == 0 )
	{
		return;
	}
	
	
	while(1)
	{
		foreach(ent in dynamic_path_ents)
		{
			ent script_mover_disconnectpaths();
		}
		
		self waittill("move_start");
		
		foreach(ent in dynamic_path_ents)
		{
			ent script_mover_connectpaths();
		}
		
		self waittill("move_end");
	}
}

script_mover_animate()
{
	self childthread script_mover_update_paths();
	
	scene_name = self.params["animation"];
	
	if(IsDefined(level.script_mover_animations[scene_name]["idle"]))
		script_mover_play_animation(level.script_mover_animations[scene_name]["idle"], false);
	
	self script_mover_delay();
	
	self notify("move_start");
	self notify("start", self);
	default_anim_info = level.script_mover_animations[scene_name]["default"];
	if(IsDefined(default_anim_info))
	{
		script_mover_play_animation(default_anim_info, true);
		self waittill("end");
	}
	self notify("move_end");
}

script_mover_play_animation(anim_info, process_notetracks)
{
	self notify("play_animation");
	if( process_notetracks )
		self thread script_mover_handle_notetracks();
	
	if(IsDefined(self.scripted_node))
	{
		self ScriptModelPlayAnimDeltaMotionFromPos(anim_info.animname, self.scripted_node.origin, self.scripted_node.angles, SCRIPT_MOVER_NOTIFY_NAME);
	}
	else
	{
		self ScriptModelPlayAnimDeltaMotion(anim_info.animname, SCRIPT_MOVER_NOTIFY_NAME);
	}
}

script_mover_handle_notetracks()
{
	self endon("play_animation");
	self endon("mover_reset");
	
	while(1)
	{
		self waittill(SCRIPT_MOVER_NOTIFY_NAME, note);
		self notify(note, self);
	}
}

script_mover_delay()
{
	if(IsDefined(self.params["delay_till"]))
		level waittill(self.params["delay_till"]);
		
	if(IsDefined(self.params["delay_till_trigger"]) && self.params["delay_till_trigger"])
		self waittill("trigger");
		
	if(self.params["delay_time"]>0)
		wait self.params["delay_time"];
}

script_mover_move_to_target(current)
{
	self endon("death");
	self endon("new_path");
	
	self childthread script_mover_update_paths();
	
	if(!IsDefined(current))
		current = self;
	
	while(current.goals.size!=0)
	{
		goal = random(current.goals);
		
		mover = self;
		
		mover script_mover_apply_move_parameters( goal );
		
		mover script_mover_delay();
		
		move_time = mover.params["move_time"];
		accel_time = mover.params["accel_time"];
		decel_time = mover.params["decel_time"];
		
		is_moveTo = false;
		is_rotateTo = false;
		
		trans = TransformMove(goal.origin, goal.angles, self.origin_ent.origin, self.origin_ent.angles, self.origin, self.angles);
		if(mover.origin != goal.origin)
		{
			if(IsDefined(mover.params["move_speed"]))
			{
				dist = distance(mover.origin, goal.origin);
				move_time = dist/mover.params["move_speed"];
			}
			
			if(IsDefined(mover.params["accel_frac"]))
			{
				accel_time = mover.params["accel_frac"] * move_time;
			}
			
			if(IsDefined(mover.params["decel_frac"]))
			{
				decel_time = mover.params["decel_frac"] * move_time;
			}
			
			if(move_time<=0)
			{
				mover DontInterpolate();
				mover.origin = trans["origin"];
			}
			else
			{
				mover MoveTo(trans["origin"], move_time, accel_time, decel_time );
			}
			
			is_moveTo = true;
		}
		
		if(anglesClamp180(trans["angles"]) != anglesClamp180(mover.angles))
		{
			if(move_time<=0)
			{
				mover DontInterpolate();
				mover.angles = trans["angles"];
			}
			else
			{
				mover RotateTo(trans["angles"], move_time, accel_time, decel_time);
			}
			
			is_rotateTo = true;
		}
		
		//Trigger movers targeted my the mover
		foreach ( targeted_mover in mover.movers )
		{
			targeted_mover notify("trigger");
			self script_mover_watch_for_reset(targeted_mover, ::script_mover_reset);
		}
		
		mover notify("move_start");
		current notify("depart", mover);
		
		if( IsDefined(mover.params["name"]) )
		{
			notifyStr = "mover_depart_" + mover.params["name"];
			mover notify(notifyStr);
			level notify(notifyStr, mover);
		}
		
		mover script_mover_allow_usable(false);
		
		if(move_time<=0)
		{
			//move instantly
		}
		else if(is_moveTo)
		{
			mover waittill("movedone");
		}
		else if(is_rotateTo)
		{
			mover waittill("rotatedone");
		}
		else
		{
			wait move_time;
		}
		
		mover notify("move_end");
		goal notify("arrive", mover);
		
		if( IsDefined(mover.params["name"]) )
		{
			notifyStr = "mover_arrive_" + mover.params["name"];
			mover notify(notifyStr);
			level notify(notifyStr, mover);
		}
		
		if(IsDefined(mover.params["solid"]))
		{
			if(mover.params["solid"])
				mover solid();
			else
				mover notsolid();
		}
		
		//Trigger movers targeted my the goal
		foreach ( targeted_mover in goal.movers )
		{
			targeted_mover notify("trigger");
			self script_mover_watch_for_reset(targeted_mover, ::script_mover_reset);
		}
		
		if(IsDefined(mover.params["wait_till"]))
			level waittill(mover.params["wait_till"]);
			
		if(mover.params["wait_time"]>0)
			wait mover.params["wait_time"];
		
		mover script_mover_allow_usable(true);
		
		current = goal;
	}
}

script_mover_watch_for_reset(ent, func)
{
	self thread script_mover_func_on_notify(ent, "mover_reset", func);
}

script_mover_init_move_parameters()
{
	self.params = [];
	
	if(!IsDefined(self.angles))
		self.angles = (0,0,0);
	
	self.angles = anglesClamp180(self.angles);
	
	script_mover_parse_move_parameters(self.script_parameters);
}

script_mover_parse_move_parameters(parameters)
{
	if(!IsDefined(parameters))
		parameters = "";
	
	params = StrTok(parameters, ";");
	foreach(param in params)
	{
		toks = strtok(param,"=");
		if(toks.size!=2)
			continue;
		
		if(toks[1]=="undefined" || toks[1]=="default")
		{
			//If default exist it will be set in script_mover_set_defaults
			self.params[toks[0]] = "<undefined>";
			continue;
		}
		
		switch(toks[0])
		{
			case "move_speed":
			case "accel_frac":
			case "decel_frac":
			case "move_time":
			case "accel_time":
			case "decel_time":
			case "wait_time":
			case "delay_time":
				self.params[toks[0]] = script_mover_parse_range(toks[1]);
				break;
			case "wait_till":
			case "delay_till":
			case "name":
			case "hintstring":
			case "animation":
				self.params[toks[0]] = toks[1];
				break;
			case "usable":
			case "delay_till_trigger":
			case "solid":
				self.params[toks[0]] = int(toks[1]);
				break;
			case "script_params":
				param_name = toks[1];
				additional_params = level.script_mover_parameters[param_name];
				if(IsDefined(additional_params))
				{
					script_mover_parse_move_parameters(additional_params);
				}
				break;
			default:
				break;
		}
	}
}

script_mover_parse_range(str)
{
	value = 0;
	
	toks = strtok(str,",");
	if(toks.size==1)
	{
		value = float(toks[0]);
	}
	else if(toks.size==2)
	{
		minValue = float(toks[0]);
		maxValue = float(toks[1]);
		
		if(minValue>=maxValue)
		{
			value = minValue;
		}
		else
		{
			value = RandomFloatRange(minValue,maxValue);
		}
	}
	
	return value;
}

//to = self
script_mover_apply_move_parameters(from)
{
	foreach(key, value in from.params)
	{
		script_mover_set_param(key, value);
	}
	
	script_mover_set_defaults();
}

script_mover_set_param(param_name, value)
{
	if(!IsDefined(param_name))
		return;
	
	if(param_name=="usable" && IsDefined(value))
	{
		self script_mover_set_usable(self, value);
	}
	
	if(IsDefined(value) && isString(value) && value=="<undefined>")
	{
		value = undefined;
	}
	
	self.params[param_name] = value;
}

script_mover_allow_usable(usable)
{
	if(self.params["usable"])
	{
		self script_mover_set_usable(self, usable);
	}
	foreach(trigger in self.use_triggers)
	{
		self script_mover_set_usable(trigger, usable);
	}
}

script_mover_set_usable(use_ent,usable)
{
	if(usable)
	{
		use_ent MakeUsable();
		use_ent SetCursorHint("HINT_ACTIVATE");
		use_ent SetHintString(level.script_mover_hintstrings[self.params["hintstring"]]);
	}
	else
	{
		use_ent MakeUnusable();
	}
}

script_mover_save_default_move_parameters()
{
	self.params_default = [];
	
	foreach(key, value in self.params)
	{
		self.params_default[key] = value;
	}
}

//Values in script_mover_defaults are assumed to always be defined
//this checks that they are.
script_mover_set_defaults()
{
	if(IsDefined(self.params_default))
	{
		foreach(key, value in self.params_default)
		{
			if(!IsDefined(self.params[key]))
				script_mover_set_param(key, value);
		}
	}	
	
	foreach(key, value in level.script_mover_defaults)
	{
		if(!IsDefined(self.params[key]))
			script_mover_set_param(key, value);
	}
}

script_mover_is_dynamic_path()
{
	return (isDefined(self.spawnflags) && (self.spawnflags&1));
}

script_mover_is_animated()
{
	return IsDefined(self.params["animation"]);
}

init()
{
	level thread script_mover_connect_watch();
	level thread script_mover_agent_spawn_watch();
}

script_mover_connect_watch()
{
	while ( 1 )
	{
		level waittill( "connected", player );
		player thread player_unresolved_collision_watch();
	}
}

script_mover_agent_spawn_watch()
{
	while ( 1 )
	{
		level waittill( "spawned_agent", agent );
		agent thread player_unresolved_collision_watch();
	}
}

player_unresolved_collision_watch()
{
	self endon("disconnect");
	if ( IsAgent( self ) )
	{
		self endon("death");
	}
		
	self.unresolved_collision_count = 0;
	while(1)
	{
		self waittill( "unresolved_collision", mover );
		self.unresolved_collision_count++;
		self thread clear_unresolved_collision_count_next_frame();
		
		unresolved_collision_notify_min = 3;
		if(IsDefined(mover) && IsDefined(mover.unresolved_collision_notify_min))
			unresolved_collision_notify_min = mover.unresolved_collision_notify_min;
		
		if(self.unresolved_collision_count>=unresolved_collision_notify_min)
		{
			if(IsDefined(mover))
			{
				if(IsDefined( mover.unresolved_collision_func ))
				{
					mover [[mover.unresolved_collision_func]](self);
				}
				else if(IsDefined(mover.unresolved_collision_kill) && mover.unresolved_collision_kill)
				{
					mover unresolved_collision_owner_damage(self);
				}
				else
				{
					mover unresolved_collision_nearest_node(self);
				}
			}
			else
			{
				unresolved_collision_nearest_node(self);
			}
			self.unresolved_collision_count = 0;
		}
	}
}

clear_unresolved_collision_count_next_frame()
{
	self endon("unresolved_collision");
	waitframe();
	if(IsDefined(self))
		self.unresolved_collision_count = 0;
}

unresolved_collision_owner_damage(player)
{
	inflictor = self;
	
	if(!IsDefined(inflictor.owner))
	{
		player maps\mp\_movers::mover_suicide();
		return;
	}
	
	canInflictorOwnerDamagePlayer = false;
	
	if( level.teambased )
	{
		if( IsDefined( inflictor.owner.team ) && inflictor.owner.team != player.team )
			canInflictorOwnerDamagePlayer = true;
	}
	else
	{
		if(player != inflictor.owner)
			canInflictorOwnerDamagePlayer = true;
	}
	
	if(!canInflictorOwnerDamagePlayer)
	{
		player maps\mp\_movers::mover_suicide();
		return;
	}
	
	damage_ammount = 1000; //Kill
	if(IsDefined(inflictor.unresolved_collision_damage))
		damage_ammount = inflictor.unresolved_collision_damage;
	
	
	player DoDamage( damage_ammount, inflictor.origin, inflictor.owner, inflictor, "MOD_CRUSH" );
}

unresolved_collision_nearest_node(player, bAllowSuicide)
{
	nodes = self.unresolved_collision_nodes;
	if(IsDefined(nodes))
	{
		nodes = SortByDistance(nodes, player.origin);
	}
	else
	{
		nodes = GetNodesInRadius( player.origin, 300, 0, 200 ); //Sorted version sorts by 2d dist, we need 3d
		nodes = SortByDistance(nodes, player.origin);
	}
		
	avoid_telefrag_offset = (0,0,-100);
	
	player CancelMantle();
	player DontInterpolate();
	player SetOrigin(player.origin + avoid_telefrag_offset);
	
	for(i=0; i<nodes.size; i++)
	{
		check_node = nodes[i];
//		if( check_node NodeIsDisconnected())
//			continue;
		
		org = check_node.origin;
		if(!CanSpawn(org))
			continue;
		
		if(PositionWouldTelefrag(org))
			continue;
		
		if(player GetStance() == "prone")
			player Setstance("crouch");
		
		player SetOrigin( org );
		return;
	}
	
	player SetOrigin(player.origin - avoid_telefrag_offset);
	
	if( !IsDefined(bAllowSuicide) )
		bAllowSuicide = true;
	
	// no nodes found, fall back on suicide
	if( bAllowSuicide )
	{
		player mover_suicide();
	}
}

unresolved_collision_void(player)
{
	
}

mover_suicide()
{
	if( IsDefined(level.isHorde) && !IsAgent( self ) )
	{
		return;
	}
	
	self _suicide();
}

//*******************************************************************
//																	*
//																	*
//*******************************************************************

player_pushed_kill( min_mph ) //self == mover
{
	self endon("death");
	self endon("stop_player_pushed_kill");
	
	while( 1 )
	{
		self waittill( "player_pushed", player, platformMPH );
		if ( isPlayer( player ) || isAgent( player ) )
		{
			mph = Length(platformMPH);
			
			if ( mph >= min_mph )
			{
				self unresolved_collision_owner_damage(player);
			}
		}
	}
}

stop_player_pushed_kill()
{
	self notify("stop_player_pushed_kill");
}

//*******************************************************************
//																	*
//																	*
//*******************************************************************
notify_moving_platform_invalid()
{
	children = self GetLinkedChildren( false );
	if ( !IsDefined( children ) )
	{
		return;
	}

	foreach ( child in children )
	{
		if( IsDefined(child.no_moving_platfrom_unlink) && child.no_moving_platfrom_unlink )
			continue;
		
		child unlink();
		child notify( "invalid_parent", self );
	}
}

process_moving_platform_death( data, platform )
{
	if(IsDefined(platform) && IsDefined(platform.no_moving_platfrom_death) && platform.no_moving_platfrom_death)
		return; 
	
	if ( IsDefined( data.playDeathFx ) )
	{
		PlayFX( getfx( "airdrop_crate_destroy" ), self.origin );
	}

	if ( IsDefined( data.deathOverrideCallback ) )
	{
		self thread [[data.deathOverrideCallback]]( data );
	}
	else 
	{
		self delete();
	}
}

handle_moving_platform_touch( data )
{
	while ( 1 )
	{
		self waittill( "touching_platform", platform );

		if ( IsDefined( data.touchingPlatformValid ) && !self [[data.touchingPlatformValid]]( platform ) )
		{
			continue;
		}
		
		if ( IsDefined( data.validateAccurateTouching ) && data.validateAccurateTouching )
		{
			if ( !self IsTouching( platform ) )
			{
				wait( 0.05 );
				continue;
			}
		}

		self thread process_moving_platform_death( data, platform );
		break;
	}
}

handle_moving_platform_invalid( data )
{
	self waittill( "invalid_parent", platform );
	if ( IsDefined( data.invalidParentOverrideCallback ) )
	{
		self thread [[data.invalidParentOverrideCallback]]( data );
	}
	else 
	{
		self thread process_moving_platform_death( data, platform );
	}
}

// data: Spawned struct passed into handler.
// data.linkParent: Parent to link this entity to.
// data.endonString: String that will kill this handler when notified to entity.
// data.playDeathFx: Flag to play a generic death effect.
// data.deathOverrideCallback: Callback that will call instead of the automatic delete (ex. ims_override_death( platform ) ).
// data.invalidParentOverrideCallback: Callback that will call instead of the automatic invalid parent handler.
// data.validateAccurateTouching: If true, performs an extra IsTouching test on the touching platform before processing. 
handle_moving_platforms( data )
{
	self notify( "handle_moving_platforms" );
	self endon( "handle_moving_platforms" );

	level endon( "game_ended" );
	self endon( "death" );
	self endon( "stop_handling_moving_platforms" );

	if ( !IsDefined( data ) )
	{
		data = SpawnStruct();
	}

	// Special endon string.
	if ( IsDefined( data.endonString ) )
	{
		self endon( data.endonString );
	}

	if ( IsDefined( data.linkParent ) )
	{
		self linkto( data.linkParent );
	}

	childthread handle_moving_platform_touch( data );
	childthread handle_moving_platform_invalid( data );
}

// Force kill this process without using data.endonString.
stop_handling_moving_platforms()
{
	self notify( "stop_handling_moving_platforms" );
}

moving_platform_empty_func( data )
{
	// Don't add anything.
}

//*******************************************************************
//																	*
//																	*
//*******************************************************************
