From ce974ff8e2826124ee040cc3bfa21183830b7733 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sun, 10 May 2026 17:58:35 +0200 Subject: [PATCH 1/6] drawbridgeStuff --- .../System/GameMemoryInitPools_GeneralsMD.inl | 1 + GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../Include/Common/SpecialPowerType.h | 2 + .../Include/GameLogic/Module/BridgeBehavior.h | 6 +- .../GameLogic/Module/BridgeTowerBehavior.h | 1 + .../GameLogic/Module/DrawBridgeTowerUpdate.h | 106 ++++++ .../Source/Common/RTS/ActionManager.cpp | 7 + .../Source/Common/RTS/SpecialPower.cpp | 2 + .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../GUI/ControlBar/ControlBarCommand.cpp | 10 + .../Object/Behavior/BridgeBehavior.cpp | 17 + .../Object/Behavior/BridgeTowerBehavior.cpp | 24 ++ .../Object/Update/DrawBridgeTowerUpdate.cpp | 346 ++++++++++++++++++ 13 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index 959a215472a..25d35455888 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -694,5 +694,6 @@ static PoolSizeRec PoolSizes[] = { "DroneCarrierContain", 8, 8 }, { "W3DDependencyCarrierDraw", 16, 16 }, { "CarrierDroneAIUpdate", 16, 16 }, + { "DrawBridgeTowerUpdate", 8, 8 }, { 0, 0, 0 } }; diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index e6de4f095ae..27a246957d5 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -505,6 +505,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/DroneCarrierContain.h Include/GameLogic/Module/CarrierDroneAIUpdate.h Include/GameLogic/Module/BridgeTowerBody.h + "Include/GameLogic/Module/DrawBridgeTowerUpdate.h" Include/GameLogic/Object.h Include/GameLogic/ObjectCreationList.h Include/GameLogic/BuffSystem.h @@ -1114,6 +1115,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Update/KodiakDeploymentUpdate.cpp Source/GameLogic/Object/Update/KodiakUpdate.cpp Source/GameLogic/Object/Update/DroneCarrierSlavedUpdate.cpp + "Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp" Source/GameLogic/Object/Upgrade/ActiveShroudUpgrade.cpp Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp Source/GameLogic/Object/Upgrade/CommandSetUpgrade.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h index 472124ad13b..5f968b4fbf8 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h @@ -220,6 +220,8 @@ enum SpecialPowerType CPP_11(: Int) SUPW_SPECIAL_SPECTRE_GUNSHIP, SUPW_SPECIAL_ORBITAL_STRIKE, + SPECIAL_TOGGLE_DRAWBRIDGE, + SPECIALPOWER_COUNT, // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NEW CONSTANTS NEED A BEHAVIORTYPE DEFINED IN THE SPECIALPOWER OR return one in getFallbackBehaviorType in ActionManager.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h index a32989e6664..6a9fa2aa7b2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h @@ -36,6 +36,7 @@ #include "GameLogic/Module/DamageModule.h" #include "GameLogic/Module/DieModule.h" #include "GameLogic/Module/UpdateModule.h" +#include "GameLogic/Module/DrawBridgeTowerUpdate.h" // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// enum BridgeTowerType CPP_11(: Int); @@ -84,7 +85,8 @@ class BridgeBehaviorInterface virtual void removeScaffolding( void ) = 0; virtual Bool isScaffoldInMotion( void ) = 0; virtual Bool isScaffoldPresent( void ) = 0; - + virtual void towerCaptured(Player* oldOwner, Player* newOwner, const Object* fromTower) {}; + virtual void towerDrawBridgeUpdate(const Object* fromTower, DrawBridgeTowerInfo towerInfo) {}; }; // ------------------------------------------------------------------------------------------------ @@ -149,6 +151,8 @@ class BridgeBehavior : public UpdateModule, virtual UpdateModuleInterface *getUpdate( void ) { return this; } virtual UpdateSleepTime update( void ); + virtual void towerCaptured(Player* oldOwner, Player* newOwner, const Object* fromTower) override; + // our own methods static BridgeBehaviorInterface *getBridgeBehaviorInterfaceFromObject( Object *obj ); virtual void setTower( BridgeTowerType towerType, Object *tower ); ///< connect tower to us diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeTowerBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeTowerBehavior.h index 6d2323286a0..3f4f5675dbd 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeTowerBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeTowerBehavior.h @@ -72,6 +72,7 @@ class BridgeTowerBehavior : public BehaviorModule, virtual void setBridge( Object *bridge ); virtual ObjectID getBridgeID( void ); virtual void setTowerType( BridgeTowerType type ); + virtual void onCapture(Player* oldOwner, Player* newOwner) override; static BridgeTowerBehaviorInterface *getBridgeTowerBehaviorInterfaceFromObject( Object *obj ); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h new file mode 100644 index 00000000000..786e70d4544 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h @@ -0,0 +1,106 @@ +// FILE: DrawBridgeTowerUpdate.h ////////////////////////////////////////////////////////////////////////// +// Desc: Update module to handle draw bridge toggling +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/KindOf.h" +#include "GameLogic/Module/SpecialPowerUpdateModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class SpecialPowerModule; +class ParticleSystem; +class FXList; +class AudioEventRTS; +enum CommandOption CPP_11(: Int); + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class DrawBridgeTowerUpdateModuleData : public ModuleData +{ +public: + SpecialPowerTemplate *m_specialPowerTemplate; + + UnsignedInt m_openAnimationFrames; + UnsignedInt m_closeAnimationFrames; + UnsignedInt m_transitionIdleFrames; + + DrawBridgeTowerUpdateModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + +private: + +}; + +enum BridgeTransitionStatus CPP_11(: Int) +{ + TRANSITIONSTATUS_CLOSED, + TRANSITIONSTATUS_OPENING, + TRANSITIONSTATUS_OPENED, + TRANSITIONSTATUS_CLOSING, + + TRANSITIONSTATUS_COUNT +}; + +enum BridgeState CPP_11(: Int) +{ + BRIDGESTATE_CLOSE, + BRIDGESTATE_OPEN, + + BRIDGESTATE_COUNT +}; + +struct DrawBridgeTowerInfo { + BridgeState currentState; + BridgeState desiredState; + BridgeTransitionStatus transitionStatus; + + UnsignedInt nextReadyFrame; +}; + +//------------------------------------------------------------------------------------------------- +/** The default update module */ +//------------------------------------------------------------------------------------------------- +class DrawBridgeTowerUpdate : public SpecialPowerUpdateModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( DrawBridgeTowerUpdate, "DrawBridgeTowerUpdate" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( DrawBridgeTowerUpdate, DrawBridgeTowerUpdateModuleData ); + +public: + + DrawBridgeTowerUpdate( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + // SpecialPowerUpdateInterface + virtual Bool initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions ); + virtual Bool isSpecialAbility() const { return false; } + virtual Bool isSpecialPower() const { return true; } + virtual Bool isActive() const {return false;} + virtual SpecialPowerUpdateInterface* getSpecialPowerUpdateInterface() { return this; } + virtual Bool doesSpecialPowerHaveOverridableDestinationActive() const { return false; } //Is it active now? + virtual Bool doesSpecialPowerHaveOverridableDestination() const { return false; } //Does it have it, even if it's not active? + virtual void setSpecialPowerOverridableDestination( const Coord3D *loc ) {} + virtual Bool isPowerCurrentlyInUse( const CommandButton *command = nullptr ) const; + + virtual void onObjectCreated(); + virtual void onDelete(); + virtual UpdateSleepTime update(); + + virtual CommandOption getCommandOption() const; + + DrawBridgeTowerInfo getTowerInfo() const; + void updateFromTowerInfo(const DrawBridgeTowerInfo& info); + +protected: + void broadcastTowerState(); + void setTransitionStatus(BridgeTransitionStatus status ); + + BridgeState m_currentState; //The current state of the bridge + BridgeState m_desiredState; //The user desired state of the bridge + BridgeTransitionStatus m_transitionStatus; + + UnsignedInt m_nextReadyFrame; + SpecialPowerModuleInterface *m_specialPowerModule; +}; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp index 1c07289db41..99b0bf3137e 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp @@ -1658,6 +1658,7 @@ Bool ActionManager::canDoSpecialPowerAtLocation( const Object *obj, const Coord3 case SPECIAL_TIMED_CHARGES: case SPECIAL_CASH_BOUNTY: case SPECIAL_CHANGE_BATTLE_PLANS: + case SPECIAL_TOGGLE_DRAWBRIDGE: return false; } } @@ -1890,6 +1891,7 @@ Bool ActionManager::canDoSpecialPowerAtObject( const Object *obj, const Object * case SPECIAL_CLEANUP_AREA: case SPECIAL_LAUNCH_BAIKONUR_ROCKET: case SPECIAL_SNEAK_ATTACK: + case SPECIAL_TOGGLE_DRAWBRIDGE: return false; case SPECIAL_REMOTE_CHARGES: @@ -2051,6 +2053,10 @@ SpecialPowerType ActionManager::getFallbackBehaviorType(SpecialPowerType type) { case SUPW_SPECIAL_CRYOBOMB: return SPECIAL_LEAFLET_DROP; + case SPECIAL_TOGGLE_DRAWBRIDGE: + // this has special code + return SPECIAL_TOGGLE_DRAWBRIDGE; + default: return SPECIAL_NEUTRON_MISSILE; } @@ -2157,6 +2163,7 @@ Bool ActionManager::canDoSpecialPower( const Object *obj, const SpecialPowerTemp case SPECIAL_DETONATE_DIRTY_NUKE: case SPECIAL_CHANGE_BATTLE_PLANS: case SPECIAL_LAUNCH_BAIKONUR_ROCKET: + case SPECIAL_TOGGLE_DRAWBRIDGE: //Detonate's any existing charges return true; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp index 08345baf7fe..44f99f76635 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp @@ -219,6 +219,8 @@ const char* const SpecialPowerMaskType::s_bitNameList[] = "SUPW_SPECIAL_SPECTRE_GUNSHIP", "SUPW_SPECIAL_ORBITAL_STRIKE", + "SPECIAL_TOGGLE_DRAWBRIDGE", + nullptr }; static_assert(ARRAY_SIZE(SpecialPowerMaskType::s_bitNameList) == SpecialPowerMaskType::NumBits + 1, "Incorrect array size"); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 2a23bca6b99..7e89248a6c9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -211,6 +211,7 @@ #include "GameLogic/Module/DroneCarrierSlavedUpdate.h" #include "GameLogic/Module/DroneCarrierContain.h" #include "GameLogic/Module/CarrierDroneAIUpdate.h" +#include "GameLogic/Module/DrawBridgeTowerUpdate.h" // upgrade includes #include "GameLogic/Module/ActiveShroudUpgrade.h" @@ -517,6 +518,7 @@ void ModuleFactory::init( void ) addModule( DroneCarrierAIUpdate ); addModule( DroneCarrierSlavedUpdate ); addModule( CarrierDroneAIUpdate ); + addModule( DrawBridgeTowerUpdate ); // upgrade modules addModule( CostModifierUpgrade ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp index fa8650e9289..6740198e215 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp @@ -51,6 +51,7 @@ #include "GameLogic/Module/SpecialAbilityUpdate.h" #include "GameLogic/Module/VeterancyGainCreate.h" #include "GameLogic/Module/HackInternetAIUpdate.h" +#include "GameLogic/Module/DrawBridgeTowerUpdate.h" #include "GameLogic/Weapon.h" #include "GameClient/InGameUI.h" @@ -1455,6 +1456,15 @@ CommandAvailability ControlBar::getCommandAvailability( const CommandButton *com return COMMAND_ACTIVE; } } + else if (mod->getSpecialPowerTemplate()->getSpecialPowerType() == SPECIAL_TOGGLE_DRAWBRIDGE) + { + static NameKeyType key_drawBridgeTowerUpdate = NAMEKEY("DrawBridgeTowerUpdate"); + DrawBridgeTowerUpdate* update = (DrawBridgeTowerUpdate*)obj->findUpdateModule(key_drawBridgeTowerUpdate); + if (update && update->getCommandOption() & command->getOptions()) + { + return COMMAND_ACTIVE; + } + } break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp index bb013f9769f..ac76f06741f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp @@ -34,6 +34,7 @@ #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" +#include "Common/Player.h" #include "GameClient/InGameUI.h" #include "GameClient/FXList.h" #include "GameClient/Line2D.h" @@ -842,6 +843,22 @@ UpdateSleepTime BridgeBehavior::update( void ) } +void BridgeBehavior::towerCaptured(Player* oldOwner, Player* newOwner, const Object* fromTower) +{ + if (oldOwner != newOwner) { + + + for (Int i = 0; i < BRIDGE_MAX_TOWERS; ++i) + { + Object* tower = TheGameLogic->findObjectByID(getTowerID((BridgeTowerType)i)); + if (tower != fromTower && (tower->getControllingPlayer() != newOwner)) { + tower->defect(newOwner->getDefaultTeam(), 0); + } + } + + } +} + // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void BridgeBehavior::onDie( const DamageInfo *damageInfo ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp index ac77d4998a3..dc77c1427ae 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp @@ -92,6 +92,30 @@ void BridgeTowerBehavior::setTowerType( BridgeTowerType type ) } +void BridgeTowerBehavior::onCapture(Player* oldOwner, Player* newOwner) +{ + Object* bridge = TheGameLogic->findObjectByID(getBridgeID()); + + // sanity + if (bridge == nullptr) + return; + + // get the bridge behavior module for our bridge + BehaviorModule** bmi; + BridgeBehaviorInterface* bridgeInterface = nullptr; + for (bmi = bridge->getBehaviorModules(); *bmi; ++bmi) + { + bridgeInterface = (*bmi)->getBridgeBehaviorInterface(); + if (bridgeInterface) + break; + } + + if (bridgeInterface != nullptr) { + // broadcast capture to all towers via bridge + bridgeInterface->towerCaptured(oldOwner, newOwner, getObject()); + } +} + static void createDebugFX(const Coord3D* pos, const char* name) { const FXList* debug_fx1 = TheFXListStore->findFXList(name); FXList::doFXPos(debug_fx1, pos); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp new file mode 100644 index 00000000000..5f336d61025 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp @@ -0,0 +1,346 @@ + +// FILE: DrawBridgeTowerUpdate.cpp ////////////////////////////////////////////////////////////////////////// +// Desc: Update module to handle state change of draw bridges +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#define DEFINE_MAXHEALTHCHANGETYPE_NAMES // for TheMaxHealthChangeTypeNames[] + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/BitFlagsIO.h" +#include "Common/Radar.h" +#include "Common/PlayerList.h" +#include "Common/ThingTemplate.h" +#include "Common/ThingFactory.h" +#include "Common/Player.h" +#include "Common/Xfer.h" + +#include "GameClient/GameClient.h" +#include "GameClient/Drawable.h" +#include "GameClient/GameText.h" +#include "GameClient/ParticleSys.h" +#include "GameClient/FXList.h" +#include "GameClient/ControlBar.h" + +#include "GameLogic/GameLogic.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Object.h" +#include "GameLogic/ObjectIter.h" +#include "GameLogic/TerrainLogic.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/BridgeTowerBehavior.h" +#include "GameLogic/Module/SpecialPowerModule.h" +#include "GameLogic/Module/DrawBridgeTowerUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/AIUpdate.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DrawBridgeTowerUpdateModuleData::DrawBridgeTowerUpdateModuleData() +{ + m_specialPowerTemplate = nullptr; + + m_openAnimationFrames = 0U; + m_closeAnimationFrames = 0U; + m_transitionIdleFrames = 0U; + +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void DrawBridgeTowerUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_specialPowerTemplate) }, + { "OpenAnimationFrames", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_openAnimationFrames) }, + { "CloseAnimationFrames", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_closeAnimationFrames) }, + { "TransitionIdleTime", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_transitionIdleFrames) }, + + { nullptr, nullptr, nullptr, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +DrawBridgeTowerUpdate::DrawBridgeTowerUpdate(Thing* thing, const ModuleData* moduleData) : + SpecialPowerUpdateModule(thing, moduleData) +{ + + m_currentState = BRIDGESTATE_CLOSE; + m_desiredState = BRIDGESTATE_CLOSE; + + m_transitionStatus = TRANSITIONSTATUS_CLOSED; + m_nextReadyFrame = 0; + + m_specialPowerModule = nullptr; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DrawBridgeTowerUpdate::~DrawBridgeTowerUpdate(void) +{ +} + +// ------------------------------------------------------------------------------------------------ +/** On delete */ +// ------------------------------------------------------------------------------------------------ +void DrawBridgeTowerUpdate::onDelete() +{ + // extend base class + UpdateModule::onDelete(); + +} + +//------------------------------------------------------------------------------------------------- +// Validate that we have the necessary data from the ini file. +//------------------------------------------------------------------------------------------------- +void DrawBridgeTowerUpdate::onObjectCreated() +{ + const DrawBridgeTowerUpdateModuleData* data = getDrawBridgeTowerUpdateModuleData(); + Object* obj = getObject(); + + if (!data->m_specialPowerTemplate) + { + DEBUG_CRASH(("%s object's BattlePlanUpdate lacks access to the SpecialPowerTemplate. Needs to be specified in ini.", obj->getTemplate()->getName().str())); + return; + } + + m_specialPowerModule = obj->getSpecialPowerModule(data->m_specialPowerTemplate); +} + +void DrawBridgeTowerUpdate::broadcastTowerState() { + // find the bridge to broadcast to other towers + + Object* obj = getObject(); + + BehaviorModule** bmi; + BridgeTowerBehaviorInterface* bridgeTowerInterface = nullptr; + for (bmi = getObject()->getBehaviorModules(); *bmi; ++bmi) + { + bridgeTowerInterface = (*bmi)->getBridgeTowerBehaviorInterface(); + if (bridgeTowerInterface) + break; + } + + if (bridgeTowerInterface == nullptr) { + DEBUG_CRASH(("%s: DrawBridgeTowerUpdate requires a BridgeTowerInterface!", obj->getTemplate()->getName().str())); + return; + } + + Object* bridge = TheGameLogic->findObjectByID(bridgeTowerInterface->getBridgeID()); + + if (bridge == nullptr) { + DEBUG_CRASH(("%s: DrawBridgeTowerUpdate requires a BridgeTowerInterface with linked bridge!", obj->getTemplate()->getName().str())); + return; + } + + // get the bridge behavior module for our bridge + BehaviorModule** bmi2; + BridgeBehaviorInterface* bridgeInterface = nullptr; + for (bmi2 = bridge->getBehaviorModules(); *bmi2; ++bmi2) + { + bridgeInterface = (*bmi2)->getBridgeBehaviorInterface(); + if (bridgeInterface) + break; + } + + if (bridgeInterface != nullptr) { + // forward to bridge + bridgeInterface->towerDrawBridgeUpdate(getObject(), getTowerInfo()); + } + + +} + +//------------------------------------------------------------------------------------------------- +Bool DrawBridgeTowerUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTemplate* specialPowerTemplate, const Object* targetObj, const Coord3D* targetPos, const Waypoint* way, UnsignedInt commandOptions) +{ + if (m_specialPowerModule->getSpecialPowerTemplate() != specialPowerTemplate) + { + DEBUG_LOG(("DRAWBRIDGE: Special Power Temmplate is not connected!.")); + //Check to make sure our modules are connected. + return FALSE; + } + BridgeState oldstate = m_desiredState; + + //Set the desired status based on the command button option! + if (BitIsSet(commandOptions, OPTION_ONE)) + { + m_desiredState = BRIDGESTATE_CLOSE; + + } + else if (BitIsSet(commandOptions, OPTION_TWO)) + { + m_desiredState = BRIDGESTATE_OPEN; + + } + else + { + DEBUG_LOG(("DRAWBRIDGE: Selected an unsupported state for draw bridge.")); + return FALSE; + } + + if (m_desiredState != oldstate) { + //broadcast + } + + return TRUE; +} + +Bool DrawBridgeTowerUpdate::isPowerCurrentlyInUse(const CommandButton* command) const +{ + //@todo -- perhaps we may need this one day... + return false; +} + +//------------------------------------------------------------------------------------------------- +CommandOption DrawBridgeTowerUpdate::getCommandOption() const +{ + switch (m_desiredState) + { + case BRIDGESTATE_CLOSE: + return OPTION_ONE; + case BRIDGESTATE_OPEN: + return OPTION_TWO; + } + return (CommandOption)0; +} + +DrawBridgeTowerInfo DrawBridgeTowerUpdate::getTowerInfo() const +{ + auto ret = DrawBridgeTowerInfo(); + ret.currentState = m_currentState; + ret.desiredState = m_desiredState; + ret.transitionStatus = m_transitionStatus; + ret.nextReadyFrame = m_nextReadyFrame; + return ret; +} + +void DrawBridgeTowerUpdate::updateFromTowerInfo(const DrawBridgeTowerInfo& info) +{ + m_currentState = info.currentState; + m_desiredState = info.desiredState; + m_transitionStatus = info.transitionStatus; + m_nextReadyFrame = info.nextReadyFrame; +} + +//------------------------------------------------------------------------------------------------- +/** The update callback. */ +//------------------------------------------------------------------------------------------------- +UpdateSleepTime DrawBridgeTowerUpdate::update() +{ + + UnsignedInt now = TheGameLogic->getFrame(); + + if (m_nextReadyFrame <= now) + { + const auto* data = getDrawBridgeTowerUpdateModuleData(); + + switch (m_transitionStatus) + { + case TRANSITIONSTATUS_CLOSED: + + //bridge is closed + + if (m_desiredState == BRIDGESTATE_OPEN) { + setTransitionStatus(TRANSITIONSTATUS_OPENING); + m_nextReadyFrame = now + data->m_openAnimationFrames; + } + + break; + case TRANSITIONSTATUS_CLOSING: + + setTransitionStatus(TRANSITIONSTATUS_CLOSED); + m_nextReadyFrame = now + 10; //todo + m_currentState = BRIDGESTATE_CLOSE; + + break; + case TRANSITIONSTATUS_OPENED: + + if (m_desiredState == BRIDGESTATE_CLOSE) { + setTransitionStatus(TRANSITIONSTATUS_CLOSING); + m_nextReadyFrame = now + data->m_closeAnimationFrames; + } + + break; + case TRANSITIONSTATUS_OPENING: + + setTransitionStatus(TRANSITIONSTATUS_OPENED); + m_nextReadyFrame = now + 10; //todo + m_currentState = BRIDGESTATE_OPEN; + + break; + } + } + + return UPDATE_SLEEP_NONE; +} + +//------------------------------------------------------------------------------------------------- +void DrawBridgeTowerUpdate::setTransitionStatus(BridgeTransitionStatus newStatus) +{ + + if (m_transitionStatus == newStatus) + { + return; + } + + //BridgeTransitionStatus oldStatus = m_transitionStatus; + + m_transitionStatus = newStatus; + + //TODO +} + +//------------------------------------------------------------------------------------------------ +void DrawBridgeTowerUpdate::crc(Xfer* xfer) +{ + + // extend base class + UpdateModule::crc(xfer); + +} + +//------------------------------------------------------------------------------------------------ +// Xfer method +// Version Info: +// 1: Initial version +//------------------------------------------------------------------------------------------------ +void DrawBridgeTowerUpdate::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + UpdateModule::xfer(xfer); + + // current state + xfer->xferUser(&m_currentState, sizeof(BridgeState)); + + // desired state + xfer->xferUser(&m_desiredState, sizeof(BridgeState)); + + // status + xfer->xferUser(&m_transitionStatus, sizeof(BridgeTransitionStatus)); + + // next ready frame + xfer->xferUnsignedInt(&m_nextReadyFrame); + +} + +//------------------------------------------------------------------------------------------------ +void DrawBridgeTowerUpdate::loadPostProcess(void) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} From c10a16361dc499d45eae748b8a165b37ea232282 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sun, 10 May 2026 21:39:11 +0200 Subject: [PATCH 2/6] ++ --- .../System/GameMemoryInitPools_GeneralsMD.inl | 1 + GeneralsMD/Code/GameEngine/CMakeLists.txt | 6 +- .../GameLogic/Module/DrawBridgeTowerUpdate.h | 18 +- .../GameLogic/Module/DrawBridgeUpdate.h | 71 ++++++ .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../Object/Update/DrawBridgeTowerUpdate.cpp | 203 ++++-------------- .../Object/Update/DrawBridgeUpdate.cpp | 189 ++++++++++++++++ 7 files changed, 317 insertions(+), 173 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index 25d35455888..0145f382915 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -695,5 +695,6 @@ static PoolSizeRec PoolSizes[] = { "W3DDependencyCarrierDraw", 16, 16 }, { "CarrierDroneAIUpdate", 16, 16 }, { "DrawBridgeTowerUpdate", 8, 8 }, + { "DrawBridgeUpdate", 4, 4 }, { 0, 0, 0 } }; diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 27a246957d5..91dc894bc54 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -505,7 +505,8 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/DroneCarrierContain.h Include/GameLogic/Module/CarrierDroneAIUpdate.h Include/GameLogic/Module/BridgeTowerBody.h - "Include/GameLogic/Module/DrawBridgeTowerUpdate.h" + Include/GameLogic/Module/DrawBridgeTowerUpdate.h + Include/GameLogic/Module/DrawBridgeUpdate.h Include/GameLogic/Object.h Include/GameLogic/ObjectCreationList.h Include/GameLogic/BuffSystem.h @@ -1115,7 +1116,8 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Update/KodiakDeploymentUpdate.cpp Source/GameLogic/Object/Update/KodiakUpdate.cpp Source/GameLogic/Object/Update/DroneCarrierSlavedUpdate.cpp - "Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp" + Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp + Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp Source/GameLogic/Object/Upgrade/ActiveShroudUpgrade.cpp Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp Source/GameLogic/Object/Upgrade/CommandSetUpgrade.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h index 786e70d4544..f8774328209 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeTowerUpdate.h @@ -7,6 +7,8 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/KindOf.h" #include "GameLogic/Module/SpecialPowerUpdateModule.h" +#include "GameLogic/Module/DrawBridgeUpdate.h" +#include "UpdateModule.h" // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class SpecialPowerModule; @@ -22,10 +24,6 @@ class DrawBridgeTowerUpdateModuleData : public ModuleData public: SpecialPowerTemplate *m_specialPowerTemplate; - UnsignedInt m_openAnimationFrames; - UnsignedInt m_closeAnimationFrames; - UnsignedInt m_transitionIdleFrames; - DrawBridgeTowerUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -90,17 +88,9 @@ class DrawBridgeTowerUpdate : public SpecialPowerUpdateModule virtual CommandOption getCommandOption() const; - DrawBridgeTowerInfo getTowerInfo() const; - void updateFromTowerInfo(const DrawBridgeTowerInfo& info); - protected: - void broadcastTowerState(); - void setTransitionStatus(BridgeTransitionStatus status ); - - BridgeState m_currentState; //The current state of the bridge - BridgeState m_desiredState; //The user desired state of the bridge - BridgeTransitionStatus m_transitionStatus; + DrawBridgeUpdate* getDrawBridgeUpdate() const; + Object* getBridge() const; - UnsignedInt m_nextReadyFrame; SpecialPowerModuleInterface *m_specialPowerModule; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h new file mode 100644 index 00000000000..f571c81dffb --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h @@ -0,0 +1,71 @@ +// FILE: DrawBridgeUpdate.h ////////////////////////////////////////////////////////////////////////// +// Desc: Update module to handle draw bridge toggling +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/KindOf.h" +#include "GameLogic/Module/SpecialPowerUpdateModule.h" +#include "SpecialPowerModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class SpecialPowerModule; +class ParticleSystem; +class FXList; +class AudioEventRTS; +enum CommandOption CPP_11(: Int); + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class DrawBridgeUpdateModuleData : public ModuleData +{ +public: + SpecialPowerTemplate *m_specialPowerTemplate; + + DrawBridgeUpdateModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + +private: + +}; + +//------------------------------------------------------------------------------------------------- +/** The default update module */ +//------------------------------------------------------------------------------------------------- +class DrawBridgeUpdate : public SpecialPowerUpdateModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( DrawBridgeUpdate, "DrawBridgeUpdate" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( DrawBridgeUpdate, DrawBridgeUpdateModuleData ); + +public: + + DrawBridgeUpdate( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + // SpecialPowerUpdateInterface + virtual Bool initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions ); + virtual Bool isSpecialAbility() const { return false; } + virtual Bool isSpecialPower() const { return true; } + virtual Bool isActive() const {return false;} + virtual SpecialPowerUpdateInterface* getSpecialPowerUpdateInterface() { return this; } + virtual Bool doesSpecialPowerHaveOverridableDestinationActive() const { return false; } //Is it active now? + virtual Bool doesSpecialPowerHaveOverridableDestination() const { return false; } //Does it have it, even if it's not active? + virtual void setSpecialPowerOverridableDestination( const Coord3D *loc ) {} + virtual Bool isPowerCurrentlyInUse( const CommandButton *command = nullptr ) const; + + virtual void onObjectCreated(); + virtual void onDelete(); + virtual UpdateSleepTime update(); + + virtual CommandOption getCommandOption() const; + + void setDrawBridgeState(bool opened, const Object* fromTower); + +protected: + + bool m_bridgeOpened; + + SpecialPowerModuleInterface *m_specialPowerModule; +}; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 7e89248a6c9..4e90f97ffbf 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -212,6 +212,7 @@ #include "GameLogic/Module/DroneCarrierContain.h" #include "GameLogic/Module/CarrierDroneAIUpdate.h" #include "GameLogic/Module/DrawBridgeTowerUpdate.h" +#include "GameLogic/Module/DrawBridgeUpdate.h" // upgrade includes #include "GameLogic/Module/ActiveShroudUpgrade.h" @@ -519,6 +520,7 @@ void ModuleFactory::init( void ) addModule( DroneCarrierSlavedUpdate ); addModule( CarrierDroneAIUpdate ); addModule( DrawBridgeTowerUpdate ); + addModule( DrawBridgeUpdate ); // upgrade modules addModule( CostModifierUpgrade ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp index 5f336d61025..1ef8504f8a6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp @@ -43,10 +43,6 @@ DrawBridgeTowerUpdateModuleData::DrawBridgeTowerUpdateModuleData() { m_specialPowerTemplate = nullptr; - m_openAnimationFrames = 0U; - m_closeAnimationFrames = 0U; - m_transitionIdleFrames = 0U; - } //------------------------------------------------------------------------------------------------- @@ -57,9 +53,6 @@ DrawBridgeTowerUpdateModuleData::DrawBridgeTowerUpdateModuleData() static const FieldParse dataFieldParse[] = { { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_specialPowerTemplate) }, - { "OpenAnimationFrames", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_openAnimationFrames) }, - { "CloseAnimationFrames", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_closeAnimationFrames) }, - { "TransitionIdleTime", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeTowerUpdateModuleData, m_transitionIdleFrames) }, { nullptr, nullptr, nullptr, 0 } }; @@ -70,13 +63,6 @@ DrawBridgeTowerUpdateModuleData::DrawBridgeTowerUpdateModuleData() DrawBridgeTowerUpdate::DrawBridgeTowerUpdate(Thing* thing, const ModuleData* moduleData) : SpecialPowerUpdateModule(thing, moduleData) { - - m_currentState = BRIDGESTATE_CLOSE; - m_desiredState = BRIDGESTATE_CLOSE; - - m_transitionStatus = TRANSITIONSTATUS_CLOSED; - m_nextReadyFrame = 0; - m_specialPowerModule = nullptr; } @@ -113,83 +99,41 @@ void DrawBridgeTowerUpdate::onObjectCreated() m_specialPowerModule = obj->getSpecialPowerModule(data->m_specialPowerTemplate); } -void DrawBridgeTowerUpdate::broadcastTowerState() { - // find the bridge to broadcast to other towers - - Object* obj = getObject(); - - BehaviorModule** bmi; - BridgeTowerBehaviorInterface* bridgeTowerInterface = nullptr; - for (bmi = getObject()->getBehaviorModules(); *bmi; ++bmi) - { - bridgeTowerInterface = (*bmi)->getBridgeTowerBehaviorInterface(); - if (bridgeTowerInterface) - break; - } - - if (bridgeTowerInterface == nullptr) { - DEBUG_CRASH(("%s: DrawBridgeTowerUpdate requires a BridgeTowerInterface!", obj->getTemplate()->getName().str())); - return; - } - - Object* bridge = TheGameLogic->findObjectByID(bridgeTowerInterface->getBridgeID()); - - if (bridge == nullptr) { - DEBUG_CRASH(("%s: DrawBridgeTowerUpdate requires a BridgeTowerInterface with linked bridge!", obj->getTemplate()->getName().str())); - return; - } - - // get the bridge behavior module for our bridge - BehaviorModule** bmi2; - BridgeBehaviorInterface* bridgeInterface = nullptr; - for (bmi2 = bridge->getBehaviorModules(); *bmi2; ++bmi2) - { - bridgeInterface = (*bmi2)->getBridgeBehaviorInterface(); - if (bridgeInterface) - break; - } - - if (bridgeInterface != nullptr) { - // forward to bridge - bridgeInterface->towerDrawBridgeUpdate(getObject(), getTowerInfo()); - } - - -} - //------------------------------------------------------------------------------------------------- Bool DrawBridgeTowerUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTemplate* specialPowerTemplate, const Object* targetObj, const Coord3D* targetPos, const Waypoint* way, UnsignedInt commandOptions) { if (m_specialPowerModule->getSpecialPowerTemplate() != specialPowerTemplate) { DEBUG_LOG(("DRAWBRIDGE: Special Power Temmplate is not connected!.")); - //Check to make sure our modules are connected. - return FALSE; + return false; } - BridgeState oldstate = m_desiredState; + + bool drawBridgeOpened{ false }; //Set the desired status based on the command button option! if (BitIsSet(commandOptions, OPTION_ONE)) { - m_desiredState = BRIDGESTATE_CLOSE; + drawBridgeOpened = false; } else if (BitIsSet(commandOptions, OPTION_TWO)) { - m_desiredState = BRIDGESTATE_OPEN; + drawBridgeOpened = true; } else { DEBUG_LOG(("DRAWBRIDGE: Selected an unsupported state for draw bridge.")); - return FALSE; + return false; } - if (m_desiredState != oldstate) { - //broadcast - } + auto* update = getDrawBridgeUpdate(); + if (update != nullptr) { - return TRUE; + update->setDrawBridgeState(drawBridgeOpened, getObject()); + return true; + } + return false; } Bool DrawBridgeTowerUpdate::isPowerCurrentlyInUse(const CommandButton* command) const @@ -201,100 +145,58 @@ Bool DrawBridgeTowerUpdate::isPowerCurrentlyInUse(const CommandButton* command) //------------------------------------------------------------------------------------------------- CommandOption DrawBridgeTowerUpdate::getCommandOption() const { - switch (m_desiredState) - { - case BRIDGESTATE_CLOSE: - return OPTION_ONE; - case BRIDGESTATE_OPEN: - return OPTION_TWO; + auto* update = getDrawBridgeUpdate(); + if (update != nullptr) { + return update->getCommandOption(); + } + else { + return (CommandOption)0; } - return (CommandOption)0; } -DrawBridgeTowerInfo DrawBridgeTowerUpdate::getTowerInfo() const +DrawBridgeUpdate* DrawBridgeTowerUpdate::getDrawBridgeUpdate() const { - auto ret = DrawBridgeTowerInfo(); - ret.currentState = m_currentState; - ret.desiredState = m_desiredState; - ret.transitionStatus = m_transitionStatus; - ret.nextReadyFrame = m_nextReadyFrame; - return ret; -} + Object* bridge = getBridge(); + if (bridge == nullptr) { + DEBUG_CRASH(("%s: DrawBridgeTowerUpdate requires a BridgeTowerInterface with linked bridge!", obj->getTemplate()->getName().str())); + return nullptr; + } -void DrawBridgeTowerUpdate::updateFromTowerInfo(const DrawBridgeTowerInfo& info) -{ - m_currentState = info.currentState; - m_desiredState = info.desiredState; - m_transitionStatus = info.transitionStatus; - m_nextReadyFrame = info.nextReadyFrame; + static NameKeyType key_drawBridgeUpdate = NAMEKEY("DrawBridgeUpdate"); + DrawBridgeUpdate* update = (DrawBridgeUpdate*)bridge->findUpdateModule(key_drawBridgeUpdate); + + return update; } -//------------------------------------------------------------------------------------------------- -/** The update callback. */ -//------------------------------------------------------------------------------------------------- -UpdateSleepTime DrawBridgeTowerUpdate::update() +Object* DrawBridgeTowerUpdate::getBridge() const { - - UnsignedInt now = TheGameLogic->getFrame(); - - if (m_nextReadyFrame <= now) + BehaviorModule** bmi; + BridgeTowerBehaviorInterface* bridgeTowerInterface = nullptr; + for (bmi = getObject()->getBehaviorModules(); *bmi; ++bmi) { - const auto* data = getDrawBridgeTowerUpdateModuleData(); - - switch (m_transitionStatus) - { - case TRANSITIONSTATUS_CLOSED: - - //bridge is closed - - if (m_desiredState == BRIDGESTATE_OPEN) { - setTransitionStatus(TRANSITIONSTATUS_OPENING); - m_nextReadyFrame = now + data->m_openAnimationFrames; - } - - break; - case TRANSITIONSTATUS_CLOSING: - - setTransitionStatus(TRANSITIONSTATUS_CLOSED); - m_nextReadyFrame = now + 10; //todo - m_currentState = BRIDGESTATE_CLOSE; - - break; - case TRANSITIONSTATUS_OPENED: - - if (m_desiredState == BRIDGESTATE_CLOSE) { - setTransitionStatus(TRANSITIONSTATUS_CLOSING); - m_nextReadyFrame = now + data->m_closeAnimationFrames; - } - + bridgeTowerInterface = (*bmi)->getBridgeTowerBehaviorInterface(); + if (bridgeTowerInterface) break; - case TRANSITIONSTATUS_OPENING: - - setTransitionStatus(TRANSITIONSTATUS_OPENED); - m_nextReadyFrame = now + 10; //todo - m_currentState = BRIDGESTATE_OPEN; + } - break; - } + if (bridgeTowerInterface == nullptr) { + DEBUG_CRASH(("%s: DrawBridgeTowerUpdate requires a BridgeTowerInterface!", obj->getTemplate()->getName().str())); + return nullptr; } - return UPDATE_SLEEP_NONE; + Object* bridge = TheGameLogic->findObjectByID(bridgeTowerInterface->getBridgeID()); + + return bridge; } + //------------------------------------------------------------------------------------------------- -void DrawBridgeTowerUpdate::setTransitionStatus(BridgeTransitionStatus newStatus) +/** The update callback. */ +//------------------------------------------------------------------------------------------------- +UpdateSleepTime DrawBridgeTowerUpdate::update() { - if (m_transitionStatus == newStatus) - { - return; - } - - //BridgeTransitionStatus oldStatus = m_transitionStatus; - - m_transitionStatus = newStatus; - - //TODO + return UPDATE_SLEEP_NONE; } //------------------------------------------------------------------------------------------------ @@ -321,19 +223,6 @@ void DrawBridgeTowerUpdate::xfer(Xfer* xfer) // extend base class UpdateModule::xfer(xfer); - - // current state - xfer->xferUser(&m_currentState, sizeof(BridgeState)); - - // desired state - xfer->xferUser(&m_desiredState, sizeof(BridgeState)); - - // status - xfer->xferUser(&m_transitionStatus, sizeof(BridgeTransitionStatus)); - - // next ready frame - xfer->xferUnsignedInt(&m_nextReadyFrame); - } //------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp new file mode 100644 index 00000000000..adc755ccf6c --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp @@ -0,0 +1,189 @@ + +// FILE: DrawBridgeUpdate.cpp ////////////////////////////////////////////////////////////////////////// +// Desc: Update module to handle state change of draw bridges +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#define DEFINE_MAXHEALTHCHANGETYPE_NAMES // for TheMaxHealthChangeTypeNames[] + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/BitFlagsIO.h" +#include "Common/Radar.h" +#include "Common/PlayerList.h" +#include "Common/ThingTemplate.h" +#include "Common/ThingFactory.h" +#include "Common/Player.h" +#include "Common/Xfer.h" + +#include "GameClient/GameClient.h" +#include "GameClient/Drawable.h" +#include "GameClient/GameText.h" +#include "GameClient/ParticleSys.h" +#include "GameClient/FXList.h" +#include "GameClient/ControlBar.h" + +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Object.h" +#include "GameLogic/ObjectIter.h" +#include "GameLogic/TerrainLogic.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/BridgeTowerBehavior.h" +#include "GameLogic/Module/SpecialPowerModule.h" +#include "GameLogic/Module/DrawBridgeUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/AIUpdate.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() +{ + m_specialPowerTemplate = nullptr; + +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void DrawBridgeUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, nullptr, offsetof(DrawBridgeUpdateModuleData, m_specialPowerTemplate) }, + { nullptr, nullptr, nullptr, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +DrawBridgeUpdate::DrawBridgeUpdate(Thing* thing, const ModuleData* moduleData) : + SpecialPowerUpdateModule(thing, moduleData) +{ + m_bridgeOpened = false; + + m_specialPowerModule = nullptr; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DrawBridgeUpdate::~DrawBridgeUpdate(void) +{ +} + +// ------------------------------------------------------------------------------------------------ +/** On delete */ +// ------------------------------------------------------------------------------------------------ +void DrawBridgeUpdate::onDelete() +{ + // extend base class + UpdateModule::onDelete(); + +} + +//------------------------------------------------------------------------------------------------- +// Validate that we have the necessary data from the ini file. +//------------------------------------------------------------------------------------------------- +void DrawBridgeUpdate::onObjectCreated() +{ + const DrawBridgeUpdateModuleData* data = getDrawBridgeUpdateModuleData(); + Object* obj = getObject(); + + if (!data->m_specialPowerTemplate) + { + DEBUG_CRASH(("%s object's DrawBridgeUpdate lacks access to the SpecialPowerTemplate. Needs to be specified in ini.", obj->getTemplate()->getName().str())); + return; + } + + m_specialPowerModule = obj->getSpecialPowerModule(data->m_specialPowerTemplate); +} + +//------------------------------------------------------------------------------------------------- +Bool DrawBridgeUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTemplate* specialPowerTemplate, const Object* targetObj, const Coord3D* targetPos, const Waypoint* way, UnsignedInt commandOptions) +{ + + return TRUE; +} + +Bool DrawBridgeUpdate::isPowerCurrentlyInUse(const CommandButton* command) const +{ + //@todo -- perhaps we may need this one day... + return false; +} + +//------------------------------------------------------------------------------------------------- +CommandOption DrawBridgeUpdate::getCommandOption() const +{ + return m_bridgeOpened ? OPTION_TWO : OPTION_ONE; +} + +void DrawBridgeUpdate::setDrawBridgeState(bool opened, const Object* fromTower) +{ + if (m_bridgeOpened != opened) { + m_bridgeOpened = opened; + + // bridge state was changed, we need to update + Bridge* bridge = TheTerrainLogic->findBridgeAt(getObject()->getPosition()); + if (bridge != nullptr) + TheAI->pathfinder()->changeBridgeState(bridge->getLayer(), !m_bridgeOpened); + + if (m_bridgeOpened) { + getObject()->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_CLOSING, MODELCONDITION_DOOR_1_OPENING); + } + else { + getObject()->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_OPENING, MODELCONDITION_DOOR_1_CLOSING); + } + } +} + +//------------------------------------------------------------------------------------------------- +/** The update callback. */ +//------------------------------------------------------------------------------------------------- +UpdateSleepTime DrawBridgeUpdate::update() +{ + + return UPDATE_SLEEP_FOREVER; //UPDATE_SLEEP_NONE; +} + + +//------------------------------------------------------------------------------------------------ +void DrawBridgeUpdate::crc(Xfer* xfer) +{ + + // extend base class + UpdateModule::crc(xfer); + +} + +//------------------------------------------------------------------------------------------------ +// Xfer method +// Version Info: +// 1: Initial version +//------------------------------------------------------------------------------------------------ +void DrawBridgeUpdate::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + UpdateModule::xfer(xfer); + + xfer->xferBool(&m_bridgeOpened); + +} + +//------------------------------------------------------------------------------------------------ +void DrawBridgeUpdate::loadPostProcess(void) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} From d16e79294fe604c91b0f975783e3977476e102f4 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sun, 17 May 2026 20:49:18 +0200 Subject: [PATCH 3/6] bridge collision stuff --- .../Include/GameClient/TerrainRoads.h | 2 + .../GameClient/Terrain/TerrainRoads.cpp | 1 + .../GameEngine/Include/GameLogic/AIPathfind.h | 5 ++ .../Include/GameLogic/Module/AIUpdate.h | 1 + .../GameLogic/Module/DrawBridgeUpdate.h | 24 ++---- .../Include/GameLogic/TerrainLogic.h | 9 ++- .../Source/GameLogic/AI/AIStates.cpp | 11 +++ .../Source/GameLogic/Map/TerrainLogic.cpp | 76 +++++++++++++++--- .../Object/Behavior/BridgeBehavior.cpp | 8 ++ .../Object/Behavior/BridgeTowerBehavior.cpp | 5 ++ .../GameLogic/Object/Update/AIUpdate.cpp | 14 ++++ .../Object/Update/DrawBridgeTowerUpdate.cpp | 3 +- .../Object/Update/DrawBridgeUpdate.cpp | 78 +++++++++---------- 13 files changed, 164 insertions(+), 73 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/TerrainRoads.h b/Core/GameEngine/Include/GameClient/TerrainRoads.h index 858a6e22b73..ceb71b709db 100644 --- a/Core/GameEngine/Include/GameClient/TerrainRoads.h +++ b/Core/GameEngine/Include/GameClient/TerrainRoads.h @@ -97,6 +97,7 @@ class TerrainRoadType : public MemoryPoolObject AsciiString getRepairedToFXString( BodyDamageType state, Int index ) { return m_repairedToFXString[ state ][ index ]; } Real getTransitionEffectsHeight( void ) { return m_transitionEffectsHeight; } Int getNumFXPerType( void ) { return m_numFXPerType; } + Real getBridgeHoleAreaPercentage( void ) { return m_bridgeHoleAreaPercentage; } // friend access methods to be used by the road collection only! void friend_setName( AsciiString name ) { m_name = name; } @@ -188,6 +189,7 @@ class TerrainRoadType : public MemoryPoolObject Real m_transitionEffectsHeight; Int m_numFXPerType; ///< for *each* fx/ocl we will make this many of them on the bridge area + Real m_bridgeHoleAreaPercentage; ///< if bridge is openable/destroyable, how much % of length becomes open }; //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/GameClient/Terrain/TerrainRoads.cpp b/Core/GameEngine/Source/GameClient/Terrain/TerrainRoads.cpp index bcc12bc8c6e..f889e0f0cce 100644 --- a/Core/GameEngine/Source/GameClient/Terrain/TerrainRoads.cpp +++ b/Core/GameEngine/Source/GameClient/Terrain/TerrainRoads.cpp @@ -84,6 +84,7 @@ const FieldParse TerrainRoadType::m_terrainBridgeFieldParseTable[] = { "RepairedToSound", INI::parseAsciiString, nullptr, offsetof( TerrainRoadType, m_repairedToSoundString[ BODY_DAMAGED ] ) }, { "TransitionToOCL", parseTransitionToOCL, nullptr, 0 }, { "TransitionToFX", parseTransitionToFX, nullptr, 0 }, + { "BridgeHoleAreaPercentage", INI::parsePercentToReal, nullptr, offsetof( TerrainRoadType, m_bridgeHoleAreaPercentage) }, { nullptr, nullptr, nullptr, 0 }, diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h index 5edb6851a9e..f0b9e8fdd43 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -760,6 +760,11 @@ class Pathfinder : PathfindServicesInterface, public Snapshot PathfindLayerEnum addBridge(Bridge *theBridge); // Adds a bridge layer, and returns the layer id. + // return if the passed layer is currently passable (checks for destroyed bridges) + inline Bool isPathfindLayerPassable(PathfindLayerEnum layer) { + return !m_layers[layer].isUnused() && !m_layers[layer].isDestroyed(); + } + void addWallPiece(Object *wallPiece); // Adds a wall piece. void removeWallPiece(Object *wallPiece); // Removes a wall piece. Real getWallHeight(void) {return m_wallHeight;} diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index a123a15a543..8bd077b1423 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -505,6 +505,7 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface Bool isBlockedAndStuck(void) const {return m_isBlockedAndStuck;} Bool canComputeQuickPath(void); ///< Returns true if we can quickly comput a path. Usually missiles & the like that just move straight to the destination. Bool computeQuickPath(const Coord3D *destination); ///< Computes a quick path to the destination. + Bool arePathLayersStillValid(); ///< Check if the current used layers are still passable Bool isMoving() const; Bool isMovingAwayFrom(Object* obj) const; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h index f571c81dffb..7a69c141bb7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h @@ -6,8 +6,7 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/KindOf.h" -#include "GameLogic/Module/SpecialPowerUpdateModule.h" -#include "SpecialPowerModule.h" +#include "GameLogic/Module/UpdateModule.h" // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class SpecialPowerModule; @@ -21,7 +20,9 @@ enum CommandOption CPP_11(: Int); class DrawBridgeUpdateModuleData : public ModuleData { public: - SpecialPowerTemplate *m_specialPowerTemplate; + //SpecialPowerTemplate *m_specialPowerTemplate; + UnsignedInt m_openingDuration; + UnsignedInt m_closingDuration; DrawBridgeUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -33,7 +34,7 @@ class DrawBridgeUpdateModuleData : public ModuleData //------------------------------------------------------------------------------------------------- /** The default update module */ //------------------------------------------------------------------------------------------------- -class DrawBridgeUpdate : public SpecialPowerUpdateModule +class DrawBridgeUpdate : public UpdateModule { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( DrawBridgeUpdate, "DrawBridgeUpdate" ) @@ -44,28 +45,17 @@ class DrawBridgeUpdate : public SpecialPowerUpdateModule DrawBridgeUpdate( Thing *thing, const ModuleData* moduleData ); // virtual destructor prototype provided by memory pool declaration - // SpecialPowerUpdateInterface - virtual Bool initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions ); - virtual Bool isSpecialAbility() const { return false; } - virtual Bool isSpecialPower() const { return true; } - virtual Bool isActive() const {return false;} - virtual SpecialPowerUpdateInterface* getSpecialPowerUpdateInterface() { return this; } - virtual Bool doesSpecialPowerHaveOverridableDestinationActive() const { return false; } //Is it active now? - virtual Bool doesSpecialPowerHaveOverridableDestination() const { return false; } //Does it have it, even if it's not active? - virtual void setSpecialPowerOverridableDestination( const Coord3D *loc ) {} - virtual Bool isPowerCurrentlyInUse( const CommandButton *command = nullptr ) const; - virtual void onObjectCreated(); virtual void onDelete(); virtual UpdateSleepTime update(); virtual CommandOption getCommandOption() const; - void setDrawBridgeState(bool opened, const Object* fromTower); + bool setDrawBridgeState(bool opened, const Object* fromTower); protected: bool m_bridgeOpened; - SpecialPowerModuleInterface *m_specialPowerModule; + UnsignedInt m_nextReadyFrame; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h index 2b734de82d0..a8e42e969bb 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -141,7 +141,9 @@ class BridgeInfo ObjectID bridgeObjectID; ObjectID towerObjectID[ BRIDGE_MAX_TOWERS ]; Bool damageStateChanged; - + // For destroyed bridges or open drawbridges there is an area of the bridge that should not collide, this should be a subarea of the bridge rectangle + Coord3D fromLeftHole, fromRightHole, toLeftHole, toRightHole; /// The 4 corners of the rectangle for a hole in the bridge + Bool drawBridgeOpened; }; //------------------------------------------------------------------------------------------------- @@ -191,7 +193,7 @@ class Bridge : public MemoryPoolObject /// Get the bridges logical info. void getBridgeInfo(class BridgeInfo *pInfo) {*pInfo = m_bridgeInfo; } /// See if the point is on the bridge. - Bool isPointOnBridge(const Coord3D *pLoc); + Bool isPointOnBridge(const Coord3D *pLoc, bool ignoreHole = true); Drawable *pickBridge(const Vector3 &from, const Vector3 &to, Vector3 *pos); void updateDamageState(void); ///< Updates a bridge's damage info. const BridgeInfo *peekBridgeInfo(void) const {return &m_bridgeInfo;} @@ -205,6 +207,9 @@ class Bridge : public MemoryPoolObject void setBridgeObjectID( ObjectID id ) { m_bridgeInfo.bridgeObjectID = id; } void setTowerObjectID( ObjectID id, BridgeTowerType which ) { m_bridgeInfo.towerObjectID[ which ] = id; } + Bool hasHoleArea(); // check if this bridge has defined a hole area for damaged/drawbridge state + Bool hasHole(); // Check if bridge currently has a hole (destroyed/drawbridge open) + void setDrawBridgeStage(bool open); // change if bridge is open/closed }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 8b7b8605a5f..14f1ae97597 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -1870,6 +1870,11 @@ StateReturnType AIInternalMoveToState::update() //DEBUG_LOG(("Info - Blocked - recomputing.")); } + // Check if all bridges on the current path are still valid + if (!forceRecompute && !ai->arePathLayersStillValid()) { + forceRecompute = true; + } + //Determine if we are on a cliff cell... if so, use the climbing model condition //instead of moving. Int cellX = REAL_TO_INT( obj->getPosition()->x / PATHFIND_CELL_SIZE ); @@ -2484,6 +2489,12 @@ Bool AIAttackApproachTargetState::computePath() return true; } + // Check if all bridges on the current path are still valid + if (!forceRepath && !ai->arePathLayersStillValid()) { + forceRepath = true; + } + + m_approachTimestamp = TheGameLogic->getFrame(); // if we have a goal object, move to it, otherwise move to goal position diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 4e3fc7054b3..98e5b3acb0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -115,7 +115,11 @@ BridgeInfo::BridgeInfo() bridgeObjectID = INVALID_ID; for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i ) towerObjectID[ i ] = INVALID_ID; - + fromLeftHole.zero(); + fromRightHole.zero(); + toLeftHole.zero(); + toRightHole.zero(); + drawBridgeOpened = false; } // ------------------------------------------------------------------------------------------------ @@ -384,6 +388,22 @@ Bridge::Bridge(Object *bridgeObj) return; } + // if defined, set hole are for destroyable bridges/drawbridges + if (bridgeTemplate->getBridgeHoleAreaPercentage() > 0.0f) { + Real factor = std::clamp(bridgeTemplate->getBridgeHoleAreaPercentage(), 0.0f, 1.0f); + Real holeSizeX = bridgeObj->getGeometryInfo().getMajorRadius() * factor; + m_bridgeInfo.fromLeftHole.set(pos->x - holeSizeX * c - halfsizeY * s, pos->y + halfsizeY * c - holeSizeX * s, pos->z); + m_bridgeInfo.toLeftHole.set(pos->x + holeSizeX * c - halfsizeY * s, pos->y + halfsizeY * c + holeSizeX * s, pos->z); + m_bridgeInfo.fromRightHole.set(pos->x - holeSizeX * c + halfsizeY * s, pos->y - halfsizeY * c - holeSizeX * s, pos->z); + m_bridgeInfo.toRightHole.set(pos->x + holeSizeX * c + halfsizeY * s, pos->y - halfsizeY * c + holeSizeX * s, pos->z); + } + else { + m_bridgeInfo.fromLeftHole.zero(); + m_bridgeInfo.toLeftHole.zero(); + m_bridgeInfo.fromRightHole.zero(); + m_bridgeInfo.toRightHole.zero(); + } + Coord2D v; v.x = m_bridgeInfo.toLeft.x - m_bridgeInfo.toRight.x; v.y = m_bridgeInfo.toLeft.y - m_bridgeInfo.toRight.y; @@ -443,11 +463,26 @@ Bridge::~Bridge() } +Bool Bridge::hasHoleArea() { + const Coord3D zero(0,0,0); + return !(m_bridgeInfo.fromLeftHole == zero && + m_bridgeInfo.fromRightHole == zero && + m_bridgeInfo.toLeftHole == zero && + m_bridgeInfo.toRightHole == zero); +} + +Bool Bridge::hasHole() { + return m_bridgeInfo.drawBridgeOpened; +} + +void Bridge::setDrawBridgeStage(bool open) { + m_bridgeInfo.drawBridgeOpened = open; +} //------------------------------------------------------------------------------------------------- /** isPointOnBridge - see if point is on bridge. */ //------------------------------------------------------------------------------------------------- -Bool Bridge::isPointOnBridge(const Coord3D *pLoc) +Bool Bridge::isPointOnBridge(const Coord3D *pLoc, bool ignoreHole) { if (pLoc->x < m_bounds.lo.x) return(false); if (pLoc->x > m_bounds.hi.x) return(false); @@ -455,13 +490,30 @@ Bool Bridge::isPointOnBridge(const Coord3D *pLoc) if (pLoc->y > m_bounds.hi.y) return(false); Vector3 testPt(pLoc->x, pLoc->y, pLoc->z); + unsigned char flags{ 0U }; + + // If bridge has hole and point is in hole area -> not on bridge + if (!ignoreHole && hasHole() && hasHoleArea()) { + Vector3 left1(m_bridgeInfo.fromLeftHole.x, m_bridgeInfo.fromLeftHole.y, m_bridgeInfo.fromLeftHole.z); + Vector3 right1(m_bridgeInfo.fromRightHole.x, m_bridgeInfo.fromRightHole.y, m_bridgeInfo.fromRightHole.z); + Vector3 left2(m_bridgeInfo.toLeftHole.x, m_bridgeInfo.toLeftHole.y, m_bridgeInfo.toLeftHole.z); + Vector3 right2(m_bridgeInfo.toRightHole.x, m_bridgeInfo.toRightHole.y, m_bridgeInfo.toRightHole.z); + + //If point is in hole area -> not on bridge + if (Point_In_Triangle_2D(left1, right1, left2, testPt, 0, 1, flags)) { + return false; + } + if (Point_In_Triangle_2D(right1, left2, right2, testPt, 0, 1, flags)) { + return false; + } + } + + // Check full bridge rectangle Vector3 left1(m_bridgeInfo.fromLeft.x, m_bridgeInfo.fromLeft.y, m_bridgeInfo.fromLeft.z); Vector3 right1(m_bridgeInfo.fromRight.x, m_bridgeInfo.fromRight.y, m_bridgeInfo.fromRight.z); Vector3 left2(m_bridgeInfo.toLeft.x, m_bridgeInfo.toLeft.y, m_bridgeInfo.toLeft.z); Vector3 right2(m_bridgeInfo.toRight.x, m_bridgeInfo.toRight.y, m_bridgeInfo.toRight.z); - unsigned char flags; - if (Point_In_Triangle_2D(left1, right1, left2, testPt, 0, 1, flags)) { return true; } @@ -854,7 +906,7 @@ Drawable *Bridge::pickBridge(const Vector3 &from, const Vector3 &to, Vector3 *po loc.y = intersectPos.Y; loc.z = intersectPos.Z; - if (isPointOnBridge(&loc)) { + if (isPointOnBridge(&loc, true)) { *pos = intersectPos; //DEBUG_LOG(("Picked bridge %.2f, %.2f, %.2f", intersectPos.X, intersectPos.Y, intersectPos.Z)); Object *bridge = TheGameLogic->findObjectByID(m_bridgeInfo.bridgeObjectID); @@ -1664,7 +1716,7 @@ Bridge * TerrainLogic::findBridgeAt( const Coord3D *pLoc) const Bridge *pBridge = getFirstBridge(); while (pBridge) { - if (pBridge->isPointOnBridge(pLoc)) { + if (pBridge->isPointOnBridge(pLoc, true)) { return(pBridge); } pBridge = pBridge->getNext(); @@ -1683,7 +1735,7 @@ Bridge * TerrainLogic::findBridgeLayerAt( const Coord3D *pLoc, PathfindLayerEnum Bridge *pBridge = getFirstBridge(); while (pBridge) { - if (pBridge->getLayer() == layer && (!clip || pBridge->isPointOnBridge(pLoc))) + if (pBridge->getLayer() == layer && (!clip || pBridge->isPointOnBridge(pLoc, true))) { return(pBridge); } @@ -1714,7 +1766,8 @@ PathfindLayerEnum TerrainLogic::getLayerForDestination(const Coord3D *pos) } while (pBridge ) { - if (pBridge->isPointOnBridge(pos) ) { + // filter out destroyed bridges or open draw bridges + if (pBridge->isPointOnBridge(pos, false) ) { Real delta = fabs(pos->z-pBridge->getBridgeHeight(pos, nullptr)); if (deltagetLayer(); @@ -1751,7 +1804,8 @@ PathfindLayerEnum TerrainLogic::getHighestLayerForDestination(const Coord3D *pos if (onlyHealthyBridges && pBridge->peekBridgeInfo()->curDamageState == BODY_RUBBLE) continue; - if (pBridge->isPointOnBridge(pos) ) { + //filter out bridges that are currently destroyed or open drawbridges + if (pBridge->isPointOnBridge(pos, false)) { Real delta = pos->z - pBridge->getBridgeHeight(pos, nullptr); // must be ABOVE (or on) the bridge for this call. (srj) if (delta >= 0 && fabs(delta) < fabs(bestDistance)) { @@ -1780,10 +1834,10 @@ Bool TerrainLogic::objectInteractsWithBridgeLayer(Object *obj, Int layer, Bool c } Bridge *pBridge = getFirstBridge(); - while (pBridge ) { + while (pBridge) { if (pBridge->getLayer() == layer) { Bool match = false; - if (pBridge->isPointOnBridge(obj->getPosition()) ) { + if (pBridge->isPointOnBridge(obj->getPosition(), false) ) { match = true; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp index ac76f06741f..beedc7b4009 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp @@ -878,6 +878,14 @@ void BridgeBehavior::onDie( const DamageInfo *damageInfo ) } } + else { + // for destroy/repairable bridges set it to have a hole at death + Bridge* bridge = TheTerrainLogic->findBridgeAt(getObject()->getPosition()); + if (bridge) + { + bridge->setDrawBridgeStage(true); + } + } // we need to handle anything that was on top of us now that we've been destroyed handleObjectsOnBridgeOnDie(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp index dc77c1427ae..1e54f22d924 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp @@ -454,6 +454,11 @@ void BridgeTowerBehavior::onHealing( DamageInfo *damageInfo ) if (body->getHealth() >= body->getMaxHealth() && bridgeBody->getHealth() <= 0.0f) { bridge->attemptHealing(bridgeBody->getMaxHealth(), getObject()); + Bridge* terrainBridge = TheTerrainLogic->findBridgeAt(bridge->getPosition()); + if (terrainBridge != nullptr) { + terrainBridge->setDrawBridgeStage(false); + } + pushObjectsOnBridgeRestore(bridge); //TODO Heal UP effect, condition state? /*BridgeBehaviorInterface* bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject(bridge); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index af175f3c532..ca41189f787 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -5593,3 +5593,17 @@ Bool AIUpdateInterface::hasLocomotorForSurface(LocomotorSurfaceType surfaceType) } // ------------------------------------------------------------------------------------------------ +Bool AIUpdateInterface::arePathLayersStillValid() { + Path* path = getPath(); + if (path != nullptr) { + const PathNode* node = nullptr; + for (node = path->getFirstNode(); node != nullptr; node = node->getNextOptimized()) { + PathfindLayerEnum layer = node->getLayer(); + if (layer > LAYER_GROUND && layer < LAYER_LAST) { + // check if layer is still valid + if (!TheAI->pathfinder()->isPathfindLayerPassable(layer)) return false; + } + } + } + return true; +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp index 1ef8504f8a6..a861d062b7a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeTowerUpdate.cpp @@ -130,8 +130,7 @@ Bool DrawBridgeTowerUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTem auto* update = getDrawBridgeUpdate(); if (update != nullptr) { - update->setDrawBridgeState(drawBridgeOpened, getObject()); - return true; + return update->setDrawBridgeState(drawBridgeOpened, getObject()); } return false; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp index adc755ccf6c..d2e6d2e6528 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp @@ -43,8 +43,8 @@ //------------------------------------------------------------------------------------------------- DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() { - m_specialPowerTemplate = nullptr; - + m_openingDuration = 0U; + m_closingDuration = 0U; } //------------------------------------------------------------------------------------------------- @@ -54,7 +54,8 @@ DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() static const FieldParse dataFieldParse[] = { - { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, nullptr, offsetof(DrawBridgeUpdateModuleData, m_specialPowerTemplate) }, + { "OpeningDuration", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openingDuration)}, + { "ClosingDuration", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingDuration)}, { nullptr, nullptr, nullptr, 0 } }; p.add(dataFieldParse); @@ -62,11 +63,10 @@ DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() //------------------------------------------------------------------------------------------------- DrawBridgeUpdate::DrawBridgeUpdate(Thing* thing, const ModuleData* moduleData) : - SpecialPowerUpdateModule(thing, moduleData) + UpdateModule(thing, moduleData) { m_bridgeOpened = false; - - m_specialPowerModule = nullptr; + m_nextReadyFrame = 0U; } //------------------------------------------------------------------------------------------------- @@ -90,29 +90,6 @@ void DrawBridgeUpdate::onDelete() //------------------------------------------------------------------------------------------------- void DrawBridgeUpdate::onObjectCreated() { - const DrawBridgeUpdateModuleData* data = getDrawBridgeUpdateModuleData(); - Object* obj = getObject(); - - if (!data->m_specialPowerTemplate) - { - DEBUG_CRASH(("%s object's DrawBridgeUpdate lacks access to the SpecialPowerTemplate. Needs to be specified in ini.", obj->getTemplate()->getName().str())); - return; - } - - m_specialPowerModule = obj->getSpecialPowerModule(data->m_specialPowerTemplate); -} - -//------------------------------------------------------------------------------------------------- -Bool DrawBridgeUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTemplate* specialPowerTemplate, const Object* targetObj, const Coord3D* targetPos, const Waypoint* way, UnsignedInt commandOptions) -{ - - return TRUE; -} - -Bool DrawBridgeUpdate::isPowerCurrentlyInUse(const CommandButton* command) const -{ - //@todo -- perhaps we may need this one day... - return false; } //------------------------------------------------------------------------------------------------- @@ -121,23 +98,41 @@ CommandOption DrawBridgeUpdate::getCommandOption() const return m_bridgeOpened ? OPTION_TWO : OPTION_ONE; } -void DrawBridgeUpdate::setDrawBridgeState(bool opened, const Object* fromTower) +bool DrawBridgeUpdate::setDrawBridgeState(bool opened, const Object* fromTower) { - if (m_bridgeOpened != opened) { - m_bridgeOpened = opened; + UnsignedInt now = TheGameLogic->getFrame(); - // bridge state was changed, we need to update - Bridge* bridge = TheTerrainLogic->findBridgeAt(getObject()->getPosition()); - if (bridge != nullptr) - TheAI->pathfinder()->changeBridgeState(bridge->getLayer(), !m_bridgeOpened); + if (m_nextReadyFrame <= now) { - if (m_bridgeOpened) { - getObject()->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_CLOSING, MODELCONDITION_DOOR_1_OPENING); - } - else { - getObject()->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_OPENING, MODELCONDITION_DOOR_1_CLOSING); + const DrawBridgeUpdateModuleData* data = getDrawBridgeUpdateModuleData(); + + m_nextReadyFrame = now + (m_bridgeOpened ? data->m_closingDuration : data->m_openingDuration); + + if (m_bridgeOpened != opened) { + m_bridgeOpened = opened; + + Object* obj = getObject(); + + // bridge state was changed, we need to update + Bridge* bridge = TheTerrainLogic->findBridgeAt(obj->getPosition()); + if (bridge != nullptr) { + TheAI->pathfinder()->changeBridgeState(bridge->getLayer(), !m_bridgeOpened); + bridge->setDrawBridgeStage(m_bridgeOpened); + } + + if (m_bridgeOpened) { + obj->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_CLOSING, MODELCONDITION_DOOR_1_OPENING); + GeometryInfo openBridgeGeom(GeometryType::GEOMETRY_BOX, true, 0.0f, 0.0f, 0.0f); + obj->setGeometryInfo(openBridgeGeom); + } + else { + obj->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_OPENING, MODELCONDITION_DOOR_1_CLOSING); + obj->setGeometryInfo(obj->getTemplate()->getTemplateGeometryInfo()); + } } + return true; } + return false; } //------------------------------------------------------------------------------------------------- @@ -177,6 +172,7 @@ void DrawBridgeUpdate::xfer(Xfer* xfer) xfer->xferBool(&m_bridgeOpened); + xfer->xferUnsignedInt(&m_nextReadyFrame); } //------------------------------------------------------------------------------------------------ From 778bd070d5df0dcea0b3b2a0bde277ac394a9a56 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sun, 24 May 2026 16:13:20 +0200 Subject: [PATCH 4/6] Update bridge pathfinding --- .../GameEngine/Include/GameLogic/AIPathfind.h | 39 ++++- .../GameEngine/Include/GameLogic/Object.h | 2 + .../Source/GameLogic/AI/AIPathfind.cpp | 145 +++++++++++++++--- .../Source/GameLogic/AI/AIPlayer.cpp | 1 + .../Source/GameLogic/AI/AISkirmishPlayer.cpp | 2 +- .../Source/GameLogic/Object/Object.cpp | 7 + .../GameLogic/Object/PartitionManager.cpp | 1 + .../GameLogic/Object/Update/AIUpdate.cpp | 18 ++- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 2 +- .../GameLogic/System/GameLogicDispatch.cpp | 2 +- 10 files changed, 183 insertions(+), 36 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h index f0b9e8fdd43..f11d8b868a2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -175,6 +175,15 @@ class Path : public MemoryPoolObject, public Snapshot /// Given a location, return closest location on path, and along-path dist to end as function result void markOptimized(void) {m_isOptimized = true;} + inline Bool needCheckBridges( void ) const { return m_moveUnderBridges != 0U; }; + + static inline UnsignedShort layerIndexToBitFlag(UnsignedShort layer_index) { + return static_cast(1u << layer_index); + }; + + inline void setPathBelowBridge(PathfindLayerEnum layer) { BitSet(m_moveUnderBridges, layerIndexToBitFlag(static_cast(layer))); }; + inline Bool isPathBelowBridge(UnsignedShort layer_index) { return BitIsSet(m_moveUnderBridges, layerIndexToBitFlag(layer_index)); }; + protected: // snapshot interface virtual void crc( Xfer *xfer ); @@ -195,6 +204,9 @@ class Path : public MemoryPoolObject, public Snapshot Coord3D m_cpopIn; ClosestPointOnPathInfo m_cpopOut; const PathNode* m_cpopRecentStart; + + // Stores opened/destroyed bridges that this path moves below. + UnsignedShort m_moveUnderBridges; // 16bit -> use as bitset for PATHFIND LAYER ENUMS (2-14 for bridges) }; //---------------------------------------------------------------------------------------------------------- @@ -372,6 +384,11 @@ class PathfindCell Short getWaterLevel(void) const { return m_waterLevel; } void setWaterLevel(Short level) { m_waterLevel = level; } + UnsignedByte getBridgeHeight(void) const { return m_bridgeHeight; } + void setBridgeHeight(UnsignedByte height) { m_bridgeHeight = height; } + PathfindLayerEnum getBridgeLayer(void) const { return (PathfindLayerEnum)m_bridgeLayer; } + void setBridgeLayer(PathfindLayerEnum layer) { m_bridgeLayer = (UnsignedByte)layer; } + Bool allocateInfo(const ICoord2D &pos); void releaseInfo(void); Bool hasInfo(void) const {return m_info!=nullptr;} @@ -392,6 +409,9 @@ class PathfindCell void setConnectLayer( PathfindLayerEnum layer ) { m_connectsToLayer = layer; } ///< set the cell layer connect id PathfindLayerEnum getConnectLayer( void ) const { return (PathfindLayerEnum)m_connectsToLayer; } ///< get the cell layer connect id + /// Get the layer if this cell under a bridge that is currently destroyed or opened or LAYER_INVALID if not + PathfindLayerEnum getUnderDestroyedBridgeLayer( void ) const; + private: PathfindCellInfo *m_info; ObjectID m_obstacleID; ///< the object ID who overlaps this cell @@ -409,6 +429,9 @@ class PathfindCell UnsignedByte m_layer : 4; ///< Layer of this cell. //This is added for ship pathing Short m_waterLevel:8; ///< how far away is this cell from land (distance transform), capped at 15 + //This is added for bridge pathing, determine if unit can go under bridge or not + UnsignedByte m_bridgeHeight : 4; // stored as number 0-15, rounded from 0.0-150.0 float. Space below bridge (to ground or water) + UnsignedByte m_bridgeLayer : 4; // Layer number of bridge above this cell }; typedef PathfindCell *PathfindCellP; @@ -660,7 +683,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot void xfer( Xfer *xfer ); void loadPostProcess( void ); - Bool clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain & buildings check - fast) + Bool clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, Short requiredBridgeHeight, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain & buildings check - fast) Bool clientSafeQuickDoesPathExistForUI( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain onlyk - fast) Bool slowDoesPathExist( Object *obj, const Coord3D *from, const Coord3D *to, ObjectID ignoreObject=INVALID_ID ); ///< Can we build any path at all between the locations (terrain, buildings & units check - slower) @@ -709,9 +732,9 @@ class Pathfinder : PathfindServicesInterface, public Snapshot void setIgnoreObstacleID( ObjectID objID ); ///< if non-zero, the pathfinder will ignore the given obstacle - Bool validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, Int requiredWaterLevel, PathfindCell *toCell, PathfindCell *fromCell = NULL ); ///< Return true if given position is a valid movement location - Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Int x, Int y ); ///< Return true if given position is a valid movement location - Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, const Coord3D *pos ); ///< Return true if given position is a valid movement location + Bool validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, Int requiredWaterLevel, Short requiredBridgeHeight, PathfindCell *toCell, PathfindCell *fromCell = NULL ); ///< Return true if given position is a valid movement location + Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Short requiredBridgeHeight, Int x, Int y ); ///< Return true if given position is a valid movement location + Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Short requiredBridgeHeight, const Coord3D *pos ); ///< Return true if given position is a valid movement location Bool validMovementTerrain( PathfindLayerEnum layer, const Locomotor* locomotor, const Coord3D *pos ); ///< Return true if given position is a valid movement location Locomotor* chooseBestLocomotorForPosition(PathfindLayerEnum layer, LocomotorSet* locomotorSet, const Coord3D* pos ); @@ -924,18 +947,18 @@ inline void Pathfinder::worldToGrid( const Coord3D *pos, ICoord2D *cellIndex ) cellIndex->y = REAL_TO_INT(pos->y/PATHFIND_CELL_SIZE); } -inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Int x, Int y ) +inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Short requiredBridgeHeight, Int x, Int y ) { - return validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), getCell(layer, x, y)); + return validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, getCell(layer, x, y)); } -inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, const Coord3D *pos ) +inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Short requiredBridgeHeight, const Coord3D *pos ) { Int x = REAL_TO_INT(pos->x/PATHFIND_CELL_SIZE); Int y = REAL_TO_INT(pos->y/PATHFIND_CELL_SIZE); - return validMovementPosition( isCrusher, layer, locomotorSet, x, y ); + return validMovementPosition( isCrusher, layer, locomotorSet, requiredBridgeHeight, x, y ); } inline const Coord3D *Pathfinder::getDebugPathPosition( void ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 44bb00de07c..ca405d49680 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -666,6 +666,8 @@ class Object : public Thing, public Snapshot // Get position where to enter this object Coord3D getEnterPosition(ObjectID enteringObject) const; + // When moving below a bridge, how high does it need to be, simplified to 0-15, where 1 is 10.0 height + Short getRequiredBridgeHeight() const; protected: void setOrRestoreTeam( Team* team, Bool restoring ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 0a5b6248227..5ad3be93033 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -253,7 +253,8 @@ m_isOptimized(FALSE), m_blockedByAlly(FALSE), m_cpopRecentStart(nullptr), m_cpopCountdown(MAX_CPOP), -m_cpopValid(FALSE) +m_cpopValid(FALSE), +m_moveUnderBridges(0U) { m_cpopIn.zero(); m_cpopOut.distAlongPath=0; @@ -362,6 +363,7 @@ void Path::xfer( Xfer *xfer ) xfer->xferUnsignedInt(&obsolete2); xfer->xferBool(&m_blockedByAlly); + xfer->xferUnsignedShort(&m_moveUnderBridges); #if defined(RTS_DEBUG) if (TheGlobalData->m_debugAI == AI_DEBUG_PATHS) @@ -1283,6 +1285,8 @@ void PathfindCell::reset( ) m_connectsToLayer = LAYER_INVALID; m_layer = LAYER_GROUND; m_waterLevel = 0; + m_bridgeHeight = 0; + m_bridgeLayer = 0; } /** @@ -2033,6 +2037,13 @@ UnsignedInt PathfindCell::costSoFar( PathfindCell *parent ) } +PathfindLayerEnum PathfindCell::getUnderDestroyedBridgeLayer( void ) const { + if (m_bridgeLayer > LAYER_GROUND && m_bridgeLayer < LAYER_WALL) { + PathfindLayerEnum layer = static_cast(m_bridgeLayer); + return (!TheAI->pathfinder()->isPathfindLayerPassable(layer)) ? layer : PathfindLayerEnum::LAYER_INVALID; + } + return PathfindLayerEnum::LAYER_INVALID; +} inline Bool typesMatch(const PathfindCell &targetCell, const PathfindCell &sourceCell) { PathfindCell::CellType targetType = targetCell.getType(); @@ -4548,6 +4559,62 @@ static void calculateWaterLevels(IRegion2D bounds, PathfindCell** map) } } +static void calculateBridgeHeights(IRegion2D bounds, PathfindCell** map) +{ + if (!TheTerrainLogic) return; + + DEBUG_LOG(("BRIDGE_HEIGHT: Checking all bridges...")); + for (Bridge* bridge = TheTerrainLogic->getFirstBridge(); bridge; bridge = bridge->getNext()) { + const Region2D* bridgeBounds = bridge->getBounds(); + PathfindLayerEnum layer = bridge->getLayer(); + + Int cellLoX = REAL_TO_INT_FLOOR(bridgeBounds->lo.x / PATHFIND_CELL_SIZE_F); + Int cellLoY = REAL_TO_INT_FLOOR(bridgeBounds->lo.y / PATHFIND_CELL_SIZE_F); + Int cellHiX = REAL_TO_INT_CEIL(bridgeBounds->hi.x / PATHFIND_CELL_SIZE_F); + Int cellHiY = REAL_TO_INT_CEIL(bridgeBounds->hi.y / PATHFIND_CELL_SIZE_F); + + if (cellLoX < bounds.lo.x) cellLoX = bounds.lo.x; + if (cellLoY < bounds.lo.y) cellLoY = bounds.lo.y; + if (cellHiX > bounds.hi.x) cellHiX = bounds.hi.x; + if (cellHiY > bounds.hi.y) cellHiY = bounds.hi.y; + + for (Int i = cellLoX; i < cellHiX; ++i) { + for (Int j = cellLoY; j < cellHiY; ++j) { + Real worldX = ((Real)i + 0.5f) * PATHFIND_CELL_SIZE_F; + Real worldY = ((Real)j + 0.5f) * PATHFIND_CELL_SIZE_F; + + // Only mark cells whose center is actually under the bridge deck. + // Cells inside the rectangular bbox but outside the (possibly rotated) deck + // must NOT be marked, otherwise they form a moat in front of the entry strip + // and the only reachable connect cells are at the bbox corners. + Coord3D cellCenter; + cellCenter.x = worldX; + cellCenter.y = worldY; + cellCenter.z = 0.0f; + if (!bridge->isPointOnBridge(&cellCenter)) { + continue; + } + + Real bridgeZ = TheTerrainLogic->getLayerHeight(worldX, worldY, layer); + Real waterZ, groundZ; + TheTerrainLogic->isUnderwater(worldX, worldY, &waterZ, &groundZ); + Real baseZ = (waterZ > groundZ) ? waterZ : groundZ; + + Real gap = bridgeZ - baseZ; + if (gap < 0.0f) gap = 0.0f; + + Int encoded = (Int)(gap / 10.0f); + if (encoded > 15) encoded = 15; + + DEBUG_LOG(("BRIDGE_HEIGHT: (%d, %d) -> %d", i, j, encoded)); + DEBUG_LOG(("BRIDGE_HEIGHT: (%d, %d) -> Layer %d", i, j, static_cast(layer))); + map[i][j].setBridgeHeight((UnsignedByte)encoded); + map[i][j].setBridgeLayer(layer); + } + } + } +} + /** * Set up for a new map. */ @@ -4676,6 +4743,10 @@ void Pathfinder::classifyMap(void) // set water depth values for ship navigation calculateWaterLevels(m_extent, m_map); + DEBUG_LOG(("BRIDGE_HEIGHT: Classify map")); + // set bridge height values and layers to all cells + calculateBridgeHeights(m_extent, m_map); + for (i=0; igetLayer() == LAYER_GROUND + && toCell->getBridgeLayer() > LAYER_GROUND + && toCell->getConnectLayer() == LAYER_INVALID + && TheAI->pathfinder()->isPathfindLayerPassable(toCell->getBridgeLayer())) { + // Do a height check + if (static_cast(toCell->getBridgeHeight()) < requiredBridgeHeight) { + return false; + } + } return true; } @@ -5220,10 +5302,10 @@ Bool Pathfinder::checkForAdjust(Object *obj, const LocomotorSet& locomotorSet, B pathExists = true; adjustedPathExists = true; } else { - pathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), dest); - adjustedPathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &adjustDest); + pathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getRequiredBridgeHeight(), obj->getPosition(), dest); + adjustedPathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getRequiredBridgeHeight(), obj->getPosition(), &adjustDest); if (!pathExists) { - if (clientSafeQuickDoesPathExist( locomotorSet, dest, &adjustDest)) { + if (clientSafeQuickDoesPathExist( locomotorSet, obj->getRequiredBridgeHeight(), dest, &adjustDest)) { adjustedPathExists = true; } } @@ -6001,7 +6083,7 @@ struct ExamineCellsStruct Bool isCrusher = d->obj ? d->obj->getCrusherLevel() > 0 : false; if (d->thePathfinder->m_isTunneling) return 1; // abort. if (from && to) { - if (!d->thePathfinder->validMovementPosition( isCrusher, d->theLoco->getValidSurfaces(), d->theLoco->getRequiredWaterLevel(), to, from )) { + if (!d->thePathfinder->validMovementPosition( isCrusher, d->theLoco->getValidSurfaces(), d->theLoco->getRequiredWaterLevel(), d->obj->getRequiredBridgeHeight(), to, from )) { return 1; } if ( (to->getLayer() == LAYER_GROUND) && !d->thePathfinder->m_zoneManager.isPassable(to_x, to_y) ) { @@ -6090,6 +6172,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * Bool isHuman, Bool centerInCell, Int radius, const ICoord2D &startCellNdx, const Object *obj, Int attackDistance) { + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; Bool canPathThroughUnits = false; if (obj && obj->getAIUpdateInterface()) { canPathThroughUnits = obj->getAIUpdateInterface()->canPathThroughUnits(); @@ -6180,7 +6263,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * continue; } - Bool movementValid = validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell); + Bool movementValid = validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, newCell, parentCell); Bool dozerHack = false; if (!movementValid && obj->isKindOf(KINDOF_DOZER) && newCell->getType() == PathfindCell::CELL_OBSTACLE) { Object* obstacle = TheGameLogic->findObjectByID(newCell->getObstacleID()); @@ -6291,7 +6374,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * } if (m_isTunneling) { - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell )) { + if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, newCell, parentCell )) { newCostSoFar += 10*COST_ORTHOGONAL; } } @@ -6326,7 +6409,8 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * Path *Pathfinder::findPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *rawTo) { - if (!clientSafeQuickDoesPathExist(locomotorSet, from, rawTo)) { + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; + if (!clientSafeQuickDoesPathExist(locomotorSet, requiredBridgeHeight, from, rawTo)) { return nullptr; } Bool isHuman = true; @@ -6370,6 +6454,8 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe getRadiusAndCenter(obj, radius, centerInCell); } + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; + Bool isHuman = true; if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { isHuman = false; // computer gets to cheat. @@ -6475,7 +6561,7 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe } // sanity check - if destination is invalid, can't path there - if (!validMovementPosition( isCrusher, destinationLayer, locomotorSet, to )) { + if (!validMovementPosition( isCrusher, destinationLayer, locomotorSet, requiredBridgeHeight, to )) { m_isTunneling = false; goalCell->releaseInfo(); parentCell->releaseInfo(); @@ -6483,7 +6569,7 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe } // sanity check - if source is invalid, we have to cheat - if (!validMovementPosition( isCrusher, layer, locomotorSet, from )) { + if (!validMovementPosition( isCrusher, layer, locomotorSet, requiredBridgeHeight, from )) { // somehow we got to an impassable location. m_isTunneling = true; } @@ -6621,7 +6707,7 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe if (obj) { Bool valid; - valid = validMovementPosition( isCrusher, obj->getLayer(), locomotorSet, to ) ; + valid = validMovementPosition( isCrusher, obj->getLayer(), locomotorSet, obj->getRequiredBridgeHeight(), to ) ; DEBUG_LOG(("%d Pathfind failed from (%f,%f) to (%f,%f), OV %d --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y, valid)); DEBUG_LOG(("Unit '%s', time %f, cells %d", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f,cellCount)); @@ -8066,12 +8152,13 @@ Bool Pathfinder::findBrokenBridge(const LocomotorSet& locoSet, * True means it is possible given the terrain, but there may be units in the way. */ Bool Pathfinder::clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, + Short requiredBridgeHeight, const Coord3D *from, const Coord3D *to ) { // See if terrain or building is blocking the destination. PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); - if (!validMovementPosition(false, destinationLayer, locomotorSet, to)) { + if (!validMovementPosition(false, destinationLayer, locomotorSet, requiredBridgeHeight, to)) { return false; } PathfindLayerEnum fromLayer = TheTerrainLogic->getLayerForDestination(from); @@ -8543,6 +8630,7 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con Bool center; Int radius; getRadiusAndCenter(obj, radius, center); + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; // determine start cell ICoord2D startCellNdx; @@ -8564,7 +8652,7 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con } } - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, parentCell ) == false) { parentCell->releaseInfo(); goalCell->releaseInfo(); return MAX_COST; @@ -8677,7 +8765,7 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con } } - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell )) { + if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, newCell, parentCell )) { continue; } @@ -8800,6 +8888,8 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet Int radius; getRadiusAndCenter(obj, radius, centerInCell); + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; + Coord3D adjustTo = *rawTo; Coord3D *to = &adjustTo; if (!centerInCell) { @@ -8858,7 +8948,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet if (parentCell == nullptr) return nullptr; - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, parentCell ) == false) { m_isTunneling = true; // We can't move from our current location. So relax the constraints. } TCheckMovementInfo info; @@ -8994,7 +9084,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet // put parent cell onto closed list - its evaluation is finished parentCell->putOnClosedList( m_closedList ); if (!m_isTunneling && checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { - if (!startedStuck || validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell )) { + if (!startedStuck || validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, parentCell )) { dx = IABS(goalCell->getXIndex()-parentCell->getXIndex()); dy = IABS(goalCell->getYIndex()-parentCell->getYIndex()); distSqr = dx*dx+dy*dy; @@ -9085,7 +9175,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet // failure - goal cannot be reached #ifdef DEBUG_LOGGING Bool valid; - valid = validMovementPosition( isCrusher, obj->getLayer(), locomotorSet, to ) ; + valid = validMovementPosition( isCrusher, obj->getLayer(), locomotorSet, requiredBridgeHeight, to ) ; DEBUG_LOG(("Pathfind(findClosestPath) failed from (%f,%f) to (%f,%f), original valid %d --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y, valid)); DEBUG_LOG(("Unit '%s', time %f", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); @@ -9231,6 +9321,13 @@ void Pathfinder::prependCells( Path *path, const Coord3D *fromPos, if (cell->isBlockedByAlly()) { path->setBlockedByAlly(true); } + + //Check if cell is below an opened/destroyed bridge + PathfindLayerEnum belowBridgeLayer = cell->getUnderDestroyedBridgeLayer(); + if (belowBridgeLayer > LAYER_GROUND) { + path->setPathBelowBridge(belowBridgeLayer); + } + if (prevCell) { prevCell->clearParentCell(); } @@ -9767,6 +9864,7 @@ struct LinePassableStruct { const LinePassableStruct* d = (const LinePassableStruct*)userData; + Short requiredBridgeHeight = d->obj ? d->obj->getRequiredBridgeHeight() : 0; Bool isCrusher = d->obj ? d->obj->getCrusherLevel() > 0 : false; TCheckMovementInfo info; info.cell.x = to_x; @@ -9796,7 +9894,7 @@ struct LinePassableStruct } } - if (pathfinder->validMovementPosition( isCrusher, d->acceptableSurfaces, d->requiredWaterDepth, to, from ) == false) + if (pathfinder->validMovementPosition( isCrusher, d->acceptableSurfaces, d->requiredWaterDepth, requiredBridgeHeight, to, from ) == false) { return 1; // bail out } @@ -10413,6 +10511,8 @@ Path *Pathfinder::getMoveAwayFromPath(Object* obj, Object *otherObj, if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { isHuman = false; // computer gets to cheat. } + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; + Bool otherCenter; Int otherRadius; getRadiusAndCenter(otherObj, otherRadius, otherCenter); @@ -10445,7 +10545,7 @@ Path *Pathfinder::getMoveAwayFromPath(Object* obj, Object *otherObj, const LocomotorSet& locomotorSet = obj->getAIUpdateInterface()->getLocomotorSet(); m_isTunneling = false; - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, parentCell ) == false) { m_isTunneling = true; // We can't move from our current location. So relax the constraints. } @@ -10829,6 +10929,7 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot return nullptr; // Should always be ok. Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; + Short requiredBridgeHeight = obj ? obj->getRequiredBridgeHeight() : 0; Int radius; Bool centerInCell; getRadiusAndCenter(obj, radius, centerInCell); @@ -10853,7 +10954,7 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot PathfindCell *aCell = getCell(obj->getLayer(), cellNdx.x, cellNdx.y); if (!aCell) break; - if (!validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), aCell)) + if (!validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, aCell)) break; if (!checkDestination(obj, cellNdx.x, cellNdx.y, obj->getLayer(), radius, centerInCell)) @@ -11105,7 +11206,7 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot } } if (checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell )) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), requiredBridgeHeight, parentCell )) { Real dx = IABS(victimCellNdx.x-parentCell->getXIndex()); Real dy = IABS(victimCellNdx.y-parentCell->getYIndex()); Real distSqr = dx*dx+dy*dy; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index e297ba90d71..5e22a7b833c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -620,6 +620,7 @@ Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildLi TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba. if (!TheAI->pathfinder()->clientSafeQuickDoesPathExist(dozer->getAI()->getLocomotorSet(), + dozer->getRequiredBridgeHeight(), dozer->getPosition(), &pos)) { AsciiString bldgName = bldgPlan->getName(); bldgName.concat(" - Dozer unable to reach building. Teleporting."); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp index 9d366e57299..b34d97c538a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp @@ -736,7 +736,7 @@ Bool AISkirmishPlayer::checkBridges(Object *unit, Waypoint *way) const LocomotorSet& locoSet = ai->getLocomotorSet(); Waypoint *curWay; for (curWay = way; curWay; curWay = curWay->getNext()) { - if (TheAI->pathfinder()->clientSafeQuickDoesPathExist(locoSet, &unitPos, curWay->getLocation())) { + if (TheAI->pathfinder()->clientSafeQuickDoesPathExist(locoSet, unit->getRequiredBridgeHeight(), & unitPos, curWay->getLocation())) { continue; } ObjectID brokenBridge = INVALID_ID; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 32a1a3d5a60..ffb67aee761 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -6798,3 +6798,10 @@ Coord3D Object::getEnterPosition(ObjectID enteringObject) const { } return ret; } + +Short Object::getRequiredBridgeHeight() const { + // Return 1-15 depending on geometry height, 0 if no_collide + if (isKindOf(KINDOF_NO_COLLIDE)) return 0; + Real geometryHeight = getGeometryInfo().getMaxHeightAbovePosition(); + return std::clamp(static_cast(geometryHeight / 10.0f), static_cast(1), static_cast(15)); +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index dd5f4e67617..398fb4781dd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -3944,6 +3944,7 @@ Bool PartitionManager::tryPosition( const Coord3D *center, // check for path existence if( ai && TheAI->pathfinder()->clientSafeQuickDoesPathExist( ai->getLocomotorSet(), + options->sourceToPathToDest->getRequiredBridgeHeight(), options->sourceToPathToDest->getPosition(), &pos ) == FALSE ) return FALSE; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index ca41189f787..ecfe20c786a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -1860,7 +1860,7 @@ Bool AIUpdateInterface::computePath( PathfindServicesInterface *pathServices, Co } PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(destination); - if (TheAI->pathfinder()->validMovementPosition( getObject()->getCrusherLevel()>0, destinationLayer, m_locomotorSet, destination ) == FALSE) + if (TheAI->pathfinder()->validMovementPosition( getObject()->getCrusherLevel()>0, destinationLayer, m_locomotorSet, getObject()->getRequiredBridgeHeight(), destination ) == FALSE) { theNewPath = nullptr; } @@ -2233,7 +2233,7 @@ Bool AIUpdateInterface::isPathAvailable( const Coord3D *destination ) const const Coord3D *myPos = getObject()->getPosition(); - return TheAI->pathfinder()->clientSafeQuickDoesPathExist( m_locomotorSet, myPos, destination ); + return TheAI->pathfinder()->clientSafeQuickDoesPathExist(m_locomotorSet, getObject()->getRequiredBridgeHeight(), myPos, destination); } @@ -2260,7 +2260,7 @@ Bool AIUpdateInterface::isQuickPathAvailable( const Coord3D *destination ) const //------------------------------------------------------------------------------------------------- Bool AIUpdateInterface::isValidLocomotorPosition(const Coord3D* pos) const { - return TheAI->pathfinder()->validMovementPosition( getObject()->getCrusherLevel()>0, getObject()->getLayer(), m_locomotorSet, pos ); + return TheAI->pathfinder()->validMovementPosition( getObject()->getCrusherLevel()>0, getObject()->getLayer(), m_locomotorSet, getObject()->getRequiredBridgeHeight(), pos ); } //------------------------------------------------------------------------------------------------- @@ -5596,6 +5596,18 @@ Bool AIUpdateInterface::hasLocomotorForSurface(LocomotorSurfaceType surfaceType) Bool AIUpdateInterface::arePathLayersStillValid() { Path* path = getPath(); if (path != nullptr) { + + //Check if going below bridges and if these are still opened/destroyed + if (path->needCheckBridges()) { + for (UnsignedShort i = PathfindLayerEnum::LAYER_GROUND + 1U; i < PathfindLayerEnum::LAYER_WALL; ++i) { + if (path->isPathBelowBridge(i)) { + // Path is below bridge -> if Bridge layer is now Passable, no longer valid -> recheck + return TheAI->pathfinder()->isPathfindLayerPassable(static_cast(i)); + } + } + } + + // Check for going over bridges const PathNode* node = nullptr; for (node = path->getFirstNode(); node != nullptr; node = node->getNextOptimized()) { PathfindLayerEnum layer = node->getLayer(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp index 2df2ce05895..d3dd0fbeb6b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -263,7 +263,7 @@ Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, bool viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, *targetPos, victim, *victimPos); bool inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, targetPos, victim, victimPos); PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(targetPos); - bool posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), targetPos); + bool posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), getObject()->getRequiredBridgeHeight(), targetPos); return !viewBlocked && inRange && posValid; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index a1dadf86a91..9bc0e3f9b43 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -165,7 +165,7 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) LocomotorSet locomotorSet; locomotorSet.addLocomotor( TheLocomotorStore->findLocomotorTemplate( key ) ); - if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &rallyPointPos) == FALSE ) + if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getRequiredBridgeHeight(), obj->getPosition(), &rallyPointPos) == FALSE ) { // user feedback From c54bfbfd5b27d66688890cfb959ac77ac6810391 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Mon, 25 May 2026 19:47:51 +0200 Subject: [PATCH 5/6] bridge push --- .../Include/GameLogic/Module/BridgeBehavior.h | 7 + .../Object/Behavior/BridgeBehavior.cpp | 123 +++++++++++- .../Object/Behavior/BridgeTowerBehavior.cpp | 183 +----------------- 3 files changed, 130 insertions(+), 183 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h index 6a9fa2aa7b2..beb805917b0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BridgeBehavior.h @@ -87,6 +87,7 @@ class BridgeBehaviorInterface virtual Bool isScaffoldPresent( void ) = 0; virtual void towerCaptured(Player* oldOwner, Player* newOwner, const Object* fromTower) {}; virtual void towerDrawBridgeUpdate(const Object* fromTower, DrawBridgeTowerInfo towerInfo) {}; + virtual void onRepaired( void ) = 0; }; // ------------------------------------------------------------------------------------------------ @@ -106,6 +107,8 @@ class BridgeBehaviorModuleData : public BehaviorModuleData BridgeFXList m_fx; ///< list of FX lists to execute BridgeOCLList m_ocl; ///< list of OCL to execute Bool m_restoreable; ///< if bridge is repairable, towers do not fully die + UnsignedInt m_repairPushDuration; ///< if bridge is repaired, push units away for this amount of frames + Real m_repairPushForce; ///< lateral acceleration applied to units while pushing them off a repaired bridge (0 = no push) static void parseFX( INI *ini, void *instance, void *store, const void* userData ); static void parseOCL( INI *ini, void *instance, void *store, const void* userData ); @@ -161,6 +164,7 @@ class BridgeBehavior : public UpdateModule, virtual void removeScaffolding( void ); ///< remove scaffolding around bridge virtual Bool isScaffoldInMotion( void ); ///< is scaffold in motion virtual Bool isScaffoldPresent( void ) { return m_scaffoldPresent; } + virtual void onRepaired(void) override; protected: @@ -179,6 +183,8 @@ class BridgeBehavior : public UpdateModule, const BridgeInfo *bridgeInfo, Coord3D *pos ); + void pushObjectsOnBridgeSideways(); + ObjectID m_towerID[ BRIDGE_MAX_TOWERS ]; ///< the towers that are a part of us // got damaged fx stuff @@ -197,5 +203,6 @@ class BridgeBehavior : public UpdateModule, ObjectIDList m_scaffoldObjectIDList; ///< list of scaffold object IDs UnsignedInt m_deathFrame; ///< frame we died on + UnsignedInt m_repairedFrame; ///< frame we got repaired }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp index beedc7b4009..e4fe419e599 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp @@ -63,6 +63,9 @@ BridgeBehaviorModuleData::BridgeBehaviorModuleData( void ) m_lateralScaffoldSpeed = 1.0f; m_verticalScaffoldSpeed = 1.0f; + m_restoreable = false; + m_repairPushDuration = 0U; + m_repairPushForce = 0.0f; } // ------------------------------------------------------------------------------------------------ @@ -91,6 +94,8 @@ BridgeBehaviorModuleData::~BridgeBehaviorModuleData( void ) { "BridgeDieFX", parseFX, nullptr, offsetof( BridgeBehaviorModuleData, m_fx ) }, { "BridgeDieOCL", parseOCL, nullptr, offsetof( BridgeBehaviorModuleData, m_ocl ) }, { "Restoreable", INI::parseBool, nullptr, offsetof(BridgeBehaviorModuleData, m_restoreable) }, + { "RepairPushDuration", INI::parseDurationUnsignedInt, nullptr, offsetof(BridgeBehaviorModuleData, m_repairPushDuration)}, + { "RepairPushForce", INI::parseAccelerationReal, nullptr, offsetof(BridgeBehaviorModuleData, m_repairPushForce)}, { nullptr, nullptr, nullptr, 0 } }; @@ -260,7 +265,7 @@ BridgeBehavior::BridgeBehavior( Thing *thing, const ModuleData *moduleData ) } m_deathFrame = 0; - + m_repairedFrame = 0; } // ------------------------------------------------------------------------------------------------ @@ -703,6 +708,100 @@ void BridgeBehavior::onBodyDamageStateChange( const DamageInfo* damageInfo, } +// This method pushes all units sideways away from the bridge area, direction depends if left or right of center. +// It is called every frame for a defined duration after the bridge is repaired and displaying an animation. +// Units already ON top of the bridge are not pushed. This shall move units that are now inside the bridge when repairing +void BridgeBehavior::pushObjectsOnBridgeSideways() { + // lateral acceleration to apply; if no push force is configured, do nothing. + // The force is scaled by each object's mass below so this is the acceleration every unit feels. + Real pushAccel = getBridgeBehaviorModuleData()->m_repairPushForce; + if( pushAccel <= 0.0f ) + return; + + Object* bridge = getObject(); + const Coord3D* bridgePos = bridge->getPosition(); + + Bridge* terrainBridge = TheTerrainLogic->findBridgeAt(bridgePos); + if (terrainBridge) + { + BridgeInfo bridgeInfo; + terrainBridge->getBridgeInfo( &bridgeInfo ); + + // polygon describing the bridge surface, used to test which objects are on it + Coord3D bridgePolygon[ 4 ]; + bridgePolygon[ 0 ] = bridgeInfo.fromLeft; + bridgePolygon[ 1 ] = bridgeInfo.fromRight; + bridgePolygon[ 2 ] = bridgeInfo.toRight; + bridgePolygon[ 3 ] = bridgeInfo.toLeft; + + // scan radius reaches from the bridge center out to a corner, covering the whole surface + Coord2D v; + v.x = bridgeInfo.toLeft.x - bridgePos->x; + v.y = bridgeInfo.toLeft.y - bridgePos->y; + Real radius = v.length(); + + // sideways axis = direction across the bridge width (fromLeft -> fromRight) + Coord2D sideVector; + sideVector.x = bridgeInfo.fromRight.x - bridgeInfo.fromLeft.x; + sideVector.y = bridgeInfo.fromRight.y - bridgeInfo.fromLeft.y; + Real width = sideVector.length(); + if( width == 0.0f ) + return; // degenerate bridge, no sensible sideways direction + sideVector.x /= width; + sideVector.y /= width; + + // scan all objects within the bridge radius + ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( bridgePos, radius, FROM_CENTER_2D ); + MemoryPoolObjectHolder hold( iter ); + Object *other; + for( other = iter->first(); other; other = iter->next() ) + { + // never push the bridge itself or its towers + if( other->isKindOf( KINDOF_BRIDGE ) || other->isKindOf( KINDOF_BRIDGE_TOWER ) ) + continue; + + // don't shove fixed structures or already-dead objects + if( other->isKindOf( KINDOF_IMMOBILE ) || other->isEffectivelyDead() ) + continue; + + // leave airborne units alone, only things resting on the bridge get pushed + if( other->isAirborneTarget() ) + continue; + + // Only push units below the bridge. Units already trying to traverse the repaired bridge are not pushed + if (other->getLayer() == terrainBridge->getLayer()) + continue; + + // only push objects actually standing in the bridge area + if( PointInsideArea2D( other->getPosition(), bridgePolygon, 4 ) == FALSE ) + continue; + + // only things with physics can be shoved + PhysicsBehavior *physics = other->getPhysics(); + if( physics == nullptr ) + continue; + + // project the object's offset from the center onto the sideways axis; the sign tells us + // which side of the center line it is on, so we push it further toward that same side + Coord2D toObj; + toObj.x = other->getPosition()->x - bridgePos->x; + toObj.y = other->getPosition()->y - bridgePos->y; + Real side = toObj.x * sideVector.x + toObj.y * sideVector.y; + Real pushDir = ( side >= 0.0f ) ? 1.0f : -1.0f; + + // F = m*a, so scaling by mass makes the resulting acceleration uniform across all units + Real strength = physics->getMass() * pushAccel; + Coord3D force; + force.x = sideVector.x * pushDir * strength; + force.y = sideVector.y * pushDir * strength; + force.z = 0.0f; + // applyMotiveForce, not applyForce: a moving (motive) unit would otherwise have this force + // reprojected onto its own facing axis, sending it along the bridge instead of across it. + physics->applyMotiveForce( &force ); + } + } +} + // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ UpdateSleepTime BridgeBehavior::update( void ) @@ -839,6 +938,22 @@ UpdateSleepTime BridgeBehavior::update( void ) } + if (m_repairedFrame > 0) { + + // get module data + const BridgeBehaviorModuleData* modData = getBridgeBehaviorModuleData(); + if (TheGameLogic->getFrame() < m_repairedFrame + modData->m_repairPushDuration) { + + // push objects away, bridge just got repaired + pushObjectsOnBridgeSideways(); + + } + else { + // stop the push + m_repairedFrame = 0; + } + } + return UPDATE_SLEEP_NONE; } @@ -1395,6 +1510,11 @@ Bool BridgeBehavior::isScaffoldInMotion( void ) } +void BridgeBehavior::onRepaired(void) +{ + m_repairedFrame = TheGameLogic->getFrame(); +} + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ @@ -1499,6 +1619,7 @@ void BridgeBehavior::xfer( Xfer *xfer ) // death frame xfer->xferUnsignedInt( &m_deathFrame ); + xfer->xferUnsignedInt( &m_repairedFrame ); } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp index 1e54f22d924..2168a7b29fa 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp @@ -121,187 +121,6 @@ static void createDebugFX(const Coord3D* pos, const char* name) { FXList::doFXPos(debug_fx1, pos); } -// Pushes a unit off a bridge -void pushUnitOffBridge(Coord2D& unitPos, const Coord2D& bridgePos, const Coord2D & sideVector, Real bridgeWidth, PhysicsBehavior* unitPhysics) -{ - if (unitPhysics == nullptr) return; - - // Get the direction from the bridge's center to the unit - Coord2D dirToUnit; - dirToUnit.x = unitPos.x - bridgePos.x; - dirToUnit.y = unitPos.y - bridgePos.y; - - - //Dot product projects the unit's position onto that sideways vector - // This tells us the left/right direction AND the exact distance from the center. - Real distanceToSide = (dirToUnit.x * sideVector.x) + (dirToUnit.y * sideVector.y); - - // Get absolute distance to check if they are actually on the bridge - //Real absDist = (distanceToSide < 0.0f) ? -distanceToSide : distanceToSide; - - // Calculate how far we need to push them to reach the edge - //Real halfWidth = bridgeWidth * 0.5f; - //Real pushAmount = halfWidth - absDist; - //Real pushAmount = bridgeWidth * 0.5f * unitPhysics->getMass(); - - - Real pushAmount = unitPhysics->getMass() * bridgeWidth * 0.5f; - - // Only push if the unit is actually currently ON the bridge width - if (pushAmount > 0.0f) - { - // Use the sign template from BaseType.h: 1 for right, -1 for left. - int pushDirection = sign(distanceToSide); - - // Edge case: if they are in the dead mathematical center, force them to one side - if (pushDirection == 0) - { - pushDirection = 1; - } - - Coord3D pushforce(sideVector.x * pushDirection * pushAmount, sideVector.y * pushDirection * pushAmount, 0.0f); - DEBUG_LOG(("BRIDGE_PUSH: %f, %f, %f: %d -> %f", pushforce.x, pushforce.y, pushforce.z, pushDirection, pushAmount)); - unitPhysics->applyForce(&pushforce); - - Coord3D fxpos(unitPos.x + sideVector.x*50.0f, unitPos.y+ sideVector.y* 50.0f, 56.0f); - - createDebugFX(&fxpos, "FX_DEBUG_MARKER_RED"); - - // Apply the push along the side vector - //unitPos->x += sideVector.x * pushDirection * pushAmount; - //unitPos->y += sideVector.y * pushDirection * pushAmount; - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void pushObjectsOnBridgeRestore(const Object* bridge) -{ - const Coord3D* bridgePos = bridge->getPosition(); - - Bridge* terrainBridge = TheTerrainLogic->findBridgeAt(bridgePos); - if (terrainBridge) - { - //PathfindLayerEnum bridgeLayer = terrainBridge->getLayer(); - - BridgeInfo bridgeInfo; - - // get the bridge info - terrainBridge->getBridgeInfo(&bridgeInfo); - - // setup a polygon area using the bridge extents - Coord3D bridgePolygon[4]; - bridgePolygon[0] = bridgeInfo.fromLeft; - bridgePolygon[1] = bridgeInfo.fromRight; - bridgePolygon[2] = bridgeInfo.toRight; - bridgePolygon[3] = bridgeInfo.toLeft; - - //DEBUG_LOG(("BRIDGE_PUSH: FromLeft: %f, %f, %f", bridgeInfo.fromLeft.x, bridgeInfo.fromLeft.y, bridgeInfo.fromLeft.z)); - //DEBUG_LOG(("BRIDGE_PUSH: FromRight: %f, %f, %f", bridgeInfo.fromRight.x, bridgeInfo.fromRight.y, bridgeInfo.fromRight.z)); - //DEBUG_LOG(("BRIDGE_PUSH: ToRight: %f, %f, %f", bridgeInfo.toRight.x, bridgeInfo.toRight.y, bridgeInfo.toRight.z)); - //DEBUG_LOG(("BRIDGE_PUSH: ToLeft: %f, %f, %f", bridgeInfo.toLeft.x, bridgeInfo.toLeft.y, bridgeInfo.toLeft.z)); - - // - // find the lowest Z point of the bridge area ... we will use this to figure out of - // objects in the bridge area are "on top" of the bridge - // - //Real lowBridgeZ = bridgePolygon[0].z; - //for (Int i = 0; i < 4; ++i) - // if (bridgePolygon[i].z < lowBridgeZ) - // lowBridgeZ = bridgePolygon[i].z; - - // - // given the polygon area, how big is the radius that we need to scan in the world - // to cover from the center of the bridge (the bridge object position) to the edge - // of the bridge - // - Coord2D v; - v.x = bridgeInfo.toLeft.x - bridgePos->x; - v.y = bridgeInfo.toLeft.y - bridgePos->y; - Real radius = v.length(); - - // Calculate the perpendicular "Sideways" vector of the bridge. - // If the bridge's forward vector is (Cos(angle), Sin(angle)), - // its sideways perpendicular vector is (-Sin(angle), Cos(angle)). - //const Coord3D* rot = bridge->getUnitDirectionVector2D(); - //Coord2D sideVector(rot->x, rot->y); - //sideVector.rotateByAngle(PI * 0.5f); - - Coord3D sideDir = bridgeInfo.fromRight; - sideDir.sub(&bridgeInfo.fromLeft); - - Coord2D sideVector(sideDir.x, sideDir.y); - //sideVector.rotateByAngle(PI*0.5); - Real width = sideVector.length(); - - if (width == 0.0f) width = 0.01f; //avoid /0 - - //normalize without recalc length - sideVector.x /= width; - sideVector.y /= width; - - //sideVector.x = -Sin(bridge->getOrientation()); - //sideVector.y = Cos(bridge->getOrientation()); - - // scan the objects in the radius - ObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(bridgePos, - radius, - FROM_CENTER_2D); - MemoryPoolObjectHolder hold(iter); - Object* other; - for (other = iter->first(); other; other = iter->next()) - { - - // ignore some kind of objects - if (other->isKindOf(KINDOF_BRIDGE) || other->isKindOf(KINDOF_BRIDGE_TOWER)) - continue; - - // ignore airborne objects - //if (other->isAboveTerrain()) - // continue; - - if (other->isAirborneTarget()) - continue; - - // ignore objects that were not actually on the bridge - //if (other->getPosition()->z < lowBridgeZ) - // continue; - - // ignore objects that are not inside the bridge polygon - if (PointInsideArea2D(other->getPosition(), bridgePolygon, 4) == FALSE) - continue; - - // if object not on same layer as bridge do nothing - //if (bridgeLayer != other->getLayer()) - // continue; - - //if (other->getLayer() == bridgeLayer) - // other->setLayer(LAYER_GROUND); - - - //push units away from bridge - PhysicsBehavior* physics = other->getPhysics(); - if (physics != nullptr) { - - Coord2D upos(other->getPosition()->x, other->getPosition()->y); - Coord2D bpos(bridgePos->x, bridgePos->y); - - pushUnitOffBridge(upos, bpos, sideVector, width, physics); - } - - // if they have physics, let 'em fall, otherwise just kill 'em - //PhysicsBehavior* physics = other->getPhysics(); - //if (physics) - // physics->setAllowToFall(true); - //else - // other->kill(); - - } - - } - -} - // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void BridgeTowerBehavior::onDamage( DamageInfo *damageInfo ) @@ -458,8 +277,8 @@ void BridgeTowerBehavior::onHealing( DamageInfo *damageInfo ) if (terrainBridge != nullptr) { terrainBridge->setDrawBridgeStage(false); } + bridgeInterface->onRepaired(); - pushObjectsOnBridgeRestore(bridge); //TODO Heal UP effect, condition state? /*BridgeBehaviorInterface* bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject(bridge); if (bbi != nullptr) { From 07b8ae13dd9010a8a5df4dea2320cecc59643a4a Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sat, 30 May 2026 16:10:37 +0200 Subject: [PATCH 6/6] bridge stuff --- .../GameLogic/Module/DrawBridgeUpdate.h | 7 + .../Source/GameLogic/AI/AIPathfind.cpp | 6 +- .../Object/Update/DrawBridgeUpdate.cpp | 233 +++++++++++++++++- 3 files changed, 241 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h index 7a69c141bb7..eb1264c0978 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h @@ -23,6 +23,8 @@ class DrawBridgeUpdateModuleData : public ModuleData //SpecialPowerTemplate *m_specialPowerTemplate; UnsignedInt m_openingDuration; UnsignedInt m_closingDuration; + Real m_openingPushForce; + UnsignedInt m_closingDamageTime; DrawBridgeUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -54,8 +56,13 @@ class DrawBridgeUpdate : public UpdateModule bool setDrawBridgeState(bool opened, const Object* fromTower); protected: + void pushObjectsOnOpeningDrawbridge( void ); + void destroyObjectsUnderClosingDrawbridge (void ); bool m_bridgeOpened; UnsignedInt m_nextReadyFrame; + + UnsignedInt m_openingFrame; ///< frame bridge started to open + UnsignedInt m_closingDamageFrame; ///< frame damage will be applied when closing }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 5ad3be93033..12244761829 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -4563,7 +4563,7 @@ static void calculateBridgeHeights(IRegion2D bounds, PathfindCell** map) { if (!TheTerrainLogic) return; - DEBUG_LOG(("BRIDGE_HEIGHT: Checking all bridges...")); + //DEBUG_LOG(("BRIDGE_HEIGHT: Checking all bridges...")); for (Bridge* bridge = TheTerrainLogic->getFirstBridge(); bridge; bridge = bridge->getNext()) { const Region2D* bridgeBounds = bridge->getBounds(); PathfindLayerEnum layer = bridge->getLayer(); @@ -4606,8 +4606,8 @@ static void calculateBridgeHeights(IRegion2D bounds, PathfindCell** map) Int encoded = (Int)(gap / 10.0f); if (encoded > 15) encoded = 15; - DEBUG_LOG(("BRIDGE_HEIGHT: (%d, %d) -> %d", i, j, encoded)); - DEBUG_LOG(("BRIDGE_HEIGHT: (%d, %d) -> Layer %d", i, j, static_cast(layer))); + //DEBUG_LOG(("BRIDGE_HEIGHT: (%d, %d) -> %d", i, j, encoded)); + //DEBUG_LOG(("BRIDGE_HEIGHT: (%d, %d) -> Layer %d", i, j, static_cast(layer))); map[i][j].setBridgeHeight((UnsignedByte)encoded); map[i][j].setBridgeLayer(layer); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp index d2e6d2e6528..7085c062de8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp @@ -18,6 +18,7 @@ #include "GameClient/GameClient.h" #include "GameClient/Drawable.h" +#include "GameClient/Line2D.h" #include "GameClient/GameText.h" #include "GameClient/ParticleSys.h" #include "GameClient/FXList.h" @@ -45,6 +46,8 @@ DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() { m_openingDuration = 0U; m_closingDuration = 0U; + m_openingPushForce = 0.0f; + m_closingDamageTime = 0U; } //------------------------------------------------------------------------------------------------- @@ -56,6 +59,8 @@ DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() { { "OpeningDuration", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openingDuration)}, { "ClosingDuration", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingDuration)}, + { "OpeningPushForce", INI::parseAccelerationReal, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openingPushForce)}, + { "ClosingDamageTime", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingDamageTime)}, { nullptr, nullptr, nullptr, 0 } }; p.add(dataFieldParse); @@ -67,6 +72,8 @@ DrawBridgeUpdate::DrawBridgeUpdate(Thing* thing, const ModuleData* moduleData) : { m_bridgeOpened = false; m_nextReadyFrame = 0U; + m_openingFrame = 0U; + m_closingDamageFrame = 0U; } //------------------------------------------------------------------------------------------------- @@ -124,10 +131,14 @@ bool DrawBridgeUpdate::setDrawBridgeState(bool opened, const Object* fromTower) obj->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_CLOSING, MODELCONDITION_DOOR_1_OPENING); GeometryInfo openBridgeGeom(GeometryType::GEOMETRY_BOX, true, 0.0f, 0.0f, 0.0f); obj->setGeometryInfo(openBridgeGeom); + m_openingFrame = TheGameLogic->getFrame(); + m_closingDamageFrame = 0U; } else { obj->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_OPENING, MODELCONDITION_DOOR_1_CLOSING); obj->setGeometryInfo(obj->getTemplate()->getTemplateGeometryInfo()); + m_openingFrame = 0U; // when rapid toggling is possible + m_closingDamageFrame = TheGameLogic->getFrame() + data->m_closingDamageTime; } } return true; @@ -135,13 +146,227 @@ bool DrawBridgeUpdate::setDrawBridgeState(bool opened, const Object* fromTower) return false; } +// This method launches units away by applying force in a 45 degree upwards angle to simulate an opening +// drawbridge. The side depends on position from center. The push is in bridge length direction. +void DrawBridgeUpdate::pushObjectsOnOpeningDrawbridge( void ) { + // horizontal acceleration to apply; if no push force is configured, do nothing. + // The force is scaled by each object's mass below so this is the acceleration every unit feels. + Real pushAccel = getDrawBridgeUpdateModuleData()->m_openingPushForce; + if( pushAccel <= 0.0f ) + return; + + Object* bridge = getObject(); + const Coord3D* bridgePos = bridge->getPosition(); + + Bridge* terrainBridge = TheTerrainLogic->findBridgeAt(bridgePos); + if (terrainBridge == nullptr) + return; + + BridgeInfo bridgeInfo; + terrainBridge->getBridgeInfo( &bridgeInfo ); + + // polygon describing the bridge hole surface, used to test which objects are on it + Coord3D bridgePolygon[ 4 ]; + bridgePolygon[ 0 ] = bridgeInfo.fromLeftHole; + bridgePolygon[ 1 ] = bridgeInfo.fromRightHole; + bridgePolygon[ 2 ] = bridgeInfo.toRightHole; + bridgePolygon[ 3 ] = bridgeInfo.toLeftHole; + + // scan radius reaches from the bridge center out to a corner, covering the whole surface + Coord2D v; + v.x = bridgeInfo.toLeft.x - bridgePos->x; + v.y = bridgeInfo.toLeft.y - bridgePos->y; + Real radius = v.length(); + + // length axis = direction along the bridge (from -> to). The two drawbridge halves hinge at the + // center and rise toward their own ends, so each unit is flung toward the end of the half it is on. + Coord2D lengthVector; + lengthVector.x = bridgeInfo.to.x - bridgeInfo.from.x; + lengthVector.y = bridgeInfo.to.y - bridgeInfo.from.y; + Real length = lengthVector.length(); + if( length == 0.0f ) + return; // degenerate bridge, no sensible length direction + lengthVector.x /= length; + lengthVector.y /= length; + + // scan all objects within the bridge radius + ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( bridgePos, radius, FROM_CENTER_2D ); + MemoryPoolObjectHolder hold( iter ); + Object *other; + for( other = iter->first(); other; other = iter->next() ) + { + // never push the bridge itself or its towers + if( other->isKindOf( KINDOF_BRIDGE ) || other->isKindOf( KINDOF_BRIDGE_TOWER ) ) + continue; + + // don't shove fixed structures or already-dead objects + if( other->isKindOf( KINDOF_IMMOBILE ) || other->isEffectivelyDead() ) + continue; + + // leave airborne units alone, only things resting on the bridge get thrown + if( other->isAirborneTarget() ) + continue; + + // only push objects within the bridge footprint + if( PointInsideArea2D( other->getPosition(), bridgePolygon, 4 ) == FALSE ) + continue; + + // Only throw units standing on the drawbridge deck, not those on the ground/water below it. + // We can't use getLayer() here: a unit crossing the bridge gets relayered to LAYER_GROUND the + // moment the bridge layer is disabled (the toggle does this before we run), so it would be + // skipped and fall through the opening hole. getBridgeHeight returns the deck height from the + // bridge's flat corner geometry regardless of open/destroyed state, so compare z to that. + const Real ON_DECK_Z_TOLERANCE = 20.0f; + Real deckZ = terrainBridge->getBridgeHeight( other->getPosition(), nullptr ); + if( other->getPosition()->z < deckZ - ON_DECK_Z_TOLERANCE ) + continue; + + // only things with physics can be shoved + PhysicsBehavior *physics = other->getPhysics(); + if( physics == nullptr ) + continue; + + // project the object's offset from the center onto the length axis; the sign tells us which + // half of the bridge it is on, so we fling it toward that same end. + Coord2D toObj; + toObj.x = other->getPosition()->x - bridgePos->x; + toObj.y = other->getPosition()->y - bridgePos->y; + Real side = toObj.x * lengthVector.x + toObj.y * lengthVector.y; + Real pushDir = ( side >= 0.0f ) ? 1.0f : -1.0f; + + // Move the unit off the bridge layer onto the ground layer before launching. Height and landing + // are both measured against the unit's layer, so while a unit sits on LAYER_BRIDGE: + // 1) Landing: the physics ground clamp snaps the unit to getLayerHeight(x, y, getLayer()). On + // LAYER_BRIDGE that is the deck, so the unit lands back on the (now open, impassable) bridge. + // On LAYER_GROUND it falls through the opening hole down to the ground/water below. + // 2) Airborne tests: getLayerHeight(LAYER_BRIDGE) == deck height, so the unit never reads as + // above terrain. On LAYER_GROUND its height is measured against the (far lower) chasm/water + // floor, so it correctly counts as airborne while arcing. + // A unit crossing the bridge was already relayered to LAYER_GROUND by the toggle; setLayer is a + // no-op for it. Standing units stay on LAYER_BRIDGE, so relayer them here. + other->setLayer( LAYER_GROUND ); + + // Let the unit leave the ground and arc freely. allowToFall leaves the z alone instead of + // snapping it back to the layer height. Physics clears it automatically when the unit lands. + physics->setAllowToFall( true ); + + // A unit that walked onto the bridge and stopped still carries OBJECT_STATUS_BRAKING: the + // locomotor sets it while braking to a halt (so the unit stops exactly on its goal) and never + // clears it once idle. While that status is set, PhysicsBehavior::update integrates ONLY the z + // velocity and discards the horizontal (PhysicsUpdate.cpp ~L714), so our horizontal launch is + // thrown away and the unit shoots straight up -- the vertical push has no such restriction. Moving + // units are not braking, which is exactly why they launched correctly. Clear it so the full 3D + // velocity integrates. + other->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); + + // F = m*a, so scaling by mass makes the resulting acceleration uniform across all units. + // Equal horizontal and vertical magnitude gives a 45 degree upward launch. + Real strength = physics->getMass() * pushAccel; + Coord3D force; + force.x = lengthVector.x * pushDir * strength; + force.y = lengthVector.y * pushDir * strength; + force.z = strength; + // A moving (motive) unit: applyForce would reproject the horizontal force onto its facing axis and + // lose the along-bridge launch, so use applyMotiveForce, which clears the motive state for this one + // application so the full 3D force applies. A standing (non-motive) unit: use plain applyForce so we + // do not flag it motive (an idle unit flagged motive gets scrubVelocity2D(0) from the locomotor). + if( physics->isMotive() ) + physics->applyMotiveForce( &force ); + else + physics->applyForce( &force ); + } +} + +// When a drawbridge is closed units or ships directly below it that are too high need to be killed +void DrawBridgeUpdate::destroyObjectsUnderClosingDrawbridge( void ) { + Object* bridge = getObject(); + const Coord3D* bridgePos = bridge->getPosition(); + + Bridge* terrainBridge = TheTerrainLogic->findBridgeAt(bridgePos); + if (terrainBridge == nullptr) + return; + + BridgeInfo bridgeInfo; + terrainBridge->getBridgeInfo(&bridgeInfo); + + // polygon describing the bridge hole surface, used to test which objects are below it + Coord3D bridgePolygon[4]; + bridgePolygon[0] = bridgeInfo.fromLeftHole; + bridgePolygon[1] = bridgeInfo.fromRightHole; + bridgePolygon[2] = bridgeInfo.toRightHole; + bridgePolygon[3] = bridgeInfo.toLeftHole; + + // scan radius reaches from the bridge center out to a corner, covering the whole surface + Coord2D v; + v.x = bridgeInfo.toLeft.x - bridgePos->x; + v.y = bridgeInfo.toLeft.y - bridgePos->y; + Real radius = v.length(); + + DamageInfo smashByBridgeDmg; + smashByBridgeDmg.in.m_sourceID = getObject()->getID(); + smashByBridgeDmg.in.m_sourceTemplate = getObject()->getTemplate(); + + const auto owner = getObject()->getControllingPlayer(); + if (owner != nullptr) + smashByBridgeDmg.in.m_sourcePlayerMask = owner->getPlayerMask(); + + smashByBridgeDmg.in.m_damageType = DAMAGE_CRUSH; + smashByBridgeDmg.in.m_deathType = DEATH_CRUSHED; + smashByBridgeDmg.in.m_amount = 999999.0f; + smashByBridgeDmg.in.m_kill = true; + + // scan all objects within the bridge radius + ObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(bridgePos, radius, FROM_CENTER_2D); + MemoryPoolObjectHolder hold(iter); + Object* other; + for (other = iter->first(); other; other = iter->next()) + { + // never damage the bridge itself or its towers + if (other->isKindOf(KINDOF_BRIDGE) || other->isKindOf(KINDOF_BRIDGE_TOWER) || other->isKindOf(KINDOF_UNATTACKABLE)) + continue; + + // unit must be below the bridge and not on top + if (other->getLayer() != LAYER_GROUND) + continue; + + // don't kill already-dead objects or airborne targets + if (other->isEffectivelyDead() || other->isAirborneTarget()) + continue; + + // only destroy objects within the bridge hole footprint + if (PointInsideArea2D(other->getPosition(), bridgePolygon, 4) == FALSE) + continue; + + // check if unit height is larger than required bridge height. + const auto * cell = TheAI->pathfinder()->getClippedCell(LAYER_GROUND, other->getPosition()); + if (cell->getBridgeLayer() == terrainBridge->getLayer() && cell->getBridgeHeight() < other->getRequiredBridgeHeight()) { + other->attemptDamage(&smashByBridgeDmg); + } + } + +} + //------------------------------------------------------------------------------------------------- /** The update callback. */ //------------------------------------------------------------------------------------------------- UpdateSleepTime DrawBridgeUpdate::update() { - - return UPDATE_SLEEP_FOREVER; //UPDATE_SLEEP_NONE; + if (m_openingFrame != 0U) { + const DrawBridgeUpdateModuleData* data = getDrawBridgeUpdateModuleData(); + if (TheGameLogic->getFrame() < (m_openingFrame + data->m_openingDuration)) { + pushObjectsOnOpeningDrawbridge(); + } + else { + m_openingFrame = 0U; //opening finished + } + } + if (m_closingDamageFrame != 0U) { + if (TheGameLogic->getFrame() >= (m_closingDamageFrame)) { + destroyObjectsUnderClosingDrawbridge(); + m_closingDamageFrame = 0U; + } + } + return UPDATE_SLEEP_NONE; } @@ -173,6 +398,10 @@ void DrawBridgeUpdate::xfer(Xfer* xfer) xfer->xferBool(&m_bridgeOpened); xfer->xferUnsignedInt(&m_nextReadyFrame); + + xfer->xferUnsignedInt(&m_openingFrame); + + xfer->xferUnsignedInt(&m_closingDamageFrame); } //------------------------------------------------------------------------------------------------