diff --git a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h index 8539de6dd4b..53360161585 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h @@ -135,7 +135,7 @@ class SpecialPowerTemplate : public Overridable EvaMessage getEvaReadyOwn(void) const { return getFO()->m_eva_ready_own; } EvaMessage getEvaReadyAlly(void) const { return getFO()->m_eva_ready_ally; } EvaMessage getEvaReadyEnemy(void) const { return getFO()->m_eva_ready_enemy; } - + Int getCost(void) const { return getFO()->m_cost; }; private: const SpecialPowerTemplate* getFO() const { return (const SpecialPowerTemplate*)friend_getFinalOverride(); } @@ -166,6 +166,7 @@ class SpecialPowerTemplate : public Overridable EvaMessage m_eva_ready_own; //< eva event when own ready EvaMessage m_eva_ready_ally; //< eva event when ally ready EvaMessage m_eva_ready_enemy; //< eva event when enemy ready + Int m_cost; ///< money cost to use special power static const FieldParse m_specialPowerFieldParse[]; ///< the parse table diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h index 80143ca1e1c..0e1dad08e18 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h @@ -71,6 +71,7 @@ class OCLSpecialPowerModuleData : public SpecialPowerModuleData Bool m_isOCLAdjustPositionToPassable; ///< Adjust target to nearest Passable cell Bool m_selectObject; ///< Select first created object + Real m_minDistToSimilarRadius; //We need to know what the final product is going to be for script placement calculations //for construction sites like the sneak attack. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp index d29ceea8d7c..66bf20bf6c8 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp @@ -1546,6 +1546,14 @@ Bool ActionManager::canDoSpecialPowerAtLocation( const Object *obj, const Coord3 //Not fully ready return false; } + + // Check for money cost + if (spTemplate->getCost() > 0) { + Player* ply = obj->getControllingPlayer(); + if (ply != nullptr && ply->getMoney()->countMoney() < spTemplate->getCost()) { + return false; + } + } } // First check terrain type, if it is cared about. Don't return a true, since there are more checks. @@ -1678,6 +1686,14 @@ Bool ActionManager::canDoSpecialPowerAtObject( const Object *obj, const Object * //Not fully ready return false; } + + // Check for money cost + if (spTemplate->getCost() > 0) { + Player* ply = obj->getControllingPlayer(); + if (ply != nullptr && ply->getMoney()->countMoney() < spTemplate->getCost()) { + return false; + } + } } @@ -2052,6 +2068,14 @@ Bool ActionManager::canDoSpecialPower( const Object *obj, const SpecialPowerTemp //Not fully ready return false; } + + // Check for money cost + if (spTemplate->getCost() > 0) { + Player* ply = obj->getControllingPlayer(); + if (ply != nullptr && ply->getMoney()->countMoney() < spTemplate->getCost()) { + return false; + } + } } //use a behaviortype for custom sp diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp index 606f28d9740..2dcd5496642 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp @@ -304,6 +304,7 @@ void SpecialPowerStore::parseSpecialPowerDefinition( INI *ini ) { "EvaReadyOwn", INI::parseEvaNameIndexList, TheEvaMessageNames, offsetof(SpecialPowerTemplate, m_eva_ready_own) }, { "EvaReadyAlly", INI::parseEvaNameIndexList, TheEvaMessageNames, offsetof(SpecialPowerTemplate, m_eva_ready_ally) }, { "EvaReadyEnemy", INI::parseEvaNameIndexList, TheEvaMessageNames, offsetof(SpecialPowerTemplate, m_eva_ready_enemy) }, + { "Cost", INI::parseInt, NULL, offsetof(SpecialPowerTemplate, m_cost) }, { NULL, NULL, NULL, 0 } }; @@ -333,7 +334,7 @@ SpecialPowerTemplate::SpecialPowerTemplate() m_eva_ready_own = EVA_Invalid; m_eva_ready_ally = EVA_Invalid; m_eva_ready_enemy = EVA_Invalid; - + m_cost = 0; } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index a65717ba145..2aa1e5f8def 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -688,8 +688,15 @@ Bool CommandButton::isValidToUseOn(const Object *sourceObj, const Object *target Bool CommandButton::isReady(const Object *sourceObj) const { SpecialPowerModuleInterface *mod = sourceObj->getSpecialPowerModule( m_specialPower ); - if( mod && mod->getPercentReady() == 1.0f ) - return true; + if (mod) { + bool ready = mod->getPercentReady() == 1.0f; + bool can_afford{ true }; + if (m_specialPower->getCost() > 0) { + can_afford = sourceObj->getControllingPlayer()->getMoney()->countMoney() >= m_specialPower->getCost(); + } + + if (ready && can_afford) return true; + } if (m_upgradeTemplate && sourceObj->affectedByUpgrade(m_upgradeTemplate) && !sourceObj->hasUpgrade(m_upgradeTemplate)) return true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp index 253b0961188..0e3074970b3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp @@ -1435,6 +1435,10 @@ CommandAvailability ControlBar::getCommandAvailability( const CommandButton *com GadgetButtonDrawInverseClock( applyToWin, percent, m_buildUpClockColor ); return COMMAND_NOT_READY; } + else if (command->getSpecialPowerTemplate()->getCost() > 0 && player->getMoney()->countMoney() < command->getSpecialPowerTemplate()->getCost()) { + // Cannot afford + return COMMAND_CANT_AFFORD; + } else if( SpecialAbilityUpdate *spUpdate = obj->findSpecialAbilityUpdate( command->getSpecialPowerTemplate()->getSpecialPowerType() ) ) { if( spUpdate && spUpdate->isPowerCurrentlyInUse( command ) ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp index 8d2659db831..4ebbdcb3d4a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp @@ -91,6 +91,7 @@ #include "GameLogic/Module/OverchargeBehavior.h" #include "GameLogic/Module/ProductionUpdate.h" #include "GameLogic/ScriptEngine.h" +#include "Common/SpecialPower.h" #include "GameNetwork/NetworkInterface.h" @@ -253,6 +254,9 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton, const ProductionPrerequisite *prereq; Bool fireScienceButton = false; UnsignedInt costToBuild = 0; + Bool isUseSpecialpowerButtonWithCost = false; + + if(commandButton) { @@ -311,6 +315,27 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton, descrip = TheGameText->fetch(commandButton->getDescriptionLabel()); Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); + + //For Specialpowers with cost, add text to the tooltip + if (commandButton->getCommandType() == GUI_COMMAND_SPECIAL_POWER || + commandButton->getCommandType() == GUI_COMMAND_SPECIAL_POWER_CONSTRUCT || + commandButton->getCommandType() == GUI_COMMAND_SPECIAL_POWER_CONSTRUCT_FROM_SHORTCUT || + commandButton->getCommandType() == GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT) { + const SpecialPowerTemplate* spt = commandButton->getSpecialPowerTemplate(); + if (spt != nullptr && spt->getCost() > 0) { + isUseSpecialpowerButtonWithCost = true; + + costToBuild = spt->getCost(); + cost.format(TheGameText->fetch("TOOLTIP:Cost"), costToBuild); + + if (player->getMoney()->countMoney() < costToBuild) { + descrip.concat(L"\n\n"); + descrip.concat(TheGameText->fetch("TOOLTIP:TooltipNotEnoughMoneyToBuild")); + } + } + } + + Object *selectedObject = draw ? draw->getObject() : NULL; if( selectedObject ) { @@ -333,7 +358,6 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton, } } } - //Special case: When building units & buildings, the CanMakeType determines reasons for not being able to buy stuff. else if( thingTemplate ) { @@ -518,10 +542,13 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton, { TheScienceStore->getNameAndDescription(st, name, descrip); - costToBuild = TheScienceStore->getSciencePurchaseCost( st ); - if( costToBuild > 0 ) - { - cost.format( TheGameText->fetch("TOOLTIP:ScienceCost"), costToBuild ); + // Do not override cost when special power has cost + if (!isUseSpecialpowerButtonWithCost) { + costToBuild = TheScienceStore->getSciencePurchaseCost(st); + if (costToBuild > 0) + { + cost.format(TheGameText->fetch("TOOLTIP:ScienceCost"), costToBuild); + } } // ask each prerequisite to give us a list of the non satisfied prerequisites diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index bcd799a13bd..f6352f0c7ee 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -1634,10 +1634,10 @@ void InGameUI::handleBuildPlacements( void ) // If shipyard move up building to at least waterheight if lower if (m_pendingPlaceType->isKindOf(KINDOF_SHIPYARD)) { - Real terrainZ{ 0 }; Real waterZ{ 0 }; - TheTerrainLogic->isUnderwater(world.x, world.y, &waterZ, &terrainZ); - world.z = std::max(terrainZ, waterZ); + if (TheTerrainLogic->isUnderwater(world.x, world.y, &waterZ)) { + world.z = std::max(world.z, waterZ); + } } m_placeIcon[ 0 ]->setPosition( &world ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp index 97f9b76ce93..bd4c66819a4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp @@ -44,6 +44,7 @@ #include "GameLogic/Object.h" #include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/TerrainLogic.h" @@ -178,6 +179,14 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess // translate the screen position of start to world target location TheTacticalView->screenToTerrain( &anchorStart, &world ); + // If shipyard move up building to at least waterheight if lower + if (build->isKindOf(KINDOF_SHIPYARD)) { + Real waterZ{ 0 }; + if (TheTerrainLogic->isUnderwater(world.x, world.y, &waterZ)) { + world.z = std::max(world.z, waterZ); + } + } + Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() ); //Kris: September 27, 2002 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp index 8ffc45a3a1e..8fdd8e0ce42 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp @@ -39,8 +39,9 @@ #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Module/OCLSpecialPower.h" -#include -#include +#include "Common/MessageStream.h" +#include "GameClient/InGameUI.h" +#include "Common/PlayerList.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // MODULE DATA //////////////////////////////////////////////////////////////////////////////////// @@ -73,6 +74,7 @@ OCLSpecialPowerModuleData::OCLSpecialPowerModuleData( void ) m_upgradeOCL.clear(); m_createLoc = CREATE_AT_EDGE_NEAR_SOURCE; m_isOCLAdjustPositionToPassable = FALSE; + m_minDistToSimilarRadius = 0.0f; } //------------------------------------------------------------------------------------------------- @@ -102,6 +104,7 @@ static void parseOCLUpgradePair( INI* ini, void * /*instance*/, void *store, con { "ReferenceObject", INI::parseAsciiString, NULL, offsetof( OCLSpecialPowerModuleData, m_referenceThingName ) }, { "OCLAdjustPositionToPassable", INI::parseBool, NULL, offsetof( OCLSpecialPowerModuleData, m_isOCLAdjustPositionToPassable ) }, { "SelectCreatedObject", INI::parseBool, NULL, offsetof( OCLSpecialPowerModuleData, m_selectObject ) }, + { "MinDistToSimilarRadius", INI::parseReal, NULL, offsetof(OCLSpecialPowerModuleData, m_minDistToSimilarRadius)}, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -260,6 +263,41 @@ void OCLSpecialPower::doSpecialPower( UnsignedInt commandOptions ) Coord3D creationCoord; creationCoord.set( getObject()->getPosition() ); + // get the module data + const OCLSpecialPowerModuleData* modData = getOCLSpecialPowerModuleData(); + + if (modData->m_minDistToSimilarRadius > 0.0f) { + const ThingTemplate* checkFor = TheThingFactory->findTemplate(modData->m_referenceThingName); + if (checkFor != nullptr) { + + PartitionFilterThing similarFilter(checkFor, true); + + PartitionFilter* filters[2]; + Int numFilters = 0; + filters[numFilters++] = &similarFilter; + filters[numFilters] = NULL; + + ObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(&creationCoord, + modData->m_minDistToSimilarRadius, + FROM_BOUNDINGSPHERE_2D, + filters, + ITER_FASTEST); + MemoryPoolObjectHolder holder(iter); + + // We have a similar object nearby, do not trigger ocl + if (iter->first() != nullptr) { + + if (getObject()->getControllingPlayer() == ThePlayerList->getLocalPlayer()) { + // play a can't do that sound (UI beep type sound) + static AudioEventRTS noCanDoSound("NoCanDoSound"); + TheAudio->addAudioEvent(&noCanDoSound); + } + + return; + } + } + } + // call the base class action cause we are *EXTENDING* functionality SpecialPowerModule::doSpecialPowerAtLocation( &creationCoord, INVALID_ANGLE, commandOptions ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp index eb4934c1f3b..f1f498fe69e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp @@ -482,6 +482,22 @@ Bool SpecialPowerModule::initiateIntentToDoSpecialPower( const Object *targetObj //------------------------------------------------------------------------------------------------- void SpecialPowerModule::triggerSpecialPower( const Coord3D *location ) { + + Int cost{ getSpecialPowerTemplate()->getCost() }; + if ( cost > 0) { + Player* ply = getObject()->getControllingPlayer(); + if (ply != nullptr && ply->getMoney()->countMoney() < cost) { + // Not enough money + return; + } + else if (ply!=nullptr) { + ply->getMoney()->withdraw(cost); + } + else { + DEBUG_LOG(("Cannot withdraw money for SpecialPower '%s', player is null", getSpecialPowerTemplate()->getName().str())); + } + } + aboutToDoSpecialPower( location ); // do BEFORE recharge createViewObject(location); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp index e901fd9df84..d4d68e21b9f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp @@ -1705,11 +1705,15 @@ Object *DozerAIUpdate::construct( const ThingTemplate *what, obj->setPosition( pos ); obj->setOrientation( angle ); - // Flatten the terrain underneath the object, then adjust to the flattened height. jba. - TheTerrainLogic->flattenTerrain(obj); - Coord3D adjustedPos = *pos; - adjustedPos.z = TheTerrainLogic->getGroundHeight(pos->x, pos->y); - obj->setPosition(&adjustedPos); + // Do not flatten shipyards + if (!obj->isKindOf(KINDOF_SHIPYARD)) { + + // Flatten the terrain underneath the object, then adjust to the flattened height. jba. + TheTerrainLogic->flattenTerrain(obj); + Coord3D adjustedPos = *pos; + adjustedPos.z = TheTerrainLogic->getGroundHeight(pos->x, pos->y); + obj->setPosition(&adjustedPos); + } // Note - very important that we add to map AFTER we flatten terrain. jba. TheAI->pathfinder()->addObjectToPathfindMap( obj );