// squadmanager.gsc

/****************************************************************************
 initialization
*****************************************************************************/

// initializes the squad management system
init_squadManager()
{
	if ( isdefined( anim.squadInitialized ) && anim.squadInitialized )
		return;

	anim.squadCreateFuncs = [];
	anim.squadCreateStrings = [];

	anim.squads = [];
	anim.squadIndex = [];
	anim.squadRand = 0;

	anim.squadInitialized = true;
}

/****************************************************************************
 functionality
*****************************************************************************/

createSquad( squadName, squadCreator )
{
	assertex( !isdefined( anim.squads[ squadName ] ), "createSquad attempted to create a squad with the same name as an existing squad" );

	//prof_begin( "createSquad" );

	anim.squads[ squadName ] = spawnstruct();
	squad = anim.squads[ squadName ];
	squad.squadName = squadName;
	
	// SRS 10/17/08: added squad.team so we can still support custom-named squads,
	//  not just "axis" and "allies"
	squad.team = getSquadTeam( squadCreator );

	squad.sightTime = 0;
	squad.origin = undefined;
	squad.forward = undefined;
	squad.enemy = undefined;
	squad.isInCombat = false;

	squad.memberCount = 0;
	squad.members = [];
	squad.officers = [];
	squad.officerCount = 0;
	squad.squadList = [];

	squad.memberAddFuncs = [];
	squad.memberAddStrings = [];
	squad.memberRemoveFuncs = [];
	squad.memberRemoveStrings = [];
	squad.squadUpdateFuncs = [];
	squad.squadUpdateStrings = [];
	squad.squadID = anim.squadIndex.size;

	squad initState( "combat", 0.75 );
	squad initState( "cover", 0.75 );
	squad initState( "move", 0.75 );
	squad initState( "stop", 0.75 );
	squad initState( "death", 0.75 );
	squad initState( "suppressed", 0.75 );
	squad initState( "attacking", 0.5 );

	// add this squad to the global index
	anim.squadIndex[ anim.squadIndex.size ] = squad;

	squad updateSquadList();

	// notifies for other scripts
	level notify( "squad created " + squadName );
	anim notify( "squad created " + squadName );

	for ( i = 0; i < anim.squadCreateFuncs.size; i++ )
	{
		squadCreateFunc = anim.squadCreateFuncs[ i ];
		squad thread [[ squadCreateFunc ]]();
	}

	// battlechatter init
//	squad animscripts\battlechatter::init_squadBattleChatter();

	// tell all other squads to add us to their lists
	for ( i = 0; i < anim.squadIndex.size; i++ )
		anim.squadIndex[ i ] updateSquadList();

	squad thread updateWaiter();
	squad thread squadTracker();
	squad thread officerWaiter();
	squad thread updateMemberStates();

	//prof_end( "createSquad" );
	return( squad );
}

deleteSquad( squadName )
{
	assertex( isdefined( anim.squads[ squadName ] ), "deleteSquad attempted to delete a squad that does not exist" );

	if ( squadName == "axis" || squadName == "team3" || squadName == "allies" )
		return;

	squadID = anim.squads[ squadName ].squadID;
	squad = anim.squads[ squadName ];
	
	squad notify( "squad_deleting" );

//	for (i = 0; i < squad.members.size; i++)
	while ( squad.members.size )
		squad.members[ 0 ] addToSquad( squad.members[ 0 ].team );

	anim.squadIndex[ squadID ] = anim.squadIndex[ anim.squadIndex.size - 1 ];
	anim.squadIndex[ squadID ].squadID = squadID;
	anim.squadIndex[ anim.squadIndex.size - 1 ] = undefined;

	anim.squads[ squadName ] = undefined;

	level notify( "squad deleted " + squadName );
	anim notify( "squad deleted " + squadName );

	for ( i = 0; i < anim.squadIndex.size; i++ )
		anim.squadIndex[ i ] updateSquadList();
}

generateSquadName()
{
	squadName = "auto" + anim.squadRand;
	anim.squadRand++ ;

	return( squadName );
}

addPlayerToSquad( squadName )
{
	if ( !isdefined( squadName ) )
	{
		if ( isdefined( self.script_squadname ) )
			squadName = self.script_squadname;
		else
			squadName = self.team;
	}

	if ( !isdefined( anim.squads[ squadName ] ) )
	{
		anim createSquad( squadName, self );
	}
	
	squad = anim.squads[ squadName ];

	self.squad = squad;

//	self.squad updateOrigin();
}

// adds the ai that calls the function to the specified squad
// if not squad is specified, the ai is added to one of the defaults
squadChange()
{
	self endon( "death" );
	wait( 10.0 );// ???

	if ( !isdefined( self.script_squadname ) )
		squadName = ( self.team + self.script_flanker );
	else
		squadName = ( self.script_squadname + self.script_flanker );

	self addToSquad( squadName );
}

getSquadTeam( squadCreator )
{
	squadTeam = "allies";  // player, allies
	if( squadCreator.team == "axis" || squadCreator.team == "neutral" || squadCreator.team == "team3" )
	{
		squadteam = squadCreator.team;
	}
	
	return squadTeam;
}

addToSquad( squadName )
{
	assertex( IsSentient( self ), "addToSquad attempted to add a non-sentient member to a squad" );

	//prof_begin( "addToSquad" );

	if ( !isdefined( squadName ) )
	{
		if ( isdefined( self.script_flanker ) )
			self thread squadChange();

		if ( isdefined( self.script_squadname ) )
			squadName = self.script_squadname;
		else
			squadName = self.team;
	}
	if ( !isdefined( anim.squads[ squadName ] ) )
		anim createSquad( squadName, self );

	squad = anim.squads[ squadName ];

	if ( isdefined( self.squad ) )
	{
		if ( self.squad == squad )
			return;
		else
			self removeFromSquad();
	}

	// move to init.gsc
	self.lastEnemySightTime = 0;
	self.combatTime = 0;
	// end move

	self.squad = squad;
	self.memberID = squad.members.size;
	squad.members[ self.memberID ] = self;
	squad.memberCount = squad.members.size;

	// this is handled in wait for loadout function
	if ( isdefined( level._loadoutComplete ) )
	{
		if ( self.team == "allies" && self animscripts\battlechatter::isOfficer() )
		{
			self addOfficerToSquad();
		}
	}

//	self.squad updateOrigin();

	for ( i = 0; i < self.squad.memberAddFuncs.size; i++ )
	{
		memberAddFunc = self.squad.memberAddFuncs[ i ];
		self thread [[ memberAddFunc ]]( self.squad.squadName );
	}

	//prof_end( "addToSquad" );

	self thread memberCombatWaiter();
	self thread memberDeathWaiter();
}

removeFromSquad()
{
	//prof_begin( "removeFromSquad" );
	ASSERTEX( IsDefined( self.squad ), "removeFromSquad attempted to remove a member who was not part of a squad (self.squad == undefined)" );

	squad = self.squad;
	memberID = -1;

	if ( IsDefined( self ) )
	{
		memberID = self.memberID;
	}
	else
	{
		for ( i = 0; i < squad.members.size; i++ )
		{
			if ( squad.members[ i ] == self )
				memberID = i;
		}
	}

	ASSERTEX( memberID > - 1, "removeFromSquad could not find memberID" );

	if ( memberID != squad.members.size - 1 )
	{
		other = squad.members[ squad.members.size - 1 ];
		squad.members[ memberID ] = other;
		if ( isdefined( other ) )
			other.memberID = memberID;
	}

	squad.members[ squad.members.size - 1 ] = undefined;
	squad.memberCount = squad.members.size;

	if ( IsDefined( self.officerID ) )
	{
		self removeOfficerFromSquad();
	}

	for ( i = 0; i < self.squad.memberRemoveFuncs.size; i++ )
	{
		memberRemoveFunc = self.squad.memberRemoveFuncs[ i ];
		self thread [[ memberRemoveFunc ]]( squad.squadName );
	}

	assert( squad.members.size == squad.memberCount );

	if ( squad.memberCount == 0 )
	{
		deleteSquad( squad.squadName );
	}

	if ( isdefined( self ) )
	{
		self.squad = undefined;
		self.memberID = undefined;
	}

	self notify( "removed from squad" );

	//prof_end( "removeFromSquad" );
}

addOfficerToSquad()
{
	squad = self.squad;

	if ( isdefined( self.officerID ) )
		return;

	assertex( !isdefined( self.officerID ), "addOfficerToSquad attempted to add a member that is already in an officers" );

	self.officerID = squad.officers.size;
	squad.officers[ self.officerID ] = self;
	squad.officerCount = squad.officers.size;
}

removeOfficerFromSquad()
{
	//prof_begin( "removeOfficerFromSquad" );

	squad = self.squad;
	officerID = -1;

	if ( isdefined( self ) )
	{
		officerID = self.officerID;
	}
	else
	{
		for ( i = 0; i < squad.officers.size; i++ )
		{
			if ( squad.officers[ i ] == self )
				officerID = i;
		}
	}

	assertex( officerID > - 1, "removeOfficerFromSquad could not find officerID" );

	if ( officerID != squad.officers.size - 1 )
	{
		other = squad.officers[ squad.officers.size - 1 ];
		squad.officers[ officerID ] = other;
		if ( isdefined( other ) )
			other.officerID = officerID;
	}

	squad.officers[ squad.officers.size - 1 ] = undefined;
	squad.officerCount = squad.officers.size;

	assert( squad.officers.size == squad.officerCount );

	if ( isdefined( self ) )
		self.officerID = undefined;

	//prof_end( "removeOfficerFromSquad" );

}

/****************************************************************************
 trackers/waiters
*****************************************************************************/

officerWaiter()
{
	if ( !isdefined( level._loadoutComplete ) )
	{
		anim waittill( "loadout complete" );
	}

	for ( i = 0; i < self.members.size; i++ )
	{
		if ( self.members[ i ] animscripts\battlechatter::isOfficer() )
		{
			self.members[ i ] addOfficerToSquad();
		}
	}
}

updateWaiter()
{
	while ( 1 )
	{
		anim waittill( "squadupdate", action );

		//prof_begin( "updateWaiter" );

		switch( action )
		{
		case "squadlist":
			self updateSquadList();
			break;
		case "combat":
			self updateCombat();
			break;
		case "origin":
			self updateOrigin();
			break;
		case "forward":
			self updateHeading();
			break;
		}

		//prof_end( "updateWaiter" );
	}
}

squadTracker()
{
	anim endon( "squad deleted " + self.squadName );
	// even with the long wait time, this is a crappy way to track things
	// ideally the updateFunctions would only be called when another function needs to know the status of something
	// rather than polling like it's doing now.
	while ( 1 )
	{
		// combat can be removed
		self updateAll();

		wait( 0.1 );
	}
}

memberDeathWaiter()
{
//	self notify ("squad change");
	self endon( "removed from squad" );

	self waittill( "death", attacker );

	if ( isdefined( self ) )
	{
		self.attacker = attacker;
	}
	
	self removeFromSquad();
}

memberCombatWaiter()
{
//	self notify ("squad change");
	self endon( "removed from squad" );

	while ( 1 )
	{
		self waittill( "enemy" );

		if ( !isdefined( self.enemy ) )
			self.squad notify( "squadupdate", "combat" );
		else
			self.squad.isInCombat = true;

		wait( 0.05 );
	}
}

/****************************************************************************
 utility
*****************************************************************************/

updateHeading()
{
	if ( isdefined( self.enemy ) )
	{
		self.forward = vectornormalize( self.enemy.origin - self.origin );
		return;
	}

	newHeading = ( 0, 0, 0 );
	numInfluences = 0;

	for ( i = 0; i < self.members.size; i++ )
	{
		if ( !isalive( self.members[ i ] ) )
			continue;
		// logic here to prune out separated members... otherwise origin could be too vague
		newHeading += anglestoforward( self.members[ i ].angles );
		numInfluences++ ;
	}

	if ( numInfluences )
		self.forward = ( newHeading[ 0 ] / numInfluences, newHeading[ 1 ] / numInfluences, newHeading[ 2 ] / numInfluences );
	else
		self.forward = newHeading;
}

updateOrigin()
{
	//prof_begin( "updateOrigin" );
	newOrigin = ( 0, 0, 0 );
	numInfluences = 0;

	for ( i = 0; i < self.members.size; i++ )
	{
		// logic here to prune out separated members... otherwise origin could be too vague

//		assertex (isdefined (self.members[i]), "updateOrigin run while a squad member was undefined");		
		if ( !isalive( self.members[ i ] ) )
			continue;

		newOrigin += self.members[ i ].origin;
		numInfluences++ ;
	}

	if ( numInfluences )
		self.origin = ( newOrigin[ 0 ] / numInfluences, newOrigin[ 1 ] / numInfluences, newOrigin[ 2 ] / numInfluences );
	else
		self.origin = newOrigin;

	//prof_end( "updateOrigin" );
}

updateCombat()
{
	//prof_begin( "updateCombat" );

	self.isInCombat = false;

	// reset squad contact status
	for ( i = 0; i < anim.squadIndex.size; i++ )
		self.squadList[ anim.squadIndex[ i ].squadName ].isInContact = false;

	for ( i = 0; i < self.members.size; i++ )
	{
		if ( isdefined( self.members[ i ].enemy ) && isdefined( self.members[ i ].enemy.squad ) && self.members[ i ].combatTime > 0 )
			self.squadList[ self.members[ i ].enemy.squad.squadName ].isInContact = true;
	}

	//prof_end( "updateCombat" );
}

updateEnemy()
{
	//prof_begin( "updateEnemy" );
	curEnemy = undefined;
	for ( i = 0; i < self.members.size; i++ )
	{
		if ( isdefined( self.members[ i ].enemy ) && isdefined( self.members[ i ].enemy.squad ) )
		{
			if ( !isdefined( curEnemy ) )
				curEnemy = self.members[ i ].enemy.squad;
			else if ( self.members[ i ].enemy.squad.memberCount > curEnemy.memberCount )
				curEnemy = self.members[ i ].enemy.squad;
		}
	}

	self.enemy = curEnemy;
	//prof_end( "updateEnemy" );
}

updateAll()
{
	//prof_begin( "updateAll" );

	newOrigin = ( 0, 0, 0 );
	numInfluences = 0;
	curEnemy = undefined;
	isInCombat = false;

	self updateCombat();

	for ( i = 0; i < self.members.size; i++ )
	{
		if ( !isalive( self.members[ i ] ) )
			continue;

		// logic here to prune out separated members... otherwise origin could be too vague
		newOrigin += self.members[ i ].origin;
		numInfluences++ ;

		if ( isdefined( self.members[ i ].enemy ) && isdefined( self.members[ i ].enemy.squad ) )
		{
			if ( !isdefined( curEnemy ) )
				curEnemy = self.members[ i ].enemy.squad;
			else if ( self.members[ i ].enemy.squad.memberCount > curEnemy.memberCount )
				curEnemy = self.members[ i ].enemy.squad;
		}
	}

	if ( numInfluences )
		self.origin = ( newOrigin[ 0 ] / numInfluences, newOrigin[ 1 ] / numInfluences, newOrigin[ 2 ] / numInfluences );
	else
		self.origin = newOrigin;

	self.isInCombat = isInCombat;
	self.enemy = curEnemy;

	// integreate this at some point
	self updateHeading();

	//prof_end( "updateAll" );
}

updateSquadList()
{
	//prof_begin( "updateSquadList" );

	for ( i = 0; i < anim.squadIndex.size; i++ )
	{
		if ( !isdefined( self.squadList[ anim.squadIndex[ i ].squadName ] ) )
		{
			self.squadList[ anim.squadIndex[ i ].squadName ] = spawnstruct();
			self.squadList[ anim.squadIndex[ i ].squadName ].isInContact = false;
		}

		for ( j = 0; j < self.squadUpdateFuncs.size; j++ )
		{
			squadUpdateFunc = self.squadUpdateFuncs[ j ];
			self thread [[ squadUpdateFunc ]]( anim.squadIndex[ i ].squadName );
		}
	}

	//prof_end( "updateSquadList" );
}

printAboveHead( string, duration, offset, color )
{
	self endon( "death" );

	if ( !isdefined( offset ) )
		offset = ( 0, 0, 0 );
	if ( !isdefined( color ) )
		color = ( 1, 0, 0 );

	for ( i = 0; i < ( duration * 2 ); i++ )
	{
		if ( !isalive( self ) )
			return;

		aboveHead = self getshootatpos() + ( 0, 0, 10 ) + offset;
		print3d( aboveHead, string, color, 1, 0.5 );	// origin, text, RGB, alpha, scale
		wait( 0.05 );
	}
}

/****************************************************************************
 ai functions
*****************************************************************************/

aiUpdateAnimState( animscript )
{
	//prof_begin( "aiUpdateAnimState" );

	switch( animscript )
	{
	case "combat":
	case "move":
	case "stop":
	case "death":
		self.a.state = animscript;
		break;

	case "pain":
	case "grenadecower":
		break;

	case "cover_crouch":
	case "cover_left":
	case "cover_prone":
	case "cover_right":
	case "cover_stand":
	case "cover_wide_left":
	case "cover_wide_right":
	case "concealment_crouch":
	case "concealment_prone":
	case "concealment_stand":
	case "stalingrad_cover_crouch":
		self.a.state = "cover";
		break;

	case "aim":
	case "l33t truckride combat":
		self.a.state = "combat";
		break;
	}

	//prof_end( "aiUpdateAnimState" );
}

/****************************************************************************
 squad functions
*****************************************************************************/

updateStates()
{
	self resetState( "combat" );
	self resetState( "cover" );
	self resetState( "move" );
	self resetState( "stop" );
	self resetState( "death" );
	self resetState( "suppressed" );
	self resetState( "attacking" );

	for ( i = 0; i < self.members.size; i++ )
	{
		if ( !isalive( self.members[ i ] ) )
			continue;

		self queryMemberAnimState( self.members[ i ] );
		self queryMemberState( self.members[ i ], "suppressed" );
		self queryMemberState( self.members[ i ], "combat" );
		self queryMemberState( self.members[ i ], "attacking" );
		self queryMemberState( self.members[ i ], "cover" );
	}
}

updateMemberStates()
{
	anim endon( "squad deleted " + self.squadName );

	timeSlice = 0.05;
	while ( 1 )
	{
		//prof_begin( "updateMemberStates" );

		for ( i = 0; i < self.members.size; i++ )
		{
			if ( !isalive( self.members[ i ] ) )
				continue;

			self.members[ i ] aiUpdateCombat( timeSlice );
			self.members[ i ] aiUpdateSuppressed( timeSlice );
		}

		//prof_end( "updateMemberStates" );
		wait( timeSlice );
	}
}

aiUpdateCombat( timeSlice )
{
	if ( isdefined( self.lastEnemySightPos ) )
	{
		if ( self.combatTime < 0 )
			self.combatTime = timeSlice;
		else
			self.combatTime += timeSlice;

		self.lastEnemySightTime = gettime();
		return;
	}
	else if ( self issuppressed() )
	{
		self.combatTime += timeSlice;
		return;
	}

	if ( self.combatTime > 0 )
		self.combatTime = ( 0 - timeSlice );
	else
		self.combatTime -= timeSlice;
}

aiUpdateSuppressed( timeSlice )
{
	if ( self.suppressed )
	{
		if ( self.suppressedTime < 0 )
			self.suppressedTime = timeSlice;
		else
			self.suppressedTime += timeSlice;

		return;
	}

	if ( self.suppressedTime > 0 )
		self.suppressedTime = ( 0 - timeSlice );
	else
		self.suppressedTime -= timeSlice;
}

initState( state, activateRatio )
{
	self.squadStates[ state ] = spawnstruct();
	self.squadStates[ state ].activateRatio = activateRatio;
	self.squadStates[ state ].isActive = false;
	self.squadStates[ state ].numActive = 0;
}

resetState( state )
{
	self.squadStates[ state ].isActive = false;
	self.squadStates[ state ].numActive = 0;
}

queryMemberAnimState( member )
{
	self.squadStates[ member.a.state ].numActive++ ;
	if ( self.squadStates[ member.a.state ].numActive > ( self.squadStates[ member.a.state ].activateRatio * self.members.size ) )
		self.squadStates[ member.a.state ].isActive = true;
}

queryMemberState( member, state )
{
	//prof_begin( "queryMemberState" );
	switch( state )
	{
	case "suppressed":
		if ( member.suppressedTime > 1.0 )
			self.squadStates[ state ].numActive++ ;
		break;
	case "combat":
		if ( member.combatTime > 0.0 )
			self.squadStates[ state ].numActive++ ;
		break;
	case "attacking":
		if ( gettime() < member.a.lastShootTime + 2000 )
			self.squadStates[ state ].numActive++ ;
		break;
	case "cover":
		if ( !member animscripts\battlechatter::isExposed() )
			self.squadStates[ state ].numActive++ ;
		break;
	}
	if ( self.squadStates[ state ].numActive > ( self.squadStates[ state ].activateRatio * self.members.size ) )
		self.squadStates[ state ].isActive = true;
	//prof_end( "queryMemberState" );
}
