| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- /*
- ** Command & Conquer Generals Zero Hour(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 <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // GameClient/Eva.cpp /////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "GameClient/Eva.h"
- #include "Common/Player.h"
- #include "Common/PlayerList.h"
- #include "GameLogic/GameLogic.h"
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- //-------------------------------------------------------------------------------------------------
- const char *TheEvaMessageNames[] =
- {
- "LOWPOWER",
- "INSUFFICIENTFUNDS",
- "SUPERWEAPONDETECTED_OWN_PARTICLECANNON",
- "SUPERWEAPONDETECTED_OWN_NUKE",
- "SUPERWEAPONDETECTED_OWN_SCUDSTORM",
- "SUPERWEAPONDETECTED_ALLY_PARTICLECANNON",
- "SUPERWEAPONDETECTED_ALLY_NUKE",
- "SUPERWEAPONDETECTED_ALLY_SCUDSTORM",
- "SUPERWEAPONDETECTED_ENEMY_PARTICLECANNON",
- "SUPERWEAPONDETECTED_ENEMY_NUKE",
- "SUPERWEAPONDETECTED_ENEMY_SCUDSTORM",
- "SUPERWEAPONLAUNCHED_OWN_PARTICLECANNON",
- "SUPERWEAPONLAUNCHED_OWN_NUKE",
- "SUPERWEAPONLAUNCHED_OWN_SCUDSTORM",
- "SUPERWEAPONLAUNCHED_ALLY_PARTICLECANNON",
- "SUPERWEAPONLAUNCHED_ALLY_NUKE",
- "SUPERWEAPONLAUNCHED_ALLY_SCUDSTORM",
- "SUPERWEAPONLAUNCHED_ENEMY_PARTICLECANNON",
- "SUPERWEAPONLAUNCHED_ENEMY_NUKE",
- "SUPERWEAPONLAUNCHED_ENEMY_SCUDSTORM",
- "SUPERWEAPONREADY_OWN_PARTICLECANNON",
- "SUPERWEAPONREADY_OWN_NUKE",
- "SUPERWEAPONREADY_OWN_SCUDSTORM",
- "SUPERWEAPONREADY_ALLY_PARTICLECANNON",
- "SUPERWEAPONREADY_ALLY_NUKE",
- "SUPERWEAPONREADY_ALLY_SCUDSTORM",
- "SUPERWEAPONREADY_ENEMY_PARTICLECANNON",
- "SUPERWEAPONREADY_ENEMY_NUKE",
- "SUPERWEAPONREADY_ENEMY_SCUDSTORM",
- "BUILDINGLOST",
- "BASEUNDERATTACK",
- "ALLYUNDERATTACK",
- "BEACONDETECTED",
- "ENEMYBLACKLOTUSDETECTED",
- "ENEMYJARMENKELLDETECTED",
- "ENEMYCOLONELBURTONDETECTED",
- "OWNBLACKLOTUSDETECTED",
- "OWNJARMENKELLDETECTED",
- "OWNCOLONELBURTONDETECTED",
- "UNITLOST",
- "GENERALLEVELUP",
- "VEHICLESTOLEN",
- "BUILDINGSTOLEN",
- "CASHSTOLEN",
- "UPGRADECOMPLETE",
- "BUILDINGBEINGSTOLEN",
- "BUILDINGSABOTAGED",
- "SUPERWEAPONLAUNCHED_OWN_GPS_SCRAMBLER",
- "SUPERWEAPONLAUNCHED_ALLY_GPS_SCRAMBLER",
- "SUPERWEAPONLAUNCHED_ENEMY_GPS_SCRAMBLER",
- "SUPERWEAPONLAUNCHED_OWN_SNEAK_ATTACK",
- "SUPERWEAPONLAUNCHED_ALLY_SNEAK_ATTACK",
- "SUPERWEAPONLAUNCHED_ENEMY_SNEAK_ATTACK",
-
- //****************************************************************************
- //Kris: Don't forget to add another handler below -- it's ghey-ly implemented.
- //****************************************************************************
- "EVA_INVALID",
- };
- //-------------------------------------------------------------------------------------------------
- const ShouldPlayFunc Eva::s_shouldPlayFuncs[] =
- {
- Eva::shouldPlayLowPower,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- Eva::shouldPlayGenericHandler,
- NULL,
- };
- //------------------------------------------------------------------------------ INI::parseEvaEvent
- void INI::parseEvaEvent( INI* ini )
- {
- AsciiString name;
- // read the name
- const char* c = ini->getNextToken();
- name.set( c );
-
- EvaCheckInfo *check = TheEva->newEvaCheckInfo( name );
- if (!check) {
- // could be null because it already exists.
- return;
- }
- // parse the ini definition
- ini->initFromINI( check, check->getFieldParse() );
- }
- //----------------------------------------------------------------------------------- EvaSideSounds
- static void parseSideSoundsList( INI *ini, void *instance, void *store, const void* userData )
- {
- std::vector<EvaSideSounds> *sounds = (std::vector<EvaSideSounds>*) store;
- EvaSideSounds newSounds;
- ini->initFromINI( &newSounds, newSounds.getFieldParse() );
- // This could be made more efficient, but to be honest, it shouldn't be that slow.
- sounds->push_back(newSounds);
- }
- //----------------------------------------------------------------------------------- EvaSideSounds
- const FieldParse EvaSideSounds::s_evaSideSounds[] =
- {
- { "Side", INI::parseAsciiString, NULL, offsetof( EvaSideSounds, m_side) },
- { "Sounds", INI::parseSoundsList, NULL, offsetof( EvaSideSounds, m_soundNames) },
- { 0, 0, 0, 0 },
- };
- //------------------------------------------------------------------------------------ EvaCheckInfo
- EvaCheckInfo::EvaCheckInfo() :
- m_message(EVA_COUNT),
- m_priority(1), // lowest of all priorities
- m_framesBetweenChecks(900), // 30 seconds at 30 fps
- m_framesToExpire(150) // 5 seconds at 30 fps
- {
-
- }
- //-------------------------------------------------------------------------------------------------
- const FieldParse EvaCheckInfo::s_evaEventInfo[] =
- {
- { "Priority", INI::parseUnsignedInt, NULL, offsetof( EvaCheckInfo, m_priority ) },
- { "TimeBetweenChecksMS", INI::parseDurationUnsignedInt, NULL, offsetof( EvaCheckInfo, m_framesBetweenChecks ) },
- { "ExpirationTimeMS", INI::parseDurationUnsignedInt, NULL, offsetof( EvaCheckInfo, m_framesToExpire) },
- { "SideSounds", parseSideSoundsList, NULL, offsetof( EvaCheckInfo, m_evaSideSounds ) },
- { 0, 0, 0, 0 },
- };
- //-------------------------------------------------------------------------------------------------
- EvaCheck::EvaCheck() :
- m_evaInfo(NULL),
- m_triggeredOnFrame(TRIGGEREDON_NOT),
- m_timeForNextCheck(NEXT_CHECK_NOW),
- m_alreadyPlayed(FALSE)
- {
- }
- //-------------------------------------------------------------------------------------------------
- Eva::Eva() :
- m_localPlayer(NULL),
- m_previousBuildingCount(0),
- m_previousUnitCount(0),
- m_enabled(TRUE)
- {
- for (Int i = 0; i < EVA_COUNT; ++i) {
- m_shouldPlay[i] = FALSE;
- }
- }
- //-------------------------------------------------------------------------------------------------
- Eva::~Eva()
- {
- EvaCheckInfoPtrVecIt it;
- for (it = m_allCheckInfos.begin(); it != m_allCheckInfos.end(); ++it) {
- if (*it)
- (*it)->deleteInstance();
- }
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::init()
- {
- // parse the INI here, etc.
- INI ini;
- ini.load( AsciiString( "Data\\INI\\Eva.ini" ), INI_LOAD_OVERWRITE, NULL);
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::reset()
- {
- m_previousUnitCount = 0;
- m_previousBuildingCount = 0;
- // remove all pending counters, etc, here.
- EvaCheckVecIt it;
- for (it = m_checks.begin(); it != m_checks.end(); /* empty */) {
- it = m_checks.erase(it);
- }
- // remove all things flagged as "need to play"
- for (Int i = 0; i < EVA_COUNT; ++i) {
- m_shouldPlay[i] = FALSE;
- }
- // If we were previously disabled, re-enable ourselves.
- m_enabled = TRUE;
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::update()
- {
- if (!m_enabled) {
- return;
- }
- m_localPlayer = ThePlayerList->getLocalPlayer();
- UnsignedInt frame = TheGameLogic->getFrame();
- // Don't update for the first few frames. This way, we don't have to deal with our initial power
- // being 0, etc.
- if (frame < 2) {
- return;
- }
- for (Int mesg = (Int)EVA_FIRST; mesg < (Int)EVA_COUNT; ++mesg) {
- if (isTimeForCheck((EvaMessage)mesg, frame)) {
- if (messageShouldPlay((EvaMessage)mesg, frame)) {
- playMessage((EvaMessage)mesg, frame);
- }
- }
- }
- processPlayingMessages(frame);
- m_localPlayer = NULL;
- // Reset all of the flags that have been set to true that haven't actually been probed, because
- // they will need to trigger again to be valid messages.
- for (Int i = EVA_FIRST; i < EVA_COUNT; ++i) {
- m_shouldPlay[i] = FALSE;
- }
- }
- //-------------------------------------------------------------------------------------------------
- EvaMessage Eva::nameToMessage(const AsciiString& name)
- {
- DEBUG_ASSERTCRASH( ELEMENTS_OF( TheEvaMessageNames ) == EVA_COUNT + 1, ("TheEvaMessageNames out of sync" ) );
- DEBUG_ASSERTCRASH( stricmp( TheEvaMessageNames[ EVA_COUNT ], "EVA_INVALID" ) == 0, ("TheEvaMessageNames out of sync" ) );
- DEBUG_ASSERTCRASH( stricmp( TheEvaMessageNames[ EVA_COUNT - 1], "EVA_INVALID" ) != 0, ("TheEvaMessageNames out of sync" ) );
- for (Int i = EVA_FIRST; i < EVA_COUNT; ++i) {
- if (name.compareNoCase(TheEvaMessageNames[i]) == 0) {
- return (EvaMessage) i;
- }
- }
- DEBUG_CRASH(("Invalid requested Eva message translation :%s: jkmcd", name.str()));
- return EVA_Invalid;
- }
- //-------------------------------------------------------------------------------------------------
- AsciiString Eva::messageToName(EvaMessage message)
- {
- DEBUG_ASSERTCRASH( ELEMENTS_OF( TheEvaMessageNames ) == EVA_COUNT + 1, ("TheEvaMessageNames out of sync" ) );
- DEBUG_ASSERTCRASH( stricmp( TheEvaMessageNames[ EVA_COUNT ], "EVA_INVALID" ) == 0, ("TheEvaMessageNames out of sync" ) );
- DEBUG_ASSERTCRASH( stricmp( TheEvaMessageNames[ EVA_COUNT - 1], "EVA_INVALID" ) != 0, ("TheEvaMessageNames out of sync" ) );
- if (message >= EVA_FIRST && message < EVA_COUNT)
- return TheEvaMessageNames[message];
- DEBUG_CRASH(("Invalid requested Eva message translation. jkmcd"));
- return AsciiString::TheEmptyString;
- }
- //-------------------------------------------------------------------------------------------------
- EvaCheckInfo *Eva::newEvaCheckInfo(AsciiString name)
- {
- EvaMessage mesg = nameToMessage(name);
- // Only return a new one if there isn't an existing one.
- EvaCheckInfoPtrVecIt it;
- for (it = m_allCheckInfos.begin(); it != m_allCheckInfos.end(); ++it) {
- if (*it && (*it)->m_message == mesg)
- return NULL;
- }
- EvaCheckInfo *checkInfo = newInstance(EvaCheckInfo);
- m_allCheckInfos.push_back(checkInfo);
- checkInfo->m_message = mesg;
- return checkInfo;
- }
- //-------------------------------------------------------------------------------------------------
- const EvaCheckInfo *Eva::getEvaCheckInfo(AsciiString name)
- {
- EvaMessage mesg = nameToMessage(name);
- // Only return a new one if there isn't an existing one.
- EvaCheckInfoPtrVecIt it;
- for (it = m_allCheckInfos.begin(); it != m_allCheckInfos.end(); ++it) {
- if (*it && (*it)->m_message == mesg)
- return *it;
- }
- return NULL;
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::setShouldPlay(EvaMessage messageToPlay)
- {
- m_shouldPlay[messageToPlay] = TRUE;
-
- // DEBUG_LOG( ( "Eva message %s play requested\n", messageToName( messageToPlay).str() ) );
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::setEvaEnabled(Bool enabled)
- {
- // clear out any waiting messages.
- for (Int i = EVA_FIRST; i < EVA_COUNT; ++i) {
- m_shouldPlay[i] = FALSE;
- }
- m_enabled = enabled;
- }
- //-------------------------------------------------------------------------------------------------
- Bool Eva::isTimeForCheck(EvaMessage messageToTest, UnsignedInt currentFrame) const
- {
- EvaCheckVec::const_iterator it;
- for (it = m_checks.begin(); it != m_checks.end(); ++it) {
- if (it->m_evaInfo->m_message == messageToTest) {
- return FALSE;
- }
- }
- return TRUE;
- }
- //-------------------------------------------------------------------------------------------------
- Bool Eva::messageShouldPlay(EvaMessage messageToTest, UnsignedInt currentFrame) const
- {
- if (m_localPlayer == NULL) {
- return FALSE;
- }
- DEBUG_ASSERTCRASH( ELEMENTS_OF( s_shouldPlayFuncs ) == EVA_COUNT + 1, ("Eva::s_shouldPlayFuncs out of sync" ) );
- DEBUG_ASSERTCRASH( s_shouldPlayFuncs[ EVA_COUNT ] == NULL, ("Eva::s_shouldPlayFuncs out of sync" ) );
- DEBUG_ASSERTCRASH( s_shouldPlayFuncs[ EVA_COUNT - 1] != NULL, ("Eva::s_shouldPlayFuncs out of sync" ) );
- m_messageBeingTested = messageToTest;
- return s_shouldPlayFuncs[messageToTest](m_localPlayer);
- }
- //-------------------------------------------------------------------------------------------------
- Bool Eva::shouldPlayLowPower( Player *localPlayer )
- {
- // @todo make eva sensitive to whether player can do anything about it...
- // "Low power, Low power, Low power, yadda yadda yadda..."
- //const ThingTemplate *chinaReactorTemplate = findTemplate(;
- //const ThingTemplate *americanReactorTemplate;
- //if ( chinaReactorTemplate && americanReactorTemplate )
- //{
- // if ( ! (localPlayer->canBuild(chinaReactorTemplate) || localPlayer->canBuild(americanReactorTemplate)) )
- // return FALSE
- //}
- return !localPlayer->getEnergy()->hasSufficientPower();
- }
- //-------------------------------------------------------------------------------------------------
- Bool Eva::shouldPlayGenericHandler( Player * )
- {
- if (TheEva->m_shouldPlay[TheEva->m_messageBeingTested]) {
- TheEva->m_shouldPlay[TheEva->m_messageBeingTested] = FALSE;
- return TRUE;
- }
- return FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::playMessage(EvaMessage messageToTest, UnsignedInt currentFrame)
- {
- EvaCheck check;
- check.m_evaInfo = getEvaCheckInfo(Eva::messageToName(messageToTest));
- if (!check.m_evaInfo) {
- return;
- }
- check.m_timeForNextCheck = currentFrame + check.m_evaInfo->m_framesBetweenChecks;
- check.m_triggeredOnFrame = currentFrame;
- check.m_alreadyPlayed = FALSE;
- m_checks.push_back(check);
- }
- //-------------------------------------------------------------------------------------------------
- void Eva::processPlayingMessages(UnsignedInt currentFrame)
- {
- // First pass, remove all the objects that can check after this frame.
- EvaCheckVecIt it;
- for (it = m_checks.begin(); it != m_checks.end(); /* empty */) {
- // These are requests that will be available next frame because they've played.
- if (it->m_timeForNextCheck <= currentFrame + 1 && it->m_alreadyPlayed) {
- it = m_checks.erase(it);
- continue;
- }
- // These are requests that never got a chance to play and have since expired.
- if (it->m_triggeredOnFrame + it->m_evaInfo->m_framesToExpire <= currentFrame && !it->m_alreadyPlayed) {
- it = m_checks.erase(it);
- continue;
- }
- ++it;
- }
- // It's possible, although unlikely, that we removed everything in the list.
- if (m_checks.begin() == m_checks.end()) {
- return;
- }
- // If we're currently playing some audio, we're done.
- if (m_evaSpeech.isCurrentlyPlaying()) {
- return;
- }
- // Okay. No one is currently playing anything, so lets find an event and trigger it.
- EvaCheckVecIt storedIt = m_checks.end();
- UnsignedInt highestPriority = 0;
- for (it = m_checks.begin(); it != m_checks.end(); ++it) {
- if (it->m_evaInfo->m_priority > highestPriority && !it->m_alreadyPlayed) {
- storedIt = it;
- highestPriority = it->m_evaInfo->m_priority;
- }
- }
- // There wasn't anything waiting to play.
- if (storedIt == m_checks.end()) {
- return;
- }
- // We've got a winner!
- AsciiString side = ThePlayerList->getLocalPlayer()->getSide();
- Int numSides = storedIt->m_evaInfo->m_evaSideSounds.size();
- // clear it. If we can't find the side we want, don't play anything
- m_evaSpeech.setEventName(AsciiString::TheEmptyString);
- for (Int i = 0; i < numSides; ++i) {
- if (side.compareNoCase(storedIt->m_evaInfo->m_evaSideSounds[i].m_side) == 0) {
- // Its this one.
- if (storedIt->m_evaInfo->m_evaSideSounds[i].m_soundNames.size() > 0) {
- Int soundToPlay = GameClientRandomValue(0, storedIt->m_evaInfo->m_evaSideSounds[i].m_soundNames.size() - 1);
- m_evaSpeech.setEventName(storedIt->m_evaInfo->m_evaSideSounds[i].m_soundNames[soundToPlay]);
- }
- break;
- }
- }
- // Update the entry
- storedIt->m_alreadyPlayed = true;
- storedIt->m_timeForNextCheck = currentFrame + storedIt->m_evaInfo->m_framesBetweenChecks;
-
- // Now that we correctly filter messages, we need to set the player index for who should hear the
- // sound to the local player.
- m_evaSpeech.setPlayerIndex(m_localPlayer->getPlayerIndex());
- m_evaSpeech.setPlayingHandle(TheAudio->addAudioEvent(&m_evaSpeech));
- }
- //-------------------------------------------------------------------------------------------------
- /** Parses the name of an Eva message from an INI file */
- //-------------------------------------------------------------------------------------------------
- /*static*/void Eva::parseEvaMessageFromIni( INI * ini, void *instance, void *store, const void* userData )
- {
- const char *token = ini->getNextToken();
-
- EvaMessage message = nameToMessage( token );
- if ( message == EVA_Invalid )
- {
- // debug message already displayed
- throw ERROR_BAD_INI;
- }
- *((EvaMessage *)store) = message;
- }
- //-------------------------------------------------------------------------------------------------
- Eva *TheEva = NULL;
|