#ifdef PRECOMPILEDHEADERS #include "Strategic All.h" #else #include "builddefines.h" #include #include #include "Strategic Movement.h" #include "MemMan.h" #include "debug.h" #include "Campaign Types.h" #include "Game Event Hook.h" #include "Game Clock.h" #include "Queen Command.h" #include "overhead.h" #include "strategicmap.h" #include "jascreens.h" #include "Strategic Pathing.h" #include "Map Screen Interface Map.h" #include "PreBattle Interface.h" #include "Strategic.h" #include "Assignments.h" #include "Text.h" #include "Font Control.h" #include "message.h" #include "mapscreen.h" #include "dialogue control.h" #include "Soldier Add.h" #include "Game Events.h" #include "Vehicles.h" #include "Map Screen Helicopter.h" #include "Map Screen Interface.h" #include "Squads.h" #include "Random.h" #include "soldier macros.h" #include "Map Information.h" #include "Tactical Save.h" #include "Player Command.h" #include "Strategic AI.h" #include "Town Militia.h" #include "Music Control.h" #include "Campaign.h" #include "Isometric Utils.h" #include "meanwhile.h" #include "Inventory Choosing.h" #include "Map Screen Interface Border.h" #include "Auto Resolve.h" #include "GameSettings.h" #include "Quests.h" #endif #include "MilitiaSquads.h" #include "Vehicles.h" #ifdef JA2UB #include "Ja25Update.h" #include "Ja25 Strategic Ai.h" //#include "Map Screen Interface Map.h" //extern JA25_SECTOR_AI *gJa25AiSectorStruct; #endif #include "connect.h" //forward declarations of common classes to eliminate includes class OBJECTTYPE; class SOLDIERTYPE; extern UINT32 guiLastTacticalRealTime; // the delay for a group about to arrive // HEADROCK HAM 5: 5 minutes? Make it 30. 5 minutes tend to pass in an instant while unpaused... #define ABOUT_TO_ARRIVE_DELAY 30 GROUP *gpGroupList; GROUP *gpPendingSimultaneousGroup = NULL; // is the bottom of the map panel dirty? extern BOOLEAN fMapScreenBottomDirty; extern BOOLEAN gfUsePersistantPBI; extern BOOLEAN gfExitViewer; BOOLEAN ValidateGroups( GROUP *pGroup ); extern BOOLEAN gubNumAwareBattles; extern INT8 SquadMovementGroups[ ]; BOOLEAN gfDelayAutoResolveStart = FALSE; BOOLEAN gfRandomizingPatrolGroup = FALSE; UINT8 gubNumGroupsArrivedSimultaneously = 0; //Doesn't require text localization. This is for debug strings only. UINT8 gszTerrain[NUM_TRAVTERRAIN_TYPES][15] = { "TOWN", "ROAD", "PLAINS", "SAND", "SPARSE", "DENSE", "SWAMP", "WATER", "HILLS", "GROUNDBARRIER", "NS_RIVER", "EW_RIVER", "EDGEOFWORLD" }; BOOLEAN gfUndergroundTacticalTraversal = FALSE; // remembers which player group is the Continue/Stop prompt about? No need to save as long as you can't save while prompt ON GROUP *gpGroupPrompting = NULL; UINT32 uniqueIDMask[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; //Internal function manipulation prototypes UINT8 AddGroupToList( GROUP *pGroup ); void HandleOtherGroupsArrivingSimultaneously( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ ); BOOLEAN PossibleToCoordinateSimultaneousGroupArrivals( GROUP *pGroup ); void HandleNonCombatGroupArrival( GROUP *pGroup, BOOLEAN fMainGroup, BOOLEAN fNeverLeft ); GROUP *gpInitPrebattleGroup = NULL; void TriggerPrebattleInterface( UINT8 ubResult ); //Save the L.L. for the playerlist into the save game file BOOLEAN SavePlayerGroupList( HWFILE hFile, GROUP *pGroup ); //Loads the LL for the playerlist from the savegame file BOOLEAN LoadPlayerGroupList( HWFILE hFile, GROUP **pGroup ); BOOLEAN SaveEnemyGroupStruct( HWFILE hFile, GROUP *pGroup ); BOOLEAN LoadEnemyGroupStructFromSavedGame( HWFILE hFile, GROUP *pGroup ); BOOLEAN LoadWayPointList(HWFILE hFile, GROUP *pGroup ); BOOLEAN SaveWayPointList( HWFILE hFile, GROUP *pGroup ); extern void RandomMercInGroupSaysQuote( GROUP *pGroup, UINT16 usQuoteNum ); void SetLocationOfAllPlayerSoldiersInGroup( GROUP *pGroup, INT16 sSectorX, INT16 sSectorZ, INT8 bSectorZ ); //If there are bloodcats in this sector, then it internally BOOLEAN TestForBloodcatAmbush( GROUP *pGroup ); void NotifyPlayerOfBloodcatBattle( UINT8 ubSectorX, UINT8 ubSectorY ); BOOLEAN GroupBetweenSectorsAndSectorXYIsInDifferentDirection( GROUP *pGroup, UINT8 ubSectorX, UINT8 ubSectorY ); void CancelEmptyPersistentGroupMovement( GROUP *pGroup ); BOOLEAN HandlePlayerGroupEnteringSectorToCheckForNPCsOfNote( GROUP *pGroup ); BOOLEAN WildernessSectorWithAllProfiledNPCsNotSpokenWith( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ); void HandlePlayerGroupEnteringSectorToCheckForNPCsOfNoteCallback( UINT8 ubExitValue ); void DelayEnemyGroupsIfPathsCross( GROUP *pPlayerGroup ); UINT8 NumberMercsInVehicleGroup( GROUP *pGroup ); // waiting for input from user BOOLEAN gfWaitingForInput = FALSE; //Player grouping functions //......................... //Creates a new player group, returning the unique ID of that group. This is the first //step before adding waypoints and members to the player group. UINT8 CreateNewPlayerGroupDepartingFromSector( UINT8 ubSectorX, UINT8 ubSectorY ) { GROUP *pNew; AssertMsg( ubSectorX >= 1 && ubSectorX <= 16, String( "CreateNewPlayerGroup with out of range sectorX value of %d", ubSectorX ) ); AssertMsg( ubSectorY >= 1 && ubSectorY <= 16, String( "CreateNewPlayerGroup with out of range sectorY value of %d", ubSectorY ) ); pNew = (GROUP*)MemAlloc( sizeof( GROUP ) ); AssertMsg( pNew, "MemAlloc failure during CreateNewPlayerGroup." ); memset( pNew, 0, sizeof( GROUP ) ); pNew->pPlayerList = NULL; pNew->pWaypoints = NULL; pNew->ubSectorX = pNew->ubNextX = ubSectorX; pNew->ubSectorY = pNew->ubNextY = ubSectorY; pNew->ubOriginalSector = (UINT8)SECTOR( ubSectorX, ubSectorY ); pNew->fPlayer = TRUE; pNew->ubMoveType = ONE_WAY; pNew->ubNextWaypointID = 0; pNew->ubFatigueLevel = 100; pNew->ubRestAtFatigueLevel = 0; pNew->ubTransportationMask = FOOT; pNew->fVehicle = FALSE; pNew->ubCreatedSectorID = pNew->ubOriginalSector; pNew->ubSectorIDOfLastReassignment = 255; return AddGroupToList( pNew ); } UINT8 CreateNewVehicleGroupDepartingFromSector( UINT8 ubSectorX, UINT8 ubSectorY , UINT32 uiUNISEDVehicleId ) { GROUP *pNew; AssertMsg( ubSectorX >= 1 && ubSectorX <= 16, String( "CreateNewVehicleGroup with out of range sectorX value of %d", ubSectorX ) ); AssertMsg( ubSectorY >= 1 && ubSectorY <= 16, String( "CreateNewVehicleGroup with out of range sectorY value of %d", ubSectorY ) ); pNew = (GROUP*)MemAlloc( sizeof( GROUP ) ); AssertMsg( pNew, "MemAlloc failure during CreateNewVehicleGroup." ); memset( pNew, 0, sizeof( GROUP ) ); pNew->pWaypoints = NULL; pNew->ubSectorX = pNew->ubNextX = ubSectorX; pNew->ubSectorY = pNew->ubNextY = ubSectorY; pNew->ubOriginalSector = (UINT8)SECTOR( ubSectorX, ubSectorY ); pNew->ubMoveType = ONE_WAY; pNew->ubNextWaypointID = 0; pNew->ubFatigueLevel = 100; pNew->ubRestAtFatigueLevel = 0; pNew->fVehicle = TRUE; pNew->fPlayer = TRUE; pNew->pPlayerList = NULL; pNew->ubCreatedSectorID = pNew->ubOriginalSector; pNew->ubSectorIDOfLastReassignment = 255; // get the type pNew->ubTransportationMask = CAR; return( AddGroupToList( pNew ) ); } //Allows you to add players to the group. BOOLEAN AddPlayerToGroup( UINT8 ubGroupID, SOLDIERTYPE *pSoldier ) { GROUP *pGroup; PLAYERGROUP *pPlayer, *curr; pGroup = GetGroup( ubGroupID ); Assert( pGroup ); pPlayer = (PLAYERGROUP*)MemAlloc( sizeof( PLAYERGROUP ) ); Assert( pPlayer ); AssertMsg( pGroup->fPlayer, "Attempting AddPlayerToGroup() on an ENEMY group!"); pPlayer->pSoldier = pSoldier; pPlayer->ubProfileID = pSoldier->ubProfile; pPlayer->ubID = pSoldier->ubID; pPlayer->bFlags = 0; pPlayer->next = NULL; if( !pGroup->pPlayerList ) { pGroup->pPlayerList = pPlayer; pGroup->ubGroupSize = 1; pGroup->ubPrevX = (UINT8)((pSoldier->ubPrevSectorID % 16) + 1); pGroup->ubPrevY = (UINT8)((pSoldier->ubPrevSectorID / 16) + 1); pGroup->ubSectorX = (UINT8)pSoldier->sSectorX; pGroup->ubSectorY = (UINT8)pSoldier->sSectorY; pGroup->ubSectorZ = (UINT8)pSoldier->bSectorZ; // set group id pSoldier->ubGroupID = ubGroupID; return TRUE; } else { curr = pGroup->pPlayerList; pSoldier->ubNumTraversalsAllowedToMerge = curr->pSoldier->ubNumTraversalsAllowedToMerge; pSoldier->ubDesiredSquadAssignment = curr->pSoldier->ubDesiredSquadAssignment; while( curr->next ) { if( curr->ubProfileID == pSoldier->ubProfile ) AssertMsg( 0, String( "Attempting to add an already existing merc to group (ubProfile=%d).", pSoldier->ubProfile ) ); curr = curr->next; } curr->next = pPlayer; // set group id pSoldier->ubGroupID = ubGroupID; pGroup->ubGroupSize++; return TRUE; } } // remove all grunts from player mvt grp BOOLEAN RemoveAllPlayersFromGroup( UINT8 ubGroupId ) { GROUP *pGroup; // grab group id pGroup = GetGroup( ubGroupId ); // init errors checks AssertMsg( pGroup, String( "Attempting to RemovePlayerFromGroup( %d ) from non-existant group", ubGroupId) ); return RemoveAllPlayersFromPGroup( pGroup ); } BOOLEAN RemoveAllPlayersFromPGroup( GROUP *pGroup ) { PLAYERGROUP *curr; AssertMsg( pGroup->fPlayer, "Attempting RemovePlayerFromGroup() on an ENEMY group!"); curr = pGroup->pPlayerList; while( curr ) { pGroup->pPlayerList = pGroup->pPlayerList->next; curr->pSoldier->ubPrevSectorID = (UINT8)SECTOR( pGroup->ubPrevX, pGroup->ubPrevY ); curr->pSoldier->ubGroupID = 0; MemFree( curr ); curr = pGroup->pPlayerList; } pGroup->ubGroupSize = 0; if( !pGroup->fPersistant ) { //remove the empty group RemovePGroup( pGroup ); } else { CancelEmptyPersistentGroupMovement( pGroup ); } return TRUE; } BOOLEAN RemovePlayerFromPGroup( GROUP *pGroup, SOLDIERTYPE *pSoldier ) { PLAYERGROUP *prev, *curr; AssertMsg( pGroup->fPlayer, "Attempting RemovePlayerFromGroup() on an ENEMY group!" ); curr = pGroup->pPlayerList; if( !curr ) { return FALSE; } if( curr->pSoldier == pSoldier ) { //possibly the only node pGroup->pPlayerList = pGroup->pPlayerList->next; //delete the node MemFree( curr ); //process info for soldier pGroup->ubGroupSize--; pSoldier->ubPrevSectorID = (UINT8)SECTOR( pGroup->ubPrevX, pGroup->ubPrevY ); pSoldier->ubGroupID = 0; // if there's nobody left in the group if( pGroup->ubGroupSize == 0 ) { if ( !pGroup->fPersistant ) { //remove the empty group RemovePGroup( pGroup ); } else { CancelEmptyPersistentGroupMovement( pGroup ); } } return TRUE; } prev = NULL; while( curr ) { //definately more than one node if( curr->pSoldier == pSoldier ) { //detach and delete the node if( prev ) { prev->next = curr->next; } MemFree( curr ); //process info for soldier pSoldier->ubGroupID = 0; pGroup->ubGroupSize--; pSoldier->ubPrevSectorID = (UINT8)SECTOR( pGroup->ubPrevX, pGroup->ubPrevY ); return TRUE; } prev = curr; curr = curr->next; } // !curr return FALSE; } BOOLEAN RemovePlayerFromGroup( UINT8 ubGroupID, SOLDIERTYPE *pSoldier ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); //KM : August 6, 1999 Patch fix // Because the release build has no assertions, it was still possible for the group to be null, // causing a crash. Instead of crashing, it'll simply return false. if( !pGroup ) { return FALSE; } //end AssertMsg( pGroup, String( "Attempting to RemovePlayerFromGroup( %d, %d ) from non-existant group", ubGroupID, pSoldier->ubProfile ) ); return RemovePlayerFromPGroup( pGroup, pSoldier ); } BOOLEAN GroupReversingDirectionsBetweenSectors( GROUP *pGroup, UINT8 ubSectorX, UINT8 ubSectorY, BOOLEAN fBuildingWaypoints ) { // if we're not between sectors, or we are but we're continuing in the same direction as before if ( !GroupBetweenSectorsAndSectorXYIsInDifferentDirection( pGroup, ubSectorX, ubSectorY ) ) { // then there's no need to reverse directions return FALSE; } //The new direction is reversed, so we have to go back to the sector we just left. //Search for the arrival event, and kill it! DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); //Adjust the information in the group to reflect the new movement. pGroup->ubPrevX = pGroup->ubNextX; pGroup->ubPrevY = pGroup->ubNextY; pGroup->ubNextX = pGroup->ubSectorX; pGroup->ubNextY = pGroup->ubSectorY; pGroup->ubSectorX = pGroup->ubPrevX; pGroup->ubSectorY = pGroup->ubPrevY; if( pGroup->fPlayer ) { // ARM: because we've changed the group's ubSectoryX and ubSectorY, we must now also go and change the sSectorX and // sSectorY of all the soldiers in this group so that they stay in synch. Otherwise pathing and movement problems // will result since the group is in one place while the merc is in another... SetLocationOfAllPlayerSoldiersInGroup( pGroup, pGroup->ubSectorX, pGroup->ubSectorY, 0 ); } // IMPORTANT: The traverse time doesn't change just because we reverse directions! It takes the same time no matter // which direction you're going in! This becomes critical in case the player reverse directions again before moving! // The time it takes to arrive there will be exactly the amount of time we have been moving away from it. SetGroupArrivalTime( pGroup, pGroup->uiTraverseTime - pGroup->uiArrivalTime + GetWorldTotalMin() * 2 ); // if they're not already there if( pGroup->uiArrivalTime > GetWorldTotalMin() ) { //Post the replacement event to move back to the previous sector! AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ); if( pGroup->fPlayer ) { if( ( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY ) > GetWorldTotalMin( ) ) { // Post the about to arrive event AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); } } } else { // IMPORTANT: this can't be called during RebuildWayPointsForGroupPath(), since it will clear the mercpath // prematurely by assuming the mercs are now at their final destination when only the first waypoint is in place!!! // To handle this situation, RebuildWayPointsForGroupPath() will issue it's own call after it's ready for it. if ( !fBuildingWaypoints ) { // never really left. Must set check for battle TRUE in order for HandleNonCombatGroupArrival() to run! GroupArrivedAtSector( pGroup->ubGroupID, TRUE, TRUE ); } } return TRUE; } BOOLEAN GroupBetweenSectorsAndSectorXYIsInDifferentDirection( GROUP *pGroup, UINT8 ubSectorX, UINT8 ubSectorY ) { INT32 currDX, currDY, newDX, newDY; UINT8 ubNumUnalignedAxes = 0; if( !pGroup->fBetweenSectors ) return( FALSE ); // Determine the direction the group is currently traveling in currDX = pGroup->ubNextX - pGroup->ubSectorX; currDY = pGroup->ubNextY - pGroup->ubSectorY; //Determine the direction the group would need to travel in to reach the given sector newDX = ubSectorX - pGroup->ubSectorX; newDY = ubSectorY - pGroup->ubSectorY; // clip the new dx/dy values to +/- 1 if( newDX ) { ubNumUnalignedAxes++; newDX /= abs( newDX ); } if( newDY ) { ubNumUnalignedAxes++; newDY /= abs( newDY ); } // error checking if( ubNumUnalignedAxes > 1 ) { AssertMsg( FALSE, String( "Checking a diagonal move for direction change, groupID %d. AM-0", pGroup->ubGroupID ) ); return FALSE; } // Compare the dx/dy's. If they're exactly the same, group is travelling in the same direction as before, so we're not // changing directions. // Note that 90-degree orthogonal changes are considered changing direction, as well as the full 180-degree reversal. // That's because the party must return to the previous sector in each of those cases, too. if( currDX == newDX && currDY == newDY ) return( FALSE ); // yes, we're between sectors, and we'd be changing direction to go to the given sector return( TRUE ); } //Appends a waypoint to the end of the list. Waypoint MUST be on the //same horizontal or vertical level as the last waypoint added. BOOLEAN AddWaypointToPGroup( GROUP* pGroup, UINT8 ubSectorX, UINT8 ubSectorY ) //Same, but overloaded { WAYPOINT *pWay; UINT8 ubNumAlignedAxes = 0; BOOLEAN fReversingDirection = FALSE; AssertMsg( ubSectorX >= 1 && ubSectorX <= 16, String( "AddWaypointToPGroup with out of range sectorX value of %d", ubSectorX ) ); AssertMsg( ubSectorY >= 1 && ubSectorY <= 16, String( "AddWaypointToPGroup with out of range sectorY value of %d", ubSectorY ) ); if( !pGroup ) return FALSE; //At this point, we have the group, and a valid coordinate. Now we must //determine that this waypoint will be aligned exclusively to either the x or y axis of //the last waypoint in the list. pWay = pGroup->pWaypoints; if( !pWay ) { if( GroupReversingDirectionsBetweenSectors( pGroup, ubSectorX, ubSectorY, TRUE ) ) { if( pGroup->fPlayer ) { // because we reversed, we must add the new current sector back at the head of everyone's mercpath AddSectorToFrontOfMercPathForAllSoldiersInGroup( pGroup, pGroup->ubSectorX, pGroup->ubSectorY ); } //Very special case that requiring specific coding. Check out the comments //at the above function for more information. fReversingDirection = TRUE; // ARM: Kris - new rulez. Must still fall through and add a waypoint anyway!!! } else { //No waypoints, so compare against the current location. if( pGroup->ubSectorX == ubSectorX ) { ubNumAlignedAxes++; } if( pGroup->ubSectorY == ubSectorY ) { ubNumAlignedAxes++; } } } else { //we do have a waypoint list, so go to the last entry while( pWay->next ) { pWay = pWay->next; } //now, we are pointing to the last waypoint in the list if( pWay->x == ubSectorX ) { ubNumAlignedAxes++; } if( pWay->y == ubSectorY ) { ubNumAlignedAxes++; } } if ( !fReversingDirection ) { if( ubNumAlignedAxes == 0 ) { AssertMsg( FALSE, String( "Invalid DIAGONAL waypoint being added for groupID %d. AM-0", pGroup->ubGroupID ) ); return FALSE; } if( ubNumAlignedAxes >= 2 ) { AssertMsg( FALSE, String( "Invalid IDENTICAL waypoint being added for groupID %d. AM-0", pGroup->ubGroupID ) ); return FALSE; } // has to be different in exactly 1 axis to be a valid new waypoint Assert( ubNumAlignedAxes == 1 ); } if( !pWay ) { //We are adding the first waypoint. pGroup->pWaypoints = (WAYPOINT*)MemAlloc( sizeof( WAYPOINT ) ); pWay = pGroup->pWaypoints; } else { //Add the waypoint to the end of the list pWay->next = (WAYPOINT*)MemAlloc( sizeof( WAYPOINT ) ); pWay = pWay->next; } AssertMsg( pWay, "Failed to allocate memory for waypoint." ); //Fill in the information for the new waypoint. pWay->x = ubSectorX; pWay->y = ubSectorY; pWay->next = NULL; //IMPORTANT: //The first waypoint added actually initiates the group's movement to the next sector. if( pWay == pGroup->pWaypoints ) { // don't do this if we have reversed directions!!! In that case, the required work has already been done back there if ( !fReversingDirection ) { //We need to calculate the next sector the group is moving to and post an event for it. InitiateGroupMovementToNextSector( pGroup ); } } if( pGroup->fPlayer ) { PLAYERGROUP *curr; //Also, nuke any previous "tactical traversal" information. curr = pGroup->pPlayerList; while( curr ) { curr->pSoldier->ubStrategicInsertionCode = 0; curr = curr->next; } } return TRUE; } BOOLEAN AddWaypointToGroup( UINT8 ubGroupID, UINT8 ubSectorX, UINT8 ubSectorY ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); return AddWaypointToPGroup( pGroup, ubSectorX, ubSectorY ); } // NOTE: This does NOT expect a strategic sector ID BOOLEAN AddWaypointIDToGroup( UINT8 ubGroupID, UINT8 ubSectorID ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); return AddWaypointIDToPGroup( pGroup, ubSectorID ); } // NOTE: This does NOT expect a strategic sector ID BOOLEAN AddWaypointIDToPGroup( GROUP *pGroup, UINT8 ubSectorID ) { UINT8 ubSectorX, ubSectorY; ubSectorX = SECTORX( ubSectorID ); ubSectorY = SECTORY( ubSectorID ); return AddWaypointToPGroup( pGroup, ubSectorX, ubSectorY ); } BOOLEAN AddWaypointStrategicIDToGroup( UINT8 ubGroupID, UINT32 uiSectorID ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); return AddWaypointStrategicIDToPGroup( pGroup, uiSectorID ); } BOOLEAN AddWaypointStrategicIDToPGroup( GROUP *pGroup, UINT32 uiSectorID ) { UINT8 ubSectorX, ubSectorY; ubSectorX = ( UINT8 ) GET_X_FROM_STRATEGIC_INDEX( uiSectorID ); ubSectorY = ( UINT8 ) GET_Y_FROM_STRATEGIC_INDEX( uiSectorID ); return AddWaypointToPGroup( pGroup, ubSectorX, ubSectorY ); } //Enemy grouping functions -- private use by the strategic AI. //............................................................ GROUP* CreateNewEnemyGroupDepartingFromSector( UINT32 uiSector, UINT8 ubNumAdmins, UINT8 ubNumTroops, UINT8 ubNumElites ) { GROUP *pNew; AssertMsg( uiSector >= 0 && uiSector <= 255, String( "CreateNewEnemyGroup with out of range value of %d", uiSector ) ); pNew = (GROUP*)MemAlloc( sizeof( GROUP ) ); AssertMsg( pNew, "MemAlloc failure during CreateNewEnemyGroup." ); memset( pNew, 0, sizeof( GROUP ) ); pNew->pEnemyGroup = (ENEMYGROUP*)MemAlloc( sizeof( ENEMYGROUP ) ); AssertMsg( pNew->pEnemyGroup, "MemAlloc failure during enemy group creation." ); memset( pNew->pEnemyGroup, 0, sizeof( ENEMYGROUP ) ); // Make sure group is not bigger than allowed! while (ubNumAdmins + ubNumTroops + ubNumElites > gGameExternalOptions.iMaxEnemyGroupSize) { if (ubNumTroops) { ubNumTroops--; } else if (ubNumAdmins) { ubNumAdmins--; } else { ubNumElites--; } } pNew->pWaypoints = NULL; pNew->ubSectorX = (UINT8)SECTORX( uiSector ); pNew->ubSectorY = (UINT8)SECTORY( uiSector ); pNew->ubOriginalSector = (UINT8)uiSector; pNew->fPlayer = FALSE; pNew->ubMoveType = CIRCULAR; pNew->ubNextWaypointID = 0; pNew->ubFatigueLevel = 100; pNew->ubRestAtFatigueLevel = 0; pNew->pEnemyGroup->ubNumAdmins = ubNumAdmins; pNew->pEnemyGroup->ubNumTroops = ubNumTroops; pNew->pEnemyGroup->ubNumElites = ubNumElites; pNew->ubGroupSize = (UINT8)(ubNumAdmins + ubNumTroops + ubNumElites); pNew->ubTransportationMask = FOOT; pNew->fVehicle = FALSE; pNew->ubCreatedSectorID = pNew->ubOriginalSector; pNew->ubSectorIDOfLastReassignment = 255; #ifdef JA2BETAVERSION { CHAR16 str[ 512 ]; if( PlayerMercsInSector( pNew->ubSectorX, pNew->ubSectorY, 0 ) || CountAllMilitiaInSector( pNew->ubSectorX, pNew->ubSectorY ) ) { swprintf( str, L"Attempting to send enemy troops from player occupied location. " L"Please ALT+TAB out of the game before doing anything else and send 'Strategic Decisions.txt' " L"and this message. You'll likely need to revert to a previous save. If you can reproduce this " L"with a save close to this event, that would really help me! -- KM:0" ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); } /* * Not valid else if( pNew->ubGroupSize > 25 ) { swprintf( str, L"Strategic AI warning: Creating an enemy group containing %d soldiers " L"(%d admins, %d troops, %d elites) in sector %c%d. This message is a temporary test message " L"to evaluate a potential problems with very large enemy groups.", pNew->ubGroupSize, ubNumAdmins, ubNumTroops, ubNumElites, pNew->ubSectorY + 'A' - 1, pNew->ubSectorX ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); } */ } #endif if( AddGroupToList( pNew ) ) return pNew; return NULL; } //INTERNAL LIST MANIPULATION FUNCTIONS //When adding any new group to the list, this is what must be done: //1) Find the first unused ID (unique) //2) Assign that ID to the new group //3) Insert the group at the end of the list. UINT8 AddGroupToList( GROUP *pGroup ) { GROUP *curr; UINT32 bit, index, mask; unsigned ID = 0; AssertNotNIL (pGroup); AssertGE (pGroup->ubSectorX, MINIMUM_VALID_X_COORDINATE); AssertLE (pGroup->ubSectorX, MAXIMUM_VALID_X_COORDINATE); AssertGE (pGroup->ubSectorY, MINIMUM_VALID_Y_COORDINATE); AssertLE (pGroup->ubSectorY, MAXIMUM_VALID_Y_COORDINATE); AssertGE (pGroup->ubSectorZ, MINIMUM_VALID_Z_COORDINATE); AssertLE (pGroup->ubSectorZ, MAXIMUM_VALID_Z_COORDINATE); //AssertGT (pGroup->ubGroupSize, 0); //First, find a unique ID while( ++ID ) { AssertLT (ID, 256); index = ID / 32; bit = ID % 32; mask = 1 << bit; if( !(uniqueIDMask[ index ] & mask) ) { //found a free ID pGroup->ubGroupID = ID; uniqueIDMask[ index ] += mask; //add group to list now. curr = gpGroupList; if( curr ) { //point to the last item in list. while( curr->next ) curr = curr->next; curr->next = pGroup; } else //new list gpGroupList = pGroup; pGroup->next = NULL; return ID; } } return FALSE; } void RemoveGroupIdFromList( UINT8 ubId ) { GROUP *pGroup; if( ubId == 0 ) { // no group, leave return; } // get group pGroup = GetGroup( ubId ); // is there in fact a group? Assert( pGroup ); // now remove this group RemoveGroupFromList( pGroup ); } //Destroys the waypoint list, detaches group from list, then deallocated the memory for the group void RemoveGroupFromList( GROUP *pGroup ) { GROUP *curr, *temp; curr = gpGroupList; if( !curr ) return; if( curr == pGroup ) { //Removing head gpGroupList = curr->next; } else while( curr->next ) { //traverse the list if( curr->next == pGroup ) { //the next node is the one we want to remove temp = curr; //curr now points to the nod we want to remove curr = curr->next; //detach the node from the list temp->next = curr->next; break; } curr = curr->next; } if( curr == pGroup ) { //we found the group, so now remove it. UINT32 bit, index, mask; //clear the unique group ID index = pGroup->ubGroupID / 32; bit = pGroup->ubGroupID % 32; mask = 1 << bit; if( !(uniqueIDMask[ index ] & mask) ) { mask = mask; } uniqueIDMask[ index ] -= mask; MemFree( curr ); curr = NULL; } } GROUP* GetGroup( UINT8 ubGroupID ) { GROUP *curr; curr = gpGroupList; while( curr ) { if( curr->ubGroupID == ubGroupID ) return curr; curr = curr->next; } return NULL; } void HandleImportantPBIQuote( SOLDIERTYPE *pSoldier, GROUP *pInitiatingBattleGroup ) { // wake merc up for THIS quote if( pSoldier->flags.fMercAsleep ) { TacticalCharacterDialogueWithSpecialEvent( pSoldier, QUOTE_ENEMY_PRESENCE, DIALOGUE_SPECIAL_EVENT_SLEEP, 0,0 ); TacticalCharacterDialogueWithSpecialEvent( pSoldier, QUOTE_ENEMY_PRESENCE, DIALOGUE_SPECIAL_EVENT_BEGINPREBATTLEINTERFACE, (UINT32)pInitiatingBattleGroup, 0 ); TacticalCharacterDialogueWithSpecialEvent( pSoldier, QUOTE_ENEMY_PRESENCE, DIALOGUE_SPECIAL_EVENT_SLEEP, 1,0 ); } else { TacticalCharacterDialogueWithSpecialEvent( pSoldier, QUOTE_ENEMY_PRESENCE, DIALOGUE_SPECIAL_EVENT_BEGINPREBATTLEINTERFACE, (UINT32)pInitiatingBattleGroup, 0 ); } } //If this is called, we are setting the game up to bring up the prebattle interface. Before doing so, //one of the involved mercs will pipe up. When he is finished, we automatically go into the mapscreen, //regardless of the mode we are in. void PrepareForPreBattleInterface( GROUP *pPlayerDialogGroup, GROUP *pInitiatingBattleGroup ) { // ATE; Changed alogrithm here... // We first loop through the group and save ubID's ov valid guys to talk.... // ( Can't if sleeping, unconscious, and EPC, etc.... // WDS - make number of mercenaries, etc. be configurable unsigned ubMercsInGroup[ CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS ] = { 0 }; unsigned ubNumMercs = 0; unsigned ubChosenMerc; SOLDIERTYPE *pSoldier; PLAYERGROUP *pPlayer; if( fDisableMapInterfaceDueToBattle ) { // WDS Note: This can validly happen when multiple teams retreat and then immediately return to a sector. // Just returning causes no problems. // AssertMsg( 0, "fDisableMapInterfaceDueToBattle is set before attempting to bring up PBI. Please send PRIOR save if possible and details on anything that just happened before this battle." ); return; } // Pipe up with quote... AssertMsg( pPlayerDialogGroup, "Didn't get a player dialog group for prebattle interface." ); pPlayer = pPlayerDialogGroup->pPlayerList; AssertMsg( pPlayer, String( "Player group %d doesn't have *any* players in it! (Finding dialog group)", pPlayerDialogGroup->ubGroupID ) ); while( pPlayer != NULL ) { pSoldier = pPlayer->pSoldier; if ( pSoldier->stats.bLife >= OKLIFE && !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !AM_A_ROBOT( pSoldier ) && !AM_AN_EPC( pSoldier ) && !is_client ) { ubMercsInGroup[ ubNumMercs ] = pSoldier->ubID; ubNumMercs++; } pPlayer = pPlayer->next; } //Set music #ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); #endif SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); if( gfTacticalTraversal && pInitiatingBattleGroup == gpTacticalTraversalGroup || pInitiatingBattleGroup && !pInitiatingBattleGroup->fPlayer && pInitiatingBattleGroup->ubSectorX == gWorldSectorX && pInitiatingBattleGroup->ubSectorY == gWorldSectorY && !gbWorldSectorZ ) { // At least say quote.... if ( ubNumMercs > 0 ) { if( pPlayerDialogGroup->uiFlags & GROUPFLAG_JUST_RETREATED_FROM_BATTLE ) { gfCantRetreatInPBI = TRUE; } ubChosenMerc = (UINT8)Random( ubNumMercs ); pSoldier = MercPtrs[ ubMercsInGroup[ ubChosenMerc ] ]; gpTacticalTraversalChosenSoldier = pSoldier; if( !gfTacticalTraversal ) { HandleImportantPBIQuote( pSoldier, pInitiatingBattleGroup ); } InterruptTime(); PauseGame(); LockPauseState( 11 ); if( !gfTacticalTraversal ) fDisableMapInterfaceDueToBattle = TRUE; } return; } // Randomly pick a valid merc from the list we have created! if ( ubNumMercs > 0 ) { if( pPlayerDialogGroup->uiFlags & GROUPFLAG_JUST_RETREATED_FROM_BATTLE ) { gfCantRetreatInPBI = TRUE; } ubChosenMerc = (UINT8)Random( ubNumMercs ); pSoldier = MercPtrs[ ubMercsInGroup[ ubChosenMerc ] ]; HandleImportantPBIQuote( pSoldier, pInitiatingBattleGroup ); InterruptTime(); PauseGame(); LockPauseState( 12 ); // disable exit from mapscreen and what not until face done talking fDisableMapInterfaceDueToBattle = TRUE; } else { // ATE: What if we have unconscious guys, etc.... // We MUST start combat, but donot play quote... InitPreBattleInterface( pInitiatingBattleGroup, TRUE ); } } #ifdef JA2BETAVERSION extern void ValidatePlayersAreInOneGroupOnly(); #endif BOOLEAN CheckConditionsForBattle( GROUP *pGroup ) { GROUP *curr; GROUP *pPlayerDialogGroup = NULL; PLAYERGROUP *pPlayer; SOLDIERTYPE *pSoldier; BOOLEAN fBattlePending = FALSE; BOOLEAN fAliveMerc = FALSE; BOOLEAN fMilitiaPresent = FALSE; BOOLEAN fCombatAbleMerc = FALSE; BOOLEAN fBloodCatAmbush = FALSE; if( gfWorldLoaded ) { //look for people arriving in the currently loaded sector. This handles reinforcements. curr = FindMovementGroupInSector( (UINT8)gWorldSectorX, (UINT8)gWorldSectorY, TRUE ); if( !gbWorldSectorZ && PlayerMercsInSector( (UINT8)gWorldSectorX, (UINT8)gWorldSectorY, gbWorldSectorZ ) && pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && curr ) { //Reinforcements have arrived! #ifdef JA2BETAVERSION if( guiCurrentScreen == AIVIEWER_SCREEN ) { gfExitViewer = TRUE; } #endif if( gTacticalStatus.fEnemyInSector ) { HandleArrivalOfReinforcements( pGroup ); return( TRUE ); } } } if( !DidGameJustStart() ) { gubEnemyEncounterCode = NO_ENCOUNTER_CODE; } HandleOtherGroupsArrivingSimultaneously( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ); curr = gpGroupList; while( curr ) { if( curr->fPlayer && curr->ubGroupSize ) { if( !curr->fBetweenSectors ) { if( curr->ubSectorX == pGroup->ubSectorX && curr->ubSectorY == pGroup->ubSectorY && !curr->ubSectorZ ) { if( !GroupHasInTransitDeadOrPOWMercs( curr ) && (!IsGroupTheHelicopterGroup( curr ) || !fHelicopterIsAirBorne) && (!curr->fVehicle || NumberMercsInVehicleGroup( curr )) ) { //Now, a player group is in this sector. Determine if the group contains any mercs that can fight. //Vehicles, EPCs and the robot doesn't count. Mercs below OKLIFE do. pPlayer = curr->pPlayerList; while( pPlayer ) { pSoldier = pPlayer->pSoldier; if( !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { if( !AM_A_ROBOT( pSoldier ) && !AM_AN_EPC( pSoldier ) && pSoldier->stats.bLife >= OKLIFE ) { fCombatAbleMerc = TRUE; } if( pSoldier->stats.bLife > 0 ) { fAliveMerc = TRUE; } } pPlayer = pPlayer->next; } if( !pPlayerDialogGroup && fCombatAbleMerc ) { pPlayerDialogGroup = curr; } if( fCombatAbleMerc ) { break; } } } } } curr = curr->next; } if( pGroup->fPlayer ) { pPlayerDialogGroup = pGroup; if( NumEnemiesInSector( pGroup->ubSectorX, pGroup->ubSectorY ) && (!IsGroupTheHelicopterGroup( pGroup ) || (gGameSettings.fOptions[ TOPTION_SILENT_SKYRIDER ] == FALSE))) { fBattlePending = TRUE; StopTimeCompression(); } if( pGroup->uiFlags & GROUPFLAG_HIGH_POTENTIAL_FOR_AMBUSH && fBattlePending ) { //This group has just arrived in a new sector from an adjacent sector that he retreated from //If this battle is an encounter type battle, then there is a 90% chance that the battle will //become an ambush scenario. gfHighPotentialForAmbush = TRUE; } //If there are bloodcats in this sector, then it internally checks and handles it if( TestForBloodcatAmbush( pGroup ) ) { fBloodCatAmbush = TRUE; fBattlePending = TRUE; StopTimeCompression(); } if( fBattlePending && (!fBloodCatAmbush || gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE) ) { if( PossibleToCoordinateSimultaneousGroupArrivals( pGroup ) ) { return FALSE; } } } else { if( CountAllMilitiaInSector( pGroup->ubSectorX, pGroup->ubSectorY ) ) { fMilitiaPresent = TRUE; fBattlePending = TRUE; } if( fAliveMerc ) { fBattlePending = TRUE; } } if( !fAliveMerc && !fMilitiaPresent ) { //empty vehicle, everyone dead, don't care. Enemies don't care. return FALSE; } if( fBattlePending ) { //A battle is pending, but the players could be all unconcious or dead. //Go through every group until we find at least one concious merc. The looping will determine //if there are any live mercs and/or concious ones. If there are no concious mercs, but alive ones, //then we will go straight to autoresolve, where the enemy will likely annihilate them or capture them. //If there are no alive mercs, then there is nothing anybody can do. The enemy will completely ignore //this, and continue on. // WANNE: ValidateGroups now returns a BOOLEAN Value (TRUE/FALSE) if the attacking is valid. // This fixes the problem that 0 enemies could attack a sector if (!ValidateGroups( pGroup )) return FALSE; StopTimeCompression(); if( gubNumGroupsArrivedSimultaneously ) { //Because this is a battle case, clear all the group flags curr = gpGroupList; while( curr && gubNumGroupsArrivedSimultaneously ) { if( curr->uiFlags & GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY ) { curr->uiFlags &= ~GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY; gubNumGroupsArrivedSimultaneously--; } curr = curr->next; } } gpInitPrebattleGroup = pGroup; if( gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE || gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ) { NotifyPlayerOfBloodcatBattle( pGroup->ubSectorX, pGroup->ubSectorY ); return TRUE; } if( !fCombatAbleMerc ) { //Prepare for instant autoresolve. gfDelayAutoResolveStart = TRUE; gfUsePersistantPBI = TRUE; if( fMilitiaPresent ) { NotifyPlayerOfInvasionByEnemyForces( pGroup->ubSectorX, pGroup->ubSectorY, 0, TriggerPrebattleInterface ); if( GetTownIdForSector( pGroup->ubSectorX, pGroup->ubSectorY ) == BLANK_SECTOR) { // CHAR16 str[ 256 ]; // UINT16 uiSectorC = L'A' + pGroup->ubSectorY - 1; // swprintf( str, gpStrategicString[ STR_DIALOG_ENEMIES_ATTACK_MILITIA ], uiSectorC, pGroup->ubSectorX ); // DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, TriggerPrebattleInterface ); TriggerPrebattleInterface(1); } } else { CHAR16 str[ 256 ]; CHAR16 pSectorStr[ 128 ]; GetSectorIDString( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ , pSectorStr, TRUE ); swprintf( str, gpStrategicString[ STR_DIALOG_ENEMIES_ATTACK_UNCONCIOUSMERCS ], pSectorStr ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, TriggerPrebattleInterface ); } } #ifdef JA2BETAVERSION if( guiCurrentScreen == AIVIEWER_SCREEN ) gfExitViewer = TRUE; #endif if( pPlayerDialogGroup ) { if( !pGroup->ubSectorZ )MilitiaHelpFromAdjacentSectors( pGroup->ubSectorX, pGroup->ubSectorY ); PrepareForPreBattleInterface( pPlayerDialogGroup, pGroup ); } return TRUE; } return FALSE; } void TriggerPrebattleInterface( UINT8 ubResult ) { StopTimeCompression(); SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_TRIGGERPREBATTLEINTERFACE, (UINT32)gpInitPrebattleGroup, 0, 0, 0, 0 ); gpInitPrebattleGroup = NULL; } void DeployGroupToSector( GROUP *pGroup ) { Assert( pGroup ); if( pGroup->fPlayer ) { //Update the sector positions of the players... return; } //Assuming enemy code from here on... } //This will get called after a battle is auto-resolved or automatically after arriving //at the next sector during a move and the area is clear. void CalculateNextMoveIntention( GROUP *pGroup ) { INT32 i; WAYPOINT *wp; Assert( pGroup ); //TEMP: Ignore resting... //Should be surely an enemy group that has just made a new decision to go elsewhere! if( pGroup->fBetweenSectors ) { return; } if( !pGroup->pWaypoints ) { return; } //If the waypoints have been cancelled, then stop moving. /* if( pGroup->fWaypointsCancelled ) { DeployGroupToSector( pGroup ); return; } */ //Determine if we are at a waypoint. i = pGroup->ubNextWaypointID; wp = pGroup->pWaypoints; while( i-- ) { //Traverse through the waypoint list to the next waypoint ID Assert( wp ); wp = wp->next; } Assert( wp ); //We have the next waypoint, now check if we are actually there. if( pGroup->ubSectorX == wp->x && pGroup->ubSectorY == wp->y ) { //We have reached the next waypoint, so now determine what the next waypoint is. switch( pGroup->ubMoveType ) { case ONE_WAY: if( !wp->next ) { //No more waypoints, so we've reached the destination. DeployGroupToSector( pGroup ); return; } //Advance destination to next waypoint ID pGroup->ubNextWaypointID++; break; case CIRCULAR: wp = wp->next; if( !wp ) { //reached the end of the patrol route. Set to the first waypoint in list, indefinately. //NOTE: If the last waypoint isn't exclusively aligned to the x or y axis of the first // waypoint, there will be an assertion failure inside the waypoint movement code. pGroup->ubNextWaypointID = 0; } else pGroup->ubNextWaypointID++; break; case ENDTOEND_FORWARDS: wp = wp->next; if( !wp ) { AssertMsg( pGroup->ubNextWaypointID, "EndToEnd patrol group needs more than one waypoint!" ); pGroup->ubNextWaypointID--; pGroup->ubMoveType = ENDTOEND_BACKWARDS; } else pGroup->ubNextWaypointID++; break; case ENDTOEND_BACKWARDS: if( !pGroup->ubNextWaypointID ) { pGroup->ubNextWaypointID++; pGroup->ubMoveType = ENDTOEND_FORWARDS; } else pGroup->ubNextWaypointID--; break; } } InitiateGroupMovementToNextSector( pGroup ); } BOOLEAN AttemptToMergeSeparatedGroups( GROUP *pGroup, BOOLEAN fDecrementTraversals ) { //SOLDIERTYPE *pSoldier = NULL, *pCharacter = NULL; #ifdef JA2BETAVERSION //INT32 counter = 0; #endif return FALSE; #if 0 //First, make sure that we have a player group that isn't empty if( !pGroup->fPlayer || !pGroup->ubGroupSize ) return FALSE; //If our group doesn't have any traversals left to be allowed to merge, then no chance. if( !pGroup->pPlayerList->pSoldier->ubNumTraversalsAllowedToMerge ) return FALSE; //Reset the merged flags because this group has just arrived in a new sector pPlayer = pGroup->pPlayerList; while( pPlayer ) { pPlayer->pSoldier->flags.uiStatusFlags &= ~SOLDIER_IS_TACTICALLY_VALID; pPlayer = pPlayer->next; } //Now, because the group just arrived in the sector, and he currently is allowed to merge, then //decrement that num traversals. if( fDecrementTraversals ) { pPlayer = pGroup->pPlayerList; while( pPlayer ) { if( pPlayer->pSoldier->ubNumTraversalsAllowedToMerge == 100 ) pPlayer->pSoldier->ubNumTraversalsAllowedToMerge = 0; else pPlayer->pSoldier->ubNumTraversalsAllowedToMerge--; pPlayer = pPlayer->next; } } curr = gpGroupList; if( pGroup->pPlayerList->pSoldier->ubDesiredSquadAssignment != NO_ASSIGNMENT ) { //This group is looking for another group to merge with. while( curr ) { if( curr != pGroup && curr->fPlayer && curr->ubGroupSize ) { //We have a player group with members if( curr->pPlayerList->pSoldier->ubNumTraversalsAllowedToMerge ) { //This group is allowed to merge if( curr->ubSectorX == pGroup->ubSectorX && curr->ubSectorY == pGroup->ubSectorY && curr->pPlayerList->pSoldier->bSectorZ == pGroup->pPlayerList->pSoldier->bSectorZ ) { //This group is in the same sector as us if( curr->pPlayerList->pSoldier->bAssignment == pGroup->pPlayerList->pSoldier->ubDesiredSquadAssignment || curr->pPlayerList->pSoldier->ubDesiredSquadAssignment == pGroup->pPlayerList->pSoldier->ubDesiredSquadAssignment ) { //This group is the one we want to join (or has the same desire)! pSoldier = pGroup->pPlayerList->pSoldier; //First, set up flags for the current group to not relocate and walk in sector (they are already there) pPlayer = curr->pPlayerList; while( pPlayer ) { if( pPlayer->pSoldier->flags.uiStatusFlags & SOLDIER_IS_TACTICALLY_VALID ) { pPlayer->pSoldier->flags.uiStatusFlags |= SOLDIER_IS_TACTICALLY_VALID; } pPlayer = pPlayer->next; } while( pGroup->ubGroupSize && curr->ubGroupSize < NUMBER_OF_SOLDIERS_PER_SQUAD ) { //while there is room in the new group, move one soldier at a time automatically. #ifdef JA2BETAVERSION counter++; if( counter > 100 ) { AssertMsg( FALSE, L"Aborting infinite loop in merge group code #2. (KM : 1)" ); return FALSE; } #endif if( curr->pPlayerList->pSoldier->bAssignment < ON_DUTY ) { RemoveCharacterFromSquads( pSoldier ); AddCharacterToSquad( pSoldier, curr->pPlayerList->pSoldier->bAssignment ); } else { // KRIS NEEDS TO FIX THIS BIG TIME!!! SetSoldierAssignment( pSoldier, curr->pPlayerList->pSoldier->bAssignment, 0, 0, 0 ); RemovePlayerFromGroup( pGroup->ubGroupID, pSoldier ); AddPlayerToGroup( curr->ubGroupID, pSoldier ); } } //Group successfully merged -- albeit one or more troops only! fSuccess = TRUE; } } } } curr = curr->next; } } else if( pGroup->ubGroupSize < NUMBER_OF_SOLDIERS_PER_SQUAD ) { //Search for other groups looking to join our group. while( curr ) { if( curr != pGroup && curr->fPlayer && curr->ubGroupSize ) { //We have a player group with members if( curr->pPlayerList->pSoldier->ubNumTraversalsAllowedToMerge ) { //This group is allowed to merge if( curr->ubSectorX == pGroup->ubSectorX && curr->ubSectorY == pGroup->ubSectorY ) { //This group is in the same sector as us if( curr->pPlayerList->pSoldier->ubDesiredSquadAssignment == pGroup->pPlayerList->pSoldier->bAssignment || curr->pPlayerList->pSoldier->ubDesiredSquadAssignment == pGroup->pPlayerList->pSoldier->ubDesiredSquadAssignment ) { //This group wants to join our group! //First, set up flags for the current group to not relocate and walk in sector (they are already there) pPlayer = curr->pPlayerList; //if( curr->ubSectorX while( pPlayer ) { if( pPlayer->pSoldier->flags.uiStatusFlags & SOLDIER_IS_TACTICALLY_VALID ) { pPlayer->pSoldier->flags.uiStatusFlags |= SOLDIER_IS_TACTICALLY_VALID; } pPlayer = pPlayer->next; } while( curr->ubGroupSize && pGroup->ubGroupSize < NUMBER_OF_SOLDIERS_PER_SQUAD ) { //while there is room in the new group, move one soldier at a time automatically. #ifdef JA2BETAVERSION counter++; if( counter > 100 ) { AssertMsg( FALSE, L"Aborting infinite loop in merge group code #3. (KM : 1)" ); return FALSE; } #endif pSoldier = curr->pPlayerList->pSoldier; if( pGroup->pPlayerList->pSoldier->bAssignment < ON_DUTY ) { RemoveCharacterFromSquads( pSoldier ); AddCharacterToSquad( pSoldier, pGroup->pPlayerList->pSoldier->bAssignment ); } else { SetSoldierAssignment( pSoldier, curr->pPlayerList->pSoldier->bAssignment, 0, 0, 0 ); RemovePlayerFromGroup( curr->ubGroupID, pSoldier ); AddPlayerToGroup( pGroup->ubGroupID, pSoldier ); } } //Group successfully merged -- albeit one or more troops only! fSuccess = TRUE; } } } } curr = curr->next; } } return fSuccess; #endif } void AwardExperienceForTravelling( GROUP * pGroup ) { // based on how long movement took, mercs gain a bit of life experience for travelling PLAYERGROUP * pPlayerGroup; SOLDIERTYPE * pSoldier; UINT32 uiPoints; UINT32 uiCarriedPercent; if ( !pGroup || !pGroup->fPlayer ) { return; } pPlayerGroup = pGroup->pPlayerList; while ( pPlayerGroup ) { pSoldier = pPlayerGroup->pSoldier; if( pSoldier && !AM_A_ROBOT( pSoldier ) && !AM_AN_EPC( pSoldier ) && !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { if ( pSoldier->stats.bLifeMax < 100 ) { // award exp... // amount was originally based on getting 100-bLifeMax points for 12 hours of travel (720) // but changed to flat rate since StatChange makes roll vs 100-lifemax as well! uiPoints = pGroup->uiTraverseTime / (450 / (100 - pSoldier->stats.bLifeMax ) ); if ( uiPoints > 0 ) { StatChange( pSoldier, HEALTHAMT, (UINT8) uiPoints, FALSE ); } } if ( pSoldier->stats.bStrength < 100 ) { uiCarriedPercent = CalculateCarriedWeight( pSoldier ); if ( uiCarriedPercent > 50 ) { uiPoints = pGroup->uiTraverseTime / (450 / (100 - pSoldier->stats.bStrength ) ); StatChange( pSoldier, STRAMT, ( UINT16 ) ( uiPoints * ( uiCarriedPercent - 50) / 100 ), FALSE ); } } } pPlayerGroup = pPlayerGroup->next; } } void AddCorpsesToBloodcatLair( INT16 sSectorX, INT16 sSectorY ) { ROTTING_CORPSE_DEFINITION Corpse; INT16 sXPos, sYPos; memset( &Corpse, 0, sizeof( ROTTING_CORPSE_DEFINITION ) ); // Setup some values! Corpse.ubBodyType = REGMALE; Corpse.sHeightAdjustment = 0; Corpse.bVisible = TRUE; SET_PALETTEREP_ID ( Corpse.HeadPal, "BROWNHEAD" ); SET_PALETTEREP_ID ( Corpse.VestPal, "YELLOWVEST" ); SET_PALETTEREP_ID ( Corpse.SkinPal, "PINKSKIN" ); SET_PALETTEREP_ID ( Corpse.PantsPal, "GREENPANTS" ); Corpse.ubDirection = (UINT8)Random(8); // Set time of death // Make sure they will be rotting! Corpse.uiTimeOfDeath = GetWorldTotalMin( ) - ( 2 * NUM_SEC_IN_DAY / 60 ); // Set type Corpse.ubType = (UINT8)SMERC_JFK; Corpse.usFlags = ROTTING_CORPSE_FIND_SWEETSPOT_FROM_GRIDNO; // 1st gridno Corpse.sGridNo = 14319; //dnl!!! ConvertGridNoToXY( Corpse.sGridNo, &sXPos, &sYPos ); Corpse.dXPos = (FLOAT)( CenterX( sXPos ) ); Corpse.dYPos = (FLOAT)( CenterY( sYPos ) ); //Add the rotting corpse info to the sectors unloaded rotting corpse file AddRottingCorpseToUnloadedSectorsRottingCorpseFile( sSectorX, sSectorY, 0, &Corpse); // 2nd gridno Corpse.sGridNo = 9835; //dnl!!! ConvertGridNoToXY( Corpse.sGridNo, &sXPos, &sYPos ); Corpse.dXPos = (FLOAT)( CenterX( sXPos ) ); Corpse.dYPos = (FLOAT)( CenterY( sYPos ) ); //Add the rotting corpse info to the sectors unloaded rotting corpse file AddRottingCorpseToUnloadedSectorsRottingCorpseFile( sSectorX, sSectorY, 0, &Corpse); // 3rd gridno Corpse.sGridNo = 11262; //dnl!!! ConvertGridNoToXY( Corpse.sGridNo, &sXPos, &sYPos ); Corpse.dXPos = (FLOAT)( CenterX( sXPos ) ); Corpse.dYPos = (FLOAT)( CenterY( sYPos ) ); //Add the rotting corpse info to the sectors unloaded rotting corpse file AddRottingCorpseToUnloadedSectorsRottingCorpseFile( sSectorX, sSectorY, 0, &Corpse); } //ARRIVALCALLBACK //............... //This is called whenever any group arrives in the next sector (player or enemy) //This function will first check to see if a battle should start, or if they //aren't at the final destination, they will move to the next sector. void GroupArrivedAtSector( UINT8 ubGroupID, BOOLEAN fCheckForBattle, BOOLEAN fNeverLeft ) { GROUP *pGroup; INT32 iVehId = -1; PLAYERGROUP *curr; UINT8 ubInsertionDirection, ubStrategicInsertionCode; SOLDIERTYPE *pSoldier = NULL; BOOLEAN fExceptionQueue = FALSE; BOOLEAN fFirstTimeInSector = FALSE; BOOLEAN fGroupDestroyed = FALSE; // reset gfWaitingForInput = FALSE; // grab the group and see if valid pGroup = GetGroup( ubGroupID ); if( pGroup == NULL ) { return; } // group arrival already processed? if ( !pGroup->fBetweenSectors && pGroup->ubNextX == 0 && pGroup->ubNextY == 0 ) { return; } if( pGroup->fPlayer ) { //Set the fact we have visited the sector curr = pGroup->pPlayerList; if( curr ) { if( curr->pSoldier->bAssignment < ON_DUTY ) { ResetDeadSquadMemberList( curr->pSoldier->bAssignment ); } } while( curr ) { curr->pSoldier->flags.uiStatusFlags &= ~SOLDIER_SHOULD_BE_TACTICALLY_VALID; curr = curr->next; } if( pGroup->fVehicle ) { if( ( iVehId = ( GivenMvtGroupIdFindVehicleId( ubGroupID ) ) ) != -1 ) { if( iVehId != iHelicopterVehicleId ) { if( pGroup->pPlayerList == NULL ) { // nobody here, better just get out now // with vehicles, arriving empty is probably ok, since passengers might have been killed but vehicle lived. return; } } } } else { if( pGroup->pPlayerList == NULL ) { // nobody here, better just get out now AssertMsg( 0, String( "Player group %d arrived in sector empty. KM 0", ubGroupID ) ); return; } } } //Check for exception cases which if( gTacticalStatus.bBoxingState != NOT_BOXING ) { if( !pGroup->fPlayer && pGroup->ubNextX == 5 && pGroup->ubNextY == 4 && pGroup->ubSectorZ == 0 ) { fExceptionQueue = TRUE; } } //First check if the group arriving is going to queue another battle. //NOTE: We can't have more than one battle ongoing at a time. if( fExceptionQueue || fCheckForBattle && gTacticalStatus.fEnemyInSector && FindMovementGroupInSector( (UINT8)gWorldSectorX, (UINT8)gWorldSectorY, TRUE ) && (pGroup->ubNextX != gWorldSectorX || pGroup->ubNextY != gWorldSectorY || gbWorldSectorZ > 0 ) || #if (defined JA2UB) //Ja25: NO meanwhiles #else AreInMeanwhile() || #endif //KM : Aug 11, 1999 -- Patch fix: Added additional checks to prevent a 2nd battle in the case // where the player is involved in a potential battle with bloodcats/civilians fCheckForBattle && HostileCiviliansPresent() || fCheckForBattle && HostileBloodcatsPresent() ) { //QUEUE BATTLE! //Delay arrival by a random value ranging from 3-5 minutes, so it doesn't get the player //too suspicious after it happens to him a few times, which, by the way, is a rare occurrence. #if (defined JA2UB) /*Ja25: No meanwhiles*/ #else if( AreInMeanwhile() ) { pGroup->uiArrivalTime ++; //tack on only 1 minute if we are in a meanwhile scene. This effectively //prevents any battle from occurring while inside a meanwhile scene. } else #endif { pGroup->uiArrivalTime += Random(3) + 3; } if( !AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ) ) AssertMsg( 0, "Failed to add movement event." ); if( pGroup->fPlayer ) { if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) { AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); } } return; } //Update the position of the group pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY; pGroup->ubSectorX = pGroup->ubNextX; pGroup->ubSectorY = pGroup->ubNextY; pGroup->ubNextX = 0; pGroup->ubNextY = 0; if( pGroup->fPlayer ) { if( pGroup->ubSectorZ == 0 ) { SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].bLastKnownEnemies = NumEnemiesInSector( pGroup->ubSectorX, pGroup->ubSectorY ); } // award life 'experience' for travelling, based on travel time! if ( !pGroup->fVehicle ) { // gotta be walking to get tougher AwardExperienceForTravelling( pGroup ); } else if( !IsGroupTheHelicopterGroup( pGroup ) ) { SOLDIERTYPE *pSoldier; INT32 iVehicleID; iVehicleID = GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ); AssertMsg( iVehicleID != -1, "GroupArrival for vehicle group. Invalid iVehicleID. " ); pSoldier = GetSoldierStructureForVehicle( iVehicleID ); AssertMsg( pSoldier, "GroupArrival for vehicle group. Invalid soldier pointer." ); SpendVehicleFuel( pSoldier, (INT16)(pGroup->uiTraverseTime*6) ); if( !VehicleFuelRemaining( pSoldier ) ) { ReportVehicleOutOfGas( iVehicleID, pGroup->ubSectorX, pGroup->ubSectorY ); //Nuke the group's path, so they don't continue moving. ClearMercPathsAndWaypointsForAllInGroup( pGroup ); } } } pGroup->uiTraverseTime = 0; SetGroupArrivalTime( pGroup, 0 ); pGroup->fBetweenSectors = FALSE; fMapPanelDirty = TRUE; fMapScreenBottomDirty = TRUE; // if a player group if( pGroup->fPlayer ) { // if this is the last sector along player group's movement path (no more waypoints) if ( GroupAtFinalDestination( pGroup ) ) { // clear their strategic movement (mercpaths and waypoints) ClearMercPathsAndWaypointsForAllInGroup( pGroup ); } // if on surface if( pGroup->ubSectorZ == 0 ) { // check for discovering secret locations INT8 bTownId = GetTownIdForSector( pGroup->ubSectorX, pGroup->ubSectorY ); if ( gfHiddenTown[ bTownId ] == FALSE ) { gfHiddenTown[ bTownId ] = TRUE; if ( gfIconTown[ bTownId ] == TRUE ) gfDrawHiddenTown[ bTownId ] = TRUE; } #ifdef JA2UB //Ja25: No tixa, orta or sam site #else /* if( bTownId == TIXA ) SetTixaAsFound(); else if( bTownId == ORTA ) SetOrtaAsFound(); else */ if( IsThisSectorASAMSector( pGroup->ubSectorX, pGroup->ubSectorY, 0 ) ) SetSAMSiteAsFound( GetSAMIdFromSector( pGroup->ubSectorX, pGroup->ubSectorY, 0 ) ); #endif } if( pGroup->ubSectorX < pGroup->ubPrevX ) { ubInsertionDirection = SOUTHWEST; ubStrategicInsertionCode = INSERTION_CODE_EAST; } else if( pGroup->ubSectorX > pGroup->ubPrevX ) { ubInsertionDirection = NORTHEAST; ubStrategicInsertionCode = INSERTION_CODE_WEST; } else if( pGroup->ubSectorY < pGroup->ubPrevY ) { ubInsertionDirection = NORTHWEST; ubStrategicInsertionCode = INSERTION_CODE_SOUTH; } else if( pGroup->ubSectorY > pGroup->ubPrevY ) { ubInsertionDirection = SOUTHEAST; ubStrategicInsertionCode = INSERTION_CODE_NORTH; } else { Assert(0); return; } if( pGroup->fVehicle == FALSE ) { // non-vehicle player group curr = pGroup->pPlayerList; while( curr ) { curr->pSoldier->flags.fBetweenSectors = FALSE; curr->pSoldier->sSectorX = pGroup->ubSectorX; curr->pSoldier->sSectorY = pGroup->ubSectorY; curr->pSoldier->bSectorZ = pGroup->ubSectorZ; curr->pSoldier->ubPrevSectorID = (UINT8)SECTOR( pGroup->ubPrevX, pGroup->ubPrevY ); curr->pSoldier->ubInsertionDirection = ubInsertionDirection; // don't override if a tactical traversal if( curr->pSoldier->ubStrategicInsertionCode != INSERTION_CODE_PRIMARY_EDGEINDEX && curr->pSoldier->ubStrategicInsertionCode != INSERTION_CODE_SECONDARY_EDGEINDEX ) { curr->pSoldier->ubStrategicInsertionCode = ubStrategicInsertionCode; } if( curr->pSoldier->pMercPath ) { // remove head from their mapscreen path list curr->pSoldier->pMercPath = RemoveHeadFromStrategicPath( curr->pSoldier->pMercPath ); } // ATE: Alrighty, check if this sector is currently loaded, if so, // add them to the tactical engine! if ( pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && pGroup->ubSectorZ == gbWorldSectorZ ) { UpdateMercInSector( curr->pSoldier, gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); } curr = curr->next; } // if there's anybody in the group if( pGroup->pPlayerList ) { // don't print any messages when arriving underground (there's no delay involved) or if we never left (cancel) if ( GroupAtFinalDestination( pGroup ) && ( pGroup->ubSectorZ == 0 ) && !fNeverLeft ) { // if assigned to a squad if( pGroup->pPlayerList->pSoldier->bAssignment < ON_DUTY ) { // squad // HEADROCK HAM 3.6: Messages are no longer yellow by default. ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pAssignmentStrings[ pGroup->pPlayerList->pSoldier->bAssignment ], pMapVertIndex[ pGroup->pPlayerList->pSoldier->sSectorY ], pMapHortIndex[ pGroup->pPlayerList->pSoldier->sSectorX ]); } else { // a loner // HEADROCK HAM 3.6: Messages are no longer yellow by default. ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pGroup->pPlayerList->pSoldier->name, pMapVertIndex[ pGroup->pPlayerList->pSoldier->sSectorY ], pMapHortIndex[ pGroup->pPlayerList->pSoldier->sSectorX ] ); } } } } else // vehicle player group { iVehId = GivenMvtGroupIdFindVehicleId( ubGroupID ); Assert(iVehId != -1 ); if( pVehicleList[ iVehId ].pMercPath ) { // remove head from vehicle's mapscreen path list pVehicleList[ iVehId ].pMercPath = RemoveHeadFromStrategicPath( pVehicleList[ iVehId ].pMercPath ); } // update vehicle position SetVehicleSectorValues( iVehId, pGroup->ubSectorX, pGroup->ubSectorY ); pVehicleList[ iVehId ].fBetweenSectors = FALSE; // update passengers position UpdatePositionOfMercsInVehicle( iVehId ); if( iVehId != iHelicopterVehicleId ) { pSoldier = GetSoldierStructureForVehicle( iVehId ); Assert( pSoldier ); pSoldier->flags.fBetweenSectors = FALSE; pSoldier->sSectorX = pGroup->ubSectorX; pSoldier->sSectorY = pGroup->ubSectorY; pSoldier->bSectorZ = pGroup->ubSectorZ; pSoldier->ubInsertionDirection = ubInsertionDirection; // ATE: Removed, may 21 - sufficient to use insertion direction... //pSoldier->pathing.bDesiredDirection = ubInsertionDirection; pSoldier->ubStrategicInsertionCode = ubStrategicInsertionCode; // if this sector is currently loaded if ( pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && pGroup->ubSectorZ == gbWorldSectorZ ) { // add vehicle to the tactical engine! UpdateMercInSector( pSoldier, gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); } // set directions of insertion curr = pGroup->pPlayerList; while( curr ) { curr->pSoldier->flags.fBetweenSectors = FALSE; curr->pSoldier->sSectorX = pGroup->ubSectorX; curr->pSoldier->sSectorY = pGroup->ubSectorY; curr->pSoldier->bSectorZ = pGroup->ubSectorZ; curr->pSoldier->ubInsertionDirection = ubInsertionDirection; // ATE: Removed, may 21 - sufficient to use insertion direction... // curr->pSoldier->pathing.bDesiredDirection = ubInsertionDirection; curr->pSoldier->ubStrategicInsertionCode = ubStrategicInsertionCode; // if this sector is currently loaded if ( pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && pGroup->ubSectorZ == gbWorldSectorZ ) { // add passenger to the tactical engine! UpdateMercInSector( curr->pSoldier, gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); } curr = curr->next; } } else { if( HandleHeliEnteringSector( pVehicleList[ iVehId ].sSectorX, pVehicleList[ iVehId ].sSectorY ) == TRUE ) { // helicopter destroyed fGroupDestroyed = TRUE; } } if ( !fGroupDestroyed ) { // don't print any messages when arriving underground, there's no delay involved if ( GroupAtFinalDestination( pGroup ) && ( pGroup->ubSectorZ == 0 ) && !fNeverLeft ) { // HEADROCK HAM 3.6: Messages are no longer yellow by default. // ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pVehicleStrings[ pVehicleList[ iVehId ].ubVehicleType ], pMapVertIndex[ pGroup->ubSectorY ], pMapHortIndex[ pGroup->ubSectorX ] ); ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], gNewVehicle[ pVehicleList[ iVehId ].ubVehicleType ].NewVehicleStrings, pMapVertIndex[ pGroup->ubSectorY ], pMapHortIndex[ pGroup->ubSectorX ] ); } } } if ( !fGroupDestroyed ) { // check if sector had been visited previously fFirstTimeInSector = !GetSectorFlagStatus( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ, SF_ALREADY_VISITED ); if (fFirstTimeInSector) { AddExtraItems( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ); } // on foot, or in a vehicle other than the chopper if ( !pGroup->fVehicle || !IsGroupTheHelicopterGroup( pGroup ) ) { // ATE: Add a few corpse to the bloodcat lair... // HEADROCK: No longer hardcoded to I16 if ( SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) == gubBloodcatLairSectorId && fFirstTimeInSector ) { AddCorpsesToBloodcatLair( pGroup->ubSectorX, pGroup->ubSectorY ); } // mark the sector as visited already SetSectorFlag( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ, SF_ALREADY_VISITED ); } } // update character info fTeamPanelDirty = TRUE; fCharacterInfoPanelDirty = TRUE; } if ( !fGroupDestroyed ) { //Determine if a battle should start. //if a battle does start, or get's delayed, then we will keep the group in memory including //all waypoints, until after the battle is resolved. At that point, we will continue the processing. if( fCheckForBattle && !CheckConditionsForBattle( pGroup ) && !gfWaitingForInput ) { GROUP *next; HandleNonCombatGroupArrival( pGroup, TRUE, fNeverLeft ); if( gubNumGroupsArrivedSimultaneously ) { pGroup = gpGroupList; while( gubNumGroupsArrivedSimultaneously && pGroup ) { next = pGroup->next; // WDS - Fix bug with 0 size group arriving at 0,0,0 if( (pGroup->ubGroupSize > 0) && pGroup->uiFlags & GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY ) { gubNumGroupsArrivedSimultaneously--; HandleNonCombatGroupArrival( pGroup, FALSE, FALSE ); } pGroup = next; } } } else { //Handle cases for pre battle conditions pGroup->uiFlags = 0; if( gubNumAwareBattles ) { //When the AI is looking for the players, and a battle is initiated, then //decrement the value, otherwise the queen will continue searching to infinity. gubNumAwareBattles--; } } } #ifdef JA2UB /* //if the enemy group is at its final destination if( !pGroup->fPlayer && GroupAtFinalDestination( pGroup ) ) { // Check to see if this is an AI sector INT16 sJa25SaiSectorValue = GetJA25SectorID( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ); if ( sJa25SaiSectorValue != -1 ) { // If we were told to attack, reset flag if ( gJa25AiSectorStruct[ sJa25SaiSectorValue ].fEnemyEnrouteToAttack ) { gJa25AiSectorStruct[ sJa25SaiSectorValue ].fEnemyEnrouteToAttack = FALSE; } } */ //add the enemies to the sector /* SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].ubNumAdmins += pGroup->pEnemyGroup->ubNumAdmins; SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].ubNumTroops += pGroup->pEnemyGroup->ubNumTroops; SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].ubNumElites += pGroup->pEnemyGroup->ubNumElites; if( fBattleGoingToStart ) { SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].ubAdminsInBattle = pGroup->pEnemyGroup->ubNumAdmins; SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].ubTroopsInBattle = pGroup->pEnemyGroup->ubNumTroops; SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ].ubElitesInBattle = pGroup->pEnemyGroup->ubNumElites; } */ //} #endif gfWaitingForInput = FALSE; } void HandleNonCombatGroupArrival( GROUP *pGroup, BOOLEAN fMainGroup, BOOLEAN fNeverLeft ) { // if any mercs are actually in the group #ifdef JA2UB //Ja25 No strategic ai #else if( StrategicAILookForAdjacentGroups( pGroup ) ) { //The routine actually just deleted the enemy group (player's don't get deleted), so we are done! return; } #endif if( pGroup->fPlayer ) { //The group will always exist after the AI was processed. //Determine if the group should rest, change routes, or continue moving. // if on foot, or in a vehicle other than the helicopter if( !pGroup->fVehicle || !IsGroupTheHelicopterGroup( pGroup ) ) { // take control of sector SetThisSectorAsPlayerControlled( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ, FALSE ); } // if this is the last sector along their movement path (no more waypoints) if ( GroupAtFinalDestination( pGroup ) ) { // if currently selected sector has nobody in it if ( PlayerMercsInSector( ( UINT8 ) sSelMapX, ( UINT8 ) sSelMapY, ( UINT8 ) iCurrentMapSectorZ ) == 0 ) { // make this sector strategically selected ChangeSelectedMapSector( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ); } // if on foot or in a vehicle other than the helicopter (Skyrider speaks for heli movement) if ( !pGroup->fVehicle || !IsGroupTheHelicopterGroup( pGroup ) ) { StopTimeCompression(); // if traversing tactically, or we never left (just canceling), don't do this if( !gfTacticalTraversal && !fNeverLeft ) { RandomMercInGroupSaysQuote( pGroup, QUOTE_MERC_REACHED_DESTINATION ); } } } //MilitiaFollowPlayer( pGroup->ubPrevX, pGroup->ubPrevY, pGroup->ubSectorX, pGroup->ubSectorY ); // look for NPCs to stop for, anyone is too tired to keep going, if all OK rebuild waypoints & continue movement // NOTE: Only the main group (first group arriving) will stop for NPCs, it's just too much hassle to stop them all PlayerGroupArrivedSafelyInSector( pGroup, fMainGroup ); } else { if( !pGroup->fDebugGroup ) { CalculateNextMoveIntention( pGroup ); } else { RemovePGroup( pGroup ); } } //Clear the non-persistant flags. pGroup->uiFlags = 0; } //Because a battle is about to start, we need to go through the event list and look for other //groups that may arrive at the same time -- enemies or players, and blindly add them to the sector //without checking for battle conditions, as it has already determined that a new battle is about to //start. void HandleOtherGroupsArrivingSimultaneously( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ ) { STRATEGICEVENT *pEvent; UINT32 uiCurrTimeStamp; GROUP *pGroup; uiCurrTimeStamp = GetWorldTotalSeconds(); pEvent = gpEventList; gubNumGroupsArrivedSimultaneously = 0; while( pEvent && pEvent->uiTimeStamp <= uiCurrTimeStamp ) { if( pEvent->ubCallbackID == EVENT_GROUP_ARRIVAL && !(pEvent->ubFlags & SEF_DELETION_PENDING) ) { pGroup = GetGroup( (UINT8)pEvent->uiParam ); Assert( pGroup ); if( pGroup->ubNextX == ubSectorX && pGroup->ubNextY == ubSectorY && pGroup->ubSectorZ == ubSectorZ ) { if( pGroup->fBetweenSectors ) { GroupArrivedAtSector( (UINT8)pEvent->uiParam, FALSE, FALSE ); pGroup->uiFlags |= GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY; gubNumGroupsArrivedSimultaneously++; DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); pEvent = gpEventList; continue; } } } pEvent = pEvent->next; } } //The user has just approved to plan a simultaneous arrival. So we will syncronize all of the involved //groups so that they arrive at the same time (which is the time the final group would arrive). void PrepareGroupsForSimultaneousArrival() { GROUP *pGroup; UINT32 uiLatestArrivalTime = 0; SOLDIERTYPE *pSoldier = NULL; INT32 iVehId = 0; pGroup = gpGroupList; while( pGroup ) { //For all of the groups that haven't arrived yet, determine which one is going to take the longest. if( pGroup != gpPendingSimultaneousGroup && pGroup->fPlayer && pGroup->fBetweenSectors && pGroup->ubNextX == gpPendingSimultaneousGroup->ubSectorX && pGroup->ubNextY == gpPendingSimultaneousGroup->ubSectorY && !IsGroupTheHelicopterGroup( pGroup ) ) { uiLatestArrivalTime = max( pGroup->uiArrivalTime, uiLatestArrivalTime ); pGroup->uiFlags |= GROUPFLAG_SIMULTANEOUSARRIVAL_APPROVED | GROUPFLAG_MARKER; } pGroup = pGroup->next; } //Now, go through the list again, and reset their arrival event to the latest arrival time. pGroup = gpGroupList; while( pGroup ) { if( pGroup->uiFlags & GROUPFLAG_MARKER ) { DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); // NOTE: This can cause the arrival time to be > GetWorldTotalMin() + TraverseTime, so keep that in mind // if you have any code that uses these 3 values to figure out how far along its route a group is! SetGroupArrivalTime( pGroup, uiLatestArrivalTime ); AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ); if( pGroup->fPlayer ) { if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) { AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); } } DelayEnemyGroupsIfPathsCross( pGroup ); pGroup->uiFlags &= ~GROUPFLAG_MARKER; } pGroup = pGroup->next; } //We still have the first group that has arrived. Because they are set up to be in the destination //sector, we will "warp" them back to the last sector, and also setup a new arrival time for them. pGroup = gpPendingSimultaneousGroup; pGroup->ubNextX = pGroup->ubSectorX; pGroup->ubNextY = pGroup->ubSectorY; pGroup->ubSectorX = pGroup->ubPrevX; pGroup->ubSectorY = pGroup->ubPrevY; SetGroupArrivalTime( pGroup, uiLatestArrivalTime ); pGroup->fBetweenSectors = TRUE; if( pGroup->fVehicle ) { if( ( iVehId = ( GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ) ) ) != -1 ) { pVehicleList[ iVehId ].fBetweenSectors = TRUE; // set up vehicle soldier pSoldier = GetSoldierStructureForVehicle( iVehId ); if( pSoldier ) { pSoldier->flags.fBetweenSectors = TRUE; } } } AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ); if( pGroup->fPlayer ) { if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) { AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); } } DelayEnemyGroupsIfPathsCross( pGroup ); } //See if there are other groups OTW. If so, and if we haven't asked the user yet to plan //a simultaneous attack, do so now, and readjust the groups accordingly. If it is possible //to do so, then we will set up the gui, and postpone the prebattle interface. BOOLEAN PossibleToCoordinateSimultaneousGroupArrivals( GROUP *pFirstGroup ) { GROUP *pGroup; UINT8 ubNumNearbyGroups = 0; //If the user has already been asked, then don't ask the question again! if( pFirstGroup->uiFlags & (GROUPFLAG_SIMULTANEOUSARRIVAL_APPROVED | GROUPFLAG_SIMULTANEOUSARRIVAL_CHECKED) || IsGroupTheHelicopterGroup( pFirstGroup ) ) { return FALSE; } //We can't coordinate simultaneous attacks on a sector without any stationary forces! Otherwise, it //is possible that they will be gone when you finally arrive. //if( !NumStationaryEnemiesInSector( pFirstGroup->ubSectorX, pFirstGroup->ubSectorY ) ) // return FALSE; //Count the number of groups that are scheduled to arrive in the same sector and are currently //adjacent to the sector in question. pGroup = gpGroupList; while( pGroup ) { if( pGroup != pFirstGroup && pGroup->fPlayer && pGroup->fBetweenSectors && pGroup->ubNextX == pFirstGroup->ubSectorX && pGroup->ubNextY == pFirstGroup->ubSectorY && !(pGroup->uiFlags & GROUPFLAG_SIMULTANEOUSARRIVAL_CHECKED) && !IsGroupTheHelicopterGroup( pGroup ) ) { pGroup->uiFlags |= GROUPFLAG_SIMULTANEOUSARRIVAL_CHECKED; ubNumNearbyGroups++; } pGroup = pGroup->next; } if( ubNumNearbyGroups ) { //postpone the battle until the user answers the dialog. CHAR16 str[255]; STR16 pStr, pEnemyType; InterruptTime(); PauseGame(); LockPauseState( 13 ); gpPendingSimultaneousGroup = pFirstGroup; //Build the string if( ubNumNearbyGroups == 1 ) { pStr = gpStrategicString[ STR_DETECTED_SINGULAR ]; } else { pStr = gpStrategicString[ STR_DETECTED_PLURAL ]; } if( gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ) { pEnemyType = gpStrategicString[ STR_PB_BLOODCATS ]; } else { pEnemyType = gpStrategicString[ STR_PB_ENEMIES ]; } //header, sector, singular/plural str, confirmation string. //Ex: Enemies have been detected in sector J9 and another squad is // about to arrive. Do you wish to coordinate a simultaneous arrival? swprintf( str, pStr, pEnemyType, //Enemy type (Enemies or bloodcats) 'A' + gpPendingSimultaneousGroup->ubSectorY - 1, gpPendingSimultaneousGroup->ubSectorX ); //Sector location wcscat( str, L" " ); wcscat( str, gpStrategicString[ STR_COORDINATE ] ); //Setup the dialog //Kris August 03, 1999 Bug fix: Changed 1st line to 2nd line to fix game breaking if this dialog came up while in tactical. // It would kick you to mapscreen, where things would break... //DoMapMessageBox( MSG_BOX_BASIC_STYLE, str, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PlanSimultaneousGroupArrivalCallback ); DoMapMessageBox( MSG_BOX_BASIC_STYLE, str, guiCurrentScreen, MSG_BOX_FLAG_YESNO, PlanSimultaneousGroupArrivalCallback ); gfWaitingForInput = TRUE; return TRUE; } return FALSE; } void PlanSimultaneousGroupArrivalCallback( UINT8 bMessageValue ) { if( bMessageValue == MSG_BOX_RETURN_YES ) { PrepareGroupsForSimultaneousArrival(); } else { PrepareForPreBattleInterface( gpPendingSimultaneousGroup, gpPendingSimultaneousGroup ); } UnLockPauseState(); UnPauseGame(); } void DelayEnemyGroupsIfPathsCross( GROUP *pPlayerGroup ) { GROUP *pGroup; pGroup = gpGroupList; while( pGroup ) { if( !pGroup->fPlayer ) { //then check to see if this group will arrive in next sector before the player group. if( pGroup->uiArrivalTime < pPlayerGroup->uiArrivalTime ) { //check to see if enemy group will cross paths with player group. if( pGroup->ubNextX == pPlayerGroup->ubSectorX && pGroup->ubNextY == pPlayerGroup->ubSectorY && pGroup->ubSectorX == pPlayerGroup->ubNextX && pGroup->ubSectorY == pPlayerGroup->ubNextY ) { //Okay, the enemy group will cross paths with the player, so find and delete the arrival event //and repost it in the future (like a minute or so after the player arrives) DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); // NOTE: This can cause the arrival time to be > GetWorldTotalMin() + TraverseTime, so keep that in mind // if you have any code that uses these 3 values to figure out how far along its route a group is! SetGroupArrivalTime( pGroup, pPlayerGroup->uiArrivalTime + 1 + Random( 10 ) ); if( !AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ) ) AssertMsg( 0, "Failed to add movement event." ); } } } pGroup = pGroup->next; } } void InitiateGroupMovementToNextSector( GROUP *pGroup ) { INT32 dx, dy; INT32 i; UINT8 ubDirection; UINT8 ubSector; WAYPOINT *wp; INT32 iVehId = -1; SOLDIERTYPE *pSoldier = NULL; UINT32 uiSleepMinutes = 0; Assert( pGroup ); i = pGroup->ubNextWaypointID; wp = pGroup->pWaypoints; while( i-- ) { //Traverse through the waypoint list to the next waypoint ID Assert( wp ); wp = wp->next; } Assert( wp ); //We now have the correct waypoint. //Analyse the group and determine which direction it will move from the current sector. dx = wp->x - pGroup->ubSectorX; dy = wp->y - pGroup->ubSectorY; if( dx && dy ) { //Shouldn't move diagonally! DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("Attempting to move to waypoint in a diagonal direction from sector %d,%d to sector %d,%d", pGroup->ubSectorX, pGroup->ubSectorY, wp->x, wp->y )); //AssertMsg( 0, String("Attempting to move to waypoint in a diagonal direction from sector %d,%d to sector %d,%d", // pGroup->ubSectorX, pGroup->ubSectorY, wp->x, wp->y ) ); } if( !dx && !dy ) //Can't move to position currently at! AssertMsg( 0, String("Attempting to move to waypoint %d, %d that you are already at!", wp->x, wp->y ) ); //Clip dx/dy value so that the move is for only one sector. if( dx >= 1 ) { ubDirection = EAST_STRATEGIC_MOVE; dx = 1; } else if( dy >= 1 ) { ubDirection = SOUTH_STRATEGIC_MOVE; dy = 1; } else if( dx <= -1 ) { ubDirection = WEST_STRATEGIC_MOVE; dx = -1; } else if( dy <= -1 ) { ubDirection = NORTH_STRATEGIC_MOVE; dy = -1; } else { Assert( 0 ); return; } //All conditions for moving to the next waypoint are now good. pGroup->ubNextX = (UINT8)( dx + pGroup->ubSectorX ); pGroup->ubNextY = (UINT8)( dy + pGroup->ubSectorY ); //Calc time to get to next waypoint... ubSector = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); if( !pGroup->ubSectorZ ) { BOOLEAN fCalcRegularTime = TRUE; if( !pGroup->fPlayer ) { //Determine if the enemy group is "sleeping". If so, then simply delay their arrival time by the amount of time //they are going to be sleeping for. if( GetWorldHour() >= 21 || GetWorldHour() <= 4 ) { //It is definitely night time. if( Chance( 67 ) ) { //2 in 3 chance of going to sleep. pGroup->uiTraverseTime = GetSectorMvtTimeForGroup( ubSector, ubDirection, pGroup ); uiSleepMinutes = 360 + Random( 121 ); //6-8 hours sleep fCalcRegularTime = FALSE; } } } if( fCalcRegularTime ) { pGroup->uiTraverseTime = GetSectorMvtTimeForGroup( ubSector, ubDirection, pGroup ); } } else { pGroup->uiTraverseTime = 1; } if( pGroup->uiTraverseTime == 0xffffffff ) { AssertMsg( 0, String("Group %d (%s) attempting illegal move from %c%d to %c%d (%s).", pGroup->ubGroupID, ( pGroup->fPlayer ) ? "Player" : "AI", pGroup->ubSectorY+'A'-1, pGroup->ubSectorX, pGroup->ubNextY+'A'-1, pGroup->ubNextX, gszTerrain[SectorInfo[ubSector].ubTraversability[ubDirection]] ) ); } // add sleep, if any pGroup->uiTraverseTime += uiSleepMinutes; if( gfTacticalTraversal && gpTacticalTraversalGroup == pGroup ) { if( gfUndergroundTacticalTraversal ) { //underground movement between sectors takes 1 minute. pGroup->uiTraverseTime = 1; } else { //strategic movement between town sectors takes 5 minutes. pGroup->uiTraverseTime = 5; } } // if group isn't already between sectors if ( !pGroup->fBetweenSectors ) { // put group between sectors pGroup->fBetweenSectors = TRUE; // and set it's arrival time SetGroupArrivalTime( pGroup, GetWorldTotalMin() + pGroup->uiTraverseTime ); } // NOTE: if the group is already between sectors, DON'T MESS WITH ITS ARRIVAL TIME! THAT'S NOT OUR JOB HERE!!! // special override for AI patrol initialization only if( gfRandomizingPatrolGroup ) { //We're initializing the patrol group, so randomize the enemy groups to have extremely quick and varying //arrival times so that their initial positions aren't easily determined. pGroup->uiTraverseTime = 1 + Random( pGroup->uiTraverseTime - 1 ); SetGroupArrivalTime( pGroup, GetWorldTotalMin() + pGroup->uiTraverseTime ); } if( pGroup->fVehicle == TRUE ) { // vehicle, set fact it is between sectors too if( ( iVehId = ( GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ) ) ) != -1 ) { pVehicleList[ iVehId ].fBetweenSectors = TRUE; pSoldier = GetSoldierStructureForVehicle( iVehId ); if( pSoldier ) { pSoldier->flags.fBetweenSectors = TRUE; // OK, Remove the guy from tactical engine! RemoveSoldierFromTacticalSector( pSoldier, TRUE ); } } } //Post the event! if( !AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ) ) AssertMsg( 0, "Failed to add movement event." ); //For the case of player groups, we need to update the information of the soldiers. if( pGroup->fPlayer ) { PLAYERGROUP *curr; if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) { AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); } curr = pGroup->pPlayerList; while( curr ) { curr->pSoldier->flags.fBetweenSectors = TRUE; // OK, Remove the guy from tactical engine! RemoveSoldierFromTacticalSector( curr->pSoldier, TRUE ); curr = curr->next; } CheckAndHandleUnloadingOfCurrentWorld(); //If an enemy group will be crossing paths with the player group, delay the enemy group's arrival time so that //the player will always encounter that group. if( !pGroup->ubSectorZ ) { DelayEnemyGroupsIfPathsCross( pGroup ); } } } void RemoveGroupWaypoints( UINT8 ubGroupID ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); Assert( pGroup ); RemovePGroupWaypoints( pGroup ); } void RemovePGroupWaypoints( GROUP *pGroup ) { WAYPOINT* wp; //if there aren't any waypoints to delete, then return. This also avoids setting //the fWaypointsCancelled flag. if( !pGroup->pWaypoints ) return; //remove all of the waypoints. while( pGroup->pWaypoints ) { wp = pGroup->pWaypoints; pGroup->pWaypoints = pGroup->pWaypoints->next; MemFree( wp ); } pGroup->ubNextWaypointID = 0; pGroup->pWaypoints = NULL; //By setting this flag, it acknowledges the possibility that the group is currently between sectors, //and will continue moving until it reaches the next sector. If the user decides to change directions, //during this process, the arrival event must be modified to send the group back. //pGroup->fWaypointsCancelled = TRUE; } // set groups waypoints as cancelled void SetWayPointsAsCanceled( UINT8 ubGroupID ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); Assert( pGroup ); //pGroup->fWaypointsCancelled = TRUE; return; } // set this groups previous sector values void SetGroupPrevSectors( UINT8 ubGroupID, UINT8 ubX, UINT8 ubY ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); Assert( pGroup ); // since we have a group, set prev sector's x and y pGroup->ubPrevX = ubX; pGroup->ubPrevY = ubY; } void RemoveGroup( UINT8 ubGroupID ) { GROUP *pGroup; pGroup = GetGroup( ubGroupID ); Assert( pGroup ); RemovePGroup( pGroup ); } BOOLEAN gfRemovingAllGroups = FALSE; void RemovePGroup( GROUP *pGroup ) { UINT32 bit, index, mask; if( pGroup->fPersistant && !gfRemovingAllGroups ) { CancelEmptyPersistentGroupMovement( pGroup ); return; DoScreenIndependantMessageBox( L"Strategic Info Warning: Attempting to delete a persistant group.", MSG_BOX_FLAG_OK, NULL ); } //if removing head, then advance head first. if( pGroup == gpGroupList ) gpGroupList = gpGroupList->next; else { //detach this node from the list. GROUP *curr; curr = gpGroupList; while( curr->next && curr->next != pGroup ) curr = curr->next; AssertMsg( curr->next == pGroup, "Trying to remove a strategic group that isn't in the list!"); curr->next = pGroup->next; } //Remove the waypoints. RemovePGroupWaypoints( pGroup ); //Remove the arrival event if applicable. DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); //Determine what type of group we have (because it requires different methods) if( pGroup->fPlayer ) { //Remove player group PLAYERGROUP *pPlayer; while( pGroup->pPlayerList ) { pPlayer = pGroup->pPlayerList; pGroup->pPlayerList = pGroup->pPlayerList->next; MemFree( pPlayer ); } } else { #ifdef JA2UB //Ja25 No strategic ai #else RemoveGroupFromStrategicAILists( pGroup->ubGroupID ); #endif MemFree( pGroup->pEnemyGroup ); } //clear the unique group ID index = pGroup->ubGroupID / 32; bit = pGroup->ubGroupID % 32; mask = 1 << bit; if( !(uniqueIDMask[ index ] & mask) ) { mask = mask; } uniqueIDMask[ index ] -= mask; if (gpBattleGroup == pGroup) { gpBattleGroup = NULL; } MemFree( pGroup ); pGroup = NULL; } void RemoveAllGroups() { // Since we are removing all groups, clear the gpBattleGroup gpBattleGroup = NULL; gfRemovingAllGroups = TRUE; while( gpGroupList ) { RemovePGroup( gpGroupList ); } gfRemovingAllGroups = FALSE; } void SetGroupSectorValue( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, UINT8 ubGroupID ) { GROUP *pGroup; PLAYERGROUP *pPlayer; // get the group pGroup = GetGroup( ubGroupID ); // make sure it is valid Assert( pGroup ); //Remove waypoints RemovePGroupWaypoints( pGroup ); // set sector x and y to passed values pGroup->ubSectorX = pGroup->ubNextX = ( UINT8 ) sSectorX; pGroup->ubSectorY = pGroup->ubNextY = ( UINT8 ) sSectorY; pGroup->ubSectorZ = ( UINT8 ) sSectorZ; pGroup->fBetweenSectors = FALSE; // set next sectors same as current pGroup->ubOriginalSector = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); // set all of the mercs in the group so that they are in the new sector too. pPlayer = pGroup->pPlayerList; while( pPlayer ) { pPlayer->pSoldier->sSectorX = sSectorX; pPlayer->pSoldier->sSectorY = sSectorY; pPlayer->pSoldier->bSectorZ = (UINT8)sSectorZ; pPlayer->pSoldier->flags.fBetweenSectors = FALSE; pPlayer->pSoldier->flags.uiStatusFlags &= ~SOLDIER_SHOULD_BE_TACTICALLY_VALID; pPlayer = pPlayer->next; } CheckAndHandleUnloadingOfCurrentWorld(); SetSectorFlag( sSectorX, sSectorY, (UINT8) sSectorZ, SF_ALREADY_VISITED); } void SetEnemyGroupSector( GROUP *pGroup, UINT8 ubSectorID ) { // make sure it is valid Assert( pGroup ); DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); //Remove waypoints if( !gfRandomizingPatrolGroup ) { RemovePGroupWaypoints( pGroup ); } // set sector x and y to passed values pGroup->ubSectorX = pGroup->ubNextX = (UINT8)SECTORX( ubSectorID ); pGroup->ubSectorY = pGroup->ubNextY = (UINT8)SECTORY( ubSectorID ); pGroup->ubSectorZ = 0; pGroup->fBetweenSectors = FALSE; //pGroup->fWaypointsCancelled = FALSE; } void SetGroupNextSectorValue( INT16 sSectorX, INT16 sSectorY, UINT8 ubGroupID ) { GROUP *pGroup; // get the group pGroup = GetGroup( ubGroupID ); // make sure it is valid Assert( pGroup ); //Remove waypoints RemovePGroupWaypoints( pGroup ); // set sector x and y to passed values pGroup->ubNextX = ( UINT8 ) sSectorX; pGroup->ubNextY = ( UINT8 ) sSectorY; pGroup->fBetweenSectors = FALSE; // set next sectors same as current pGroup->ubOriginalSector = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); } // get eta of the group with this id INT32 CalculateTravelTimeOfGroupId( UINT8 ubId ) { GROUP *pGroup; // get the group pGroup = GetGroup( ubId ); if( pGroup == NULL ) { return( 0 ); } return( CalculateTravelTimeOfGroup( pGroup ) ); } INT32 CalculateTravelTimeOfGroup( GROUP *pGroup ) { INT32 iDelta; UINT32 uiEtaTime = 0; WAYPOINT *pNode = NULL; WAYPOINT pCurrent, pDest; // check if valid group if( pGroup == NULL ) { // return current time return( uiEtaTime ); } // set up next node pNode = pGroup->pWaypoints; // now get the delta in current sector and next sector iDelta = ( INT32 )( SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) - SECTOR( pGroup->ubNextX, pGroup->ubNextY ) ); if( iDelta == 0 ) { // not going anywhere...return current time return( uiEtaTime ); } // if already on the road if ( pGroup->fBetweenSectors ) { // to get travel time to the first sector, use the arrival time, this way it accounts for delays due to simul. arrival if ( pGroup->uiArrivalTime >= GetWorldTotalMin( ) ) { uiEtaTime += ( pGroup->uiArrivalTime - GetWorldTotalMin( ) ); } // first waypoint is NEXT sector pCurrent.x = pGroup->ubNextX; pCurrent.y = pGroup->ubNextY; } else { // first waypoint is CURRENT sector pCurrent.x = pGroup->ubSectorX; pCurrent.y = pGroup->ubSectorY; } while( pNode ) { pDest.x = pNode->x; pDest.y = pNode->y; // update eta time by the path between these 2 waypts uiEtaTime += FindTravelTimeBetweenWaypoints( &pCurrent, &pDest, pGroup ); pCurrent.x = pNode->x; pCurrent.y = pNode->y; // next waypt pNode = pNode->next; } return( uiEtaTime ); } INT32 FindTravelTimeBetweenWaypoints( WAYPOINT * pSource, WAYPOINT * pDest, GROUP *pGroup ) { UINT8 ubStart=0, ubEnd = 0; INT32 iDelta = 0; INT32 iCurrentCostInTime = 0; UINT8 ubCurrentSector = 0; UINT8 ubDirection; INT32 iThisCostInTime; // find travel time between waypoints if( !pSource || !pDest ) { // no change return( iCurrentCostInTime ); } // get start and end setor values ubStart = SECTOR( pSource->x, pSource->y ); ubEnd = SECTOR( pDest->x, pDest->y ); // are we in fact moving? if( ubStart == ubEnd ) { // no return( iCurrentCostInTime ); } iDelta = ( INT32 )( ubEnd - ubStart ); // which direction are we moving? if( iDelta > 0 ) { if( iDelta % ( SOUTH_MOVE - 2 ) == 0 ) { iDelta = ( SOUTH_MOVE - 2 ) ; ubDirection = SOUTH_STRATEGIC_MOVE; } else { iDelta = EAST_MOVE; ubDirection = EAST_STRATEGIC_MOVE; } } else { if( iDelta % ( NORTH_MOVE + 2 ) == 0 ) { iDelta = ( NORTH_MOVE + 2 ); ubDirection = NORTH_STRATEGIC_MOVE; } else { iDelta = WEST_MOVE; ubDirection = WEST_STRATEGIC_MOVE; } } for( ubCurrentSector = ubStart; ubCurrentSector != ubEnd; ubCurrentSector += ( INT8 ) iDelta ) { // find diff between current and next iThisCostInTime = GetSectorMvtTimeForGroup( ubCurrentSector, ubDirection, pGroup ); if( iThisCostInTime == 0xffffffff ) { AssertMsg( 0, String("Group %d (%s) attempting illegal move from sector %d, dir %d (%s).", pGroup->ubGroupID, ( pGroup->fPlayer ) ? "Player" : "AI", ubCurrentSector, ubDirection, gszTerrain[SectorInfo[ubCurrentSector].ubTraversability[ubDirection]] ) ); } // accumulate it iCurrentCostInTime += iThisCostInTime; } return( iCurrentCostInTime ); } #define FOOT_TRAVEL_TIME 89 #define CAR_TRAVEL_TIME 30 #define TRUCK_TRAVEL_TIME 32 #define TRACKED_TRAVEL_TIME 46 #define AIR_TRAVEL_TIME 10 //CHANGES: ubDirection contains the strategic move value, not the delta value. INT32 GetSectorMvtTimeForGroup( UINT8 ubSector, UINT8 ubDirection, GROUP *pGroup ) { INT32 iTraverseTime; INT32 iBestTraverseTime = 1000000; INT32 iEncumbrance, iHighestEncumbrance = 0; SOLDIERTYPE *pSoldier; PLAYERGROUP *curr; BOOLEAN fFoot, fCar, fTruck, fTracked, fAir; UINT8 ubTraverseType; UINT8 ubTraverseMod; // THIS FUNCTION WAS WRITTEN TO HANDLE MOVEMENT TYPES WHERE MORE THAN ONE TRANSPORTAION TYPE IS AVAILABLE. //Determine the group's method(s) of tranportation. If more than one, //we will always use the highest time. // WANNE: If we have an old savegame (from the dev. trunk, where the skyrider can only move on roads), just change the "pGroup->ubTransportationMask" to 16 for the heli to fix it in the savegame fFoot = (UINT8)(pGroup->ubTransportationMask & FOOT); fCar = (UINT8)(pGroup->ubTransportationMask & CAR); fTruck = (UINT8)(pGroup->ubTransportationMask & TRUCK); fTracked = (UINT8)(pGroup->ubTransportationMask & TRACKED); fAir = (UINT8)(pGroup->ubTransportationMask & AIR); ubTraverseType = SectorInfo[ ubSector ].ubTraversability[ ubDirection ]; if( ubTraverseType == EDGEOFWORLD ) return 0xffffffff; //can't travel here! // ARM: Made air-only travel take its normal time per sector even through towns. Because Skyrider charges by the sector, // not by flying time, it's annoying when his default route detours through a town to save time, but costs extra money. // This isn't exactly unrealistic, since the chopper shouldn't be faster flying over a town anyway... Not that other // kinds of travel should be either - but the towns represents a kind of warping of our space-time scale as it is... if( ( ubTraverseType == TOWN ) && ( pGroup->ubTransportationMask != AIR ) ) return 5; //very fast, and vehicle types don't matter. if( fFoot ) { switch( ubTraverseType ) { case ROAD: ubTraverseMod = 100;break; case PLAINS: ubTraverseMod = 85; break; case SAND: ubTraverseMod = 50; break; case SPARSE: ubTraverseMod = 70; break; case DENSE: ubTraverseMod = 60; break; case SWAMP: ubTraverseMod = 35; break; case WATER: ubTraverseMod = 25; break; case HILLS: ubTraverseMod = 50; break; case GROUNDBARRIER: ubTraverseMod = 0; break; case NS_RIVER: ubTraverseMod = 25; break; case EW_RIVER: ubTraverseMod = 25; break; default: Assert( 0 ); return 0xffffffff; } if( ubTraverseMod == 0 ) return 0xffffffff; //Group can't traverse here. iTraverseTime = FOOT_TRAVEL_TIME * 100 / ubTraverseMod; if( iTraverseTime < iBestTraverseTime ) iBestTraverseTime = iTraverseTime; if( pGroup->fPlayer ) { curr = pGroup->pPlayerList; while( curr ) { pSoldier = curr->pSoldier; if( pSoldier->bAssignment != VEHICLE ) { //Soldier is on foot and travelling. Factor encumbrance into movement rate. iEncumbrance = CalculateCarriedWeight( pSoldier ); if( iEncumbrance > iHighestEncumbrance ) { iHighestEncumbrance = iEncumbrance; } } curr = curr->next; } if( iHighestEncumbrance > 100 ) { iBestTraverseTime = iBestTraverseTime * iHighestEncumbrance / 100; } } } if( fCar ) { switch( ubTraverseType ) { case ROAD: ubTraverseMod = 100; break; default: ubTraverseMod = 0; break; } if( ubTraverseMod == 0 ) return 0xffffffff; //Group can't traverse here. iTraverseTime = CAR_TRAVEL_TIME * 100 / ubTraverseMod; if( iTraverseTime < iBestTraverseTime ) iBestTraverseTime = iTraverseTime; } if( fTruck ) { switch( ubTraverseType ) { case ROAD: ubTraverseMod = 100; break; case PLAINS: ubTraverseMod = 75; break; case SPARSE: ubTraverseMod = 60; break; case HILLS: ubTraverseMod = 50; break; default: ubTraverseMod = 0; break; } if( ubTraverseMod == 0 ) return 0xffffffff; //Group can't traverse here. iTraverseTime = TRUCK_TRAVEL_TIME * 100 / ubTraverseMod; if( iTraverseTime < iBestTraverseTime ) iBestTraverseTime = iTraverseTime; } if( fTracked ) { switch( ubTraverseType ) { case ROAD: ubTraverseMod = 100; break; case PLAINS: ubTraverseMod = 100; break; case SAND: ubTraverseMod = 70; break; case SPARSE: ubTraverseMod = 60; break; case HILLS: ubTraverseMod = 60; break; case NS_RIVER: ubTraverseMod = 20; break; case EW_RIVER: ubTraverseMod = 20; break; case WATER: ubTraverseMod = 10; break; default: ubTraverseMod = 0; break; } if( ubTraverseMod == 0 ) return 0xffffffff; //Group can't traverse here. iTraverseTime = TRACKED_TRAVEL_TIME * 100 / ubTraverseMod; if( iTraverseTime < iBestTraverseTime ) iBestTraverseTime = iTraverseTime; } if( fAir ) { iTraverseTime = AIR_TRAVEL_TIME; if( iTraverseTime < iBestTraverseTime ) iBestTraverseTime = iTraverseTime; } /////////////////////////////////////////////////////////////////////////////// // SANDRO - STOMP traits - ranger reduces time needed for travelling around if( pGroup->fPlayer && !fAir && gGameOptions.fNewTraitSystem) { // see if we have any ranger here UINT8 ubRangerHere = 0; curr = pGroup->pPlayerList; while( curr ) { pSoldier = curr->pSoldier; if( HAS_SKILL_TRAIT( pSoldier, RANGER_NT )) ubRangerHere += NUM_SKILL_TRAITS( pSoldier, RANGER_NT ); curr = curr->next; } // yes, we have... if( ubRangerHere > 0 ) { // no more than certain number of simultaneous bonuses ubRangerHere = min( gSkillTraitValues.ubRAMaxBonusesToTravelSpeed, ubRangerHere ); // on foot, the bonus should be higher if( fFoot ) { // however, we cannot be quicker than the helicopter iBestTraverseTime = max( 10, (iBestTraverseTime * (100 - ( ubRangerHere * gSkillTraitValues.ubRAGroupTimeSpentForTravellingFoot )) / 100)); } // all other types (except air) else { // however, we cannot be quicker than the helicopter iBestTraverseTime = max( 10, (iBestTraverseTime * (100 - ( ubRangerHere * gSkillTraitValues.ubRAGroupTimeSpentForTravellingVehicle )) / 100)); } } } /////////////////////////////////////////////////////////////////////////////// return iBestTraverseTime; } //Counts the number of live mercs in any given sector. UINT8 PlayerMercsInSector( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ ) { GROUP *pGroup; PLAYERGROUP *pPlayer; UINT8 ubNumMercs = 0; pGroup = gpGroupList; while( pGroup ) { if( pGroup->fPlayer && !pGroup->fBetweenSectors) { if ( pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY && pGroup->ubSectorZ == ubSectorZ ) { //we have a group, make sure that it isn't a group containing only dead members. pPlayer = pGroup->pPlayerList; while( pPlayer ) { // robots count as mercs here, because they can fight, but vehicles don't if( ( pPlayer->pSoldier->stats.bLife ) && !( pPlayer->pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) ) { ubNumMercs++; } pPlayer = pPlayer->next; } } } pGroup = pGroup->next; } return ubNumMercs; } UINT8 PlayerGroupsInSector( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ ) { GROUP *pGroup; PLAYERGROUP *pPlayer; UINT8 ubNumGroups = 0; pGroup = gpGroupList; while( pGroup ) { if( pGroup->fPlayer && !pGroup->fBetweenSectors) { if ( pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY && pGroup->ubSectorZ == ubSectorZ ) { //we have a group, make sure that it isn't a group containing only dead members. pPlayer = pGroup->pPlayerList; while( pPlayer ) { if( pPlayer->pSoldier->stats.bLife ) { ubNumGroups++; break; } pPlayer = pPlayer->next; } } } pGroup = pGroup->next; } return ubNumGroups; } // is the player group with this id in motion? BOOLEAN PlayerIDGroupInMotion( UINT8 ubID ) { GROUP *pGroup; // get the group pGroup = GetGroup( ubID ); // make sure it is valid // no group if( pGroup == NULL ) { return ( FALSE ); } return( PlayerGroupInMotion( pGroup ) ); } // is the player group in motion? BOOLEAN PlayerGroupInMotion( GROUP *pGroup ) { return( pGroup->fBetweenSectors ); } // get travel time for this group INT32 GetTravelTimeForGroup( UINT8 ubSector, UINT8 ubDirection, UINT8 ubGroup ) { GROUP *pGroup; // get the group pGroup = GetGroup( ubGroup ); // make sure it is valid Assert( pGroup ); return( GetSectorMvtTimeForGroup( ubSector, ubDirection, pGroup ) ); } INT32 GetTravelTimeForFootTeam( UINT8 ubSector, UINT8 ubDirection ) { GROUP Group; // group going on foot Group.ubTransportationMask = FOOT; return( GetSectorMvtTimeForGroup( ubSector, ubDirection, &( Group ) ) ); } //Add this group to the current battle fray! //NOTE: For enemies, only MAX_STRATEGIC_TEAM_SIZE at a time can be in a battle, so //if it ever gets past that, god help the player, but we'll have to insert them //as those slots free up. void HandleArrivalOfReinforcements( GROUP *pGroup ) { SOLDIERTYPE *pSoldier; SECTORINFO *pSector; INT32 iNumEnemiesInSector; INT32 cnt; if( pGroup->fPlayer ) { //We don't have to worry about filling up the player slots, because it is impossible //to have more players in the game than the number of slots available for the player. PLAYERGROUP *pPlayer; UINT8 ubStrategicInsertionCode; //First, determine which entrypoint to use, based on the travel direction of the group. if( pGroup->ubSectorX < pGroup->ubPrevX ) ubStrategicInsertionCode = INSERTION_CODE_EAST; else if( pGroup->ubSectorX > pGroup->ubPrevX ) ubStrategicInsertionCode = INSERTION_CODE_WEST; else if( pGroup->ubSectorY < pGroup->ubPrevY ) ubStrategicInsertionCode = INSERTION_CODE_SOUTH; else if( pGroup->ubSectorY > pGroup->ubPrevY ) ubStrategicInsertionCode = INSERTION_CODE_NORTH; else { Assert( 0 ); return; } pPlayer = pGroup->pPlayerList; cnt = 0; while( pPlayer ) { pSoldier = pPlayer->pSoldier; Assert( pSoldier ); pSoldier->ubStrategicInsertionCode = ubStrategicInsertionCode; UpdateMercInSector( pSoldier, pGroup->ubSectorX, pGroup->ubSectorY, 0 ); pPlayer = pPlayer->next; // DO arrives quote.... if ( cnt == 0 ) { TacticalCharacterDialogue( pSoldier, QUOTE_MERC_REACHED_DESTINATION ); } cnt++; } ScreenMsg( FONT_YELLOW, MSG_INTERFACE, Message[ STR_PLAYER_REINFORCEMENTS ] ); } else { gfPendingEnemies = TRUE; ResetMortarsOnTeamCount(); ResetNumSquadleadersInArmyGroup(); // added by SANDRO AddPossiblePendingEnemiesToBattle(); } //Update the known number of enemies in the sector. pSector = &SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ]; iNumEnemiesInSector = NumEnemiesInSector( pGroup->ubSectorX, pGroup->ubSectorY ); if( iNumEnemiesInSector ) { if( pSector->bLastKnownEnemies >= 0 ) { pSector->bLastKnownEnemies = (INT8)iNumEnemiesInSector; } //if we don't know how many enemies there are, then we can't update this value. } else { pSector->bLastKnownEnemies = 0; } } BOOLEAN PlayersBetweenTheseSectors( INT16 sSource, INT16 sDest, INT32 *iCountEnter, INT32 *iCountExit, BOOLEAN *fAboutToArriveEnter ) { GROUP *curr = gpGroupList; INT16 sBattleSector = -1; BOOLEAN fMayRetreatFromBattle = FALSE; BOOLEAN fRetreatingFromBattle = FALSE; BOOLEAN fHelicopterGroup = FALSE; UINT8 ubMercsInGroup = 0; *iCountEnter = 0; *iCountExit = 0; *fAboutToArriveEnter = FALSE; if( gpBattleGroup ) { // Assert( gfPreBattleInterfaceActive ); sBattleSector = (INT16)SECTOR( gpBattleGroup->ubSectorX, gpBattleGroup->ubSectorY ); } // debug only if ( gfDisplayPotentialRetreatPaths == TRUE ) { // Assert( gfPreBattleInterfaceActive ); } // get number of characters entering/existing between these two sectors. Special conditions during // pre-battle interface to return where this function is used to show potential retreating directions instead! // check all groups while( curr ) { // if player group if( curr->fPlayer == TRUE ) { fHelicopterGroup = IsGroupTheHelicopterGroup( curr ); // if this group is aboard the helicopter and we're showing the airspace layer, don't count any mercs aboard the // chopper, because the chopper icon itself serves the function of showing the location/size of this group if ( !fHelicopterGroup || !fShowAircraftFlag ) { // if only showing retreat paths, ignore groups not in the battle sector // if NOT showing retreat paths, ignore groups not between sectors if ( ( gfDisplayPotentialRetreatPaths == TRUE ) && ( sBattleSector == sSource ) || ( gfDisplayPotentialRetreatPaths == FALSE ) && ( curr->fBetweenSectors == TRUE ) ) { fMayRetreatFromBattle = FALSE; fRetreatingFromBattle = FALSE; if ((curr->ubSectorX != 0) && (curr->ubSectorY != 0) && (curr->ubNextX != 0) && (curr->ubNextY != 0)) { if( ( sBattleSector == sSource ) && ( SECTOR( curr->ubSectorX, curr->ubSectorY ) == sSource ) && ( SECTOR( curr->ubPrevX, curr->ubPrevY ) == sDest ) ) { fMayRetreatFromBattle = TRUE; } if( ( sBattleSector == sDest ) && ( SECTOR( curr->ubSectorX, curr->ubSectorY ) == sDest ) && ( SECTOR( curr->ubPrevX, curr->ubPrevY ) == sSource ) ) { fRetreatingFromBattle = TRUE; } ubMercsInGroup = curr->ubGroupSize; if (((SECTOR(curr->ubSectorX, curr->ubSectorY) == sSource) && (SECTOR(curr->ubNextX, curr->ubNextY) == sDest)) || (fMayRetreatFromBattle)) { // if it's a valid vehicle, but not the helicopter (which can fly empty) if ( curr->fVehicle && !fHelicopterGroup && ( GivenMvtGroupIdFindVehicleId( curr->ubGroupID ) != -1 ) ) { // make sure empty vehicles (besides helicopter) aren't in motion! Assert( ubMercsInGroup > 0 ); // subtract 1, we don't wanna count the vehicle itself for purposes of showing a number on the map ubMercsInGroup--; } *iCountEnter += ubMercsInGroup; if( ( curr->uiArrivalTime - GetWorldTotalMin( ) <= ABOUT_TO_ARRIVE_DELAY ) || ( fMayRetreatFromBattle ) ) { *fAboutToArriveEnter = TRUE; } } else if ((SECTOR( curr->ubSectorX, curr->ubSectorY ) == sDest) && (SECTOR( curr->ubNextX, curr->ubNextY ) == sSource) || (fRetreatingFromBattle)) { // if it's a valid vehicle, but not the helicopter (which can fly empty) if ( curr->fVehicle && !fHelicopterGroup && ( GivenMvtGroupIdFindVehicleId( curr->ubGroupID ) != -1 ) ) { // make sure empty vehicles (besides helicopter) aren't in motion! Assert( ubMercsInGroup > 0 ); // subtract 1, we don't wanna count the vehicle itself for purposes of showing a number on the map ubMercsInGroup--; } *iCountExit += ubMercsInGroup; } } } } } // next group curr = curr->next; } // if there was actually anyone leaving this sector and entering next if( *iCountEnter > 0 ) { return ( TRUE ); } else { return( FALSE ); } } void MoveAllGroupsInCurrentSectorToSector( UINT8 ubSectorX, UINT8 ubSectorY, UINT8 ubSectorZ ) { GROUP *pGroup; PLAYERGROUP *pPlayer; pGroup = gpGroupList; while( pGroup ) { if( pGroup->fPlayer && pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && pGroup->ubSectorZ == gbWorldSectorZ && !pGroup->fBetweenSectors ) { //This player group is in the currently loaded sector... pGroup->ubSectorX = ubSectorX; pGroup->ubSectorY = ubSectorY; pGroup->ubSectorZ = ubSectorZ; pPlayer = pGroup->pPlayerList; while( pPlayer ) { pPlayer->pSoldier->sSectorX = ubSectorX; pPlayer->pSoldier->sSectorY = ubSectorY; pPlayer->pSoldier->bSectorZ = ubSectorZ; pPlayer->pSoldier->flags.fBetweenSectors = FALSE; pPlayer = pPlayer->next; } } pGroup = pGroup->next; } CheckAndHandleUnloadingOfCurrentWorld(); } void GetGroupPosition( UINT8 *ubNextX, UINT8 *ubNextY, UINT8 *ubPrevX, UINT8 *ubPrevY, UINT32 *uiTraverseTime, UINT32 *uiArriveTime, UINT8 ubGroupId ) { GROUP *pGroup; // get the group pGroup = GetGroup( ubGroupId ); // make sure it is valid // no group if( pGroup == NULL ) { *ubNextX = 0; *ubNextY = 0; *ubPrevX = 0; *ubPrevY = 0; *uiTraverseTime = 0; *uiArriveTime = 0; return; } // valid group, grab values *ubNextX = pGroup->ubNextX; *ubNextY = pGroup->ubNextY; *ubPrevX = pGroup->ubPrevX; *ubPrevY = pGroup->ubPrevY; *uiTraverseTime = pGroup->uiTraverseTime; *uiArriveTime = pGroup->uiArrivalTime; return; } // this is only for grunts who were in mvt groups between sectors and are set to a new squad...NOTHING ELSE!!!!! void SetGroupPosition( UINT8 ubNextX, UINT8 ubNextY, UINT8 ubPrevX, UINT8 ubPrevY, UINT32 uiTraverseTime, UINT32 uiArriveTime, UINT8 ubGroupId ) { GROUP *pGroup; PLAYERGROUP *pPlayer; // get the group pGroup = GetGroup( ubGroupId ); // no group if( pGroup == NULL ) { return; } // valid group, grab values pGroup->ubNextX = ubNextX; pGroup->ubNextY = ubNextY ; pGroup->ubPrevX = ubPrevX; pGroup->ubPrevY = ubPrevY; pGroup->uiTraverseTime = uiTraverseTime; SetGroupArrivalTime( pGroup, uiArriveTime ); pGroup->fBetweenSectors = TRUE; AddWaypointToPGroup( pGroup, pGroup->ubNextX, pGroup->ubNextY ); //now, if player group set all grunts in the group to be between secotrs if( pGroup->fPlayer == TRUE ) { pPlayer = pGroup->pPlayerList; while( pPlayer ) { pPlayer->pSoldier->flags.fBetweenSectors = TRUE; pPlayer = pPlayer->next; } } return; } BOOLEAN SaveStrategicMovementGroupsToSaveGameFile( HWFILE hFile ) { GROUP *pGroup=NULL; UINT32 uiNumberOfGroups=0; UINT32 uiNumBytesWritten=0; pGroup = gpGroupList; //Count the number of active groups while( pGroup ) { uiNumberOfGroups++; pGroup = pGroup->next; } // Save the number of movement groups to the saved game file FileWrite( hFile, &uiNumberOfGroups, sizeof( UINT32 ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } pGroup = gpGroupList; //Loop through the linked lists and add each node while( pGroup ) { // Save each node in the LL FileWrite( hFile, pGroup, sizeof( GROUP ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( GROUP ) ) { //Error Writing group node to disk return( FALSE ); } // // Save the linked list, for the current type of group // // If its a player group if( pGroup->fPlayer ) { //if there is a player list, add it if( pGroup->ubGroupSize ) { //Save the player group list SavePlayerGroupList( hFile, pGroup ); } } else //else its an enemy group { //Make sure the pointer is valid Assert( pGroup->pEnemyGroup ); // SaveEnemyGroupStruct( hFile, pGroup ); } //Save the waypoint list for the group, if they have one SaveWayPointList( hFile, pGroup ); pGroup = pGroup->next; } // Save the unique id mask FileWrite( hFile, uniqueIDMask, sizeof( UINT32 ) * 8, &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( UINT32 ) * 8 ) { //Error Writing size of L.L. to disk return( FALSE ); } return( TRUE ); } BOOLEAN LoadStrategicMovementGroupsFromSavedGameFile( HWFILE hFile ) { GROUP *pGroup=NULL; GROUP *pTemp=NULL; UINT32 uiNumberOfGroups=0; //UINT32 uiNumBytesWritten=0; UINT32 uiNumBytesRead=0; UINT32 cnt; UINT32 bit, index, mask; UINT8 ubNumPlayerGroupsEmpty = 0; UINT8 ubNumEnemyGroupsEmpty = 0; UINT8 ubNumPlayerGroupsFull = 0; UINT8 ubNumEnemyGroupsFull = 0; //delete the existing group list while( gpGroupList ) RemoveGroupFromList( gpGroupList ); //load the number of nodes in the list FileRead( hFile, &uiNumberOfGroups, sizeof( UINT32 ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } pGroup = gpGroupList; //loop through all the nodes and add them to the LL for( cnt=0; cnt< uiNumberOfGroups; cnt++ ) { //allocate memory for the node pTemp = (GROUP *) MemAlloc( sizeof( GROUP )); if( pTemp == NULL ) return( FALSE ); memset( pTemp, 0, sizeof( GROUP ) ); //Read in the node FileRead( hFile, pTemp, sizeof( GROUP ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( GROUP ) ) { //Error Writing size of L.L. to disk return( FALSE ); } // // Add either the pointer or the linked list. // if( pTemp->fPlayer ) { //if there is a player list, add it if( pTemp->ubGroupSize ) { //Save the player group list LoadPlayerGroupList( hFile, &pTemp ); } } else //else its an enemy group { LoadEnemyGroupStructFromSavedGame( hFile, pTemp ); } //Save the waypoint list for the group, if they have one LoadWayPointList( hFile, pTemp ); pTemp->next = NULL; //add the node to the list //if its the firs node if( cnt == 0 ) { gpGroupList = pTemp; pGroup = gpGroupList; } else { pGroup->next = pTemp; pGroup = pGroup->next; } } // Load the unique id mask FileRead( hFile, uniqueIDMask, sizeof( UINT32 ) * 8, &uiNumBytesRead ); //@@@ TEMP! //Rebuild the uniqueIDMask as a very old bug broke the uniqueID assignments in extremely rare cases. memset( uniqueIDMask, 0, sizeof( UINT32 ) * 8 ); pGroup = gpGroupList; while( pGroup ) { if( pGroup->fPlayer ) { if( pGroup->ubGroupSize ) { ubNumPlayerGroupsFull++; } else { ubNumPlayerGroupsEmpty++; } } else { if( pGroup->ubGroupSize ) { ubNumEnemyGroupsFull++; } else { ubNumEnemyGroupsEmpty++; } } if( ubNumPlayerGroupsEmpty || ubNumEnemyGroupsEmpty ) { //report error? } index = pGroup->ubGroupID / 32; bit = pGroup->ubGroupID % 32; mask = 1 << bit; uniqueIDMask[ index ] += mask; pGroup = pGroup->next; } if( uiNumBytesRead != sizeof( UINT32 ) * 8 ) { return( FALSE ); } return( TRUE ); } //Saves the Player's group list to the saved game file BOOLEAN SavePlayerGroupList( HWFILE hFile, GROUP *pGroup ) { UINT32 uiNumberOfNodesInList=0; PLAYERGROUP *pTemp=NULL; UINT32 uiNumBytesWritten=0; UINT32 uiProfileID; pTemp = pGroup->pPlayerList; while( pTemp ) { uiNumberOfNodesInList++; pTemp = pTemp->next; } //Save the number of nodes in the list FileWrite( hFile, &uiNumberOfNodesInList, sizeof( UINT32 ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } pTemp = pGroup->pPlayerList; //Loop trhough and save only the players profile id while( pTemp ) { // Save the ubProfile ID for this node uiProfileID = pTemp->ubProfileID; FileWrite( hFile, &uiProfileID, sizeof( UINT32 ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } pTemp = pTemp->next; } return( TRUE ); } BOOLEAN LoadPlayerGroupList( HWFILE hFile, GROUP **pGroup ) { PLAYERGROUP *pTemp=NULL; PLAYERGROUP *pHead=NULL; UINT32 uiNumberOfNodes=0; UINT32 uiProfileID=0; UINT32 uiNumBytesRead; UINT32 cnt=0; INT16 sTempID; GROUP *pTempGroup = *pGroup; // pTemp = pGroup; // pHead = *pGroup->pPlayerList; // Load the number of nodes in the player list FileRead( hFile, &uiNumberOfNodes, sizeof( UINT32 ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } //loop through all the nodes and set them up for( cnt=0; cnt< uiNumberOfNodes; cnt++) { //allcate space for the current node pTemp = (PLAYERGROUP *) MemAlloc( sizeof( PLAYERGROUP ) ); if( pTemp == NULL ) return( FALSE ); // Load the ubProfile ID for this node FileRead( hFile, &uiProfileID, sizeof( UINT32 ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } //Set up the current node pTemp->ubProfileID = (UINT8)uiProfileID; sTempID = GetSoldierIDFromMercID( pTemp->ubProfileID ); //Should never happen Assert( sTempID != -1 ); pTemp->ubID = (UINT8) sTempID; pTemp->pSoldier = &Menptr[ pTemp->ubID ]; pTemp->next = NULL; //if its the first time through if( cnt == 0 ) { pTempGroup->pPlayerList = pTemp; pHead = pTemp; } else { pHead->next = pTemp; //move to the next node pHead = pHead->next; } } return( TRUE ); } //Saves the enemy group struct to the saved game struct BOOLEAN SaveEnemyGroupStruct( HWFILE hFile, GROUP *pGroup ) { UINT32 uiNumBytesWritten=0; //Save the enemy struct info to the saved game file FileWrite( hFile, pGroup->pEnemyGroup, sizeof( ENEMYGROUP ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( ENEMYGROUP ) ) { //Error Writing size of L.L. to disk return( FALSE ); } return( TRUE ); } //Loads the enemy group struct from the saved game file BOOLEAN LoadEnemyGroupStructFromSavedGame( HWFILE hFile, GROUP *pGroup ) { UINT32 uiNumBytesRead = 0; ENEMYGROUP *pEnemyGroup=NULL; //Alllocate memory for the enemy struct pEnemyGroup = (ENEMYGROUP *) MemAlloc( sizeof( ENEMYGROUP ) ); if( pEnemyGroup == NULL ) return( FALSE ); memset( pEnemyGroup, 0, sizeof( ENEMYGROUP ) ); //Load the enemy struct FileRead( hFile, pEnemyGroup, sizeof( ENEMYGROUP ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( ENEMYGROUP ) ) { //Error Writing size of L.L. to disk return( FALSE ); } //Assign the struct to the group list pGroup->pEnemyGroup = pEnemyGroup; return( TRUE ); } void CheckMembersOfMvtGroupAndComplainAboutBleeding( SOLDIERTYPE *pSoldier ) { // run through members of group UINT8 ubGroupId = pSoldier->ubGroupID; GROUP *pGroup; PLAYERGROUP *pPlayer=NULL; SOLDIERTYPE *pCurrentSoldier=NULL; pGroup = GetGroup( ubGroupId ); // valid group? if( pGroup == NULL ) { return; } // player controlled group? if( pGroup->fPlayer == FALSE ) { return; } // make sure there are members in the group..if so, then run through and make each bleeder compain pPlayer = pGroup->pPlayerList; // is there a player list? if( pPlayer == NULL ) { return; } BeginLoggingForBleedMeToos( TRUE ); while( pPlayer ) { pCurrentSoldier = pPlayer->pSoldier; if( pCurrentSoldier->bBleeding > 0 ) { // complain about bleeding TacticalCharacterDialogue( pCurrentSoldier, QUOTE_STARTING_TO_BLEED ); } pPlayer = pPlayer->next; } BeginLoggingForBleedMeToos( FALSE ); } BOOLEAN SaveWayPointList( HWFILE hFile, GROUP *pGroup ) { UINT32 cnt=0; UINT32 uiNumberOfWayPoints=0; UINT32 uiNumBytesWritten=0; WAYPOINT *pWayPoints = pGroup->pWaypoints; //loop trhough and count all the node in the waypoint list while( pWayPoints != NULL ) { uiNumberOfWayPoints++; pWayPoints = pWayPoints->next; } //Save the number of waypoints FileWrite( hFile, &uiNumberOfWayPoints, sizeof( UINT32 ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } if( uiNumberOfWayPoints ) { pWayPoints = pGroup->pWaypoints; for(cnt=0; cntnext; } } return( TRUE ); } BOOLEAN LoadWayPointList(HWFILE hFile, GROUP *pGroup ) { UINT32 cnt=0; UINT32 uiNumberOfWayPoints=0; UINT32 uiNumBytesRead=0; WAYPOINT *pWayPoints = pGroup->pWaypoints; WAYPOINT *pTemp=NULL; //Load the number of waypoints FileRead( hFile, &uiNumberOfWayPoints, sizeof( UINT32 ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( UINT32 ) ) { //Error Writing size of L.L. to disk return( FALSE ); } if( uiNumberOfWayPoints ) { pWayPoints = pGroup->pWaypoints; for(cnt=0; cntnext = NULL; //if its the first node if( cnt == 0 ) { pGroup->pWaypoints = pTemp; pWayPoints = pTemp; } else { pWayPoints->next = pTemp; //Advance to the next waypoint pWayPoints = pWayPoints->next; } } } else pGroup->pWaypoints = NULL; return( TRUE ); } void CalculateGroupRetreatSector( GROUP *pGroup ) { SECTORINFO *pSector; UINT32 uiSectorID; uiSectorID = SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); pSector = &SectorInfo[ uiSectorID ]; if( pSector->ubTraversability[ NORTH_STRATEGIC_MOVE ] != GROUNDBARRIER && pSector->ubTraversability[ NORTH_STRATEGIC_MOVE ] != EDGEOFWORLD ) { pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY - 1; } else if( pSector->ubTraversability[ EAST_STRATEGIC_MOVE ] != GROUNDBARRIER && pSector->ubTraversability[ EAST_STRATEGIC_MOVE ] != EDGEOFWORLD ) { pGroup->ubPrevX = pGroup->ubSectorX + 1; pGroup->ubPrevY = pGroup->ubSectorY; } else if( pSector->ubTraversability[ WEST_STRATEGIC_MOVE ] != GROUNDBARRIER && pSector->ubTraversability[ WEST_STRATEGIC_MOVE ] != EDGEOFWORLD ) { pGroup->ubPrevX = pGroup->ubSectorX - 1; pGroup->ubPrevY = pGroup->ubSectorY; } else if( pSector->ubTraversability[ SOUTH_STRATEGIC_MOVE ] != GROUNDBARRIER && pSector->ubTraversability[ SOUTH_STRATEGIC_MOVE ] != EDGEOFWORLD ) { pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY + 1; } else { AssertMsg( 0, String("Player group cannot retreat from sector %c%d ", pGroup->ubSectorY+'A'-1, pGroup->ubSectorX ) ); return; } if( pGroup->fPlayer ) { //update the previous sector for the mercs PLAYERGROUP *pPlayer; pPlayer = pGroup->pPlayerList; while( pPlayer ) { pPlayer->pSoldier->ubPrevSectorID = (UINT8)SECTOR( pGroup->ubPrevX, pGroup->ubPrevY ); pPlayer = pPlayer->next; } } } //Called when all checks have been made for the group (if possible to retreat, etc.) This function //blindly determines where to move the group. void RetreatGroupToPreviousSector( GROUP *pGroup ) { UINT8 ubSector, ubDirection = 255; INT32 iVehId, dx, dy; Assert( pGroup ); AssertMsg( !pGroup->fBetweenSectors, "Can't retreat a group when between sectors!" ); if( pGroup->ubPrevX != 16 || pGroup->ubPrevY != 16 ) { //Group has a previous sector pGroup->ubNextX = pGroup->ubPrevX; pGroup->ubNextY = pGroup->ubPrevY; //Determine the correct direction. dx = pGroup->ubNextX - pGroup->ubSectorX; dy = pGroup->ubNextY - pGroup->ubSectorY; if( dy == -1 && !dx ) ubDirection = NORTH_STRATEGIC_MOVE; else if( dx == 1 && !dy ) ubDirection = EAST_STRATEGIC_MOVE; else if( dy == 1 && !dx ) ubDirection = SOUTH_STRATEGIC_MOVE; else if( dx == -1 && !dy ) ubDirection = WEST_STRATEGIC_MOVE; else { AssertMsg( 0, String("Player group attempting illegal retreat from %c%d to %c%d.", pGroup->ubSectorY+'A'-1, pGroup->ubSectorX, pGroup->ubNextY+'A'-1, pGroup->ubNextX ) ); } } else { //Group doesn't have a previous sector. Create one, then recurse CalculateGroupRetreatSector( pGroup ); RetreatGroupToPreviousSector( pGroup ); } //Calc time to get to next waypoint... ubSector = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); pGroup->uiTraverseTime = GetSectorMvtTimeForGroup( ubSector, ubDirection, pGroup ); if( pGroup->uiTraverseTime == 0xffffffff ) { AssertMsg( 0, String("Group %d (%s) attempting illegal move from %c%d to %c%d (%s).", pGroup->ubGroupID, ( pGroup->fPlayer ) ? "Player" : "AI", pGroup->ubSectorY+'A', pGroup->ubSectorX, pGroup->ubNextY+'A', pGroup->ubNextX, gszTerrain[SectorInfo[ubSector].ubTraversability[ubDirection]] ) ); } if( !pGroup->uiTraverseTime ) { //Because we are in the strategic layer, don't make the arrival instantaneous (towns). pGroup->uiTraverseTime = 5; } SetGroupArrivalTime( pGroup, GetWorldTotalMin() + pGroup->uiTraverseTime ); pGroup->fBetweenSectors = TRUE; pGroup->uiFlags |= GROUPFLAG_JUST_RETREATED_FROM_BATTLE; if( pGroup->fVehicle == TRUE ) { // vehicle, set fact it is between sectors too if( ( iVehId = ( GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ) ) ) != -1 ) { pVehicleList[ iVehId ].fBetweenSectors = TRUE; } } //Post the event! if( !AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ) ) AssertMsg( 0, "Failed to add movement event." ); //For the case of player groups, we need to update the information of the soldiers. if( pGroup->fPlayer ) { PLAYERGROUP *curr; curr = pGroup->pPlayerList; if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) { AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); } while( curr ) { curr->pSoldier->flags.fBetweenSectors = TRUE; // OK, Remove the guy from tactical engine! RemoveSoldierFromTacticalSector( curr->pSoldier, TRUE ); curr = curr->next; } } } GROUP* FindMovementGroupInSector( UINT8 ubSectorX, UINT8 ubSectorY, BOOLEAN fPlayer ) { GROUP *pGroup; pGroup = gpGroupList; while( pGroup ) { if( pGroup->fPlayer ) { // NOTE: These checks must always match the INVOLVED group checks in PBI!!! if( fPlayer && pGroup->ubGroupSize && !pGroup->fBetweenSectors && pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY && !pGroup->ubSectorZ && !GroupHasInTransitDeadOrPOWMercs( pGroup ) && ( !IsGroupTheHelicopterGroup( pGroup ) || !fHelicopterIsAirBorne ) ) { return pGroup; } } else if( !fPlayer && pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY && !pGroup->ubSectorZ ) return pGroup; pGroup = pGroup->next; } return NULL; } BOOLEAN GroupAtFinalDestination( GROUP *pGroup ) { WAYPOINT *wp; if( pGroup->ubMoveType != ONE_WAY ) return FALSE; //Group will continue to patrol, hence never stops. //Determine if we are at the final waypoint. wp = GetFinalWaypoint( pGroup ); if( !wp ) { //no waypoints, so the group is at it's destination. This happens when //an enemy group is created in the destination sector (which is legal for //staging groups which always stop adjacent to their real sector destination) return TRUE; } // if we're there if( ( pGroup->ubSectorX == wp->x ) && ( pGroup->ubSectorY == wp->y ) ) { return TRUE; } return FALSE; } WAYPOINT *GetFinalWaypoint( GROUP *pGroup ) { WAYPOINT *wp; Assert( pGroup ); //Make sure they're on a one way route, otherwise this request is illegal Assert( pGroup->ubMoveType == ONE_WAY ); wp = pGroup->pWaypoints; if( wp ) { while( wp->next ) { wp = wp->next; } } return( wp ); } //The sector supplied resets ALL enemy groups in the sector specified. See comments in //ResetMovementForEnemyGroup() for more details on what the resetting does. void ResetMovementForEnemyGroupsInLocation( UINT8 ubSectorX, UINT8 ubSectorY ) { GROUP *pGroup, *next; INT16 sSectorX, sSectorY, sSectorZ; GetCurrentBattleSectorXYZ( &sSectorX, &sSectorY, &sSectorZ ); pGroup = gpGroupList; while( pGroup ) { next = pGroup->next; if( !pGroup->fPlayer ) { if( pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) { ResetMovementForEnemyGroup( pGroup ); } } pGroup = next; } } //This function is used to reset the location of the enemy group if they are //currently between sectors. If they were 50% of the way from sector A10 to A11, //then after this function is called, then that group would be 0% of the way from //sector A10 to A11. In no way does this function effect the strategic path for //the group. void ResetMovementForEnemyGroup( GROUP *pGroup ) { //Validate that the group is an enemy group and that it is moving. if( pGroup->fPlayer ) { return; } if( !pGroup->fBetweenSectors || !pGroup->ubNextX || !pGroup->ubNextY ) { //Reset the group's assignment by moving it to the group's original sector as it's pending group. #ifdef JA2UB //Ja25 No strategic ai #else RepollSAIGroup( pGroup ); #endif return; } //Cancel the event that is posted. DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); //Calculate the new arrival time (all data pertaining to movement should be valid) if( pGroup->uiTraverseTime > 400 ) { //The group was likely sleeping which makes for extremely long arrival times. Shorten it //arbitrarily. Doesn't really matter if this isn't accurate. pGroup->uiTraverseTime = 90; } SetGroupArrivalTime( pGroup, GetWorldTotalMin() + pGroup->uiTraverseTime ); //Add a new event AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ); } void UpdatePersistantGroupsFromOldSave( UINT32 uiSavedGameVersion ) { GROUP *pGroup = NULL; BOOLEAN fDone = FALSE; INT32 cnt; BOOLEAN fDoChange = FALSE; // ATE: If saved game is < 61, we need to do something better! if( uiSavedGameVersion < 61 ) { for( cnt = 0; cnt < 55; cnt++ ) { // create mvt groups pGroup = GetGroup( (UINT8)cnt ); if( pGroup != NULL && pGroup->fPlayer ) { pGroup->fPersistant = TRUE; } } fDoChange = TRUE; } else if( uiSavedGameVersion < 63 ) { for( cnt = 0; cnt < NUMBER_OF_SQUADS; cnt++ ) { // create mvt groups pGroup = GetGroup( SquadMovementGroups[ cnt ] ); if ( pGroup != NULL ) { pGroup->fPersistant = TRUE; } } for( cnt = 0; cnt < MAX_VEHICLES; cnt++ ) { pGroup = GetGroup( gubVehicleMovementGroups[ cnt ] ); if ( pGroup != NULL ) { pGroup->fPersistant = TRUE; } } fDoChange = TRUE; } if ( fDoChange ) { //Remove all empty groups fDone = FALSE; while( !fDone ) { pGroup = gpGroupList; while( pGroup ) { if( !pGroup->ubGroupSize && !pGroup->fPersistant ) { RemovePGroup( pGroup ); break; } pGroup = pGroup->next; if( !pGroup ) { fDone = TRUE; } } } } } //Determines if any particular group WILL be moving through a given sector given it's current //position in the route and the pGroup->ubMoveType must be ONE_WAY. If the group is currently //IN the sector, or just left the sector, it will return FALSE. BOOLEAN GroupWillMoveThroughSector( GROUP *pGroup, UINT8 ubSectorX, UINT8 ubSectorY ) { WAYPOINT *wp; INT32 i, dx, dy; UINT8 ubOrigX, ubOrigY; Assert( pGroup ); AssertMsg( pGroup->ubMoveType == ONE_WAY, String( "GroupWillMoveThroughSector() -- Attempting to test group with an invalid move type. ubGroupID: %d, ubMoveType: %d, sector: %c%d -- KM:0", pGroup->ubGroupID, pGroup->ubMoveType, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX ) ); //Preserve the original sector values, as we will be temporarily modifying the group's ubSectorX/Y values //as we traverse the waypoints. ubOrigX = pGroup->ubSectorX; ubOrigY = pGroup->ubSectorY; i = pGroup->ubNextWaypointID; wp = pGroup->pWaypoints; if( !wp ) { //This is a floating group!? return FALSE; } while( i-- ) { //Traverse through the waypoint list to the next waypoint ID Assert( wp ); wp = wp->next; } Assert( wp ); while( wp ) { while( pGroup->ubSectorX != wp->x || pGroup->ubSectorY != wp->y ) { //We now have the correct waypoint. //Analyse the group and determine which direction it will move from the current sector. dx = wp->x - pGroup->ubSectorX; dy = wp->y - pGroup->ubSectorY; if( dx && dy ) { //Can't move diagonally! //AssertMsg( 0, String( "GroupWillMoveThroughSector() -- Attempting to process waypoint in a diagonal direction from sector %c%d to sector %c%d for group at sector %c%d -- KM:0", // pGroup->ubSectorY + 'A', pGroup->ubSectorX, wp->y + 'A' - 1, wp->x, ubOrigY + 'A' - 1, ubOrigX ) ); pGroup->ubSectorX = ubOrigX; pGroup->ubSectorY = ubOrigY; return TRUE; } if( !dx && !dy ) //Can't move to position currently at! { //AssertMsg( 0, String( "GroupWillMoveThroughSector() -- Attempting to process same waypoint at %c%d for group at %c%d -- KM:0", // wp->y + 'A' - 1, wp->x, ubOrigY + 'A' - 1, ubOrigX ) ); pGroup->ubSectorX = ubOrigX; pGroup->ubSectorY = ubOrigY; return TRUE; } //Clip dx/dy value so that the move is for only one sector. if( dx >= 1 ) { dx = 1; } else if( dy >= 1 ) { dy = 1; } else if( dx <= -1 ) { dx = -1; } else if( dy <= -1 ) { dy = -1; } else { Assert( 0 ); pGroup->ubSectorX = ubOrigX; pGroup->ubSectorY = ubOrigY; return TRUE; } //Advance the sector value pGroup->ubSectorX = (UINT8)( dx + pGroup->ubSectorX ); pGroup->ubSectorY = (UINT8)( dy + pGroup->ubSectorY ); //Check to see if it the sector we are checking to see if this group will be moving through. if( pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY ) { pGroup->ubSectorX = ubOrigX; pGroup->ubSectorY = ubOrigY; return TRUE; } } //Advance to the next waypoint. wp = wp->next; } pGroup->ubSectorX = ubOrigX; pGroup->ubSectorY = ubOrigY; return FALSE; } INT16 CalculateFuelCostBetweenSectors( UINT8 ubSectorID1, UINT8 ubSectorID2 ) { return(0); } BOOLEAN VehicleHasFuel( SOLDIERTYPE *pSoldier ) { Assert( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ); if( pSoldier->sBreathRed ) { return TRUE; } return FALSE; } INT16 VehicleFuelRemaining( SOLDIERTYPE *pSoldier ) { Assert( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ); return pSoldier->sBreathRed; } BOOLEAN SpendVehicleFuel( SOLDIERTYPE* pSoldier, INT16 sFuelSpent ) { Assert( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ); pSoldier->sBreathRed -= sFuelSpent; pSoldier->sBreathRed = (INT16)max( 0, pSoldier->sBreathRed ); pSoldier->bBreath = (INT8)((pSoldier->sBreathRed+99) / 100); return( FALSE ); } void AddFuelToVehicle( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVehicle ) { OBJECTTYPE *pItem; INT16 sFuelNeeded, sFuelAvailable, sFuelAdded; pItem = &pSoldier->inv[ HANDPOS ]; if( !Item[pItem->usItem].gascan ) { #ifdef JA2BETAVERSION CHAR16 str[ 100 ]; swprintf( str, L"%s is supposed to have gas can in hand. ATE:0", pSoldier->name ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); #endif return; } //Soldier has gas can, so now add gas to vehicle while removing gas from the gas can. //A gas can with 100 status translate to 50% of a fillup. if( pVehicle->sBreathRed == 10000 ) { //Message for vehicle full? return; } if( (*pItem)[0]->data.objectStatus ) { //Fill 'er up. sFuelNeeded = 10000 - pVehicle->sBreathRed; sFuelAvailable = (*pItem)[0]->data.objectStatus * 50; sFuelAdded = min( sFuelNeeded, sFuelAvailable ); //Add to vehicle pVehicle->sBreathRed += sFuelAdded; pVehicle->bBreath = (INT8)(pVehicle->sBreathRed / 100); //Subtract from item (*pItem)[0]->data.objectStatus = (INT8)((*pItem)[0]->data.objectStatus - sFuelAdded / 50); if( !(*pItem)[0]->data.objectStatus ) { //Gas can is empty, so toast the item. pItem->RemoveObjectsFromStack(1); } } } void ReportVehicleOutOfGas( INT32 iVehicleID, UINT8 ubSectorX, UINT8 ubSectorY ) { CHAR16 str[255]; //Report that the vehicle that just arrived is out of gas. swprintf( str, gzLateLocalizedString[ 5 ], pVehicleStrings[ pVehicleList[ iVehicleID ].ubVehicleType ], ubSectorY + 'A' - 1, ubSectorX ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); } void SetLocationOfAllPlayerSoldiersInGroup( GROUP *pGroup, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ) { PLAYERGROUP *pPlayer = NULL; SOLDIERTYPE *pSoldier = NULL; pPlayer = pGroup->pPlayerList; while( pPlayer ) { pSoldier = pPlayer->pSoldier; if ( pSoldier != NULL ) { pSoldier->sSectorX = sSectorX; pSoldier->sSectorY = sSectorY; pSoldier->bSectorZ = bSectorZ; } pPlayer = pPlayer->next; } // if it's a vehicle if ( pGroup->fVehicle ) { INT32 iVehicleId = -1; VEHICLETYPE *pVehicle = NULL; iVehicleId = GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ); Assert ( iVehicleId != -1 ); pVehicle = &( pVehicleList[ iVehicleId ] ); pVehicle->sSectorX = sSectorX; pVehicle->sSectorY = sSectorY; pVehicle->sSectorZ = bSectorZ; // if it ain't the chopper if ( iVehicleId != iHelicopterVehicleId ) { pSoldier = GetSoldierStructureForVehicle( iVehicleId ); Assert ( pSoldier ); // these are apparently unnecessary, since vehicles are part of the pPlayerList in a vehicle group. Oh well. pSoldier->sSectorX = sSectorX; pSoldier->sSectorY = sSectorY; pSoldier->bSectorZ = bSectorZ; } } } void RandomizePatrolGroupLocation( GROUP *pGroup ) { //Make sure this is an enemy patrol group WAYPOINT *wp; UINT8 ubMaxWaypointID = 0; UINT8 ubTotalWaypoints; UINT8 ubChosen; UINT8 ubSectorID; //return; //disabled for now Assert( !pGroup->fPlayer ); Assert( pGroup->ubMoveType == ENDTOEND_FORWARDS ); Assert( pGroup->pEnemyGroup->ubIntention == PATROL ); //Search for the event, and kill it (if it exists)! DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); //count the group's waypoints wp = pGroup->pWaypoints; while( wp ) { if( wp->next ) { ubMaxWaypointID++; } wp = wp->next; } //double it (they go back and forth) -- it's using zero based indices, so you have to add one to get the number of actual //waypoints in one direction. ubTotalWaypoints = (UINT8)((ubMaxWaypointID) * 2); //pick the waypoint they start at ubChosen = (UINT8)Random( ubTotalWaypoints ); if( ubChosen >= ubMaxWaypointID ) { //They chose a waypoint going in the reverse direction, so translate it //to an actual waypointID and switch directions. pGroup->ubMoveType = ENDTOEND_BACKWARDS; pGroup->ubNextWaypointID = ubChosen - ubMaxWaypointID; ubChosen = pGroup->ubNextWaypointID + 1; } else { pGroup->ubMoveType = ENDTOEND_FORWARDS; pGroup->ubNextWaypointID = ubChosen + 1; } //Traverse through the waypoint list again, to extract the location they are at. wp = pGroup->pWaypoints; while( wp && ubChosen ) { ubChosen--; wp = wp->next; } //logic error if this fails. We should have a null value for ubChosen Assert( !ubChosen ); Assert( wp ); //Move the group to the location of this chosen waypoint. ubSectorID = (UINT8)SECTOR( wp->x, wp->y ); //Set up this global var to randomize the arrival time of the group from //1 minute to actual traverse time between the sectors. gfRandomizingPatrolGroup = TRUE; SetEnemyGroupSector( pGroup, ubSectorID ); InitiateGroupMovementToNextSector( pGroup ); //Immediately turn off the flag once finished. gfRandomizingPatrolGroup = FALSE; } //Whenever a player group arrives in a sector, and if bloodcats exist in the sector, //roll the dice to see if this will become an ambush random encounter. BOOLEAN TestForBloodcatAmbush( GROUP *pGroup ) { #ifdef JA2UB //Ja25: No BLoodcat ambushes return( FALSE ); #else SECTORINFO *pSector; INT32 iHoursElapsed; UINT8 ubSectorID; UINT8 ubChance; UINT8 ubDifficulty = gGameOptions.ubDifficultyLevel-1; // For use by Placements Array. Novice = 0 instead of 1. UINT8 ubRange; UINT8 ubRandomCats; INT8 bNumMercMaxCats; UINT8 PlacementType; // 0 = Ambush site. 1 = Static placement. 2 = Lair. UINT8 ubMinBloodcats; // XML minimum. UINT8 ubMaxBloodcats; // XML maximum. BOOLEAN fAlreadyAmbushed = FALSE; if( pGroup->ubSectorZ ) { //no ambushes underground (no bloodcats either) return FALSE; } ubSectorID = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); pSector = &SectorInfo[ ubSectorID ]; // Read chance from XML data, based on difficulty. ubChance = gBloodcatPlacements[ ubSectorID ][ ubDifficulty ].ubAmbushChance; iHoursElapsed = __max(0,(GetWorldTotalMin() - pSector->uiTimeCurrentSectorWasLastLoaded) / 60); // Decrease chance to 0% if sector has been visited this hour. Every hour after that, increase the chance by 1% // until it reaches the default value again. ubChance = (UINT8)__min((INT32)ubChance, iHoursElapsed); ubChance = __max(0,ubChance); // Determine what type of placement this is. PlacementType = gBloodcatPlacements[ ubSectorID ][0].PlacementType; // At lair? if( PlacementType == BLOODCAT_PLACEMENT_LAIR ) { // Bloodcats still exist here, but aren't at the optimal number? if( pSector->bBloodCats > 0 && pSector->bBloodCats < pSector->bBloodCatPlacements ) { //Slowly have them recuperate if we haven't been here for a long time. The population will //come back up to the maximum if left long enough. INT32 iBloodCatDiff; iBloodCatDiff = pSector->bBloodCatPlacements - pSector->bBloodCats; pSector->bBloodCats += (INT8)min( iHoursElapsed / 18, iBloodCatDiff ); } //Once 0, the bloodcats will never recupe. } // At ambush sector? else if( PlacementType == BLOODCAT_PLACEMENT_AMBUSH ) { //If there's any chance of ambush in this sector, and we beat that chance... if( ubChance && (gfAutoAmbush || PreChance( ubChance )) ) { //randomly choose from 5-8, 7-10, 9-12 bloodcats based on easy, normal, and hard, respectively //bDifficultyMaxCats = (INT8)( Random( 4 ) + gGameOptions.ubDifficultyLevel*2 + 3 ); //maximum of 3 bloodcats or 1 for every 6%, 5%, 4% progress based on easy, normal, and hard, respectively //bProgressMaxCats = (INT8)max( CurrentPlayerProgressPercentage() / (7 - gGameOptions.ubDifficultyLevel), 3 ); //choose the lowest number of cats calculated by difficulty and progress. //pSector->bBloodCats = (INT8)min( bDifficultyMaxCats, bProgressMaxCats ); /////////////////////////////////////////////////////////////////////////////////////////////////////// // HEADROCK HAM 3.6: Minimum and Maximum are now modder-defined, so pick a random number between them. // Read Min/Max from XML ubMaxBloodcats = gBloodcatPlacements[ ubSectorID ][ ubDifficulty ].ubMaxBloodcats; ubMinBloodcats = gBloodcatPlacements[ ubSectorID ][ ubDifficulty ].ubMinBloodcats; // Find range between Min and Max ubRange = ubMaxBloodcats-ubMinBloodcats; // Adjust range based on current progress. ubRange = __max(0,( ubRange * CurrentPlayerProgressPercentage() ) / 100); // Randomize "extra" bloodcats ubRandomCats = PreRandom(ubRange+1); // Add "extra" bloodcats to minimum value ubRandomCats += ubMinBloodcats; // SET NUMBER OF CATS THAT WILL APPEAR pSector->bBloodCats = ubRandomCats; if( gGameOptions.ubDifficultyLevel < DIF_LEVEL_HARD ) { //At NOVICE/EXPERIENCED difficulty, ensure cats never outnumber mercs by a factor of more than 2. bNumMercMaxCats = (INT8)(PlayerMercsInSector( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ) * 2); pSector->bBloodCats = (INT8)min( pSector->bBloodCats, bNumMercMaxCats ); //pSector->bBloodCats = (INT8)max( pSector->bBloodCats, 3 ); } //ensure that there aren't more bloodcats than placements pSector->bBloodCats = (INT8)min( pSector->bBloodCats, pSector->bBloodCatPlacements ); // Once again, make absolutely sure we're still within XML-set limits pSector->bBloodCats = (INT8)min( pSector->bBloodCats, ubMaxBloodcats); pSector->bBloodCats = (INT8)max( pSector->bBloodCats, ubMinBloodcats); } else { // Chance was 0. fAlreadyAmbushed = TRUE; } } // An ambush is occuring? if( !fAlreadyAmbushed && PlacementType != BLOODCAT_PLACEMENT_STATIC && pSector->bBloodCats > 0 && !pGroup->fVehicle && !NumEnemiesInSector( pGroup->ubSectorX, pGroup->ubSectorY ) ) { /////////////////////////////////////////////////////////////////////////////////////////// // SANDRO - STOMP traits - Scouting prevents the ambush of bloodcats BOOLEAN fBloodCatAmbushPrevented = FALSE; // The player has entered the lair if(( PlacementType == BLOODCAT_PLACEMENT_LAIR )) // Default: Sector I16 { // We know about the lair if( gubFact[ FACT_PLAYER_KNOWS_ABOUT_BLOODCAT_LAIR ] ) { gubEnemyEncounterCode = ENTERING_BLOODCAT_LAIR_CODE; } else { if ( gGameOptions.fNewTraitSystem && ScoutIsPresentInSquad( pGroup->ubSectorX, pGroup->ubSectorY ) && gSkillTraitValues.fSCPreventsBloodcatsAmbushes) { // Ha..! We've found the lair, safely if ( gSkillTraitValues.fSCThrowMessageIfAmbushPrevented ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_BLOODCATS_AMBUSH_PREVENTED] ); fBloodCatAmbushPrevented = TRUE; gubEnemyEncounterCode = ENTERING_BLOODCAT_LAIR_CODE; } else { // Ambush in the lair. gubEnemyEncounterCode = BLOODCAT_AMBUSH_CODE; } } } // Wilderness ambush else { if ( gGameOptions.fNewTraitSystem && ScoutIsPresentInSquad( pGroup->ubSectorX, pGroup->ubSectorY ) && gSkillTraitValues.fSCPreventsBloodcatsAmbushes) { // We are safe if ( gSkillTraitValues.fSCThrowMessageIfAmbushPrevented ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_BLOODCATS_AMBUSH_PREVENTED] ); fBloodCatAmbushPrevented = TRUE; gubEnemyEncounterCode = NO_ENCOUNTER_CODE; } else { // Ambush occurs. gubEnemyEncounterCode = BLOODCAT_AMBUSH_CODE; } } // merc recoeds - get a point to scouts if ( fBloodCatAmbushPrevented ) { for( UINT16 i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) { if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { if ( MercPtrs[ i ]->sSectorX == pGroup->ubSectorX && MercPtrs[ i ]->sSectorY == pGroup->ubSectorY && MercPtrs[ i ]->bAssignment != ASSIGNMENT_POW && MercPtrs[ i ]->stats.bLife > OKLIFE ) { if( HAS_SKILL_TRAIT( MercPtrs[ i ], SCOUTING_NT ) && MercPtrs[ i ]->ubProfile != NO_PROFILE ) { gMercProfiles[ MercPtrs[ i ]->ubProfile ].records.usAmbushesExperienced++; } } } } } return( ( gubEnemyEncounterCode == NO_ENCOUNTER_CODE ) ? FALSE : TRUE); /////////////////////////////////////////////////////////////////////////////////////////// } else { // No special bloodcat encounter. This ALWAYS happens in STATIC PLACEMENT sectors. gubEnemyEncounterCode = NO_ENCOUNTER_CODE; return FALSE; } #endif } void NotifyPlayerOfBloodcatBattle( UINT8 ubSectorX, UINT8 ubSectorY ) { CHAR16 str[ 256 ]; CHAR16 zTempString[ 128 ]; if( gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE ) { GetSectorIDString( ubSectorX, ubSectorY, 0, zTempString, TRUE ); swprintf( str, pMapErrorString[ 12 ], zTempString ); } else if( gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ) { GetSectorIDString( ubSectorX, ubSectorY, 0, zTempString, FALSE ); swprintf( str, pMapErrorString[ 13 ], zTempString ); } if( guiCurrentScreen == MAP_SCREEN ) { //Force render mapscreen (need to update the position of the group before the dialog appears. fMapPanelDirty = TRUE; MapScreenHandle(); InvalidateScreen(); RefreshScreen( NULL ); } gfUsePersistantPBI = TRUE; DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, TriggerPrebattleInterface ); } void PlaceGroupInSector( UINT8 ubGroupID, INT16 sPrevX, INT16 sPrevY, INT16 sNextX, INT16 sNextY, INT8 bZ, BOOLEAN fCheckForBattle ) { ClearMercPathsAndWaypointsForAllInGroup( GetGroup( ubGroupID ) ); // change where they are and where they're going SetGroupPrevSectors( ubGroupID, ( UINT8 ) sPrevX, ( UINT8 ) sPrevY ); SetGroupSectorValue( sPrevX, sPrevY, bZ, ubGroupID ); SetGroupNextSectorValue( sNextX, sNextY, ubGroupID ); // call arrive event GroupArrivedAtSector( ubGroupID, fCheckForBattle, FALSE ); } // ARM: centralized it so we can do a comprehensive Assert on it. Causing problems with helicopter group! void SetGroupArrivalTime( GROUP *pGroup, UINT32 uiArrivalTime ) { // PLEASE CENTRALIZE ALL CHANGES TO THE ARRIVAL TIMES OF GROUPS THROUGH HERE, ESPECIALLY THE HELICOPTER GROUP!!! // if this group is the helicopter group, we have to make sure that its arrival time is never greater than the sum // of the current time and its traverse time, 'cause those 3 values are used to plot its map position! Because of this // the chopper groups must NEVER be delayed for any reason - it gets excluded from simultaneous arrival logic // Also note that non-chopper groups can currently be delayed such that this assetion would fail - enemy groups by // DelayEnemyGroupsIfPathsCross(), and player groups via PrepareGroupsForSimultaneousArrival(). So we skip the assert. if ( IsGroupTheHelicopterGroup( pGroup ) ) { // make sure it's valid (NOTE: the correct traverse time must be set first!) if ( uiArrivalTime > ( GetWorldTotalMin() + pGroup->uiTraverseTime ) ) { AssertMsg( FALSE, String( "SetGroupArrivalTime: Setting invalid arrival time %d for group %d, WorldTime = %d, TraverseTime = %d", uiArrivalTime, pGroup->ubGroupID, GetWorldTotalMin(), pGroup->uiTraverseTime ) ); // fix it if assertions are disabled uiArrivalTime = GetWorldTotalMin() + pGroup->uiTraverseTime; } } pGroup->uiArrivalTime = uiArrivalTime; } // non-persistent groups should be simply removed instead! void CancelEmptyPersistentGroupMovement( GROUP *pGroup ) { Assert( pGroup ); Assert( pGroup->ubGroupSize == 0 ); Assert( pGroup->fPersistant ); // don't do this for vehicle groups - the chopper can keep flying empty, // while other vehicles still exist and teleport to nearest sector instead if ( pGroup->fVehicle ) { return; } // prevent it from arriving empty DeleteStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->ubGroupID ); // release memory for its waypoints RemoveGroupWaypoints( pGroup->ubGroupID ); pGroup->uiTraverseTime = 0; SetGroupArrivalTime( pGroup, 0 ); pGroup->fBetweenSectors = FALSE; pGroup->ubPrevX = 0; pGroup->ubPrevY = 0; pGroup->ubSectorX = 0; pGroup->ubSectorY = 0; pGroup->ubNextX = 0; pGroup->ubNextY = 0; } // look for NPCs to stop for, anyone is too tired to keep going, if all OK rebuild waypoints & continue movement void PlayerGroupArrivedSafelyInSector( GROUP *pGroup, BOOLEAN fCheckForNPCs ) { BOOLEAN fPlayerPrompted = FALSE; Assert( pGroup ); Assert( pGroup->fPlayer ); // if we haven't already checked for NPCs, and the group isn't empty if ( fCheckForNPCs && ( HandlePlayerGroupEnteringSectorToCheckForNPCsOfNote( pGroup ) == TRUE ) ) { // wait for player to answer/confirm prompt before doing anything else fPlayerPrompted = TRUE; } // if we're not prompting the player if ( !fPlayerPrompted ) { // and we're not at the end of our road if ( !GroupAtFinalDestination( pGroup ) ) { if ( AnyMercInGroupCantContinueMoving( pGroup ) ) { // stop: clear their strategic movement (mercpaths and waypoints) ClearMercPathsAndWaypointsForAllInGroup( pGroup ); // NOTE: Of course, it would be better if they continued onwards once everyone was ready to go again, in which // case we'd want to preserve the plotted path, but since the player can mess with the squads, etc. // in the mean-time, that just seemed to risky to try to support. They could get into a fight and be too // injured to move, etc. Basically, we'd have run a complete CanCharacterMoveInStrategic(0 check on all of them. // It's a wish list task for AM... // stop time so player can react if group was already on the move and suddenly halts StopTimeCompression(); } else { // continue onwards: rebuild way points, initiate movement RebuildWayPointsForGroupPath( GetGroupMercPathPtr( pGroup ), pGroup->ubGroupID ); } } } } BOOLEAN HandlePlayerGroupEnteringSectorToCheckForNPCsOfNote( GROUP *pGroup ) { INT16 sSectorX = 0, sSectorY = 0; INT8 bSectorZ = 0; CHAR16 sString[ 128 ]; CHAR16 wSectorName[ 128 ]; INT16 sStrategicSector; Assert( pGroup ); Assert( pGroup->fPlayer ); // nobody in the group (perfectly legal with the chopper) if ( pGroup->pPlayerList == NULL ) { return( FALSE ); } // chopper doesn't stop for NPCs if ( IsGroupTheHelicopterGroup( pGroup ) ) { return( FALSE ); } // if we're already in the middle of a prompt (possible with simultaneously group arrivals!), don't try to prompt again if ( gpGroupPrompting != NULL ) { return( FALSE ); } // get the sector values sSectorX = pGroup->ubSectorX; sSectorY = pGroup->ubSectorY; bSectorZ = pGroup->ubSectorZ; // don't do this for underground sectors if ( bSectorZ != 0 ) { return( FALSE ); } // get the strategic sector value sStrategicSector = sSectorX + MAP_WORLD_X * sSectorY; #ifdef JA2UB // ATE: if this is a custom map, return Ja25 UB if ( SectorInfo[ SECTOR( sSectorY, sSectorX ) ].fCustomSector ) { return( FALSE ); } #endif // skip towns/pseudo-towns (anything that shows up on the map as being special) if( StrategicMap[ sStrategicSector ].bNameId != BLANK_SECTOR ) { return( FALSE ); } // skip SAM-sites if ( IsThisSectorASAMSector( sSectorX, sSectorY, bSectorZ ) ) { return( FALSE ); } // check for profiled NPCs in sector if( WildernessSectorWithAllProfiledNPCsNotSpokenWith( sSectorX, sSectorY, bSectorZ ) == FALSE ) { return( FALSE ); } // store the group ptr for use by the callback function gpGroupPrompting = pGroup; // build string for squad GetSectorIDString( sSectorX, sSectorY, bSectorZ, wSectorName, FALSE ); swprintf( sString, pLandMarkInSectorString[ 0 ], pGroup->pPlayerList->pSoldier->bAssignment + 1, wSectorName ); if ( GroupAtFinalDestination( pGroup ) ) { // do an OK message box DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, HandlePlayerGroupEnteringSectorToCheckForNPCsOfNoteCallback ); } else { // do a CONTINUE/STOP message box DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_CONTINUESTOP, HandlePlayerGroupEnteringSectorToCheckForNPCsOfNoteCallback ); } // wait, we're prompting the player return( TRUE ); } BOOLEAN WildernessSectorWithAllProfiledNPCsNotSpokenWith( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ) { UINT8 ubProfile; MERCPROFILESTRUCT * pProfile; BOOLEAN fFoundSomebody = FALSE; //for ( ubProfile = FIRST_RPC; ubProfile < GASTON; ubProfile++ ) //new profiles by Jazz for ( ubProfile = 0; ubProfile < NUM_PROFILES; ubProfile++ ) { if ( gProfilesRPC[ubProfile].ProfilId == ubProfile || gProfilesNPC[ubProfile].ProfilId == ubProfile || gProfilesVehicle[ubProfile].ProfilId == ubProfile) { pProfile = &gMercProfiles[ ubProfile ]; // skip stiffs if ( ( pProfile->bMercStatus == MERC_IS_DEAD ) || ( pProfile->bLife <= 0 ) ) { continue; } // skip vehicles //if ( ubProfile >= PROF_HUMMER && ubProfile <= PROF_HELICOPTER ) if ( gProfilesVehicle[ubProfile].ProfilId == ubProfile ) { continue; } // in this sector? if ( pProfile->sSectorX == sSectorX && pProfile->sSectorY == sSectorY && pProfile->bSectorZ == bSectorZ ) { // if we haven't talked to him yet, and he's not currently recruired/escorted by player (!) if ( ( pProfile->ubLastDateSpokenTo == 0 ) && !(pProfile->ubMiscFlags & (PROFILE_MISC_FLAG_RECRUITED | PROFILE_MISC_FLAG_EPCACTIVE) ) ) { // then this is a guy we need to stop for... fFoundSomebody = TRUE; } else { // already spoke to this guy, don't prompt about this sector again, regardless of status of other NPCs here // (although Hamous wanders around, he never shares the same wilderness sector as other important NPCs) return( FALSE ); } } } } return( fFoundSomebody ); } void HandlePlayerGroupEnteringSectorToCheckForNPCsOfNoteCallback( UINT8 ubExitValue ) { Assert( gpGroupPrompting ); if ( (ubExitValue == MSG_BOX_RETURN_YES) || (ubExitValue == MSG_BOX_RETURN_OK) ) { // NPCs now checked, continue moving if appropriate PlayerGroupArrivedSafelyInSector( gpGroupPrompting, FALSE ); } else if( ubExitValue == MSG_BOX_RETURN_NO ) { // stop here // clear their strategic movement (mercpaths and waypoints) ClearMercPathsAndWaypointsForAllInGroup( gpGroupPrompting ); // // if currently selected sector has nobody in it // if ( PlayerMercsInSector( ( UINT8 ) sSelMapX, ( UINT8 ) sSelMapY, ( UINT8 ) iCurrentMapSectorZ ) == 0 ) // New: ALWAYS make this sector strategically selected, even if there were mercs in the previously selected one { ChangeSelectedMapSector( gpGroupPrompting->ubSectorX, gpGroupPrompting->ubSectorY, gpGroupPrompting->ubSectorZ ); } StopTimeCompression(); } gpGroupPrompting = NULL; fMapPanelDirty = TRUE; fMapScreenBottomDirty = TRUE; return; } BOOLEAN DoesPlayerExistInPGroup( UINT8 ubGroupID, SOLDIERTYPE *pSoldier ) { GROUP *pGroup; PLAYERGROUP *curr; pGroup = GetGroup( ubGroupID ); Assert( pGroup ); curr = pGroup->pPlayerList; if( !curr ) { return FALSE; } while( curr ) { //definately more than one node if( curr->pSoldier == pSoldier ) { return TRUE; } curr = curr->next; } // !curr return FALSE; } BOOLEAN GroupHasInTransitDeadOrPOWMercs( GROUP *pGroup ) { PLAYERGROUP *pPlayer; pPlayer = pGroup->pPlayerList; while( pPlayer ) { if ( pPlayer->pSoldier ) { if( ( pPlayer->pSoldier->bAssignment == IN_TRANSIT ) || ( pPlayer->pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pPlayer->pSoldier->bAssignment == ASSIGNMENT_DEAD ) ) { // yup! return( TRUE ); } } pPlayer = pPlayer->next; } // nope return( FALSE ); } UINT8 NumberMercsInVehicleGroup( GROUP *pGroup ) { INT32 iVehicleID; iVehicleID = GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ); Assert( iVehicleID != -1 ); if( iVehicleID != -1 ) { return (UINT8)GetNumberInVehicle( iVehicleID ); } return 0; } BOOLEAN ValidateGroups( GROUP *pGroup ) { BOOLEAN isValid = TRUE; //Do error checking, and report group #ifdef JA2BETAVERSION ValidatePlayersAreInOneGroupOnly(); #endif if( !pGroup->fPlayer && !pGroup->ubGroupSize ) { isValid = FALSE; //report error CHAR16 str[ 512 ]; if( pGroup->ubSectorIDOfLastReassignment == 255 ) { swprintf( str, L"Enemy group found with 0 troops in it. This is illegal and group will be deleted." L" Group %d in sector %c%d originated from sector %c%d.", pGroup->ubGroupID, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX, SECTORY( pGroup->ubCreatedSectorID ) + 'A' - 1, SECTORX( pGroup->ubCreatedSectorID ) ); } else { swprintf( str, L"Enemy group found with 0 troops in it. This is illegal and group will be deleted." L" Group %d in sector %c%d originated from sector %c%d and last reassignment location was %c%d.", pGroup->ubGroupID, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX, SECTORY( pGroup->ubCreatedSectorID ) + 'A' - 1, SECTORX( pGroup->ubCreatedSectorID ), SECTORY( pGroup->ubSectorIDOfLastReassignment ) + 'A' - 1, SECTORX( pGroup->ubSectorIDOfLastReassignment ) ); } //correct error #ifdef JA2BETAVERSION DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); #endif } return isValid; } // SANDRO - added check if we have a scout in group BOOLEAN ScoutIsPresentInSquad( INT16 ubSectorNumX, INT16 ubSectorNumY ) { BOOLEAN fScoutPresent = FALSE; INT32 i; for( i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) { if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { if ( MercPtrs[ i ]->sSectorX == ubSectorNumX && MercPtrs[ i ]->sSectorY == ubSectorNumY && MercPtrs[ i ]->bAssignment != ASSIGNMENT_POW && MercPtrs[ i ]->stats.bLife > OKLIFE ) { if( HAS_SKILL_TRAIT( MercPtrs[ i ], SCOUTING_NT )) { fScoutPresent = TRUE; } } } } return ( fScoutPresent ); } #ifdef JA2UB GROUP* CreateNewEnemyGroupDepartingFromSectorUsingZLevel( UINT32 uiSector, UINT8 ubSectorZ, UINT8 ubNumAdmins, UINT8 ubNumTroops, UINT8 ubNumElites ) { GROUP *pNew; AssertMsg( uiSector >= 0 && uiSector <= 255, String( "CreateNewEnemyGroup with out of range value of %d", uiSector ) ); pNew = (GROUP*)MemAlloc( sizeof( GROUP ) ); AssertMsg( pNew, "MemAlloc failure during CreateNewEnemyGroup." ); memset( pNew, 0, sizeof( GROUP ) ); pNew->pEnemyGroup = (ENEMYGROUP*)MemAlloc( sizeof( ENEMYGROUP ) ); AssertMsg( pNew->pEnemyGroup, "MemAlloc failure during enemy group creation." ); memset( pNew->pEnemyGroup, 0, sizeof( ENEMYGROUP ) ); pNew->pWaypoints = NULL; pNew->ubSectorX = (UINT8)SECTORX( uiSector ); pNew->ubSectorY = (UINT8)SECTORY( uiSector ); pNew->ubSectorZ = ubSectorZ; pNew->ubOriginalSector = (UINT8)uiSector; pNew->fPlayer = FALSE; pNew->ubMoveType = CIRCULAR; pNew->ubNextWaypointID = 0; pNew->ubFatigueLevel = 100; pNew->ubRestAtFatigueLevel = 0; pNew->pEnemyGroup->ubNumAdmins = ubNumAdmins; pNew->pEnemyGroup->ubNumTroops = ubNumTroops; pNew->pEnemyGroup->ubNumElites = ubNumElites; pNew->ubGroupSize = (UINT8)(ubNumAdmins + ubNumTroops + ubNumElites); pNew->ubTransportationMask = FOOT; pNew->fVehicle = FALSE; pNew->ubCreatedSectorID = pNew->ubOriginalSector; pNew->ubSectorIDOfLastReassignment = 255; /* #ifdef JA2BETAVERSION { UINT16 str[ 512 ]; //if( PlayerMercsInSector( pNew->ubSectorX, pNew->ubSectorY, pNew->ubSectorZ ) || CountAllMilitiaInSector( pNew->ubSectorX, pNew->ubSectorY ) ) //{ // swprintf( str, L"Attempting to send enemy troops from player occupied location. " // L"Please ALT+TAB out of the game before doing anything else and send 'Strategic Decisions.txt' " // L"and this message. You'll likely need to revert to a previous save. If you can reproduce this " // L"with a save close to this event, that would really help me! -- KM:0" ); // DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); //} if( pNew->ubGroupSize > 25 ) { swprintf( str, L"Strategic AI warning: Creating an enemy group containing %d soldiers " L"(%d admins, %d troops, %d elites) in sector %c%d. This message is a temporary test message " L"to evaluate a potential problems with very large enemy groups.", pNew->ubGroupSize, ubNumAdmins, ubNumTroops, ubNumElites, pNew->ubSectorY + 'A' - 1, pNew->ubSectorX ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); } } #endif */ if( AddGroupToList( pNew ) ) return pNew; return NULL; } #endif