/*
** 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 .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GameLogicDispatch.cpp ////////////////////////////////////////////////////////////////////
// Author: Mike Booth, Colin Day
// Description: Message logic to drive the game play
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRCDebug.h"
#include "Common/GameAudio.h"
#include "Common/GameEngine.h"
#include "Common/GlobalData.h"
#include "Common/NameKeyGenerator.h"
#include "Common/ThingFactory.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/PlayerTemplate.h"
#include "Common/MessageStream.h"
#include "Common/MultiplayerSettings.h"
#include "Common/Recorder.h"
#include "Common/BuildAssistant.h"
#include "Common/SpecialPower.h"
#include "Common/ThingTemplate.h"
#include "Common/Upgrade.h"
#include "Common/StatsCollector.h"
#include "Common/Radar.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/ObjectIter.h"
//#include "GameLogic/PartitionManager.h"
#include "GameLogic/AI.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/OpenContain.h"
#include "GameLogic/Module/ProductionUpdate.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#include "GameLogic/ScriptActions.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/VictoryConditions.h"
#include "GameLogic/Weapon.h"
#include "GameClient/CommandXlat.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.h"
#include "GameClient/GameWindowTransitions.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GuiCallbacks.h"
#include "GameClient/InGameUI.h"
#include "GameClient/KeyDefs.h"
#include "GameClient/Mouse.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Shell.h"
#include "GameClient/Module/BeaconClientUpdate.h"
#include "GameClient/LookAtXlat.h"
#include "GameNetwork/NetworkInterface.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
#define MAX_PATH_SUBJECTS 64
static Bool theBuildPlan = false;
static Object *thePlanSubject[ MAX_PATH_SUBJECTS ];
static int thePlanSubjectCount = 0;
//static WindowLayout *background = NULL;
// ------------------------------------------------------------------------------------------------
/** Issue the movement command to the object */
// ------------------------------------------------------------------------------------------------
static void doMoveTo( Object *obj, const Coord3D *pos )
{
AIUpdateInterface *ai = obj->getAIUpdateInterface();
DEBUG_ASSERTCRASH(ai, ("Attemped doMoveTo() on an Object with no AI\n"));
if (ai)
{
if (theBuildPlan)
{
int i;
// if this object isn't in the buildPlan set, add it
for( i=0; iqueueWaypoint( pos );
}
else
{
ai->clearWaypointQueue();
obj->leaveGroup();
obj->releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks.
ai->aiMoveToPosition( pos, CMD_FROM_PLAYER );
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
static void doSetRallyPoint( Object *obj, const Coord3D& pos )
{
Bool isLocalPlayer = obj->isLocallyControlled();
//
// we must be able to find a path from the object to the point they have chosen, cause setting
// a rally point at a invalid location would suck. To be super nice, we have to make sure
// that every type of object that can be created from the thing setting the rally point
// can actually find a path from the thing to the point
//
// to see the never-finished code to check all locomotor sets, see past revs of GUICommandTranslator.cpp -MDC
//
// for now, just use the basic human locomotor ... and enable the above code when Steven
// tells me how to get the locomotor sets based on a thing template (CBD)
//
NameKeyType key = NAMEKEY( "BasicHumanLocomotor" );
LocomotorSet locomotorSet;
locomotorSet.addLocomotor( TheLocomotorStore->findLocomotorTemplate( key ) );
if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &pos ) == FALSE )
{
// user feedback
if( isLocalPlayer )
{
// display error message to user
TheInGameUI->message( TheGameText->fetch( "GUI:RallyPointNoPath" ) );
// play the no can do sound
static AudioEventRTS rallyNotSet("UnableToSetRallyPoint");
rallyNotSet.setPosition(&pos);
TheAudio->addAudioEvent(&rallyNotSet);
} // end if
return;
} // end if
// feedback to the player
if( isLocalPlayer )
{
// print a message to the user
UnicodeString info;
info.format( TheGameText->fetch( "GUI:RallyPointSet" ),
obj->getTemplate()->getDisplayName().str() );
TheInGameUI->message( info );
// play a sound for setting the rally point
static AudioEventRTS rallyPointSet("RallyPointSet");
rallyPointSet.setPosition(&pos);
rallyPointSet.setPlayerIndex(obj->getControllingPlayer()->getPlayerIndex());
TheAudio->addAudioEvent(&rallyPointSet);
// mark the UI as dirty so that we re-evaluate the selection and show the rally point
Drawable *draw = obj->getDrawable();
if( draw && draw->isSelected() )
TheControlBar->markUIDirty();
} // end if
// if this object has a ProductionExitUpdate interface, we are setting a rally point
ExitInterface *exitInterface = obj->getObjectExitInterface();
if( exitInterface )
{
// set the rally point
exitInterface->setRallyPoint( &pos );
}
}
static Object * getSingleObjectFromSelection(const AIGroup *currentlySelectedGroup)
{
if( currentlySelectedGroup )
{
const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
DEBUG_ASSERTCRASH(selectedObjects.size() == 1, ("Trying to get single object from multiple selection!"));
VecObjectID::const_iterator it = selectedObjects.begin();
return TheGameLogic->findObjectByID(*it);
}
return NULL;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GameLogic::closeWindows( void )
{
HideDiplomacy();
ResetDiplomacy();
HideInGameChat();
ResetInGameChat();
TheControlBar->hidePurchaseScience();
TheControlBar->hideSpecialPowerShortcut();
HideQuitMenu();
// hide the options menu
NameKeyType buttonID = TheNameKeyGenerator->nameToKey( "OptionsMenu.wnd:ButtonBack" );
GameWindow *button = TheWindowManager->winGetWindowFromId( NULL, buttonID );
GameWindow *window = TheWindowManager->winGetWindowFromId( NULL, TheNameKeyGenerator->nameToKey("OptionsMenu.wnd:OptionsMenuParent") );
if(window)
TheWindowManager->winSendSystemMsg( window, GBM_SELECTED,
(WindowMsgData)button, buttonID );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GameLogic::clearGameData( Bool showScoreScreen )
{
if( !isInGame() )
{
DEBUG_CRASH(("We tried to clear the game data when we weren't in a game"));
return;
}
setClearingGameData( TRUE );
// m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd");
// DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd"));
// m_background->hide(FALSE);
// m_background->bringForward();
// reset the game engine to accept data for a new game
if(TheStatsCollector)
TheStatsCollector->writeFileEnd();
TheScriptActions->closeWindows(FALSE); // Close victory or defeat windows.
Bool shellGame = FALSE;
if ((!isInShellGame() || !isInGame()) && showScoreScreen)
{
shellGame = TRUE;
TheTransitionHandler->setGroup("FadeWholeScreen");
TheShell->push("Menus/ScoreScreen.wnd");
TheShell->showShell(FALSE); // by passing in false, we don't want to run the Init on the shell screen we just pushed on
TheTransitionHandler->reverse("FadeWholeScreen");
void FixupScoreScreenMovieWindow( void );
FixupScoreScreenMovieWindow();
}
TheGameEngine->reset();
setGameMode(GAME_NONE);
// m_background->bringForward();
// if(shellGame)
if (TheGlobalData->m_initialFile.isEmpty() == FALSE)
{
TheGameEngine->setQuitting(TRUE);
}
HideControlBar();
closeWindows();
TheMouse->setVisibility(TRUE);
if(m_background)
{
m_background->destroyWindows();
m_background->deleteInstance();
m_background = NULL;
}
setClearingGameData( FALSE );
}
// ------------------------------------------------------------------------------------------------
/** Prepare for a new game */
// ------------------------------------------------------------------------------------------------
void GameLogic::prepareNewGame( Int gameMode, GameDifficulty diff, Int rankPoints )
{
//Added By Sadullah Nader
//Fix for loading game scene
//Kris: Commented this out, but leaving it around incase it bites us later. I cleaned up the
// nomenclature. Look for setLoadingMap() and setLoadingSave()
//setGameLoading(TRUE);
TheScriptEngine->setGlobalDifficulty(diff);
if(!m_background)
{
m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd");
DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd"));
m_background->hide(FALSE);
m_background->bringForward();
}
m_background->getFirstWindow()->winClearStatus(WIN_STATUS_IMAGE);
TheGameLogic->setGameMode( gameMode );
if (!TheGlobalData->m_pendingFile.isEmpty())
{
TheWritableGlobalData->m_mapName = TheGlobalData->m_pendingFile;
TheWritableGlobalData->m_pendingFile.clear();
}
m_rankPointsToAddAtGameStart = rankPoints;
DEBUG_LOG(("GameLogic::prepareNewGame() - m_rankPointsToAddAtGameStart = %d\n", m_rankPointsToAddAtGameStart));
// If we're about to start a game, hide the shell.
if(!TheGameLogic->isInShellGame())
TheShell->hideShell();
m_startNewGame = FALSE;
} // end prepareNewGame
//-------------------------------------------------------------------------------------------------
/** This message handles dispatches object command messages to the
* appropriate objects.
* @todo Rename this to "CommandProcessor", or similiar. */
//-------------------------------------------------------------------------------------------------
void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData )
{
#ifdef _DEBUG
DEBUG_ASSERTCRASH(msg != NULL && msg != (GameMessage*)0xdeadbeef, ("bad msg"));
#endif
Player *thisPlayer = ThePlayerList->getNthPlayer( msg->getPlayerIndex() );
DEBUG_ASSERTCRASH( thisPlayer, ("logicMessageDispatcher: Processing message from unknown player (player index '%d')\n",
msg->getPlayerIndex()) );
AIGroup *currentlySelectedGroup = NULL;
if (isInGame())
{
if (msg->getType() >= GameMessage::MSG_BEGIN_NETWORK_MESSAGES && msg->getType() <= GameMessage::MSG_END_NETWORK_MESSAGES)
{
if (msg->getType() != GameMessage::MSG_LOGIC_CRC && msg->getType() != GameMessage::MSG_SET_REPLAY_CAMERA)
{
currentlySelectedGroup = TheAI->createGroup(); // can't do this outside a game - it'll cause sync errors galore.
CRCGEN_LOG(( "Creating AIGroup %d in GameLogic::logicMessageDispatcher()\n", (currentlySelectedGroup)?currentlySelectedGroup->getID():0 ));
thisPlayer->getCurrentSelectionAsAIGroup(currentlySelectedGroup);
// We can't issue commands to groups that contain units that don't belong the issuing player, so pretend like
// there's nothing selected. Also, if currentlySelectedGroup is empty, go ahead and delete it, so that we can skip
// any processing on it.
if (currentlySelectedGroup->isEmpty())
{
TheAI->destroyGroup(currentlySelectedGroup);
currentlySelectedGroup = NULL;
}
// If there are any units that the player doesn't own, then remove them from the "currentlySelectedGroup"
if (currentlySelectedGroup)
if (currentlySelectedGroup->removeAnyObjectsNotOwnedByPlayer(thisPlayer))
currentlySelectedGroup = NULL;
if(TheStatsCollector)
TheStatsCollector->collectMsgStats(msg);
}
}
}
#ifdef DEBUG_LOGGING
AsciiString commandName;
commandName = msg->getCommandAsAsciiString();
if (msg->getType() < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || msg->getType() > GameMessage::MSG_END_NETWORK_MESSAGES)
{
commandName.concat(" (NON-LOGIC-MESSAGE!!!)");
}
else if (msg->getType() == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
{
commandName = " (CRC message!)";
}
#if 0
if (commandName.isNotEmpty() /*&& msg->getType() != GameMessage::MSG_FRAME_TICK*/)
{
DEBUG_LOG(("Frame %d: GameLogic::logicMessageDispatcher() saw a %s from player %d (%ls)\n", getFrame(), commandName.str(),
msg->getPlayerIndex(), thisPlayer->getPlayerDisplayName().str()));
}
#endif
#endif // DEBUG_LOGGING
// process the message
GameMessage::Type msgType = msg->getType();
switch( msgType )
{
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_NEW_GAME:
{
//DEBUG_ASSERTCRASH(msg->getArgumentCount() == 1 || msg->getArgumentCount() == 2, ("%d arguments to MSG_NEW_GAME", msg->getArgumentCount()));
Int gameMode = msg->getArgument( 0 )->integer;
Int rankPoints = 0;
GameDifficulty diff = DIFFICULTY_NORMAL;
if ( msg->getArgumentCount() >= 2 )
diff = (GameDifficulty)msg->getArgument( 1 )->integer;
if ( msg->getArgumentCount() >= 3 )
rankPoints = msg->getArgument( 2 )->integer;
if ( msg->getArgumentCount() >= 4 )
{
Int maxFPS = msg->getArgument( 3 )->integer;
if (maxFPS < 1 || maxFPS > 1000)
maxFPS = TheGlobalData->m_framesPerSecondLimit;
DEBUG_LOG(("Setting max FPS limit to %d FPS\n", maxFPS));
TheGameEngine->setFramesPerSecondLimit(maxFPS);
TheWritableGlobalData->m_useFpsLimit = true;
}
// prepare for new game
prepareNewGame( gameMode, diff, rankPoints );
// start new game
startNewGame( FALSE );
break;
} // end new game
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_CLEAR_GAME_DATA:
{
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheDisplay && TheGlobalData->m_dumpAssetUsage)
TheDisplay->dumpAssetUsage(TheGlobalData->m_mapName.str());
#endif
if (currentlySelectedGroup)
TheAI->destroyGroup(currentlySelectedGroup);
currentlySelectedGroup = NULL;
TheGameLogic->clearGameData();
break;
} // end clear game data
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_META_BEGIN_PATH_BUILD:
{
DEBUG_LOG(("META: begin path build\n"));
DEBUG_ASSERTCRASH(!theBuildPlan, ("mismatched theBuildPlan"));
if (theBuildPlan == false)
{
theBuildPlan = true;
thePlanSubjectCount = 0;
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_META_END_PATH_BUILD:
{
DEBUG_LOG(("META: end path build\n"));
DEBUG_ASSERTCRASH(theBuildPlan, ("mismatched theBuildPlan"));
// tell everyone who participated in the plan to move
for( int i=0; igetAIUpdateInterface();
if (ai)
ai->executeWaypointQueue();
}
theBuildPlan = false;
thePlanSubjectCount = 0;
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_SET_RALLY_POINT:
{
Object *obj = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
Coord3D dest = msg->getArgument( 1 )->location;
if (obj)
{
doSetRallyPoint( obj, dest );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_WEAPON:
{
WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
Int maxShotsToFire = msg->getArgument( 1 )->integer;
// lock it just till the weapon is empty or the attack is "done"
if( currentlySelectedGroup && currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_TEMPORARILY ))
{
currentlySelectedGroup->groupAttackPosition( NULL, maxShotsToFire, CMD_FROM_PLAYER );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_COMBATDROP_AT_OBJECT:
{
Object *targetObject = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// issue command for either single object or for selected group
if( currentlySelectedGroup )
currentlySelectedGroup->groupCombatDrop( targetObject,
*targetObject->getPosition(),
CMD_FROM_PLAYER );
/*
if( sourceObject && targetObject )
{
AIUpdateInterface* sourceAI = sourceObject->getAIUpdateInterface();
if (sourceAI)
{
sourceAI->aiCombatDrop( targetObject, *targetObject->getPosition(), CMD_FROM_PLAYER );
}
}
*/
break;
} // end GameMessage::MSG_COMBATDROP_AT_OBJECT
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_COMBATDROP_AT_LOCATION:
{
Coord3D targetLoc = msg->getArgument( 0 )->location;
if( currentlySelectedGroup )
currentlySelectedGroup->groupCombatDrop( NULL, targetLoc, CMD_FROM_PLAYER );
/*
if( sourceObject )
{
AIUpdateInterface* sourceAI = sourceObject->getAIUpdateInterface();
if (sourceAI)
{
sourceAI->aiCombatDrop( NULL, targetLoc, CMD_FROM_PLAYER );
}
}
*/
break;
} // end GameMessage::MSG_COMBATDROP_AT_LOCATION
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_WEAPON_AT_OBJECT:
{
// Lock the weapon choice to the right weapon, then give an attack command
WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
Object *targetObject = TheGameLogic->findObjectByID( msg->getArgument( 1 )->objectID );
Int maxShotsToFire = msg->getArgument( 2 )->integer;
// sanity
if( targetObject == NULL )
break;
// issue command for either single object or for selected group
if( currentlySelectedGroup )
{
// lock it just till the weapon is empty or the attack is "done"
if (currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_TEMPORARILY ))
currentlySelectedGroup->groupAttackObject( targetObject, maxShotsToFire, CMD_FROM_PLAYER );
} // end if, command for group
break;
} // end do weapon at object
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_SWITCH_WEAPONS:
{
// use the selected group
WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
// lock until un-switched, or switched to something else.
if( currentlySelectedGroup )
currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_PERMANENTLY );
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_SET_MINE_CLEARING_DETAIL:
{
if( currentlySelectedGroup )
{
currentlySelectedGroup->setMineClearingDetail(true);
}
break;
}
case GameMessage::MSG_ENABLE_RETALIATION_MODE:
{
//Logically turns on or off retaliation mode for a specified player.
Int playerIndex = msg->getArgument( 0 )->integer;
Bool enableRetaliation = msg->getArgument( 1 )->boolean;
Player *player = ThePlayerList->getNthPlayer( playerIndex );
if( player )
{
player->setLogicalRetaliationModeEnabled( enableRetaliation );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_WEAPON_AT_LOCATION:
{
WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
Coord3D targetLoc = msg->getArgument( 1 )->location;
Int maxShotsToFire = msg->getArgument( 2 )->integer;
// issue command for either single object or for selected group
if( currentlySelectedGroup )
{
// lock it just till the weapon is empty or the attack is "done"
if (currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_TEMPORARILY ))
currentlySelectedGroup->groupAttackPosition( &targetLoc, maxShotsToFire, CMD_FROM_PLAYER );
} // end if, command for group
break;
} //end do weapon at location
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SPECIAL_POWER:
{
// first argument is the special power ID
UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
// Command button options -- special power may care about variance options
UnsignedInt options = msg->getArgument( 1 )->integer;
// check for possible specific source, ignoring selection.
ObjectID sourceID = msg->getArgument(2)->objectID;
Object* source = TheGameLogic->findObjectByID(sourceID);
if (source != NULL)
{
AIGroup* theGroup = TheAI->createGroup();
theGroup->add(source);
theGroup->groupDoSpecialPower( specialPowerID, options );
TheAI->destroyGroup(theGroup);
}
else
{
//Use the selected group!
if( currentlySelectedGroup )
{
currentlySelectedGroup->groupDoSpecialPower( specialPowerID, options );
}
}
break;
} // end do special
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SPECIAL_POWER_AT_LOCATION:
{
// first argument is the special power ID
UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
// Location argument 2 is destination
Coord3D targetCoord = msg->getArgument(1)->location;
// Angle argument 3 is the orientation of the special power (if applicable)
Real angle = msg->getArgument(2)->real;
// Object in way -- if applicable (some specials care, others don't)
ObjectID objectID = msg->getArgument( 3 )->objectID;
Object *objectInWay = TheGameLogic->findObjectByID( objectID );
// Command button options -- special power may care about variance options
UnsignedInt options = msg->getArgument( 4 )->integer;
// check for possible specific source, ignoring selection.
ObjectID sourceID = msg->getArgument(5)->objectID;
Object* source = TheGameLogic->findObjectByID(sourceID);
if (source != NULL)
{
AIGroup* theGroup = TheAI->createGroup();
theGroup->add(source);
theGroup->groupDoSpecialPowerAtLocation( specialPowerID, &targetCoord, angle, objectInWay, options );
TheAI->destroyGroup(theGroup);
}
else
{
//Use the selected group!
if( currentlySelectedGroup )
{
currentlySelectedGroup->groupDoSpecialPowerAtLocation( specialPowerID, &targetCoord, angle, objectInWay, options );
}
}
break;
} // end do special at location
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SPECIAL_POWER_AT_OBJECT:
{
// first argument is the special power ID
UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
// argument 2 is target object
ObjectID targetID = msg->getArgument(1)->objectID;
Object *target = TheGameLogic->findObjectByID( targetID );
if( !target )
{
break;
}
// Command button options -- special power may care about variance options
UnsignedInt options = msg->getArgument( 2 )->integer;
// check for possible specific source, ignoring selection.
ObjectID sourceID = msg->getArgument(3)->objectID;
Object* source = TheGameLogic->findObjectByID(sourceID);
if (source != NULL)
{
AIGroup* theGroup = TheAI->createGroup();
theGroup->add(source);
theGroup->groupDoSpecialPowerAtObject( specialPowerID, target, options );
TheAI->destroyGroup(theGroup);
}
else
{
if( currentlySelectedGroup )
{
currentlySelectedGroup->groupDoSpecialPowerAtObject( specialPowerID, target, options );
}
}
break;
} // end do special at object
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_ATTACKMOVETO:
{
Coord3D dest = msg->getArgument( 0 )->location;
if (currentlySelectedGroup)
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupAttackMoveToPosition( &dest, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_FORCEMOVETO:
{
Coord3D dest = msg->getArgument( 0 )->location;
if (currentlySelectedGroup)
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupMoveToPosition( &dest, false, CMD_FROM_PLAYER );
}
break;
}
//---------------------------------------------------------------------------------------------
// MSG_DO_SALVAGE is intentionally set up to mimic the moveto.
case GameMessage::MSG_DO_SALVAGE:
case GameMessage::MSG_DO_MOVETO:
{
Coord3D dest = msg->getArgument( 0 )->location;
if( currentlySelectedGroup )
{
//DEBUG_LOG(("GameLogicDispatch - got a MSG_DO_MOVETO command\n"));
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupMoveToPosition( &dest, false, CMD_FROM_PLAYER );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_ADD_WAYPOINT:
{
Coord3D dest = msg->getArgument( 0 )->location;
if( currentlySelectedGroup )
{
//DEBUG_LOG(("GameLogicDispatch - got a MSG_DO_MOVETO command\n"));
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupMoveToPosition( &dest, true, CMD_FROM_PLAYER );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_GUARD_POSITION:
{
Coord3D loc = msg->getArgument( 0 )->location;
GuardMode gm = (GuardMode)msg->getArgument( 1 )->integer;
if (currentlySelectedGroup)
{
currentlySelectedGroup->groupGuardPosition(&loc, gm, CMD_FROM_PLAYER);
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_GUARD_OBJECT:
{
Object* obj = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
if (!obj)
break;
GuardMode gm = (GuardMode)msg->getArgument( 1 )->integer;
if (currentlySelectedGroup)
{
currentlySelectedGroup->groupGuardObject(obj, gm, CMD_FROM_PLAYER);
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_STOP:
{
if (currentlySelectedGroup)
{
currentlySelectedGroup->groupIdle(CMD_FROM_PLAYER);
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SCATTER:
{
if (currentlySelectedGroup)
{
currentlySelectedGroup->groupScatter(CMD_FROM_PLAYER);
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_CREATE_FORMATION:
{
if (currentlySelectedGroup)
{
currentlySelectedGroup->groupCreateFormation(CMD_FROM_PLAYER);
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_CLEAR_INGAME_POPUP_MESSAGE:
{
if( TheInGameUI )
{
TheInGameUI->clearPopupMessageData();
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_CHEER:
{
//All selected units play cheer animation.
if( currentlySelectedGroup )
{
currentlySelectedGroup->groupCheer( CMD_FROM_PLAYER );
}
break;
}
#if defined(_DEBUG) || defined(_INTERNAL) || defined (_ALLOW_DEBUG_CHEATS_IN_RELEASE)
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DEBUG_KILL_SELECTION:
{
//All selected units die
if( currentlySelectedGroup )
{
const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
{
Object *obj = findObjectByID(*it);
if (obj)
{
obj->kill();
}
}
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DEBUG_HURT_OBJECT:
{
Object* objToHurt = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
if (objToHurt)
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_NORMAL;
damageInfo.in.m_sourceID = INVALID_ID;
damageInfo.in.m_amount = objToHurt->getBodyModule()->getMaxHealth() / 10.0f;
objToHurt->attemptDamage( &damageInfo );
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DEBUG_KILL_OBJECT:
{
Object* objToHurt = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
if (objToHurt)
{
objToHurt->kill();
}
break;
}
#endif
#ifdef ALLOW_SURRENDER
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SURRENDER:
{
//All selected units surrender
if( currentlySelectedGroup )
{
Object* objWeSurrenderedTo = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
Bool surrender = msg->getArgument( 1 )->boolean;
currentlySelectedGroup->groupSurrender( objWeSurrenderedTo, surrender, CMD_FROM_PLAYER );
}
break;
}
#endif
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_ENTER:
{
Object *enter = TheGameLogic->findObjectByID( msg->getArgument( 1 )->objectID );
// sanity
if( enter == NULL )
break;
if( currentlySelectedGroup )
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupEnter( enter, CMD_FROM_PLAYER );
}
break;
} // end GameMessage::MSG_ENTER
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_EXIT:
{
Object *objectWantingToExit = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
Object *objectContainingExiter = getSingleObjectFromSelection(currentlySelectedGroup);
// sanity
if( objectWantingToExit == NULL )
break;
if( objectContainingExiter == NULL )
break;
// sanity, the player must actually control this object
if( objectWantingToExit->getControllingPlayer() != thisPlayer )
break;
objectWantingToExit->releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks.
// exit whatever objectWantingToExit is INSIDE of
AIUpdateInterface *ai = objectWantingToExit->getAIUpdateInterface();
if( ai )
ai->aiExit( objectContainingExiter, CMD_FROM_PLAYER );
// Just like Enter, Exit needs to know the thing to exit. This can no longer be assumed because of the Tunnel system.
// If you do not specify the thing to Exit, it will Exit the thing it thinks it is in. For a tunnel network,
// that will be the specific Tunnel it entered. (Scripts can talk directly to the guy to say Get Out Regardless)
break;
} // end GameMessage::MSG_EXIT
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_EVACUATE:
{
// issue command for either single object or for selected group
// AIGroup *group = TheAI->findGroup( *selectedGroupID );
if( currentlySelectedGroup )
{
//Coord3D pos;
//Bool hasArgs = FALSE;
//hasArgs = (msg->getArgumentCount() > 0);
//if (hasArgs)
// pos = msg->getArgument(0)->location;
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
// evacuate message is for the selected group
//if (hasArgs)
// currentlySelectedGroup->groupMoveToAndEvacuate( &pos, CMD_FROM_PLAYER );
//else
currentlySelectedGroup->groupEvacuate( CMD_FROM_PLAYER );
// no, this is bad, don't do here, do when POSTING message
// pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_EVACUATE );
} // end if, command for group
break;
} // end GameMessage::MSG_EVACUATE
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_EXECUTE_RAILED_TRANSPORT:
{
// issue command to currently selected objects
if( currentlySelectedGroup )
currentlySelectedGroup->groupExecuteRailedTransport( CMD_FROM_PLAYER );
break;
} // end GameMessage::MSG_EXECUTE_RAILED_TRANSPORT
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_INTERNET_HACK:
{
// ObjectID sourceID = msg->getArgument( 0 )->objectID;
if( currentlySelectedGroup )
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupHackInternet( CMD_FROM_PLAYER );
}
break;
}
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_GET_REPAIRED:
{
Object *repairDepot = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// sanity
if( repairDepot == NULL )
break;
// tell the currently selected group to go get repaired
if( currentlySelectedGroup )
currentlySelectedGroup->groupGetRepaired( repairDepot, CMD_FROM_PLAYER );
break;
} // end get repaired
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_DOCK:
{
Object *dockBuilding = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// sanity
if( dockBuilding == NULL )
break;
// tell the currently selected group to go get repaired
if( currentlySelectedGroup )
currentlySelectedGroup->groupDock( dockBuilding, CMD_FROM_PLAYER );
break;
} // end get repaired
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_GET_HEALED:
{
Object *healDest = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// sanity
if( healDest == NULL )
break;
// tell the currently selected group to enter the building for healing
if( currentlySelectedGroup )
currentlySelectedGroup->groupGetHealed( healDest, CMD_FROM_PLAYER );
break;
} // end get repaired
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_REPAIR:
{
Object *repairTarget = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// sanity
if( repairTarget == NULL )
break;
//
// tell the currently selected group of objects to go repair the target object, note
// that only one of them will actually go ahead and do the repair
//
if( currentlySelectedGroup )
currentlySelectedGroup->groupRepair( repairTarget, CMD_FROM_PLAYER );
break;
} // end get repaired
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_RESUME_CONSTRUCTION:
{
Object *constructTarget = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// sanity
if( constructTarget == NULL )
break;
//
// tell the currently selected group of objects to resume construction on
// the target object, note that only one of them will go off and resume construction
// on the target
//
if( currentlySelectedGroup )
currentlySelectedGroup->groupResumeConstruction( constructTarget, CMD_FROM_PLAYER );
// no, this is bad, don't do here, do when POSTING message
// pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), msg->getType() );
break;
} // end resume construction
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION:
{
const Coord3D *loc = &msg->getArgument( 0 )->location;
SpecialPowerType spType = (SpecialPowerType)msg->getArgument( 1 )->integer;
ObjectID sourceID = msg->getArgument(2)->objectID;
Object* source = TheGameLogic->findObjectByID(sourceID);
if (source != NULL)
{
AIGroup* theGroup = TheAI->createGroup();
theGroup->add(source);
theGroup->groupOverrideSpecialPowerDestination( spType, loc, CMD_FROM_PLAYER );
TheAI->destroyGroup(theGroup);
}
else
{
if( currentlySelectedGroup )
{
currentlySelectedGroup->groupOverrideSpecialPowerDestination( spType, loc, CMD_FROM_PLAYER );
}
}
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_ATTACK_OBJECT:
{
Object *enemy = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// Check enemy, as it is possible that he died this frame.
if (enemy)
{
if (currentlySelectedGroup)
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupAttackObject( enemy, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
}
}
break;
} // end GameMessage::MSG_DO_ATTACK_GROUND_OBJECT
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_FORCE_ATTACK_OBJECT:
{
Object *enemy = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
// Check enemy, as it is possible that he died this frame.
if (enemy)
{
if (currentlySelectedGroup)
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
currentlySelectedGroup->groupForceAttackObject( enemy, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
}
}
break;
} // end GameMessage::MSG_DO_ATTACK_GROUND_OBJECT
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_FORCE_ATTACK_GROUND:
{
const Coord3D *pos = &msg->getArgument( 0 )->location;
if (currentlySelectedGroup)
{
/////////////////////////////////////////////////////////////////////
//Lorenzen sez: unclear, yet how to solve this for all cases
//Kris: This code was added to allow the toxin tractor to force attack
// while contaminating. When this change was made, it was causing
// rangers and scud launchers to reset to primary weapon mode whenever
// force attacking while not idle. I fixed this by enforcing the
// temporary and permanent modes that are already set when attempting
// the new lock. In this case, the temp lock attempt will fail whenever
// a permanent lock is in effect, thus fixing the ranger and scud and
// allowing the tox tractor to work as well.
Bool forceAttackRequiresPrimaryWeapon = !currentlySelectedGroup->isIdle();
if ( forceAttackRequiresPrimaryWeapon )
{
currentlySelectedGroup->setWeaponLockForGroup( PRIMARY_WEAPON, LOCKED_TEMPORARILY );
currentlySelectedGroup->groupAttackPosition( pos, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY);
}
else
///////////////////////////////////////////////////////////////////
{
currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY);
currentlySelectedGroup->groupAttackPosition( pos, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
}
}
break;
} // end GameMessage::MSG_DO_ATTACK_GROUND_OBJECT
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_QUEUE_UPGRADE:
{
const UpgradeTemplate *upgradeT = TheUpgradeCenter->findUpgradeByKey( (NameKeyType)(msg->getArgument( 1 )->integer) );
if (!upgradeT) // sanity
break;
if (currentlySelectedGroup)
currentlySelectedGroup->queueUpgrade( upgradeT );
break;
} // end queue upgrade
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_CANCEL_UPGRADE:
{
Object *producer = getSingleObjectFromSelection(currentlySelectedGroup);
const UpgradeTemplate *upgradeT = TheUpgradeCenter->findUpgradeByKey( (NameKeyType)(msg->getArgument( 0 )->integer) );
// sanity
if( producer == NULL || upgradeT == NULL )
break;
// the player must actually control the producer object
if( producer->getControllingPlayer() != thisPlayer )
break;
// producer must have a production update
ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
if( pu == NULL )
break;
// cancel the upgrade
pu->cancelUpgrade( upgradeT );
break;
} // end cancel upgrade
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_QUEUE_UNIT_CREATE:
{
Object *producer = getSingleObjectFromSelection(currentlySelectedGroup);
const ThingTemplate *whatToCreate;
ProductionID productionID;
// get data from the message
whatToCreate = TheThingFactory->findByTemplateID( msg->getArgument( 0 )->integer );
productionID = (ProductionID)msg->getArgument( 1 )->integer;
// sanity
if ( producer == NULL || whatToCreate == NULL )
break;
// get the production interface for the producer
ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
if( pu == NULL )
{
DEBUG_ASSERTCRASH( 0, ("MSG_QUEUE_UNIT_CREATE: Producer '%s' doesn't have a unit production interface\n",
producer->getTemplate()->getName().str()) );
break;
} // end if
// queue the build
pu->queueCreateUnit( whatToCreate, productionID );
break;
} // end GameMessage::MSG_QUEUE_UNIT_CREATE
//-------------------------------------------------------------------------------------------------
case GameMessage::MSG_CANCEL_UNIT_CREATE:
{
Object *producer = getSingleObjectFromSelection(currentlySelectedGroup);
ProductionID productionID = (ProductionID)msg->getArgument( 0 )->integer;
// sanity
if( producer == NULL )
break;
// sanity, the player must control the producer
if( producer->getControllingPlayer() != thisPlayer )
break;
// get the unit production interface
ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
if( pu == NULL )
return;
// cancel the production
pu->cancelUnitCreate( productionID );
break;
} // end GameMessage::MSG_CANCEL_UNIT_CREATE
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DOZER_CONSTRUCT:
case GameMessage::MSG_DOZER_CONSTRUCT_LINE:
{
const ThingTemplate *place;
Coord3D loc;
Real angle;
// get player, what to place, and location
Object *constructorObject = getSingleObjectFromSelection(currentlySelectedGroup);
place = TheThingFactory->findByTemplateID( msg->getArgument( 0 )->integer );
loc = msg->getArgument( 1 )->location;
angle = msg->getArgument( 2 )->real;
if( place == NULL || constructorObject == NULL )
break; //These are not crashes, as the object may have died before this message came in
if( msg->getType() == GameMessage::MSG_DOZER_CONSTRUCT )
{
TheBuildAssistant->buildObjectNow( constructorObject, place, &loc, angle,
constructorObject->getControllingPlayer() );
} // end if
else
{
Coord3D locEnd;
// get the end of the line location in the world
locEnd = msg->getArgument( 3 )->location;
// place the line of structures, the end location being present will make it happen
TheBuildAssistant->buildObjectLineNow( constructorObject, place, &loc, &locEnd, angle,
constructorObject->getControllingPlayer() );
} // end else
// place the sound for putting a building down
static AudioEventRTS placeBuilding(AsciiString("PlaceBuilding"));
placeBuilding.setObjectID(constructorObject->getID());
TheAudio->addAudioEvent( &placeBuilding );
// no, this is bad, don't do here, do when POSTING message
// pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), msg->getType() );
break;
} // end build start
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DOZER_CANCEL_CONSTRUCT:
{
// get the building to cancel construction on
Object *building = getSingleObjectFromSelection(currentlySelectedGroup);
if( building == NULL )
break;
// the player sending this message must actually control this building
if( building->getControllingPlayer() != thisPlayer )
break;
// Check to make sure it is actually under construction
if( !building->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) )
break;
// OK, refund the money to the player, unless it is a rebuilding Hole.
if( !building->testStatus(OBJECT_STATUS_RECONSTRUCTING))
{
Money *money = thisPlayer->getMoney();
UnsignedInt amount = building->getTemplate()->calcCostToBuild( thisPlayer );
money->deposit( amount );
}
//
// Destroy the building ... killing the
// building will automatically cause the dozer also cancel its own building
// behavior and it will go on its merry way doing other assigned tasks
//
building->kill();
break;
} // end cancel dozer construction
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_SELL:
{
// use the selected group
if( currentlySelectedGroup )
currentlySelectedGroup->groupSell( CMD_FROM_PLAYER );
break;
} // end sell
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_TOGGLE_OVERCHARGE:
{
// use the selected group
if( currentlySelectedGroup )
currentlySelectedGroup->groupToggleOvercharge( CMD_FROM_PLAYER );
break;
} // end toggle overcharge
#ifdef ALLOW_SURRENDER
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_PICK_UP_PRISONER:
{
Object *prisoner = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
if( prisoner )
{
// use selected group
if( currentlySelectedGroup )
currentlySelectedGroup->groupPickUpPrisoner( prisoner, CMD_FROM_PLAYER );
} // end if
break;
} // end pick up prisoner
#endif
#ifdef ALLOW_SURRENDER
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_RETURN_TO_PRISON:
{
// use selected group
if( currentlySelectedGroup )
currentlySelectedGroup->groupReturnToPrison( NULL, CMD_FROM_PLAYER );
break;
} // end return to prison
#endif
//---------------------------------------------------------------------------------------------
// No sound does exactly the same logical processing as the usual message. Just double them up.
case GameMessage::MSG_CREATE_SELECTED_GROUP_NO_SOUND:
case GameMessage::MSG_CREATE_SELECTED_GROUP:
{
Bool createNewGroup = msg->getArgument( 0 )->boolean;
Player *player = ThePlayerList->getNthPlayer(msg->getPlayerIndex());
if (player == NULL) {
DEBUG_CRASH(("GameLogicDispatch - MSG_CREATE_SELECTED_GROUP had an invalid player nubmer"));
break;
}
Bool firstObject = TRUE;
for (Int i = 1; i < msg->getArgumentCount(); ++i) {
Object *obj = TheGameLogic->findObjectByID( msg->getArgument( i )->objectID );
if (!obj) {
continue;
}
TheGameLogic->selectObject(obj, createNewGroup && firstObject, player->getPlayerMask());
firstObject = FALSE;
}
break;
} // end build start
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_REMOVE_FROM_SELECTED_GROUP:
{
Player *player = ThePlayerList->getNthPlayer(msg->getPlayerIndex());
if (player == NULL) {
DEBUG_CRASH(("GameLogicDispatch - MSG_CREATE_SELECTED_GROUP had an invalid player nubmer"));
break;
}
for (Int i = 0; i < msg->getArgumentCount(); ++i) {
ObjectID objID = msg->getArgument(i)->objectID;
Object *objToRemove = TheGameLogic->findObjectByID(objID);
if (!objToRemove) {
continue;
}
TheGameLogic->deselectObject(objToRemove, player->getPlayerMask());
}
break;
} // end MSG_REMOVE_FROM_SELECTED_GROUP
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DESTROY_SELECTED_GROUP:
{
Player *player = ThePlayerList->getNthPlayer(msg->getPlayerIndex());
if (player != NULL)
{
player->setCurrentlySelectedAIGroup(NULL);
}
break;
} // end build start
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_SELECTED_GROUP_COMMAND:
{
break;
} // end selected group command
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_PLACE_BEACON:
{
// how many does this player have active?
Coord3D pos = msg->getArgument( 0 )->location;
Region3D r;
TheTerrainLogic->getExtent(&r);
if (!r.isInRegionNoZ(&pos))
pos = TheTerrainLogic->findClosestEdgePoint(&pos);
const ThingTemplate *thing = TheThingFactory->findTemplate( thisPlayer->getPlayerTemplate()->getBeaconTemplate() );
if (thing && !TheVictoryConditions->hasSinglePlayerBeenDefeated(thisPlayer))
{
Int count;
thisPlayer->countObjectsByThingTemplate( 1, &thing, false, &count );
DEBUG_LOG(("Player already has %d beacons active\n", count));
if (count >= TheMultiplayerSettings->getMaxBeaconsPerPlayer())
{
if (thisPlayer == ThePlayerList->getLocalPlayer())
{
// tell the user
TheInGameUI->message( TheGameText->fetch("GUI:TooManyBeacons") );
// play a sound
static AudioEventRTS aSound("BeaconPlacementFailed");
aSound.setPosition(&pos);
aSound.setPlayerIndex(thisPlayer->getPlayerIndex());
TheAudio->addAudioEvent(&aSound);
}
break;
}
Object *object = TheThingFactory->newObject( thing, thisPlayer->getDefaultTeam() );
object->setPosition( &pos );
object->setProducer(NULL);
if (thisPlayer->getRelationship( ThePlayerList->getLocalPlayer()->getDefaultTeam() ) == ALLIES || ThePlayerList->getLocalPlayer()->isPlayerObserver())
{
// tell the user
UnicodeString s;
s.format(TheGameText->fetch("GUI:BeaconPlaced"), thisPlayer->getPlayerDisplayName().str());
TheInGameUI->message( s );
// play a sound
static AudioEventRTS aSound("BeaconPlaced");
aSound.setPlayerIndex(thisPlayer->getPlayerIndex());
aSound.setPosition(&pos);
TheAudio->addAudioEvent(&aSound);
// beacons are a rare event; play a nifty radar event thingie
TheRadar->createEvent( object->getPosition(), RADAR_EVENT_INFORMATION );
if (ThePlayerList->getLocalPlayer()->getRelationship(thisPlayer->getDefaultTeam()) == ALLIES)
TheEva->setShouldPlay(EVA_BeaconDetected);
TheControlBar->markUIDirty(); // check if we should grey out the button
}
else
{
Int updateCount = 0;
static NameKeyType nameKeyClientUpdate = NAMEKEY("BeaconClientUpdate");
ClientUpdateModule ** clientModules = object->getDrawable()->getClientUpdateModules();
if (clientModules)
{
while (*clientModules)
{
if ((*clientModules)->getModuleNameKey() == nameKeyClientUpdate)
{
(*(BeaconClientUpdate **)clientModules)->hideBeacon();
++updateCount;
}
++clientModules;
}
}
DEBUG_ASSERTCRASH(updateCount == 1, ("Saw %d update modules for the beacon!", updateCount));
}
}
else
{
// tell the user
TheInGameUI->message( TheGameText->fetch("GUI:BeaconPlacementFailed") );
// play a sound
static AudioEventRTS aSound("BeaconPlacementFailed");
aSound.setPosition(&pos);
aSound.setPlayerIndex(thisPlayer->getPlayerIndex());
TheAudio->addAudioEvent(&aSound);
}
break;
} // end beacon placement
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_REMOVE_BEACON:
{
AIGroup *allSelectedObjects = NULL;
allSelectedObjects = TheAI->createGroup();
thisPlayer->getCurrentSelectionAsAIGroup(allSelectedObjects); // need to act on all objects, so we can hide teammates' beacons.
if( allSelectedObjects )
{
const VecObjectID& selectedObjects = allSelectedObjects->getAllIDs();
for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
{
Object *beacon = findObjectByID(*it);
if (beacon)
{
const ThingTemplate *thing = TheThingFactory->findTemplate( beacon->getControllingPlayer()->getPlayerTemplate()->getBeaconTemplate() );
if (thing->isEquivalentTo(beacon->getTemplate()))
{
if (beacon->getControllingPlayer() == thisPlayer)
{
TheGameLogic->destroyObject(beacon); // the owner is telling it to go away. such is life.
TheControlBar->markUIDirty(); // check if we should un-grey out the button
}
else if (thisPlayer == ThePlayerList->getLocalPlayer())
{
Drawable *beaconDrawable = beacon->getDrawable();
if (beaconDrawable)
{
static NameKeyType nameKeyClientUpdate = NAMEKEY("BeaconClientUpdate");
ClientUpdateModule ** clientModules = beaconDrawable->getClientUpdateModules();
if (clientModules)
{
while (*clientModules)
{
if ((*clientModules)->getModuleNameKey() == nameKeyClientUpdate)
(*(BeaconClientUpdate **)clientModules)->hideBeacon();
++clientModules;
}
}
}
}
}
}
}
if (allSelectedObjects->isEmpty())
{
TheAI->destroyGroup(allSelectedObjects);
allSelectedObjects = NULL;
}
}
break;
} // end beacon removal
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_SET_BEACON_TEXT:
{
if( currentlySelectedGroup )
{
const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
{
Object *beacon = findObjectByID(*it);
if (beacon)
{
Drawable *beaconDrawable = beacon->getDrawable();
if (beaconDrawable)
{
UnicodeString s;
for( int i=0; igetArgumentCount(); i++ )
{
s.concat( msg->getArgument(i)->wChar );
}
if (s.isEmpty())
beaconDrawable->clearCaptionText();
else
beaconDrawable->setCaptionText(s);
}
}
}
}
break;
} // end beacon text
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_SELF_DESTRUCT:
{
if (msg->getArgument(0)->boolean)
{
// transfer control to any living ally
for (Int i=0; igetPlayerCount(); ++i)
{
if (i != msg->getPlayerIndex())
{
Player *otherPlayer = ThePlayerList->getNthPlayer(i);
if (thisPlayer->getRelationship(otherPlayer->getDefaultTeam()) == ALLIES &&
otherPlayer->getRelationship(thisPlayer->getDefaultTeam()) == ALLIES)
{
if (TheVictoryConditions->hasSinglePlayerBeenDefeated(otherPlayer))
continue;
// a living ally! hooray!
otherPlayer->transferAssetsFromThat(thisPlayer);
thisPlayer->killPlayer(); // just to be safe (and to kill beacons etc that don't transfer)
break;
}
}
}
if (i == ThePlayerList->getPlayerCount())
{
// didn't find any allies. die, loner!
thisPlayer->killPlayer();
}
}
else
{
thisPlayer->killPlayer();
}
// There is no reason to do any notification here, it now takes place in the victory conditions.
// bonehead.
break;
}
// --------------------------------------------------------------------------------------------
case GameMessage::MSG_SET_REPLAY_CAMERA:
{
if (TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer)
{
if (TheTacticalView->isCameraMovementFinished())
{
ViewLocation loc;
Coord3D pos;
Real pitch, angle, zoom;
pos = msg->getArgument( 0 )->location;
angle = msg->getArgument( 1 )->real;
pitch = msg->getArgument( 2 )->real;
zoom = msg->getArgument( 3 )->real;
loc.init(pos.x, pos.y, pos.z, angle, pitch, zoom);
TheTacticalView->setLocation( &loc );
if (!TheLookAtTranslator->hasMouseMovedRecently())
{
TheMouse->setCursor( (Mouse::MouseCursor)(msg->getArgument( 4 )->integer) );
ICoord2D mousePos = msg->getArgument( 5 )->pixel;
TheMouse->setPosition( mousePos.x, mousePos.y );
TheLookAtTranslator->setCurrentPos( mousePos );
}
}
}
break;
} // end beacon text
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_CREATE_TEAM0:
case GameMessage::MSG_CREATE_TEAM1:
case GameMessage::MSG_CREATE_TEAM2:
case GameMessage::MSG_CREATE_TEAM3:
case GameMessage::MSG_CREATE_TEAM4:
case GameMessage::MSG_CREATE_TEAM5:
case GameMessage::MSG_CREATE_TEAM6:
case GameMessage::MSG_CREATE_TEAM7:
case GameMessage::MSG_CREATE_TEAM8:
case GameMessage::MSG_CREATE_TEAM9:
{
Int playerIndex = msg->getPlayerIndex();
Player *player = ThePlayerList->getNthPlayer(playerIndex);
DEBUG_ASSERTCRASH(player != NULL, ("Could not find player for create team message"));
if (player == NULL)
{
break;
}
player->processCreateTeamGameMessage(msg->getType() - GameMessage::MSG_CREATE_TEAM0, msg);
break;
} // end create team command
case GameMessage::MSG_SELECT_TEAM0:
case GameMessage::MSG_SELECT_TEAM1:
case GameMessage::MSG_SELECT_TEAM2:
case GameMessage::MSG_SELECT_TEAM3:
case GameMessage::MSG_SELECT_TEAM4:
case GameMessage::MSG_SELECT_TEAM5:
case GameMessage::MSG_SELECT_TEAM6:
case GameMessage::MSG_SELECT_TEAM7:
case GameMessage::MSG_SELECT_TEAM8:
case GameMessage::MSG_SELECT_TEAM9:
{
Int playerIndex = msg->getPlayerIndex();
Player *player = ThePlayerList->getNthPlayer(playerIndex);
DEBUG_ASSERTCRASH(player != NULL, ("Could not find player for select team message"));
if (player == NULL)
{
break;
}
player->processSelectTeamGameMessage(msg->getType() - GameMessage::MSG_SELECT_TEAM0, msg);
break;
}
case GameMessage::MSG_ADD_TEAM0:
case GameMessage::MSG_ADD_TEAM1:
case GameMessage::MSG_ADD_TEAM2:
case GameMessage::MSG_ADD_TEAM3:
case GameMessage::MSG_ADD_TEAM4:
case GameMessage::MSG_ADD_TEAM5:
case GameMessage::MSG_ADD_TEAM6:
case GameMessage::MSG_ADD_TEAM7:
case GameMessage::MSG_ADD_TEAM8:
case GameMessage::MSG_ADD_TEAM9:
{
Int playerIndex = msg->getPlayerIndex();
Player *player = ThePlayerList->getNthPlayer(playerIndex);
DEBUG_ASSERTCRASH(player != NULL, ("Could not find player for add team message"));
if (player == NULL)
{
break;
}
player->processAddTeamGameMessage(msg->getType() - GameMessage::MSG_ADD_TEAM0, msg);
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_LOGIC_CRC:
{
if (TheNetwork)
{
Int slotIndex = -1;
for (Int i=0; igetPlayerType() == PLAYER_HUMAN && TheNetwork->getPlayerName(i) == thisPlayer->getPlayerDisplayName())
{
slotIndex = i;
break;
}
}
if (slotIndex < 0 || !TheNetwork->isPlayerConnected(slotIndex))
break;
if (thisPlayer->isLocalPlayer())
{
#if defined(_DEBUG) || defined(_INTERNAL)
// don't even put this in release, cause someone might hack it.
if (!TheDebugIgnoreSyncErrors)
{
#endif
m_shouldValidateCRCs = TRUE;
#if defined(_DEBUG) || defined(_INTERNAL)
}
#endif
}
//UnsignedInt oldCRC = m_cachedCRCs[msg->getPlayerIndex()];
UnsignedInt newCRC = msg->getArgument(0)->integer;
//DEBUG_LOG(("Recieved CRC of %8.8X from %ls on frame %d\n", newCRC,
//thisPlayer->getPlayerDisplayName().str(), m_frame));
m_cachedCRCs[msg->getPlayerIndex()] = newCRC; // to mask problem: = (oldCRC < newCRC)?newCRC:oldCRC;
}
else if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK)
{
UnsignedInt newCRC = msg->getArgument(0)->integer;
//DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d\n",
//newCRC, thisPlayer->getPlayerIndex(), getCRC(), msg->getArgumentCount()));
TheRecorder->handleCRCMessage(newCRC, thisPlayer->getPlayerIndex(), (msg->getArgument(1)->boolean));
}
break;
} // end CRC message
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_PURCHASE_SCIENCE:
{
ScienceType science = (ScienceType)msg->getArgument( 0 )->integer;
// sanity
if( science == SCIENCE_INVALID || thisPlayer == NULL )
break;
thisPlayer->attemptToPurchaseScience(science);
break;
} // end pick specialized science
} // end switch
/**/ /// @todo: multiplayer semantics
if (currentlySelectedGroup && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/)
{
const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
TheInGameUI->deselectAllDrawables();
for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
{
const Object *obj = findObjectByID(*it);
if (obj)
{
Drawable *draw = obj->getDrawable();
if (draw)
TheInGameUI->selectDrawable(draw);
}
}
}
/**/
if( currentlySelectedGroup != NULL )
{
TheAI->destroyGroup(currentlySelectedGroup);
}
} // end logicMessageDispatches