// 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" ); }