/* ** Command & Conquer Generals(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: Radar.cpp //////////////////////////////////////////////////////////////////////////////// // Author: Colin Day, January 2002 // Desc: Radar logic implementation /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/GameAudio.h" #include "Common/GameState.h" #include "Common/MiscAudio.h" #include "Common/Radar.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/ThingTemplate.h" #include "Common/GlobalData.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/Eva.h" #include "GameClient/GameWindowManager.h" #include "GameClient/InGameUI.h" #include "GameClient/ControlBar.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Module/ContainModule.h" #include "GameLogic/Module/StealthUpdate.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // GLOBALS //////////////////////////////////////////////////////////////////////////////////////// Radar *TheRadar = NULL; ///< the radar global singleton // PRIVATE //////////////////////////////////////////////////////////////////////////////////////// #define RADAR_QUEUE_TERRAIN_REFRESH_DELAY (LOGICFRAMES_PER_SECOND * 3.0f) //------------------------------------------------------------------------------------------------- /** Delete list resources used by the radar and return them to the memory pools */ //------------------------------------------------------------------------------------------------- void Radar::deleteListResources( void ) { RadarObject *nextObject; // delete entries from the local object list while( m_localObjectList ) { // get next object nextObject = m_localObjectList->friend_getNext(); // remove radar data from object m_localObjectList->friend_getObject()->friend_setRadarData( NULL ); // delete the head of the list m_localObjectList->deleteInstance(); // set head of the list to the next object m_localObjectList = nextObject; } // end while // delete entries from the regular object list while( m_objectList ) { // get next object nextObject = m_objectList->friend_getNext(); // remove radar data from object m_objectList->friend_getObject()->friend_setRadarData( NULL ); // delete the head of the list m_objectList->deleteInstance(); // set head of the list to the next object m_objectList = nextObject; } // end while Object *obj; for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) { DEBUG_ASSERTCRASH( obj->friend_getRadarData() == NULL, ("oops") ); } } // end deleteListResources // PUBLIC METHODS ///////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- RadarObject::RadarObject( void ) { m_object = NULL; m_next = NULL; m_color = GameMakeColor( 255, 255, 255, 255 ); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- RadarObject::~RadarObject( void ) { } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Bool RadarObject::isTemporarilyHidden() const { Drawable* draw = m_object->getDrawable(); if (draw->getStealthLook() == STEALTHLOOK_INVISIBLE || draw->isDrawableEffectivelyHidden()) return true; return false; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void RadarObject::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void RadarObject::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // object id ObjectID objectID = m_object ? m_object->getID() : INVALID_ID; xfer->xferObjectID( &objectID ); if( xfer->getXferMode() == XFER_LOAD ) { // find the object and save m_object = TheGameLogic->findObjectByID( objectID ); if( m_object == NULL ) { DEBUG_CRASH(( "RadarObject::xfer - Unable to find object for radar data\n" )); throw SC_INVALID_DATA; } // end if // tell the object we now have some radar data m_object->friend_setRadarData( this ); } // end if // color xfer->xferColor( &m_color ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void RadarObject::loadPostProcess( void ) { } // end loadPostProcess //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Radar::Radar( void ) { m_radarWindow = NULL; m_objectList = NULL; m_localObjectList = NULL; m_radarHidden = false; m_radarForceOn = false; m_terrainAverageZ = 0.0f; m_waterAverageZ = 0.0f; m_xSample = 0.0f; m_ySample = 0.0f; m_mapExtent.lo.x = 0.0f; m_mapExtent.lo.y = 0.0f; m_mapExtent.lo.z = 0.0f; m_mapExtent.hi.x = 0.0f; m_mapExtent.hi.y = 0.0f; m_mapExtent.hi.z = 0.0f; m_queueTerrainRefreshFrame = 0; // clear the radar events clearAllEvents(); } // end Radar //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Radar::~Radar( void ) { // delete list resources deleteListResources(); } // end ~Radar //------------------------------------------------------------------------------------------------- /** Clear all radar events */ //------------------------------------------------------------------------------------------------- void Radar::clearAllEvents( void ) { // set next free index to the first one m_nextFreeRadarEvent = 0; m_lastRadarEvent = -1; // zero out all data for( Int i = 0; i < MAX_RADAR_EVENTS; ++i ) { m_event[ i ].type = RADAR_EVENT_INVALID; m_event[ i ].active = FALSE; m_event[ i ].createFrame = 0; m_event[ i ].dieFrame = 0; m_event[ i ].fadeFrame = 0; m_event[ i ].color1.red = 0; m_event[ i ].color1.green = 0; m_event[ i ].color1.blue = 0; m_event[ i ].color2.red = 0; m_event[ i ].color2.green = 0; m_event[ i ].color2.blue = 0; m_event[ i ].worldLoc.x = 0.0f; m_event[ i ].worldLoc.y = 0.0f; m_event[ i ].worldLoc.z = 0.0f; m_event[ i ].radarLoc.x = 0; m_event[ i ].radarLoc.y = 0; m_event[ i ].soundPlayed = FALSE; } // end for i } // end clearAllEvents //------------------------------------------------------------------------------------------------- /** Reset radar data */ //------------------------------------------------------------------------------------------------- void Radar::reset( void ) { // delete list resources deleteListResources(); // clear all events clearAllEvents(); // stop forcing the radar on m_radarForceOn = false; } // end reset //------------------------------------------------------------------------------------------------- /** Radar per frame update */ //------------------------------------------------------------------------------------------------- void Radar::update( void ) { Int i; UnsignedInt thisFrame = TheGameLogic->getFrame(); // // traverse the radar event list, if an event has a creationFrame it means that it // exists ... check to see if it's time for that event to die and if so, just clear // out the active status // for( i = 0; i < MAX_RADAR_EVENTS; i++ ) { if( m_event[ i ].active == TRUE && m_event[ i ].createFrame && thisFrame > m_event[ i ].dieFrame ) m_event[ i ].active = FALSE; } // end for i // see if we should refresh the terrain if( m_queueTerrainRefreshFrame != 0 && TheGameLogic->getFrame() - m_queueTerrainRefreshFrame > RADAR_QUEUE_TERRAIN_REFRESH_DELAY ) { // refresh the terrain refreshTerrain( TheTerrainLogic ); } // end if } // end update //------------------------------------------------------------------------------------------------- /** Reset the radar for the new map data being given to it */ //------------------------------------------------------------------------------------------------- void Radar::newMap( TerrainLogic *terrain ) { // keep a pointer for our radar window Int id = NAMEKEY( "ControlBar.wnd:LeftHUD" ); m_radarWindow = TheWindowManager->winGetWindowFromId( NULL, id ); DEBUG_ASSERTCRASH( m_radarWindow, ("Radar::newMap - Unable to find radar game window\n") ); // reset all the data in the radar reset(); // get the extents of the new map terrain->getExtent( &m_mapExtent ); // we will sample at these intervals across the map m_xSample = m_mapExtent.width() / RADAR_CELL_WIDTH; m_ySample = m_mapExtent.height() / RADAR_CELL_HEIGHT; // find the "middle" height for the terrain (most used value) and water table Int x, y, z; Int terrainSamples = 0, waterSamples = 0; m_terrainAverageZ = 0.0f; m_waterAverageZ = 0.0f; ICoord2D radarPoint; Coord3D worldPoint; for( y = 0; y < RADAR_CELL_HEIGHT; y++ ) for( x = 0; x < RADAR_CELL_WIDTH; x++ ) { radarPoint.x = x; radarPoint.y = y; radarToWorld( &radarPoint, &worldPoint ); z = terrain->getGroundHeight( worldPoint.x, worldPoint.y ); Real waterZ; if( terrain->isUnderwater( worldPoint.x, worldPoint.y, &waterZ ) ) { m_waterAverageZ += z; waterSamples++; } else { m_terrainAverageZ += z; terrainSamples++; } } // end for x // avoid divide by zeros if( terrainSamples == 0 ) terrainSamples = 1; if( waterSamples == 0 ) waterSamples = 1; // compute averages m_terrainAverageZ = m_terrainAverageZ / INT_TO_REAL( terrainSamples ); m_waterAverageZ = m_waterAverageZ / INT_TO_REAL( waterSamples ); } // end newMap //------------------------------------------------------------------------------------------------- /** Add an object to the radar list. The object will be sorted in the list to be grouped * using it's radar priority */ //------------------------------------------------------------------------------------------------- void Radar::addObject( Object *obj ) { // get the radar priority for this object RadarPriorityType newPriority = obj->getRadarPriority(); if( isPriorityVisible( newPriority ) == FALSE ) return; // if this object is on the radar, remove it in favor of the new add RadarObject **list; RadarObject *newObj; // sanity DEBUG_ASSERTCRASH( obj->friend_getRadarData() == NULL, ("Radar: addObject - non NULL radar data for '%s'\n", obj->getTemplate()->getName().str()) ); // allocate a new object newObj = newInstance(RadarObject); // set the object data newObj->friend_setObject( obj ); // set color for this object on the radar const Player *player = obj->getControllingPlayer(); Bool useIndicatorColor = true; if( obj->isKindOf( KINDOF_DISGUISER ) ) { //Because we have support for disguised units pretending to be units from another //team, we need to intercept it here and make sure it's rendered appropriately //based on which client is rendering it. static NameKeyType key_StealthUpdate = NAMEKEY( "StealthUpdate" ); StealthUpdate *update = (StealthUpdate*)obj->findUpdateModule( key_StealthUpdate ); if( update ) { if( update->isDisguised() ) { Player *clientPlayer = ThePlayerList->getLocalPlayer(); Player *disguisedPlayer = ThePlayerList->getNthPlayer( update->getDisguisedPlayerIndex() ); if( player->getRelationship( clientPlayer->getDefaultTeam() ) != ALLIES && clientPlayer->isPlayerActive() ) { //Neutrals and enemies will see this disguised unit as the team it's disguised as. player = disguisedPlayer; if( player ) useIndicatorColor = false; } //Otherwise, the color will show up as the team it really belongs to (already set above). } } } if( obj->getContain() ) { // To handle Stealth garrison, ask containers what color they are drawing with to the local player. // Local is okay because radar display is not synced. player = obj->getContain()->getApparentControllingPlayer( ThePlayerList->getLocalPlayer() ); if( player ) useIndicatorColor = false; } if( useIndicatorColor || (player == NULL) ) { newObj->setColor( obj->getIndicatorColor() ); } else { newObj->setColor( player->getPlayerColor() ); } // set a chunk of radar data in the object obj->friend_setRadarData( newObj ); // // we will put this on either the local object list for objects that belong to the // local player, or on the regular object list for all other objects // if( obj->isLocallyControlled() ) list = &m_localObjectList; else list = &m_objectList; // link object to master list at the head of it's priority section if( *list == NULL ) *list = newObj; // trivial case, an empty list else { RadarPriorityType prevPriority, currPriority; RadarObject *currObject, *prevObject, *nextObject; prevObject = NULL; prevPriority = RADAR_PRIORITY_INVALID; for( currObject = *list; currObject; currObject = nextObject ) { // get the next object nextObject = currObject->friend_getNext(); // get the priority of this entry in the list (currPriority) currPriority = currObject->friend_getObject()->getRadarPriority(); // // if there is no previous object, or the previous priority is less than the // our new priority, and the current object in the list has a priority // higher than our equal to our own we need to be inserted here // if( (prevObject == NULL || prevPriority < newPriority ) && (currPriority >= newPriority) ) { // insert into the list just ahead of currObject if( prevObject ) { // the new entry next points to what the previous one used to point to newObj->friend_setNext( prevObject->friend_getNext() ); // the previous one next now points to the new entry prevObject->friend_setNext( newObj ); } // end if else { // the new object next points to the current object newObj->friend_setNext( currObject ); // new list head is now newObj *list = newObj; } // end else break; // exit for, stop the insert } // end if else if( nextObject == NULL ) { // at the end of the list, put object here currObject->friend_setNext( newObj ); } // end else if // our current object is now the previous object prevObject = currObject; prevPriority = currPriority; } // end if } // end else } // end addObject //------------------------------------------------------------------------------------------------- /** Try to delete an object from a specific list */ //------------------------------------------------------------------------------------------------- Bool Radar::deleteFromList( Object *obj, RadarObject **list ) { RadarObject *radarObject, *prevObject = NULL; // find the object in list for( radarObject = *list; radarObject; radarObject = radarObject->friend_getNext() ) { if( radarObject->friend_getObject() == obj ) { // unlink the object from list if( prevObject == NULL ) *list = radarObject->friend_getNext(); // removing head of list else prevObject->friend_setNext( radarObject->friend_getNext() ); // set the object radar data to NULL obj->friend_setRadarData( NULL ); // delete the object instance radarObject->deleteInstance(); // all done, object found and deleted return TRUE; } // end if // save this object as previous one encountered in the list prevObject = radarObject; } // end for, radarObject // object was not found in this list return FALSE; } // end deleteFromList //------------------------------------------------------------------------------------------------- /** Remove an object from the radar, the object may reside in any list */ //------------------------------------------------------------------------------------------------- void Radar::removeObject( Object *obj ) { // sanity if( obj->friend_getRadarData() == NULL ) return; if( deleteFromList( obj, &m_localObjectList ) == TRUE ) return; else if( deleteFromList( obj, &m_objectList ) == TRUE ) return; else { // sanity DEBUG_ASSERTCRASH( 0, ("Radar: Tried to remove object '%s' which was not found\n", obj->getTemplate()->getName().str()) ); } // end else } // end removeObject //------------------------------------------------------------------------------------------------- /** Translate a 2D spot on the radar (from (0,0) to (RADAR_CELL_WIDTH,RADAR_CELL_HEIGHT) * to a 3D spot in the world on the terrain * Return TRUE if the radar points translates to a valid world position * Return FALSE if the radar point is not a valid world position */ //------------------------------------------------------------------------------------------------- Bool Radar::radarToWorld( const ICoord2D *radar, Coord3D *world ) { Int x, y; // sanity if( radar == NULL || world == NULL ) return FALSE; // get the coords x = radar->x; y = radar->y; // more sanity if( x < 0 ) x = 0; if( x >= RADAR_CELL_WIDTH ) x = RADAR_CELL_WIDTH - 1; if( y < 0 ) y = 0; if( y >= RADAR_CELL_HEIGHT ) y = RADAR_CELL_HEIGHT - 1; // translate to world world->x = x * m_xSample; world->y = y * m_ySample; // find the terrain height here world->z = TheTerrainLogic->getGroundHeight( world->x, world->y ); return TRUE; // valid translation } // end radarToWorld //------------------------------------------------------------------------------------------------- /** Translate a point in the world to the 2D radar (x,y) * Return TRUE if the world point successfully translates to a radar point * Return FALSE if world point is a bogus off the map position */ //------------------------------------------------------------------------------------------------- Bool Radar::worldToRadar( const Coord3D *world, ICoord2D *radar ) { // sanity if( world == NULL || radar == NULL ) return FALSE; // sanity check the world position // if( world->x < m_mapExtent.lo.x || world->x > m_mapExtent.hi.x || // world->y < m_mapExtent.lo.y || world->y > m_mapExtent.hi.y ) // return FALSE; // This is actually an insanity check. Nobody uses the return value, so this just leaves garbage in the // return pointer. The reason the question gets asked is there are 60 partition cells to 128 radar cells // (for example), and the radar wants to draw a horizontal line. This line ends up three pixels long // at the right side, so the radar gives up and doesn't draw the middle one. // We bind to on radar anyway, and we only ask if we are on radar, so don't intentionally add edge weirdness. // convert radar->x = world->x / m_xSample; radar->y = world->y / m_ySample; // keep it in bounds if( radar->x < 0 ) radar->x = 0; if( radar->x >= RADAR_CELL_WIDTH ) radar->x = RADAR_CELL_WIDTH - 1; if( radar->y < 0 ) radar->y = 0; if( radar->y >= RADAR_CELL_HEIGHT ) radar->y = RADAR_CELL_HEIGHT - 1; return TRUE; // valid translation } // end worldToRadar // ------------------------------------------------------------------------------------------------ /** Translate an actual pixel location (relative pixel with (0,0) being the top left of * the radar area) to the "logical" radar coords that would cover the entire area of display * on the screen. This is needed because some maps are "long" or "tall" and need a translation * to any radar image that has been scaled to preserve the map aspect ratio */ // ------------------------------------------------------------------------------------------------ Bool Radar::localPixelToRadar( const ICoord2D *pixel, ICoord2D *radar ) { // sanity if( pixel == NULL || radar == NULL ) return FALSE; // get window size of the radar ICoord2D size; m_radarWindow->winGetSize( &size.x, &size.y ); // // act like we're going to draw and find the aspect ratio adjusted points of the // terrain radar positions // ICoord2D start = { 0, 0 }; ICoord2D ul, lr; findDrawPositions( start.x, start.y, size.x, size.y, &ul, &lr ); // get the scaled width and height Int scaledWidth = lr.x - ul.x; Int scaledHeight = lr.y - ul.y; // if the pixel is outsize of the adjusted radar area there are no logical coords if( pixel->x < ul.x || pixel->x > lr.x || pixel->y < ul.y || pixel->y > lr.y ) return FALSE; if( scaledWidth >= scaledHeight ) { // just normal conversion from full stretched to radar cells radar->x = (pixel->x - ul.x)* RADAR_CELL_WIDTH / scaledWidth; // conversion for scaled Y direction in map radar->y = REAL_TO_INT( ((pixel->y - ul.y) / INT_TO_REAL( scaledHeight )) * size.y ); // // radar->y now refers to a point that was "as if" the map was square, translate to radar // note that y is inverted to have the radar align with the world (+x = right, -y = down) // radar->y = (size.y - radar->y) * RADAR_CELL_HEIGHT / size.y; } // end if else { // conversion for scaled Y direction in map radar->x = REAL_TO_INT( ((pixel->x - ul.x) / INT_TO_REAL( scaledWidth )) * size.x ); // radar->x now refers to a point that was "as if" the map was square, translate to radar radar->x = radar->x * RADAR_CELL_WIDTH / size.x; // // just normal conversion from full stretched to radar cells, note that y is inverted // to have the radar align with the world (+x = right, -y = down) // radar->y = (size.y - pixel->y) * RADAR_CELL_HEIGHT / size.y; } // end else return TRUE; } // end localPixelToRadar // ------------------------------------------------------------------------------------------------ /** Translate a screen mouse position to world coords if the screen position is within * the radar window and that spot in the radar corresponds to a point in the world */ // ------------------------------------------------------------------------------------------------ Bool Radar::screenPixelToWorld( const ICoord2D *pixel, Coord3D *world ) { // sanity if( pixel == NULL || world == NULL ) return FALSE; // if we have no radar window can't do the conversion if( m_radarWindow == NULL ) return FALSE; // translate pixel coords to local pixel coords relative to the radar window ICoord2D localPixel; ICoord2D screenPos; m_radarWindow->winGetScreenPosition( &screenPos.x, &screenPos.y ); localPixel.x = pixel->x - screenPos.x; localPixel.y = pixel->y - screenPos.y; // translate local pixel to radar ICoord2D radar; if( localPixelToRadar( &localPixel, &radar ) == FALSE ) return FALSE; // translate radar to world return radarToWorld( &radar, world ); } // end screenPixelToWorld // ------------------------------------------------------------------------------------------------ /** Given the pixel coordinates, see if there is an object that is exactly in this * spot represented on the radar */ // ------------------------------------------------------------------------------------------------ Object *Radar::objectUnderRadarPixel( const ICoord2D *pixel ) { // sanity if( pixel == NULL ) return NULL; // convert pixel location to radar logical radar location ICoord2D radar; if( localPixelToRadar( pixel, &radar ) == FALSE ) return NULL; // object we will return Object *obj = NULL; // // scan the objects on the radar list and return any object that maps its world location // to the radar location // // search the local object list obj = searchListForRadarLocationMatch( m_localObjectList, &radar ); // search all other objects if not found if( obj == NULL ) obj = searchListForRadarLocationMatch( m_objectList, &radar ); // return the object found (if any) return obj; } // end objectUnderRadarPixel // ------------------------------------------------------------------------------------------------ /** Search the object list for an object that maps to the given logical radar coords */ // ------------------------------------------------------------------------------------------------ Object *Radar::searchListForRadarLocationMatch( RadarObject *listHead, ICoord2D *radarMatch ) { // sanity if( listHead == NULL || radarMatch == NULL ) return NULL; // scan the list RadarObject *radarObject; ICoord2D radar; for( radarObject = listHead; radarObject; radarObject = radarObject->friend_getNext() ) { // get object Object *obj = radarObject->friend_getObject(); // sanity if( obj == NULL ) { DEBUG_CRASH(( "Radar::searchListForRadarLocationMatch - NULL object encountered in list\n" )); continue; } // end if // convert object position to logical radar worldToRadar( obj->getPosition(), &radar ); // see if this matches our match radar location if( radar.x >= radarMatch->x - 1 && radar.x <= radarMatch->x + 1 && radar.y >= radarMatch->y - 1 && radar.y <= radarMatch->y + 1 ) return obj; } // end for, radarObject // no match found return NULL; } // end searchListForRadarLocationMatch // ------------------------------------------------------------------------------------------------ /** Given the RELATIVE SCREEN start X and Y, the width and height of the area to draw the whole * radar in, compute what the upper left (ul) and lower right (lr) local coordinates are * that represent the actual terrain image part of the radar that will preserve the * aspect ratio of the map */ // ------------------------------------------------------------------------------------------------ void Radar::findDrawPositions( Int startX, Int startY, Int width, Int height, ICoord2D *ul, ICoord2D *lr ) { Real ratioWidth; Real ratioHeight; Coord2D radar; ratioWidth = m_mapExtent.width()/(width * 1.0f); ratioHeight = m_mapExtent.height()/(height* 1.0f); if( ratioWidth >= ratioHeight) { radar.x = m_mapExtent.width() / ratioWidth; radar.y = m_mapExtent.height()/ ratioWidth; ul->x = 0; ul->y = (height - radar.y) / 2.0f; lr->x = radar.x; lr->y = height - ul->y; } else { radar.x = m_mapExtent.width() / ratioHeight; radar.y = m_mapExtent.height()/ ratioHeight; ul->x = (width - radar.x ) / 2.0f; ul->y = 0; lr->x = width - ul->x; lr->y = radar.y; } /* if( m_mapExtent.width() > m_mapExtent.height() ) { // // +---------------+ // | | // | | // +---------------+ // | map area | // +---------------+ // | | // | | // +---------------+ // ul->x = 0; ul->y = (height - (m_mapExtent.height() / m_mapExtent.width() * height)) / 2.0f; lr->x = width; lr->y = height - ul->y; } // end if else if( m_mapExtent.height() > m_mapExtent.width() ) { // +-----+---+-----+ // | | m | | // | | a | | // | | p | | // | | | | // | | a | | // | | r | | // | | e | | // | | a | | // +-----+---+-----+ // ul->x = (width - (m_mapExtent.width() / m_mapExtent.height() * width)) / 2.0f; ul->y = 0; lr->x = width - ul->x; lr->y = height; } // end else else { ul->x = 0; ul->y = 0; lr->x = width; lr->y = height; } // end else */ // make them pixel positions ul->x += startX; ul->y += startY; lr->x += startX; lr->y += startY; } // end findDrawPositions //------------------------------------------------------------------------------------------------- /** Radar color lookup table */ //------------------------------------------------------------------------------------------------- struct RadarColorLookup { RadarEventType event; RGBAColorInt color1; RGBAColorInt color2; }; static RadarColorLookup radarColorLookupTable[] = { /* Radar Event Color 1 Color 2 */ { RADAR_EVENT_CONSTRUCTION, { 128, 128, 255, 255 }, { 128, 255, 255, 255 } }, { RADAR_EVENT_UPGRADE, { 128, 0, 64, 255 }, { 255, 185, 220, 255 } }, { RADAR_EVENT_UNDER_ATTACK, { 255, 0, 0, 255 }, { 255, 128, 128, 255 } }, { RADAR_EVENT_INFORMATION, { 255, 255, 0, 255 }, { 255, 255, 128, 255 } }, { RADAR_EVENT_BEACON_PULSE, { 255, 255, 0, 255 }, { 255, 255, 128, 255 } }, { RADAR_EVENT_INFILTRATION, { 0, 255, 255, 255 }, { 128, 255, 255, 255 } }, { RADAR_EVENT_BATTLE_PLAN, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } }, { RADAR_EVENT_STEALTH_DISCOVERED, { 0, 255, 0, 255 }, { 0, 128, 0, 255 } }, { RADAR_EVENT_STEALTH_NEUTRALIZED, { 0, 255, 0, 255 }, { 0, 128, 0, 255 } }, { RADAR_EVENT_FAKE, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, { RADAR_EVENT_INVALID, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } }; //------------------------------------------------------------------------------------------------- /** Create a new radar event */ //------------------------------------------------------------------------------------------------- void Radar::createEvent( const Coord3D *world, RadarEventType type, Real secondsToLive ) { // sanity if( world == NULL ) return; // lookup the colors we are to used based on the event RGBAColorInt color[ 2 ]; for( Int i = 0; radarColorLookupTable[ i ].event != RADAR_EVENT_INVALID; ++i ) { if( radarColorLookupTable[ i ].event == type ) { color[ 0 ] = radarColorLookupTable[ i ].color1; color[ 1 ] = radarColorLookupTable[ i ].color2; break; } // end if } // end while // check for no match found in color table if( radarColorLookupTable[ i ].event == RADAR_EVENT_INVALID ) { static RGBAColorInt color1 = { 255, 255, 255, 255 }; static RGBAColorInt color2 = { 255, 255, 255, 255 }; DEBUG_CRASH(( "Radar::createEvent - Event not found in color table, using default colors\n" )); color[ 0 ] = color1; color[ 1 ] = color2; } // end if // call the internal method to create the event with these colors internalCreateEvent( world, type, secondsToLive, &color[ 0 ], &color[ 1 ] ); } // end createEvent // ------------------------------------------------------------------------------------------------ /** Create radar event using a specific colors from the player */ // ------------------------------------------------------------------------------------------------ void Radar::createPlayerEvent( Player *player, const Coord3D *world, RadarEventType type, Real secondsToLive ) { // sanity if( player == NULL || world == NULL ) return; // figure out the two colors we should use Color c; UnsignedByte r, g, b, a; RGBAColorInt color[ 2 ]; // color 1 c = player->getPlayerColor(); GameGetColorComponents( c, &r, &g, &b, &a ); color[ 0 ].red = r; color[ 0 ].green = g; color[ 0 ].blue = b; color[ 0 ].alpha = a; // the second will be a darker color 1 Real darkScale = 0.75f; color[ 1 ] = color[ 0 ]; color[ 1 ].red -= REAL_TO_INT( color[ 0 ].red * darkScale ); if( color[ 1 ].red < 0 ) color[ 1 ].red = 0; color[ 1 ].green -= REAL_TO_INT( color[ 0 ].green * darkScale ); if( color[ 1 ].green < 0 ) color[ 1 ].green = 0; color[ 1 ].blue -= REAL_TO_INT( color[ 0 ].blue * darkScale ); if( color[ 1 ].blue < 0 ) color[ 1 ].blue = 0; // create the events using these colors internalCreateEvent( world, type, secondsToLive, &color[ 0 ], &color[ 1 ] ); } // end createPlayerEvent //------------------------------------------------------------------------------------------------- /** Create a new radar event */ //------------------------------------------------------------------------------------------------- void Radar::internalCreateEvent( const Coord3D *world, RadarEventType type, Real secondsToLive, const RGBAColorInt *color1, const RGBAColorInt *color2 ) { static Real secondsBeforeDieToFade = 0.5f; ///< this many seconds before we hit the die frame we start to fade away // sanity if( world == NULL || color1 == NULL || color2 == NULL ) return; // translate the world coord to radar coords ICoord2D radar; worldToRadar( world, &radar ); // add to the list of radar events m_event[ m_nextFreeRadarEvent ].type = type; m_event[ m_nextFreeRadarEvent ].active = TRUE; m_event[ m_nextFreeRadarEvent ].createFrame = TheGameLogic->getFrame(); m_event[ m_nextFreeRadarEvent ].dieFrame = TheGameLogic->getFrame() + LOGICFRAMES_PER_SECOND * secondsToLive; m_event[ m_nextFreeRadarEvent ].fadeFrame = m_event[ m_nextFreeRadarEvent ].dieFrame - LOGICFRAMES_PER_SECOND * secondsBeforeDieToFade; m_event[ m_nextFreeRadarEvent ].color1 = *color1; m_event[ m_nextFreeRadarEvent ].color2 = *color2; m_event[ m_nextFreeRadarEvent ].worldLoc = *world; m_event[ m_nextFreeRadarEvent ].radarLoc = radar; m_event[ m_nextFreeRadarEvent ].soundPlayed = FALSE; // record the index of this, our "last" radar event. if ( type != RADAR_EVENT_BEACON_PULSE ) m_lastRadarEvent = m_nextFreeRadarEvent; // // increment the next radar event index, wrapping to the beginning. If we ever have so many // events that they fill up the buffer the oldest ones will just drop off, eh ... should be fine. // m_nextFreeRadarEvent++; if( m_nextFreeRadarEvent >= MAX_RADAR_EVENTS ) m_nextFreeRadarEvent = 0; } // end createEvent //------------------------------------------------------------------------------------------------- /** Get the last event position, if any. * Returns TRUE if event was found * Returns FALSE if there is no "last event" */ //------------------------------------------------------------------------------------------------- Bool Radar::getLastEventLoc( Coord3D *eventPos ) { // if we have an index for the last event, one was present if( m_lastRadarEvent != -1 ) { if( eventPos ) *eventPos = m_event[ m_lastRadarEvent ].worldLoc; return TRUE; } // end if return FALSE; // no last event } // end getLastEventLoc // ------------------------------------------------------------------------------------------------ /** Try to create a radar event for "we're under attack". This will be called every time * actual damage is dealt to an object that the player owns that shows up on the radar. * We don't want to create under attack events every time we are damaged and we also want * to limit them based on time and local area of other recent attack events */ // ------------------------------------------------------------------------------------------------ void Radar::tryUnderAttackEvent( const Object *obj ) { // sanity if( obj == NULL ) return; // try to create the event Bool eventCreated = tryEvent( RADAR_EVENT_UNDER_ATTACK, obj->getPosition() ); // if event created, do some more feedback if( eventCreated ) { TheControlBar->triggerRadarAttackGlow(); // ///@todo Should make an INI data driven table for radar event strings, and audio events // // UI feedback for being under attack (note that we display these messages and audio // queues even if we don't have a radar) // Player *player = ThePlayerList->getLocalPlayer(); // create a message for the attack event if( obj->isKindOf( KINDOF_INFANTRY ) || obj->isKindOf( KINDOF_VEHICLE ) ) { AudioEventRTS unitAttackSound; if( obj->isKindOf(KINDOF_HARVESTER) ) { // display special message TheInGameUI->message( "RADAR:HarvesterUnderAttack" ); // play special audio event unitAttackSound = TheAudio->getMiscAudio()->m_radarHarvesterUnderAttackSound; } else { // display message TheInGameUI->message( "RADAR:UnitUnderAttack" ); // play audio event unitAttackSound = TheAudio->getMiscAudio()->m_radarStructureUnderAttackSound; } unitAttackSound.setPlayerIndex(player->getPlayerIndex()); TheAudio->addAudioEvent( &unitAttackSound ); } // end if else if( obj->isKindOf( KINDOF_STRUCTURE ) && obj->isKindOf( KINDOF_MP_COUNT_FOR_VICTORY ) ) { // play EVA. If its our object, play Base under attack. if (obj->getControllingPlayer()->isLocalPlayer()) TheEva->setShouldPlay(EVA_BaseUnderAttack); else if (ThePlayerList->getLocalPlayer()->getRelationship(obj->getTeam()) == ALLIES) TheEva->setShouldPlay(EVA_AllyUnderAttack); // display message TheInGameUI->message( "RADAR:StructureUnderAttack" ); // play audio event static AudioEventRTS structureAttackSound = TheAudio->getMiscAudio()->m_radarStructureUnderAttackSound; structureAttackSound.setPlayerIndex(player->getPlayerIndex()); TheAudio->addAudioEvent( &structureAttackSound ); } // end else if else { // display message TheInGameUI->message( "RADAR:UnderAttack" ); // play audio event static AudioEventRTS underAttackSound = TheAudio->getMiscAudio()->m_radarStructureUnderAttackSound; underAttackSound.setPlayerIndex(player->getPlayerIndex()); TheAudio->addAudioEvent( &underAttackSound ); } // end else } // end if } // end tryUnderAttackEvent // ------------------------------------------------------------------------------------------------ /** Try to create a radar event for "infiltration". This happens whenever a unit is hijacked, defected, converted to carbomb, hacked, or otherwise snuck into */ // ------------------------------------------------------------------------------------------------ void Radar::tryInfiltrationEvent( const Object *obj ) { // We should only be warned against infiltrations that are taking place against us. if (obj->getControllingPlayer() != ThePlayerList->getLocalPlayer()) return; // create the radar event createEvent( obj->getPosition(), RADAR_EVENT_INFILTRATION ); // ///@todo Should make an INI data driven table for radar event strings, and audio events // // UI feedback for being under attack (note that we display these messages and audio // queues even if we don't have a radar) // Player *player = ThePlayerList->getLocalPlayer(); // display message TheInGameUI->message( "RADAR:Infiltration" ); // play audio event static AudioEventRTS infiltrationWarningSound = TheAudio->getMiscAudio()->m_radarInfiltrationSound; infiltrationWarningSound.setPlayerIndex(player->getPlayerIndex()); TheAudio->addAudioEvent( &infiltrationWarningSound ); } // end tryInfiltrationEvent // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Bool Radar::tryEvent( RadarEventType event, const Coord3D *pos ) { // sanity if( event <= RADAR_EVENT_INVALID || event >= RADAR_EVENT_NUM_EVENTS || pos == NULL ) return FALSE; // see if there was an event of this type within the given range within the given time UnsignedInt currentFrame = TheGameLogic->getFrame(); const Real closeEnoughDistanceSq = 250.0f * 250.0f; const UnsignedInt framesBetweenEvents = LOGICFRAMES_PER_SECOND * 10; // // see if there was any matching radar event within range of this one // that wasn't too long ago, if there was we won't create another and get outta here // for( Int i = 0; i < MAX_RADAR_EVENTS; ++i ) { // only pay attention to under attack events if( m_event[ i ].type == event ) { // get distance from our new event location to this event location in 2D Real distSquared = m_event[ i ].worldLoc.x - pos->x * m_event[ i ].worldLoc.x - pos->x + m_event[ i ].worldLoc.y - pos->y * m_event[ i ].worldLoc.y - pos->y; if( distSquared <= closeEnoughDistanceSq ) { // finally only reject making a new event of this existing one is "recent enough" if( currentFrame - m_event[ i ].createFrame < framesBetweenEvents ) return FALSE; // reject it } // end if } // end if } // end for i // if we got here then we want to create a new event createEvent( pos, event ); // return TRUE for successfully created event return TRUE; } // end tryEvent // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void Radar::refreshTerrain( TerrainLogic *terrain ) { // no future queue is valid now m_queueTerrainRefreshFrame = 0; } // end refreshTerrain // ------------------------------------------------------------------------------------------------ /** Queue a refresh of the radar terrain, we have this so that if there is code that * rapidly needs to refresh the radar, it should use this so we aren't continually * rebuilding the radar graphic because that process is slow. If you need to update * the terrain on the radar immediately use refreshTerrain() */ // ------------------------------------------------------------------------------------------------ void Radar::queueTerrainRefresh( void ) { // // we just simply overwrite the frame we have recorded for a radar refresh. If there was // already one there, it's simply just forgotten and whatever changes we wanted to see // with that refresh will have to wait until enough time has passed to show these // changes as well. why you ask ... well, because if we're calling this in close enough // proximity for us to overwrite something, we're changing the terrain features // quite often and can't afford the expense of rebuilding the radar visual // m_queueTerrainRefreshFrame = TheGameLogic->getFrame(); } // end queueTerrainRefresh // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void Radar::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer a radar object list given the head pointer as a parameter * Version Info; * 1: Initial version */ // ------------------------------------------------------------------------------------------------ static void xferRadarObjectList( Xfer *xfer, RadarObject **head ) { RadarObject *radarObject; // sanity DEBUG_ASSERTCRASH( head != NULL, ("xferRadarObjectList - Invalid parameters\n" )); // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // write could of objects in list UnsignedShort count = 0; for( radarObject = *head; radarObject; radarObject = radarObject->friend_getNext() ) count++; xfer->xferUnsignedShort( &count ); // xfer the list data if( xfer->getXferMode() == XFER_SAVE ) { // save each object for( radarObject = *head; radarObject; radarObject = radarObject->friend_getNext() ) { // save this object xfer->xferSnapshot( radarObject ); } // end for, radarObject } // end if, save else { // the list should be empty at this point as we are loading it as a whole if( *head != NULL ) { #if 1 // srj sez: yeah, it SHOULD be, but a few rogue things come into existence (via their ctor) preloaded // with stuff (eg, "CabooseFullOfTerrorists"). we immediately destroy 'em, but they don't go away just yet. // so just ignore 'em if possible. for( radarObject = *head; radarObject; radarObject = radarObject->friend_getNext() ) { if (!radarObject->friend_getObject()->isDestroyed()) { DEBUG_CRASH(( "xferRadarObjectList - List head should be NULL, or contain only destroyed objects\n" )); throw SC_INVALID_DATA; } } #else DEBUG_CRASH(( "xferRadarObjectList - List head should be NULL, but isn't\n" )); throw SC_INVALID_DATA; #endif } // end if // read each element for( UnsignedShort i = 0; i < count; ++i ) { // alloate a new radar object radarObject = newInstance(RadarObject); // link to the end of the list if( *head == NULL ) *head = radarObject; else { RadarObject *other; for( other = *head; other->friend_getNext() != NULL; other = other->friend_getNext() ) { } // end for, other // set the end of the list to point to the new object other->friend_setNext( radarObject ); } // end else // load the data xfer->xferSnapshot( radarObject ); } // end for i } // end else, load } // end xferRadarObjectList // ------------------------------------------------------------------------------------------------ /** Xfer Method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void Radar::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // radar hidden xfer->xferBool( &m_radarHidden ); // radar force on xfer->xferBool( &m_radarForceOn ); // save our local object list xferRadarObjectList( xfer, &m_localObjectList ); // save the regular object list xferRadarObjectList( xfer, &m_objectList ); // save the radar event count and data UnsignedShort eventCountVerify = MAX_RADAR_EVENTS; UnsignedShort eventCount = eventCountVerify; xfer->xferUnsignedShort( &eventCount ); if( eventCount != eventCountVerify ) { DEBUG_CRASH(( "Radar::xfer - size of MAX_RADAR_EVENTS has changed, you must version this xfer method to accomodate the new array size. Was '%d' and is now '%d'\n", eventCount, eventCountVerify )); throw SC_INVALID_DATA; } // end if for( UnsignedShort i = 0; i < eventCount; ++i ) { // xfer event data xfer->xferUser( &m_event[ i ].type, sizeof( RadarEventType ) ); xfer->xferBool( &m_event[ i ].active ); xfer->xferUnsignedInt( &m_event[ i ].createFrame ); xfer->xferUnsignedInt( &m_event[ i ].dieFrame ); xfer->xferUnsignedInt( &m_event[ i ].fadeFrame ); xfer->xferRGBAColorInt( &m_event[ i ].color1 ); xfer->xferRGBAColorInt( &m_event[ i ].color2 ); xfer->xferCoord3D( &m_event[ i ].worldLoc ); xfer->xferICoord2D( &m_event[ i ].radarLoc ); xfer->xferBool( &m_event[ i ].soundPlayed ); } // end for i // next event index xfer->xferInt( &m_nextFreeRadarEvent ); // last event index xfer->xferInt( &m_lastRadarEvent ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void Radar::loadPostProcess( void ) { // // refresh the radar texture now that all the objects (specifically bridges) have // been loaded with their correct damage states from save game file // refreshTerrain( TheTerrainLogic ); } // end loadPostProcess // ------------------------------------------------------------------------------------------------ /** Is the priority type passed in a "visible" one that can show up on the radar */ // ------------------------------------------------------------------------------------------------ Bool Radar::isPriorityVisible( RadarPriorityType priority ) const { switch( priority ) { case RADAR_PRIORITY_INVALID: case RADAR_PRIORITY_NOT_ON_RADAR: return FALSE; default: return TRUE; } // end switch } // end isPriorityVisible