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

init()
{
	level.friendlyDogModel = "german_shepherd";
	level.enemyDogModel = "german_shepherd_black";

	precacheModel(level.friendlyDogModel);
	precacheModel(level.enemyDogModel);
	precacheItem("dog_bite_mp");
	precacheShellshock("dog_bite");

	level.maxDogsAttackingPerPlayer = 2;
	level.spawnTimeWaitMin = 2;
	level.spawnTimeWaitMax = 5;
		
	level.no_dogs = false;
		
	init_node_arrays();
	
	if ( level.no_pathnodes )
		level.no_dogs = true;

	regenTime = 5;
	level.dogHealth_RegularRegenDelay = regenTime * 1000;
	level.dogHealthRegenDisabled = (level.dogHealth_RegularRegenDelay <= 0);
	
	dog_dvar_update();
	thread dog_dvar_updater();
	thread dog_usage_init();
	
	thread init_all_preexisting_dogs();

	/#
		level thread devgui_dog_think();
	#/

//	create_dogs();
}

initKillstreak()
{
	// register the dog hardpoint
	if ( maps\mp\gametypes\_tweakables::getTweakableValue( "killstreak", "allowdogs" ) )
	{
		maps\mp\gametypes\_hardpoints::registerKillstreak("dogs_mp", "dogs_mp", "killstreak_dogs","dogs_used", ::useKillstreakDogs, true);
		maps\mp\gametypes\_hardpoints::registerKillstreakStrings("dogs_mp", &"KILLSTREAK_EARNED_DOGS", &"KILLSTREAK_DOGS_NOT_AVAILABLE", &"KILLSTREAK_DOGS_INBOUND" );
		maps\mp\gametypes\_hardpoints::registerKillstreakDialog("dogs_mp", "mpl_killstreak_dogs", "kls_dogs_used", "","kls_dogs_enemy", "", "kls_dogs_ready");
		maps\mp\gametypes\_hardpoints::registerKillstreakDevDvar("dogs_mp", "scr_givedogs");
	
		maps\mp\gametypes\_hardpoints::registerKillstreakAltWeapon("dogs_mp", "dog_bite_mp" );
	}
}

useKillstreakDogs(hardpointType)
{
	if ( self maps\mp\_killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false )
		return false;
	
	self notify( "called_in_the_dogs" );
	level notify( "called_in_the_dogs" );
	team = self.team;
	otherTeam = level.otherTeam[team];
		
	if ( level.teambased )
	{
		thread maps\mp\gametypes\_battlechatter_mp::onKillstreakUsed( "dogs", otherTeam );
		//for ( i = 0; i < level.players.size; i++ )
		//{
		//	player = level.players[i];
		//	playerteam = player.team;
		//	if ( isdefined( playerteam ) )
		//	{
		//		if ( playerteam == team )
		//		{
		//			player iprintln( &"MP_DOGS_INBOUND", self.name );
		//			player iprintln( "\n");
		//		}
		//	}
		//}
	}
	
	self maps\mp\gametypes\_hardpoints::playKillstreakStartDialog( "dogs_mp", team, true);
	self maps\mp\gametypes\_persistence::statAdd( "DOGS_USED", 1, false );
	level.globalKillstreaksCalled++;
	self maps\mp\gametypes\_globallogic_score::incItemStatByReference( "killstreak_dogs", 1, "used" );

	if ( maps\mp\gametypes\_tweakables::getTweakableValue( "team", "allowHardpointStreakAfterDeath" ) )
	{
		ownerDeathCount = self.deathCount;
	}
	else
	{
		ownerDeathCount = self.pers["hardPointItemDeathCount" + hardpointType];
	}
	if ( self maps\mp\_killstreakrules::killstreakStart("dogs_mp", self.team) == false )
		return false;

	self thread maps\mp\_dogs::dog_manager_spawn_dogs( team, otherTeam, ownerDeathCount );
	self thread playerDisconnectWatcher();

	return true;
}

playerDisconnectWatcher()
{
	level endon( "dogs done" );
	level endon( "dogs leaving" );

	self waittill( "disconnect" );

	level notify( "dogs leaving" );
}

pick_random_nodes( from, count )
{
	to = [];
	
	if ( from.size < count )
	{
		to = from;
	}
	else
	{
		for ( i = 0; i < count; i++ )
		{
			to[i] = from[randomInt(from.size)];
		}
	}
	
	return to;
}

init_node_arrays()
{
	nodes = getallnodes();

	pathnodes = [];
	
	for ( i = 0; i < nodes.size; i++ )
	{
		// anything with a scriptworthy is automatically a non-patrol node
		if ( isdefined(nodes[i].script_noteworthy) /* || nodes[i].script_noteworthy != "" */)
			continue;
			
		if ( isdefined(nodes[i].targetname) && nodes[i].targetname == "traverse" )
			continue;
			
		pathnodes[pathnodes.size] = nodes[i]; 
	} 

	if ( pathnodes.size == 0 )
	{
		level.no_pathnodes = true;
	}
	else
	{
		level.no_pathnodes = false;
	}
	
	level.patrolnodes = [];
	
	level.patrolnodes = pick_random_nodes( pathnodes, 200 ); 
	
	level.dogspawnnodes = [];
	level.dogspawnnodes = getnodearray( "spawn", "script_noteworthy");
	
	if ( level.dogspawnnodes.size == 0 )
	{
/#
		println("DOG PATHING:  Could not find spawn nodes");
#/		
		// pick a random set of spawn nodes so we do not tax the spawn logic to much
		level.dogspawnnodes = pick_random_nodes( pathnodes, 20 );
	}
	
	level.dogexitnodes = [];
	level.dogexitnodes = getnodearray( "exit", "script_noteworthy");
	
	if ( level.dogexitnodes.size == 0 )
	{
/#
		println("DOG PATHING:  Could not find exit nodes");
#/
		// pick a random set of spawn nodes so we do not tax the spawn logic to much
		level.dogexitnodes = pick_random_nodes( pathnodes, 20 );
	}
}

dog_dvar_update()
{
	level.dog_time = dog_get_dvar_int("scr_dog_time", "60" );
	level.dog_health = dog_get_dvar_int("scr_dog_health", "100" );
	level.dog_count = dog_get_dvar_int("scr_dog_count", "8" );
	level.dog_count_max_at_once = dog_get_dvar_int("scr_dog_max_at_once", "4" );
	
	if ( level.dog_count < level.dog_count_max_at_once )
	{
		level.dog_count_max_at_once = level.dog_count;
	}
		
	level.dog_debug = dog_get_dvar_int("debug_dogs", "0" );
	level.dog_debug_sound = dog_get_dvar_int("debug_dog_sound", "0" );
	level.dog_debug_anims = dog_get_dvar_int("debug_dog_anims", "0" );
	level.dog_debug_anims_ent = dog_get_dvar_int("debug_dog_anims_ent", "0" );
	level.dog_debug_turns = dog_get_dvar_int("debug_dog_turns", "0" );
	level.dog_debug_orient = dog_get_dvar_int("debug_dog_orient", "0" );
	level.dog_debug_usage = dog_get_dvar_int("debug_dog_usage", "1" );
}

dog_dvar_updater()
{
	dogs_in_the_bsp = count_preexisting_dogs();
	while(1)
	{
		dog_dvar_update();
		
		// 16 is max allowed by engine
		if ( level.dog_count + dogs_in_the_bsp > 16 )
		{
			level.dog_count = 16 - dogs_in_the_bsp;
		}
		wait (1);
	}
}

count_preexisting_dogs()
{
		dogs = getentarray( "actor_enemy_dog_mp", "classname" );
		
	 	alive_count = 0;
	 	for ( i = 0; i < dogs.size; i ++ )
	 	{
	 		if ( !isdefined(dogs[i]) )
	 			continue;
	 			
	 		if ( !isai(dogs[i]) )
	 			continue;
	 			
	 		alive_count++;
	 	}	
	 	
	 	return alive_count;
}

init_all_preexisting_dogs()
{
	array_thread( getentarray( "actor_enemy_dog_mp", "classname" ), ::preexisting_init_dog );
}

preexisting_init_dog()
{
	self init_dog();
}

dog_set_model()
{
	self setModel(level.friendlyDogModel);
	self setEnemyModel(level.enemyDogModel);
}

init_dog()
{
	if ( !isai(self) )
		return;

	self.aiteam = "axis";
	
	self.animTree = "dog.atr";
	self.type = "dog";
	self.accuracy = 0.2;
	self.health = level.dog_health;
	self.maxhealth = level.dog_health;  // this currently does not hook to code maxhealth
	self.aiweapon = "dog_bite_mp";
	self.secondaryweapon = "";
	self.sidearm = "";
	self.grenadeAmmo = 0;
	self.goalradius = 128;
	self.noDodgeMove = true;
	self.ignoreSuppression = true;
	self.suppressionThreshold = 1;
	self.disableArrivals = false;
	self.pathEnemyFightDist = 512;
	self.halt_patrol = false;

	self.meleeAttackDist = 102; 

	self dog_set_model();

	self thread dogHealthRegen();
	
	self thread selfDefenseChallenge();
}

get_spawn_node( team )
{
	if ( !level.teambased )
	{
		node = dog_pick_node_away_from_enemy( level.dogspawnnodes, team );
	}
	else
	{
		node = dog_pick_node_near_team( level.dogspawnnodes, team );
	}

	bbPrint( "mpdogspawnused: x %f y %f z %f weight %f num_players %i num_dogs %i dist_all %f dist_allies %f dist_axis %f dist_dogs %f", node.origin, node.weight, node.numPlayersAtLastUpdate, node.numDogsAtLastUpdate, node.distSum["all"], node.distSum["allies"], node.distSum["axis"], node.distSum["dogs"] );

	return node;
}

dog_watch_for_owner_team_change(owner)
{
	self endon("death");
	owner endon("disconnect");
	
	while(1)
	{
		owner waittill("joined_team");
		
		if ( owner.team != self.aiteam )
		{
			self clearentityowner();
			self notify("clear_owner");
			return;
		}
	}
}

dog_set_owner( owner, team, requiredDeathCount )
{
/#
	// no owner so he attacks the person who called him in
	if ( level.dog_debug )
		return;
#/

	if ( !isdefined( owner ) )
		return;
		
	// if the owner switches teams while dogs are still spawning
	// do not set the owner 
	if ( level.teambased && isplayer(owner) && owner.team != team )
		return;
		
	self setentityowner(owner);
	
	self.requiredDeathCount = requiredDeathCount;
	
	self thread dog_watch_for_owner_team_change(owner);
	
	self endon("death");
	owner waittill("disconnect");
	
	self clearentityowner();
}

dog_create_spawn_influencer()
{
	self maps\mp\gametypes\_spawning::create_dog_influencers();
}

dog_manager_spawn_dog( owner, team, spawn_node, index, requiredDeathCount )
{
	dog = level.dog_spawner spawnactor();
	
	dog forceteleport(spawn_node.origin, spawn_node.angles);
	dog setgoalnode( spawn_node );
	dog.spawnnode = spawn_node;
	dog show();
	
	dog init_dog();
	dog dog_set_team(team);	
	dog dog_set_model();
	dog dog_create_spawn_influencer();
	dog thread dog_set_owner(owner, team, requiredDeathCount );

	dog thread dog_usage(index);
	dog thread dog_owner_kills();
	dog thread dog_clean_up();
	dog thread dog_notify_level_on_death();
	dog dog_thread_behavior_function();
	dog thread maps\mp\gametypes\_weapons::monitor_dog_special_grenades();

		
	return dog;
}

get_debug_team( team )
{	
/#
	if ( level.teambased )
	{
		otherteam = getotherteam(team);
		if ( level.dog_debug )
			return otherteam;
	}
#/

	if ( !level.teambased )
		return "free";
		
	return team;
}

dog_manager_spawn_dogs( team, enemyTeam, deathCount )
{
	// this can hit if the round ends as the dogs are getting called in
	level endon("dogs done");
	level endon("dogs leaving");
	self endon("disconnect");

	if ( level.no_dogs )
		return;
		
	team = get_debug_team(team);
	
	requiredDeathCount = deathCount;
	
	level.dog_spawner = getent("dog_spawner","targetname" );
	
	if ( !isdefined( level.dog_spawner ) )
		return;
	
	level.dogs = [];

	/#
		level.debug_spawn_nodes = [];
	#/

	level thread dog_manager_game_ended();
	level thread dog_manager_dog_alive_tracker( team );
	level thread dog_manager_dog_time_limit();
	level thread dog_usage_monitor();
	
	for ( i = 0; i < level.dog_count_max_at_once; i++ )
	{
		node = self get_spawn_node( team );

		/#
			level.debug_spawn_nodes[ level.debug_spawn_nodes.size ] = node;
		#/

		level.dogs[i] = dog_manager_spawn_dog( self, team, node, i, requiredDeathCount );

		wait ( randomfloat( level.spawnTimeWaitMin, level.spawnTimeWaitMax ) );
	}
	
	level thread dog_manager_spawn_more_dogs_on_death( self, level.dog_count - level.dog_count_max_at_once, team );
}
 
dog_manager_spawn_more_dogs_on_death( owner, count, team )
{
	level endon("dogs done");
	level endon("dogs leaving");

	while( count > 0 )
	{
		level waittill("dog died");
	
		// wait a bit before sending in the next dog
		wait ( randomfloat( level.spawnTimeWaitMin, level.spawnTimeWaitMax ) );
		
		node = get_spawn_node( team );
		level.dogs[level.dogs.size] = dog_manager_spawn_dog( owner, team, node, level.dogs.size );
		count -= 1;
	} 
	
/#
	iprintln("All dogs spawned");
#/
	level notify("all dogs spawned");
}

dog_manager_dog_time_limit()
{
	level endon("dogs done");
	level endon("dogs leaving");
	wait( level.dog_time );

/#
	dog_debug_print( "time limit hit notify dogs leaving" );
#/	
	// this will shut this thread down
	level notify("dogs leaving");
}

dog_cleanup_wait( wait_for, notify_name )
{
	self endon( notify_name );
	self waittill( wait_for );
	self notify( notify_name, wait_for ); 
}

dog_cleanup_waiter()
{
	self thread dog_cleanup_wait( "all dogs spawned", "start_tracker");
	self thread dog_cleanup_wait( "dogs leaving", "start_tracker" );

	self waittill( "start_tracker", wait_for );
/#
	self dog_debug_print("starting dog_manager_dog_alive_tracker reason " + wait_for );
#/
}

dog_manager_dog_alive_tracker( team )
{
	level dog_cleanup_waiter();
	
	while (1)
	{
	 	alive_count = 0;
	 	for ( i = 0; i < level.dogs.size; i ++ )
	 	{
	 		if ( !isdefined(level.dogs[i]) )
	 			continue;
	 			
	 		if ( !isalive(level.dogs[i]) )
	 			continue;
	 			
	 		alive_count++;
	 	}	
	 	
	 	if ( alive_count == 0 )
	 	{
 			wait(1);
	 		dog_manager_delete_dogs();
	 		level notify("dogs done");
	 		maps\mp\_killstreakrules::killstreakStop( "dogs_mp", team );
	 		return;
		}	 	
		
		wait (1);
	}
}

dog_manager_delete_dogs()
{
		for ( i = 0; i < level.dogs.size; i ++ )
	 	{
	 		if ( !isdefined(level.dogs[i]) )
	 			continue;
	 			
	 		level.dogs[i] delete();
		} 	

		level.dogs = undefined;
}

dog_manager_game_ended()
{
	level waittill("game_ended");
	make_all_dogs_leave();
}

make_all_dogs_leave()
{
/#
	dog_debug_print( "make_all_dogs_leave notify dogs leaving" );
#/	
	level notify("dogs leaving");
}

dog_set_team( team )
{
	self.aiteam = team;
}

dog_clean_up()
{
	self endon("death");
	self endon("leaving");
	level waittill("dogs leaving");
	
	thread dog_leave();
}

dog_notify_level_on_death()
{
	self endon("leaving");
	self waittill("death");
	
	// do not access self past this point as its not valid
	
	level notify("dog died");
}

dog_thread_behavior_function()
{
		self thread dog_patrol_when_no_enemy();
//		self thread dog_attack_when_enemy();
}

dog_leave()
{
	self notify("leaving");

	self thread dog_leave_failsafe();
	
	// have them run to an exit node
	self clearentitytarget();
	self.ignoreall = true;
	self.goalradius = 30;
	self setgoalnode( self dog_get_exit_node() );
	
	self waittill("goal");
	self delete();
}

dog_leave_failsafe()
{
	self endon("death");

	start_origin = self.origin;
	
	wait(2);
	
	if ( distance( start_origin, self.origin ) < 10 )
	{
/#
		println( "DOG DELETE FAILSAFE:  Dog appears to be stuck at " + self.origin );
#/
		self delete();
		return;
	}	
	
	wait(20);
/#
	println( "DOG DELETE FAILSAFE:  Dog has not gotten to it's delete point after 20 seconds.  Currently at " + self.origin );
#/
	self delete();
}


dog_patrol_when_no_enemy()
{
	self endon("death");
	self endon("leaving");
	
	while(1)
	{
		if ( !isdefined(self.enemy) )
		{
			self dog_debug_print( "no enemy starting patrol" );
			self thread dog_patrol();	
		}

		self waittill("enemy");	
	}	
}

dog_patrol_wait( wait_for, notify_name )
{
	self endon("attacking");
	dog_wait( wait_for, notify_name );
}

dog_wait( wait_for, notify_name )
{
	self endon("death");
	self endon("leaving");
	self endon( notify_name );

	self waittill( wait_for );
	self notify( notify_name, wait_for ); 
}

dog_patrol_path_waiter()
{
	self thread dog_patrol_wait( "bad_path", "next_patrol_point");
	self thread dog_patrol_wait( "goal", "next_patrol_point" );

	self waittill( "next_patrol_point", wait_for );
/#
	self dog_debug_print("ending patrol wait recieved " + wait_for );
#/
}

dog_patrol()
{
	self endon("death");
	self endon("enemy");
	self endon("leaving");
	self endon("attacking");
	self notify("on patrol");
	
	self dog_patrol_debug();

	node = level.patrolnodes[randomInt(level.patrolnodes.size)];
	
	while(1)
	{
		if ( self.halt_patrol )
		{
			self setgoalpos( self.origin );
			wait( 1 );
			continue;
		}

		for( count = 0; count < 25; count++ )
		{
			newnode = level.patrolnodes[randomInt(level.patrolnodes.size)];

			if ( DistanceSquared( node.origin, newnode.origin ) > ( 1024 * 1024 ) )
			{
				node = newnode;
				break;
			}

			node = newnode;
		}
		
		// ignore all nodes with a script_noteworthy on them
		if ( !isdefined( node.script_noteworthy ) )
		{
/#
			self dog_debug_print("patroling to node at " + node.origin );
#/
			self setgoalnode( node );
			self dog_patrol_path_waiter();
		}
	}	
}

dog_wait_print( wait_for )
{
/#
	self endon("kill dog_wait_prints");

	self waittill( wait_for );
	self notify("kill dog_wait_prints");
	self dog_debug_print( "PATROL ENDING " + wait_for );
#/
}

dog_patrol_debug()
{
	self thread dog_wait_print("death");
	self thread dog_wait_print("enemy");
	self thread dog_wait_print("leaving");
	self thread dog_wait_print("attacking");
}

dog_get_dvar_int( dvar, def )
{
	return int( dog_get_dvar( dvar, def ) );
}

// dvar set/fetch/check
dog_get_dvar( dvar, def )
{
	if ( getdvar( dvar ) != "" )
		return getdvarfloat( dvar );
	else
	{
		setdvar( dvar, def );
		return def;
	}
}

dog_usage_init()
{
	level.dog_usage = [];
	
	for ( index = 0; index < level.dog_count; index++ )
	{
		level.dog_usage[index] = spawnStruct();
		level.dog_usage[index].spawn_time = 0;
		level.dog_usage[index].death_time = 0;
		level.dog_usage[index].kills = 0;
		level.dog_usage[index].died = false;
	}
}

dog_usage_monitor()
{
	start_time = GetTime();
	
	level waittill("dogs done");

	index = 0;
	total_kills = 0;
	last_alive = 0;
	all_dead = true;
	alive_count = 0;
	never_spawned_count = 0;
	total_count = 0;
	
	for ( index = 0; index < level.dog_count; index++ )
	{
		total_count++;
		
		if ( level.dog_usage[index].spawn_time == 0 )
		{
			never_spawned_count++;
			continue;
		}
		else if ( !level.dog_usage[index].died )
		{
			alive_count++;
			all_dead = false;
		}		
		
		seconds = (level.dog_usage[index].death_time - level.dog_usage[index].spawn_time) / 1000;
		if ( seconds > last_alive )
		{
			last_alive = seconds;
		}		
		
		total_kills += level.dog_usage[index].kills;
	}	
	
/#
	seconds = (GetTime() - start_time) / 1000;
	msg = "Dogs- Time: " + seconds + " Kills: " + total_kills + " Last: " + last_alive;
	
	// make sure that the dogs have printed everything
	wait (1);
	iprintln( msg );
	println( msg );

	level.debug_spawn_nodes = [];
#/
}

dog_usage(index)
{
	level.dog_usage[index].spawn_time = GetTime();
	level.dog_usage[index].death_time = 0;
	level.dog_usage[index].kills = 0;
	level.dog_usage[index].died = false;
	
	self thread dog_usage_kills(index);
	self thread dog_usage_time_alive(index);
}

dog_usage_kills(index)
{
	self endon("death");
	
	while(1)
	{
		self waittill("killed", player);
		level.dog_usage[index].kills++;
	}	
}

dog_owner_kills(index)
{
	if ( !isdefined( self.script_owner ) )
		return;
		
	self endon("clear_owner");
	self endon("death");
	self.script_owner endon("disconnect");
	
	while(1)
	{
		self waittill("killed", player);

		if ( IsDefined( self.script_owner ) )
		{
			self.script_owner notify( "dog_handler" );
		}
	}	
}

dog_usage_time_alive(index)
{
	self endon("leaving");

	self waittill("death");
	level.dog_usage[index].death_time = GetTime();
	level.dog_usage[index].died = true;
	
	seconds = (level.dog_usage[index].death_time - level.dog_usage[index].spawn_time) / 1000 ;
/#
	iprintln( "Dog#" + index + " killed. Alive for: "+ seconds + " seconds. Kills: " + level.dog_usage[index].kills );
	println( "Dog#" + index + " killed. Alive for: "+ seconds + " seconds. Kills: " + level.dog_usage[index].kills );	
#/
}


dogHealthRegen()
{
	self endon("death");
	self endon("end_healthregen");
	
	if ( self.health <= 0 )
	{
		assert( !isalive( self ) );
		return;
	}
	
	maxhealth = self.health;
	oldhealth = maxhealth;
	dog = self;
	health_add = 0;
	
	regenRate = 0.1; // 0.017;
	veryHurt = false;
	
	lastSoundTime_Recover = 0;
	hurtTime = 0;
	newHealth = 0;
	
	for (;;)
	{
		wait (0.05);
		if (dog.health == maxhealth)
		{
			veryHurt = false;
			continue;
		}
					
		if (dog.health <= 0)
			return;

		wasVeryHurt = veryHurt;
		ratio = dog.health / maxHealth;
		if (ratio <= 0.55)
		{
			veryHurt = true;
			if (!wasVeryHurt)
			{
				hurtTime = gettime();
			}
		}
			
		if (dog.health >= oldhealth)
		{
			if (gettime() - hurttime < level.dogHealth_RegularRegenDelay)
				continue;
			
			if ( level.dogHealthRegenDisabled )
				continue;

			if (veryHurt)
			{
				newHealth = ratio;
				if (gettime() > hurtTime + 3000)
					newHealth += regenRate;
			}
			else
				newHealth = 1;
							
			if ( newHealth >= 1.0 )
			{
				newHealth = 1.0;
			}
				
			if (newHealth <= 0)
			{
				// dog is dead
				return;
			}
			
			dog setnormalhealth (newHealth);
			oldhealth = dog.health;
			continue;
		}

		oldhealth = dog.health;
			
		health_add = 0;
		hurtTime = gettime();
	}
}


selfDefenseChallenge()
{
	self waittill ("death", attacker);

	if ( isdefined( attacker ) && isPlayer( attacker ) )
	{
		if (isdefined ( self.script_owner ) && self.script_owner == attacker)
			return;
		if ( level.teambased && isdefined ( self.script_owner ) && self.script_owner.team == attacker.team )
			return;

		attacker notify ("selfdefense_dog");	
	}
		
}

dog_get_exit_node()
{
	return getclosest( self.origin, level.dogexitnodes );
}

dog_debug_print( message )
{
/#
	if ( level.dog_debug )
	{
		if ( isai( self ) )
		{
			println( " " + gettime() + " DOG " + self getentnum() + ": " + message );
		}
		else
		{
			println( " " + gettime() + " DOGS: " + message );
		}
	}
#/
}

// ================================================

getAllOtherPlayers()
{
	aliveplayers = [];

	// Make a list of fully connected, non-spectating, alive players
	for(i = 0; i < level.players.size; i++)
	{
		if ( !isdefined( level.players[i] ) )
			continue;
		player = level.players[i];
		
		if ( player.sessionstate != "playing" || player == self )
			continue;

		aliveplayers[aliveplayers.size] = player;
	}
	return aliveplayers;
}

dog_pick_node_near_team( nodes, team )
{
	// There are no valid nodes in the map
	if(!isdefined(nodes))
		return undefined;
		
	if ( !level.teambased )
		return dog_pick_node_away_from_enemy(level.dogspawnnodes, team);
		
	//prof_begin("basic_spawnlogic");
	
	initWeights(nodes);
	update_all_nodes( nodes, team );

	//prof_begin(" getteams");
	obj = spawnstruct();
	getAllAlliedAndEnemyPlayers(obj, team);
	//prof_end(" getteams");
	
	numplayers = obj.allies.size + obj.enemies.size;
	
	alliedDistanceWeight = 2;
	dogDistanceWeight = 3;
	
	//prof_begin(" sumdists");
	myTeam = team;
	
	enemyTeam = getOtherTeam( myTeam );
	for (i = 0; i < nodes.size; i++)
	{
		node = nodes[i];

		node.weight = 0;

		if ( node.numPlayersAtLastUpdate > 0 )
		{
			allyDistSum = node.distSum[ myTeam ];
			enemyDistSum = node.distSum[ enemyTeam ];
			
			// high enemy distance is good, high ally distance is bad
			node.weight = (enemyDistSum - alliedDistanceWeight*allyDistSum) / node.numPlayersAtLastUpdate;
		}
		
		if ( node.numDogsAtLastUpdate > 0 )
		{
			dogDistSum = node.distSum[ "dogs" ];
			
			// high ally distance is bad
			node.weight -= (dogDistSum*dogDistSum) / node.numDogsAtLastUpdate;	
		}
	}
	//prof_end(" sumdists");
	
	//prof_end("basic_spawnlogic");

	//prof_begin("complex_spawnlogic");

	avoidSpawnReuse(nodes);
	avoidEnemies(nodes, team, true);
	
	//prof_end("complex_spawnlogic");

	result = dog_pick_node_final(nodes, team, obj.enemies);
	
	return result;
}


dog_pick_node_away_from_enemy(nodes, team)
{
	// There are no valid nodes in the map
	if(!isdefined(nodes))
		return undefined;
	
	initWeights(nodes);
	update_all_nodes( nodes, team );

	aliveplayers = getAllOtherPlayers();
	
	// new logic: we want most players near idealDist units away.
	// players closer than badDist units will be considered negatively
	idealDist = 1600;
	badDist = 1200;
	
	if (aliveplayers.size > 0)
	{
		for (i = 0; i < nodes.size; i++)
		{
			totalDistFromIdeal = 0;
			nearbyBadAmount = 0;
			for (j = 0; j < aliveplayers.size; j++)
			{
				dist = distance(nodes[i].origin, aliveplayers[j].origin);
				
				if (dist < badDist)
					nearbyBadAmount += (badDist - dist) / badDist;
				
				distfromideal = abs(dist - idealDist);
				totalDistFromIdeal += distfromideal;
			}
			avgDistFromIdeal = totalDistFromIdeal / aliveplayers.size;
			
			wellDistancedAmount = (idealDist - avgDistFromIdeal) / idealDist;
			
			// wellDistancedAmount is between -inf and 1, 1 being best (likely around 0 to 1)
			// nearbyBadAmount is between 0 and inf,
			// and it is very important that we get a bad weight if we have a high nearbyBadAmount.
			
			nodes[i].weight = wellDistancedAmount - nearbyBadAmount * 2 + randomfloat(.2);
		}
	}
	
	avoidSpawnReuse(nodes);
	avoidEnemies(nodes, team, true);
	
	return dog_pick_node_final(nodes, team, aliveplayers);
}

dog_pick_node_random( nodes, team )
{
	// There are no valid nodes in the map
	if(!isdefined(nodes))
		return undefined;

	// randomize order
	for(i = 0; i < nodes.size; i++)
	{
		j = randomInt(nodes.size);
		node = nodes[i];
		nodes[i] = nodes[j];
		nodes[j] = node;
	}
	
	return dog_pick_node_final(nodes, team, undefined, false);
}

// selects a node, preferring ones with heigher weights (or toward the beginning of the array if no weights).
// also does final things like setting self.lastnode to the one chosen.
// this takes care of avoiding telefragging, so it doesn't have to be considered by any other function.
dog_pick_node_final( nodes, team, enemies, useweights )
{
	//prof_begin( "dog_pick_node_final" );
	
	bestnode = undefined;
	
	if ( !isdefined( nodes ) || nodes.size == 0 )
		return undefined;
	
	if ( !isdefined( useweights ) )
		useweights = true;
	
	if ( useweights )
	{
		// choose node with best weight
		// (if a tie, choose randomly from the best)
		bestnode = getBestWeightedNode( nodes, team, enemies );
	}
	else
	{
		// (only place we actually get here from is dog_pick_node_random() )
		// no weights. prefer nodes toward beginning of array
		for ( i = 0; i < nodes.size; i++ )
		{
			if ( positionWouldTelefrag( nodes[i].origin ) )
				continue;
			
			bestnode = nodes[i];
			break;
		}
	}
	
	if ( !isdefined( bestnode ) )
	{
		// couldn't find a useable node! all will telefrag.
		if ( useweights )
		{
			// at this point, forget about weights. just take a random one.
			bestnode = nodes[randomint(nodes.size)];
		}
		else
		{
			bestnode = nodes[0];
		}
	}
	
	self finalizeNodeChoice( bestnode );
	
	//prof_end( "dog_pick_node_final" );

	return bestnode;
}

getBestWeightedNode( nodes, team, enemies )
{
	maxSightTracedNodes = 3;
	for ( try = 0; try <= maxSightTracedNodes; try++ )
	{
		bestnodes = [];
		bestweight = undefined;
		bestnode = undefined;
		for ( i = 0; i < nodes.size; i++ )
		{
			if ( !isdefined( bestweight ) || nodes[i].weight > bestweight ) 
			{
				if ( positionWouldTelefrag( nodes[i].origin ) )
					continue;
				
				bestnodes = [];
				bestnodes[0] = nodes[i];
				bestweight = nodes[i].weight;
			}
			else if ( nodes[i].weight == bestweight ) 
			{
				if ( positionWouldTelefrag( nodes[i].origin ) )
					continue;
				
				bestnodes[bestnodes.size] = nodes[i];
			}
		}
		if ( bestnodes.size == 0 )
			return undefined;
		
		// pick randomly from the available nodes with the best weight
		bestnode = bestnodes[randomint( bestnodes.size )];
		
		if ( try == maxSightTracedNodes )
			return bestnode;
		
		if ( isdefined( bestnode.lastSightTraceTime ) && bestnode.lastSightTraceTime == gettime() )
			return bestnode;
		
		if ( !lastMinuteSightTraces( bestnode, team, enemies ) )
			return bestnode;
		
		penalty = getLosPenalty();
		bestnode.weight -= penalty;
		
		bestnode.lastSightTraceTime = gettime();
	}
}

finalizeNodeChoice( node )
{
	time = getTime();
	
	self.lastnode = node;
	self.lastspawntime = time;
	node.lastspawneddog = self;
	node.lastspawntime = time;
}

getLosPenalty()
{
	return 100000;
}

lastMinuteSightTraces( node, dog_team, enemies )
{
	//prof_begin("lastMinuteSightTraces");
	
	team = "all";
	if ( level.teambased )
		team = getOtherTeam( dog_team );
	
	if ( !isdefined( enemies ) )
		return false;
	
	closest = undefined;
	closestDistsq = undefined;
	secondClosest = undefined;
	secondClosestDistsq = undefined;

	for ( i = 0; i < enemies.size; i++ )
	{
		player = node.nearbyPlayers[team][i];
		
		if ( !isdefined( player ) )
			continue;
		if ( player.sessionstate != "playing" )
			continue;
		if ( player == self )
			continue;
		
		distsq = distanceSquared( node.origin, player.origin );
		if ( !isdefined( closest ) || distsq < closestDistsq )
		{
			secondClosest = closest;
			secondClosestDistsq = closestDistsq;
			
			closest = player;
			closestDistSq = distsq;
		}
		else if ( !isdefined( secondClosest ) || distsq < secondClosestDistSq )
		{
			secondClosest = player;
			secondClosestDistSq = distsq;
		}
	}
	
	if ( isdefined( closest ) )
	{
		if ( bullettracepassed( closest.origin  + (0,0,50), node.origin + (0,0,50), false, undefined) )
			return true;
	}
	if ( isdefined( secondClosest ) )
	{
		if ( bullettracepassed( secondClosest.origin + (0,0,50), node.origin + (0,0,50), false, undefined) )
			return true;
	}
	
	return false;
}

update_all_nodes( nodes, team )
{
	for ( i = 0; i < nodes.size; i++ )
	{
		nodeUpdate( nodes[i], team );
	}
}

avoidEnemies(nodes, team,teambased)
{
	lospenalty = getLosPenalty();
	
	otherteam = "axis";
	if ( team == "axis" )
		otherteam = "allies";

	minDistTeam = otherteam;
	
	if ( !teambased )
	{
		minDistTeam = "all";
	}
	
	avoidWeight = GetDvarFloat( #"scr_spawn_enemyavoidweight");
	if ( avoidWeight != 0 )
	{
		nearbyEnemyOuterRange = GetDvarFloat( #"scr_spawn_enemyavoiddist");
		nearbyEnemyOuterRangeSq = nearbyEnemyOuterRange * nearbyEnemyOuterRange;
		nearbyEnemyPenalty = 1500 * avoidWeight; // typical base weights tend to peak around 1500 or so. this is large enough to upset that while only locally dominating it.
		nearbyEnemyMinorPenalty = 800 * avoidWeight; // additional negative weight for distances up to 2 * nearbyEnemyOuterRange
		
		lastAttackerOrigin = (-99999,-99999,-99999);
		lastDeathPos = (-99999,-99999,-99999);
		
		for ( i = 0; i < nodes.size; i++ )
		{
			// penalty for nearby enemies
			mindist = nodes[i].minDist[minDistTeam];
			if ( mindist < nearbyEnemyOuterRange*2 )
			{
				penalty = nearbyEnemyMinorPenalty * (1 - mindist / (nearbyEnemyOuterRange*2));
				if ( mindist < nearbyEnemyOuterRange )
					penalty += nearbyEnemyPenalty * (1 - mindist / nearbyEnemyOuterRange);
				if ( penalty > 0 )
				{
					nodes[i].weight -= penalty;
				}
			}
		}
	}
				
	// DEBUG
	//prof_end(" spawn_sc");
}

nodeUpdate( node, team )
{
		if ( level.teambased )
		{
			node.sights["axis"] = 0;
			node.sights["allies"] = 0;
			
			node.nearbyPlayers["axis"] = [];
			node.nearbyPlayers["allies"] = [];
		}
		else
		{
			node.sights = 0;
			
			node.nearbyPlayers["all"] = [];
		}
		
		node.nearbyDogs = [];
		
		nodedir = node.forward;
		
		debug = false;
		
		node.distSum["all"] = 0;
		node.distSum["allies"] = 0;
		node.distSum["axis"] = 0;
		node.distSum["dogs"] = 0;
		
		node.minDist["all"] = 9999999;
		node.minDist["allies"] = 9999999;
		node.minDist["axis"] = 9999999;
		node.minDist["dogs"] = 9999999;
	
		node.numPlayersAtLastUpdate = 0;
		node.numDogsAtLastUpdate = 0;
		
		for (i = 0; i < level.players.size; i++)
		{
			player = level.players[i];
			
			if ( player.sessionstate != "playing" )
				continue;
			
			diff = player.origin - node.origin;
			diff = (diff[0], diff[1], 0);
			dist = length( diff ); // needs to be actual distance for distSum value
						
			player_team = "all";
			if ( level.teambased )
				player_team = player.team;

			if ( dist < 1024 )
			{
				node.nearbyPlayers[player_team][node.nearbyPlayers[player_team].size] = player;
			}
			
			if ( dist < node.minDist[player_team] )
				node.minDist[player_team] = dist;
		
			node.distSum[ player_team ] += dist;
			node.numPlayersAtLastUpdate++;
		}
		
		for (i = 0; i < level.dogs.size; i++)
		{
			dog = level.dogs[i];
			
			if ( !isdefined(dog) || !isalive(dog) )
				continue;
			
			diff = dog.origin - node.origin;
			diff = (diff[0], diff[1], 0);
			dist = length( diff ); // needs to be actual distance for distSum value
						
			if ( dist < 1024 )
			{
				node.nearbyDogs[node.nearbyDogs.size] = dog;
			}
			
			if ( dist < node.minDist["dogs"] )
				node.minDist["dogs"] = dist;
		
			node.distSum[ "dogs" ] += dist;
			node.numDogsAtLastUpdate++;
		}
}

initWeights(nodes)
{
	for (i = 0; i < nodes.size; i++)
		nodes[i].weight = 0;
}

getAllAlliedAndEnemyPlayers( obj, team )
{
	if ( level.teambased )
	{
		if ( team == "allies" )
		{
			obj.allies = level.alivePlayers["allies"];
			obj.enemies = level.alivePlayers["axis"];
		}
		else
		{
			assert( team == "axis" );
			obj.allies = level.alivePlayers["axis"];
			obj.enemies = level.alivePlayers["allies"];
		}
	}
	else
	{
		obj.allies = [];
		obj.enemies = level.activePlayers;
	}
}

avoidSpawnReuse(nodes)
{
	time = getTime();
	
	maxtime = 3*1000;
	maxdistSq = 1024 * 1024;

	for (i = 0; i < nodes.size; i++)
	{
		node = nodes[i];
		
		if (!isdefined(node.lastspawntime))
			continue;
			
		timepassed = time - node.lastspawntime;
		if (timepassed < maxtime)
		{
			worsen = 1000 * (1 - timepassed/maxtime);
			node.weight -= worsen;
		}
	}
}

flash_dogs( area )
{
	self endon("disconnect");
	
	if ( isdefined(level.dogs) )
	{
		for (i = 0; i < level.dogs.size; i++)
		{
			dog = level.dogs[i];
			
			if ( !isalive(dog) )
					continue;
			if ( dog istouching(area) )
			{
				do_flash = true;
				if ( isPlayer( self ) )
				{
					if ( level.teamBased && (dog.aiteam == self.team) )
					{
						do_flash = false;
					}
					else if ( !level.teambased && isdefined(dog.script_owner) && self == dog.script_owner )
					{
						do_flash = false;
					}
				}
				
				if ( isdefined( dog.lastFlashed ) && dog.lastFlashed + 1500 > gettime()  )
				{	
					do_flash = false;
				}
				
				if ( do_flash )
				{
					dog setFlashBanged( true, 500 );
					dog.lastFlashed = gettime();
				}
			}	
		}	
	}
}

/#

devgui_dog_think()
{
	SetDvar( "devgui_dog", "" );

	level.debug_spawn_nodes = [];
	
	for( ;; )
	{
		cmd = GetDvar( #"devgui_dog" );

		switch( cmd )
		{
		case "spawn_friendly":	
			player = getHostPlayer();
			devgui_dog_spawn( player.team );
			break;

		case "spawn_enemy":	
			player = getHostPlayer();
			devgui_dog_spawn( getOtherTeam( player.team ) );
			break;

		case "delete_dogs":
			devgui_dog_delete();
			break;

		case "dog_camera":
			devgui_dog_camera();
			break;

		case "spawn_crate":
			devgui_crate_spawn();
			break;

		case "delete_crates":
			devgui_crate_delete();
			break;

		case "show_spawns":
			devgui_spawn_show();
			break;
		
		case "show_exits":
			devgui_exit_show();
			break;

		case "debug_spawn":
			level thread devgui_debug_spawn();
			break;
		}

		if ( cmd != "" )
		{
			SetDvar( "devgui_dog", "" );
		}

		wait( 0.5 );
	}
}

devgui_dog_spawn( team )
{
	player = getHostPlayer();

	dog_spawner = GetEnt( "dog_spawner", "targetname" );
	
	if( !IsDefined( dog_spawner ) )
	{
		iprintln( "No dog spawners found in map" );
	}
	else
	{
		iprintln( "Spawning dog at your crosshair position" );
	}

	// Trace to where the player is looking
	direction = player GetPlayerAngles();
	direction_vec = AnglesToForward( direction );
	eye = player GetEye();

	scale = 8000;
	direction_vec = ( direction_vec[0] * scale, direction_vec[1] * scale, direction_vec[2] * scale );
	trace = bullettrace( eye, eye + direction_vec, 0, undefined );

	dog = dog_spawner spawnactor();

	direction_vec = player.origin - trace["position"];
	direction = VectorToAngles( direction_vec );
	
	dog forceteleport( trace["position"], direction );
	//dog setgoalnode( spawn_node );
	//dog.spawnnode = spawn_node;
	dog show();
	
	dog init_dog();
	dog dog_set_team( team );	
	dog dog_set_model();
	//dog dog_create_spawn_influencer();
	dog thread dog_set_owner( player, team, 5 );

	dog thread dog_usage( 0 );
	dog thread dog_owner_kills();
	dog thread dog_clean_up();
	dog thread dog_notify_level_on_death();
	dog dog_thread_behavior_function();
	dog thread maps\mp\gametypes\_weapons::monitor_dog_special_grenades();

	if ( !IsDefined( level.dogs ) )
	{
		level.dogs = [];
	}
	
	level.dogs[ level.dogs.size ] = dog;
}

devgui_dog_delete()
{
	player = getHostPlayer();
	player CameraActivate( false );	

	if ( !IsDefined( level.dogs ) )
	{
		return;
	}

	for ( i = 0; i < level.dogs.size; i++ )
	{
		dog = level.dogs[i];

		if ( IsDefined( dog.cam ) )
		{
			dog.cam delete();
		}
	}

	dog_manager_delete_dogs();
}

devgui_dog_camera()
{
	player = getHostPlayer();

	if ( !IsDefined( level.devgui_dog_camera ) )
	{
		level.devgui_dog_camera = 0;
	}

	if ( !IsDefined( level.dogs ) )
	{
		level.devgui_dog_camera = undefined;
		player CameraActivate( false );	
		return;
	}

	dog = undefined;
	
	for ( i = 0; i < level.dogs.size; i++ )
	{
		dog = level.dogs[i];

		if ( !IsDefined( dog ) || !IsAlive( dog ) )
		{
			dog = undefined;
			continue;
		}

		if ( !IsDefined( dog.cam ) )
		{
			forward = AnglesToForward( dog.angles );
			dog.cam = Spawn( "script_model", dog.origin + ( 0, 0, 50 ) + forward * -100 );
			dog.cam SetModel( "tag_origin" );
			dog.cam LinkTo( dog );
		}

		if ( dog GetEntityNumber() <= level.devgui_dog_camera )
		{
			dog = undefined;
			continue;
		}

		break;
	}
	
	if ( IsDefined( dog ) )
	{
		level.devgui_dog_camera = dog GetEntityNumber();
		
		player CameraSetPosition( dog.cam );
		player CameraSetLookAt( dog );
		player CameraActivate( true );	
	}
	else
	{
		level.devgui_dog_camera = undefined;
		player CameraActivate( false );	
	}
}

devgui_crate_spawn()
{
	player = getHostPlayer();

	// Trace to where the player is looking
	direction = player GetPlayerAngles();
	direction_vec = AnglesToForward( direction );
	eye = player GetEye();

	scale = 8000;
	direction_vec = ( direction_vec[0] * scale, direction_vec[1] * scale, direction_vec[2] * scale );
	trace = bullettrace( eye, eye + direction_vec, 0, undefined );

	//trace[ "position" ] = player.origin + ( 0, 0, 4096 );

	killCamEnt = spawn( "script_model", player.origin );
	level thread maps\mp\gametypes\_supplydrop::dropCrate( trace[ "position" ] + ( 0, 0, 25 ), direction, "supplydrop_mp", player, player.team, killcamEnt );
}

devgui_crate_delete()
{
	if ( !IsDefined( level.devgui_crates ) )
	{
		return;
	}

	for ( i = 0; i < level.devgui_crates.size; i++ )
	{
		level.devgui_crates[i] delete();
	}

	level.devgui_crates = [];
}

devgui_spawn_show()
{
	if ( !IsDefined( level.dog_spawn_show ) )
	{
		level.dog_spawn_show = true;
	}
	else
	{
		level.dog_spawn_show = !level.dog_spawn_show;
	}

	if ( !level.dog_spawn_show )
	{
		level notify( "hide_dog_spawns" );
		return;
	}

	color = ( 0, 1, 0 );

	for ( i = 0; i < level.dogspawnnodes.size; i++ )
	{
		maps\mp\gametypes\_dev::showOneSpawnPoint( level.dogspawnnodes[i], color, "hide_dog_spawns", 32, "dog_spawn" );
	}
}

devgui_exit_show()
{
	if ( !IsDefined( level.dog_exit_show ) )
	{
		level.dog_exit_show = true;
	}
	else
	{
		level.dog_exit_show = !level.dog_exit_show;
	}

	if ( !level.dog_exit_show )
	{
		level notify( "hide_dog_exits" );
		return;
	}

	color = ( 1, 0, 0 );

	for ( i = 0; i < level.dogexitnodes.size; i++ )
	{
		maps\mp\gametypes\_dev::showOneSpawnPoint( level.dogexitnodes[i], color, "hide_dog_exits", 32, "dog_exit" );
	}
}

devgui_debug_spawn()
{
	level endon( "devgui_debug_spawn" );
	
	if ( !IsDefined( level.devgui_debug_spawn ) )
	{
		level.devgui_debug_spawn = true;
	}
	else
	{
		level.devgui_debug_spawn = undefined;
		level notify( "debug_spawn_refresh" );
		level notify( "devgui_debug_spawn" );
	}

	yellow = ( 1, 1, 0 );
	green = ( 0, 1, 0 );

	for ( ;; )
	{
		for ( i = 0; i < level.dogspawnnodes.size; i++ )
		{
			
			text = [];
			text["weight"]		= "n/a";
			text["num_players"] = "n/a";
			text["num_dogs"]	= "n/a";

			text["dist_all"]	= "n/a";
			text["dist_allies"] = "n/a";
			text["dist_axis"]	= "n/a";
			text["dist_dogs"]	= "n/a";

			node = level.dogspawnnodes[i];
			color = yellow;

			if ( IsDefined( node.weight ) )
				text["weight"] = node.weight;

			if ( IsDefined( node.numPlayersAtLastUpdate ) )
				text["num_players"] = node.numPlayersAtLastUpdate;

			if ( IsDefined( node.numDogsAtLastUpdate ) )
				text["num_dogs"] = node.numDogsAtLastUpdate;

			if ( IsDefined( node.distSum ) )
			{
				text["dist_all"]	= node.distSum["all"];
				text["dist_allies"] = node.distSum["allies"];
				text["dist_axis"]	= node.distSum["axis"];
				text["dist_dogs"]	= node.distSum["dogs"];
			}

			if ( node_used( node ) )
			{
				color = green;
			}
			
			print_text( node.origin, text, 64, color, "debug_spawn_refresh" );
			maps\mp\gametypes\_dev::showOneSpawnPoint( node, color, "debug_spawn_refresh", 32, "" );
		}

		wait( 1 );
		level notify( "debug_spawn_refresh" );
	}
}

print_text( origin, text_array, height, color, notification )
{
	keys = GetArrayKeys( text_array );
	z = height;

	for ( i = keys.size - 1; i >= 0; i-- )
	{
		string = keys[i] + ": " + text_array[ keys[i] ];
				
		thread maps\mp\gametypes\_dev::print3DUntilNotified( origin + ( 0, 0, z ), string, color, 1, 0.5, notification );
		z = z - 6;
	}
}

node_used( node )
{
	for ( i = 0; i < level.debug_spawn_nodes.size; i++ )
	{
		if ( node == level.debug_spawn_nodes[i] )
		{
			return true;
		}
	}

	return false;
}

#/
