364 lines
11 KiB
C++

/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "GameClient/SelectionInfo.h"
#include "Common/ActionManager.h"
#include "GameLogic/Damage.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"
#include "GameClient/CommandXlat.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameClient/GameClient.h"
#include "GameClient/KeyDefs.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
SelectionInfo::SelectionInfo() :
currentCountEnemies(0),
currentCountCivilians(0),
currentCountMine(0),
currentCountMineInfantry(0),
currentCountMineBuildings(0),
currentCountFriends(0),
newCountEnemies(0),
newCountCivilians(0),
newCountCrates(0),
newCountMine(0),
newCountMineBuildings(0),
newCountFriends(0),
newCountGarrisonableBuildings(0),
selectEnemies(FALSE),
selectCivilians(FALSE),
selectMine(FALSE),
selectMineBuildings(FALSE),
selectFriends(FALSE)
{ }
//-------------------------------------------------------------------------------------------------
PickDrawableStruct::PickDrawableStruct() : drawableListToFill(NULL)
{
//Added By Sadullah Nader
//Initializations inserted
drawableListToFill = FALSE;
//
forceAttackMode = TheInGameUI->isInForceAttackMode();
UnsignedInt pickType = getPickTypesForContext(forceAttackMode);
translatePickTypesToKindof(pickType, kindofsToMatch);
if (!forceAttackMode)
{
kindofsToMatch.set(KINDOF_ALWAYS_SELECTABLE);
}
}
//-------------------------------------------------------------------------------------------------
/**
* Given a list of currently selected things and a list of things that are currently under
* the selection (pointer or drag), generate some useful information about each.
*/
extern Bool contextCommandForNewSelection(const DrawableList *currentlySelectedDrawables,
const DrawableList *newlySelectedDrawables,
SelectionInfo *outSelectionInfo,
Bool selectionIsPoint)
{
if (!(currentlySelectedDrawables && newlySelectedDrawables && outSelectionInfo))
return FALSE;
Bool forceFire = TheInGameUI->isInForceAttackMode();
Bool forceMove = TheInGameUI->isInForceMoveToMode();
if (forceFire || forceMove) {
return FALSE;
}
Player *localPlayer = ThePlayerList->getLocalPlayer();
DrawableListCIt it;
for (it = currentlySelectedDrawables->begin(); it != currentlySelectedDrawables->end(); ++it) {
if (!(*it)) {
continue;
}
Object *obj = (*it)->getObject();
if (!obj) {
continue;
}
if (obj->isLocallyControlled()) {
++outSelectionInfo->currentCountMine;
if (obj->isKindOf(KINDOF_INFANTRY)) {
++outSelectionInfo->currentCountMineInfantry;
} else if (obj->isKindOf(KINDOF_STRUCTURE)) {
++outSelectionInfo->currentCountMineBuildings;
}
} else {
Relationship rel = localPlayer->getRelationship(obj->getTeam());
if (rel == ALLIES) {
++outSelectionInfo->currentCountFriends;
} else if (rel == ENEMIES) {
++outSelectionInfo->currentCountEnemies;
} else if (rel == NEUTRAL) {
++outSelectionInfo->currentCountCivilians;
}
}
}
Drawable *newMine = NULL;
Drawable *newFriendly = NULL;
Drawable *newEnemy = NULL;
Drawable *newCivilian = NULL;
for (it = newlySelectedDrawables->begin(); it != newlySelectedDrawables->end(); ++it) {
if (!(*it)) {
continue;
}
Object *obj = (*it)->getObject();
if (!obj) {
continue;
}
if (TheActionManager->canPlayerGarrison(localPlayer, obj, CMD_FROM_PLAYER)) {
++outSelectionInfo->newCountGarrisonableBuildings;
}
if (obj->isKindOf(KINDOF_CRATE)) {
++outSelectionInfo->newCountCrates;
}
if (obj->isLocallyControlled()) {
++outSelectionInfo->newCountMine;
newMine = *it;
if (obj->isKindOf(KINDOF_STRUCTURE)) {
++outSelectionInfo->newCountMineBuildings;
}
} else {
Relationship rel = localPlayer->getRelationship(obj->getTeam());
if (rel == ALLIES) {
newFriendly = *it;
++outSelectionInfo->newCountFriends;
} else if (rel == ENEMIES) {
newEnemy = *it;
++outSelectionInfo->newCountEnemies;
} else if (rel == NEUTRAL) {
newCivilian = *it;
++outSelectionInfo->newCountCivilians;
}
}
}
DEBUG_ASSERTCRASH(outSelectionInfo->currentCountEnemies <= 1, ("Selection bug. jkmcd"));
DEBUG_ASSERTCRASH(outSelectionInfo->currentCountFriends <= 1, ("Selection bug. jkmcd"));
DEBUG_ASSERTCRASH(outSelectionInfo->currentCountCivilians <= 1, ("Selection bug. jkmcd"));
if (outSelectionInfo->currentCountEnemies > 0) {
// If we have an enemy selected, there are no context sensitive commands
return FALSE;
}
if (outSelectionInfo->currentCountFriends > 0) {
return FALSE;
}
if (outSelectionInfo->currentCountCivilians > 0) {
return FALSE;
}
if (TheGlobalData->m_useAlternateMouse) {
// context sensitive commands never apply when selecting in alternate mouse mode
return FALSE;
}
if (outSelectionInfo->currentCountMine > 0) {
if (outSelectionInfo->newCountEnemies > 0) {
if (outSelectionInfo->newCountEnemies == 1 && selectionIsPoint) {
return TheGameClient->evaluateContextCommand(newEnemy, newEnemy->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
}
return selectionIsPoint;
}
if (outSelectionInfo->newCountMine > 0) {
if (outSelectionInfo->newCountMine == 1 && selectionIsPoint && !TheInGameUI->isInPreferSelectionMode()) {
return TheGameClient->evaluateContextCommand(newMine, newMine->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
}
return FALSE;
}
if (outSelectionInfo->newCountFriends > 0) {
if (outSelectionInfo->newCountFriends == 1 && selectionIsPoint) {
return TheGameClient->evaluateContextCommand(newFriendly, newFriendly->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
}
return FALSE;
}
if (outSelectionInfo->currentCountMineInfantry > 0 && outSelectionInfo->newCountGarrisonableBuildings == 1) {
return TRUE;
}
if (outSelectionInfo->newCountCivilians > 0) {
if (outSelectionInfo->newCountCivilians == 1 && selectionIsPoint) {
return TheGameClient->evaluateContextCommand(newCivilian, newCivilian->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
}
return FALSE;
}
if (outSelectionInfo->newCountCrates > 0) {
return (outSelectionInfo->newCountCrates == 1 && selectionIsPoint);
}
}
if (outSelectionInfo->currentCountMine == 0) {
return FALSE;
}
return selectionIsPoint;
}
//-------------------------------------------------------------------------------------------------
UnsignedInt getPickTypesForContext( Bool forceAttackMode )
{
UnsignedInt types = PICK_TYPE_SELECTABLE;
if (forceAttackMode)
types |= PICK_TYPE_FORCEATTACKABLE;
//
// if we have a gui context command that allows for a shrubbery target then we want to
// pick that type too (generally shrubbery aren't pickable cause it would get in
// the way with movement and general selection)
//
const CommandButton *command = TheInGameUI->getGUICommand();
if (command != NULL) {
if (BitTest( command->getOptions(), ALLOW_MINE_TARGET)) {
types |= PICK_TYPE_MINES;
}
if (BitTest( command->getOptions(), ALLOW_SHRUBBERY_TARGET ) ) {
types |= PICK_TYPE_SHRUBBERY;
}
} else {
types |= getPickTypesForCurrentSelection(forceAttackMode);
}
return types;
} // end getPickTypesForContext
//-------------------------------------------------------------------------------------------------
UnsignedInt getPickTypesForCurrentSelection( Bool forceAttackMode )
{
UnsignedInt retVal = 0;
if (!TheInGameUI->areSelectedObjectsControllable()) {
return retVal;
}
const DrawableList *allSelectedDrawables = TheInGameUI->getAllSelectedDrawables();
for (DrawableListCIt cit = allSelectedDrawables->begin(); cit != allSelectedDrawables->end(); ++cit) {
Drawable *draw = *cit;
if (!draw) {
continue;
}
Object *obj = draw->getObject();
if (!obj) {
continue;
}
// srj sez: thanks to new, area-effect disarming, we NO LONGER want to do this...
// if (obj->hasWeaponToDealDamageType(DAMAGE_DISARM)) {
// retVal |= PICK_TYPE_MINES;
// }
if (obj->hasWeaponToDealDamageType(DAMAGE_FLAME) && forceAttackMode ) {
retVal |= PICK_TYPE_SHRUBBERY;
}
// For efficiency.
if (BitTest(retVal, PICK_TYPE_MINES | PICK_TYPE_SHRUBBERY)) {
break;
}
}
return retVal;
}
//-------------------------------------------------------------------------------------------------
void translatePickTypesToKindof(UnsignedInt pickTypes, KindOfMaskType& outMask)
{
if (BitTest(pickTypes, PICK_TYPE_SELECTABLE)) {
outMask.set(KINDOF_SELECTABLE);
}
if (BitTest(pickTypes, PICK_TYPE_SHRUBBERY)) {
outMask.set(KINDOF_SHRUBBERY);
}
if (BitTest(pickTypes, PICK_TYPE_MINES)) {
outMask.set(KINDOF_MINE);
}
if (BitTest(pickTypes, PICK_TYPE_FORCEATTACKABLE)) {
outMask.set(KINDOF_FORCEATTACKABLE);
}
}
//-------------------------------------------------------------------------------------------------
// Given a drawable, add it to an stl list specified by userData.
// Useful for iterateDrawablesInRegion.
Bool addDrawableToList( Drawable *draw, void *userData )
{
PickDrawableStruct *pds = (PickDrawableStruct *) userData;
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_allowUnselectableSelection) {
pds->drawableListToFill->push_back(draw);
return TRUE;
}
#endif
if (!pds->drawableListToFill)
return FALSE;
if (!draw->getTemplate()->isAnyKindOf(pds->kindofsToMatch))
return FALSE;
if (!draw->isSelectable())
return FALSE;
pds->drawableListToFill->push_back(draw);
return TRUE;
}